Compare commits
44 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3486bbf6b8 | ||
|
|
0d7a8296c5 | ||
|
|
0f1e7a5a43 | ||
|
|
3da48cf899 | ||
|
|
4ec46dd65d | ||
|
|
7073f63c6b | ||
|
|
80966cbd03 | ||
|
|
98ea15dca4 | ||
|
|
e1910e85ea | ||
|
|
4d311d7294 | ||
|
|
c36d90cae6 | ||
|
|
af5aa8424f | ||
|
|
67b99652fc | ||
|
|
c8d292976b | ||
|
|
daffaa2339 | ||
|
|
a462fc5779 | ||
|
|
fe3491c5aa | ||
|
|
c0877ba69a | ||
|
|
8e449cc78c | ||
|
|
1b6071cabd | ||
|
|
debb9d9709 | ||
|
|
d2c6459756 | ||
|
|
47533b6336 | ||
|
|
00b73b68d3 | ||
|
|
cef4b52d12 | ||
|
|
47ce547dcf | ||
|
|
e2a7594eae | ||
|
|
823149ecd9 | ||
|
|
e57aec63d8 | ||
|
|
837aca98c9 | ||
|
|
dbb16b0e5e | ||
|
|
e14af67254 | ||
|
|
4780850748 | ||
|
|
3374a70f47 | ||
|
|
131f88b504 | ||
|
|
4333e9a686 | ||
|
|
a60d15ff05 | ||
|
|
ceffbc69da | ||
|
|
c75129e629 | ||
|
|
f39fb80c52 | ||
|
|
fcd422d246 | ||
|
|
ed9bf89038 | ||
|
|
7262b4219f | ||
|
|
02b0b25147 |
14
.gitignore
vendored
14
.gitignore
vendored
@@ -1,7 +1,13 @@
|
|||||||
build/
|
build/
|
||||||
node_modules/
|
node_modules/
|
||||||
coverage/
|
coverage/
|
||||||
/lib/**/*.js
|
/*.js
|
||||||
/lib/**/*.ts
|
/*.ts
|
||||||
/lib/**/*.d.ts.map
|
/*.js.map
|
||||||
/curve-definitions/lib
|
/*.d.ts.map
|
||||||
|
/esm/*.js
|
||||||
|
/esm/*.ts
|
||||||
|
/esm/*.js.map
|
||||||
|
/esm/*.d.ts.map
|
||||||
|
/esm/abstract
|
||||||
|
/abstract/
|
||||||
|
|||||||
766
README.md
766
README.md
@@ -1,73 +1,81 @@
|
|||||||
# noble-curves
|
# noble-curves
|
||||||
|
|
||||||
Minimal, auditable JS implementation of elliptic curve cryptography.
|
Audited & minimal JS implementation of elliptic curve cryptography.
|
||||||
|
|
||||||
|
- **noble** family, zero dependencies
|
||||||
- Short Weierstrass, Edwards, Montgomery curves
|
- 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](https://datatracker.ietf.org/doc/draft-irtf-cfrg-hash-to-curve/)
|
||||||
for encoding or hashing an arbitrary string to a point on an elliptic curve
|
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
|
||||||
- Auditable
|
|
||||||
- 🏎 [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. Wycheproof vectors included
|
||||||
- 🔻 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
|
||||||
|
|
||||||
There are two parts of the package:
|
Package consists of two parts:
|
||||||
|
|
||||||
1. `abstract/` directory specifies zero-dependency EC algorithms
|
1. [Abstract](#abstract-api), zero-dependency EC algorithms
|
||||||
2. root directory utilizes one dependency `@noble/hashes` and provides 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 secp192r1/P192, secp224r1/P224, secp256r1/P256, secp384r1/P384, secp521r1/P521
|
||||||
- SECG curve secp256k1
|
- SECG curve secp256k1
|
||||||
|
- ed25519/curve25519/x25519/ristretto255, edwards448/curve448/x448 RFC7748 / RFC8032 / ZIP215 stuff
|
||||||
- pairing-friendly curves bls12-381, bn254
|
- pairing-friendly curves bls12-381, bn254
|
||||||
- ed25519/curve25519/x25519/ristretto, edwards448/curve448/x448 RFC7748 / RFC8032 / ZIP215 stuff
|
|
||||||
|
|
||||||
Curves incorporate work from previous noble packages
|
Check out [Upgrading](#upgrading) if you've previously used single-feature noble packages
|
||||||
([secp256k1](https://github.com/paulmillr/noble-secp256k1),
|
([secp256k1](https://github.com/paulmillr/noble-secp256k1), [ed25519](https://github.com/paulmillr/noble-ed25519)).
|
||||||
[ed25519](https://github.com/paulmillr/noble-ed25519),
|
See [In the wild](#in-the-wild) for real-world software that uses curves.
|
||||||
[bls12-381](https://github.com/paulmillr/noble-bls12-381)),
|
|
||||||
which had security audits and were developed from 2019 to 2022.
|
|
||||||
|
|
||||||
### This library belongs to _noble_ crypto
|
### This library belongs to _noble_ crypto
|
||||||
|
|
||||||
> **noble-crypto** — high-security, easily auditable set of contained cryptographic libraries and tools.
|
> **noble-crypto** — high-security, easily auditable set of contained cryptographic libraries and tools.
|
||||||
|
|
||||||
- Minimal dependencies, small files
|
- No dependencies, protection against supply chain attacks
|
||||||
- Easily auditable TypeScript/JS code
|
- Easily auditable TypeScript/JS code
|
||||||
- Supported in all major browsers and stable node.js versions
|
- Supported in all major browsers and stable node.js versions
|
||||||
- All releases are signed with PGP keys
|
- All releases are signed with PGP keys
|
||||||
- Check out [homepage](https://paulmillr.com/noble/) & all libraries:
|
- Check out [homepage](https://paulmillr.com/noble/) & all libraries:
|
||||||
[curves](https://github.com/paulmillr/noble-curves) ([secp256k1](https://github.com/paulmillr/noble-secp256k1),
|
[curves](https://github.com/paulmillr/noble-curves)
|
||||||
[ed25519](https://github.com/paulmillr/noble-ed25519),
|
([secp256k1](https://github.com/paulmillr/noble-secp256k1),
|
||||||
[bls12-381](https://github.com/paulmillr/noble-bls12-381)),
|
[ed25519](https://github.com/paulmillr/noble-ed25519)),
|
||||||
[hashes](https://github.com/paulmillr/noble-hashes)
|
[hashes](https://github.com/paulmillr/noble-hashes)
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
Use NPM in node.js / browser, or include single file from
|
Use NPM for browser / node.js:
|
||||||
[GitHub's releases page](https://github.com/paulmillr/noble-curves/releases):
|
|
||||||
|
|
||||||
> npm install @noble/curves
|
> npm install @noble/curves
|
||||||
|
|
||||||
The library does not have an entry point. It allows you to select specific primitives and drop everything else. If you only want to use secp256k1, just use the library with rollup or other bundlers. This is done to make your bundles tiny.
|
For [Deno](https://deno.land), use it with npm specifier. In browser, you could also include the single file from
|
||||||
|
[GitHub's releases page](https://github.com/paulmillr/noble-curves/releases).
|
||||||
|
|
||||||
|
The library is tree-shaking-friendly and does not expose root entry point as `import * from '@noble/curves'`.
|
||||||
|
Instead, you need to import specific primitives. This is done to ensure small size of your apps.
|
||||||
|
|
||||||
|
### Implementations
|
||||||
|
|
||||||
|
Each curve can be used in the following way:
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
// Common.js and ECMAScript Modules (ESM)
|
import { secp256k1 } from '@noble/curves/secp256k1'; // ECMAScript Modules (ESM) and Common.js
|
||||||
import { secp256k1 } from '@noble/curves/secp256k1';
|
// import { secp256k1 } from 'npm:@noble/curves@1.2.0/secp256k1'; // Deno
|
||||||
|
const priv = secp256k1.utils.randomPrivateKey();
|
||||||
const key = secp256k1.utils.randomPrivateKey();
|
const pub = secp256k1.getPublicKey(priv);
|
||||||
const pub = secp256k1.getPublicKey(key);
|
|
||||||
const msg = new Uint8Array(32).fill(1);
|
const msg = new Uint8Array(32).fill(1);
|
||||||
const sig = secp256k1.sign(msg, key);
|
const sig = secp256k1.sign(msg, priv);
|
||||||
secp256k1.verify(sig, msg, pub) === true;
|
secp256k1.verify(sig, msg, pub) === true;
|
||||||
sig.recoverPublicKey(msg) === pub;
|
|
||||||
const someonesPub = secp256k1.getPublicKey(secp256k1.utils.randomPrivateKey());
|
const privHex = '46c930bc7bb4db7f55da20798697421b98c4175a52c630294d75a84b9c126236'
|
||||||
const shared = secp256k1.getSharedSecret(key, someonesPub);
|
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:
|
||||||
|
|
||||||
```ts
|
```typescript
|
||||||
import { secp256k1 } from '@noble/curves/secp256k1';
|
import { secp256k1, schnorr } from '@noble/curves/secp256k1';
|
||||||
import { ed25519, ed25519ph, ed25519ctx, x25519, RistrettoPoint } from '@noble/curves/ed25519';
|
import { ed25519, ed25519ph, ed25519ctx, x25519, RistrettoPoint } from '@noble/curves/ed25519';
|
||||||
import { ed448, ed448ph, ed448ctx, x448 } from '@noble/curves/ed448';
|
import { ed448, ed448ph, ed448ctx, x448 } from '@noble/curves/ed448';
|
||||||
import { p256 } from '@noble/curves/p256';
|
import { p256 } from '@noble/curves/p256';
|
||||||
@@ -80,88 +88,305 @@ import { bn254 } from '@noble/curves/bn';
|
|||||||
import { jubjub } from '@noble/curves/jubjub';
|
import { jubjub } from '@noble/curves/jubjub';
|
||||||
```
|
```
|
||||||
|
|
||||||
To define a custom curve, check out API below.
|
Weierstrass curves feature recovering public keys from signatures and ECDH key agreement:
|
||||||
|
|
||||||
## API
|
|
||||||
|
|
||||||
- [Overview](#overview)
|
|
||||||
- [abstract/edwards: Twisted Edwards curve](#abstractedwards-twisted-edwards-curve)
|
|
||||||
- [abstract/montgomery: Montgomery curve](#abstractmontgomery-montgomery-curve)
|
|
||||||
- [abstract/weierstrass: Short Weierstrass curve](#abstractweierstrass-short-weierstrass-curve)
|
|
||||||
- [abstract/hash-to-curve: Hashing strings to curve points](#abstracthash-to-curve-hashing-strings-to-curve-points)
|
|
||||||
- [abstract/poseidon: Poseidon hash](#abstractposeidon-poseidon-hash)
|
|
||||||
- [abstract/modular](#abstractmodular)
|
|
||||||
- [abstract/utils](#abstractutils)
|
|
||||||
|
|
||||||
### Overview
|
|
||||||
|
|
||||||
There are following zero-dependency abstract algorithms:
|
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
import { bls } from '@noble/curves/abstract/bls';
|
// extraEntropy https://moderncrypto.org/mail-archive/curves/2017/000925.html
|
||||||
import { twistedEdwards } from '@noble/curves/abstract/edwards';
|
const sigImprovedSecurity = secp256k1.sign(msg, priv, { extraEntropy: true });
|
||||||
import { montgomery } from '@noble/curves/abstract/montgomery';
|
sig.recoverPublicKey(msg) === pub; // public key recovery
|
||||||
import { weierstrass } from '@noble/curves/abstract/weierstrass';
|
const someonesPub = secp256k1.getPublicKey(secp256k1.utils.randomPrivateKey());
|
||||||
import * as mod from '@noble/curves/abstract/modular';
|
const shared = secp256k1.getSharedSecret(priv, someonesPub); // ECDH (elliptic curve diffie-hellman)
|
||||||
import * as utils from '@noble/curves/abstract/utils';
|
|
||||||
```
|
```
|
||||||
|
|
||||||
They allow to define a new curve in a few lines of code:
|
secp256k1 has schnorr signature implementation which follows
|
||||||
|
[BIP340](https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki):
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
import { Fp } from '@noble/curves/abstract/modular';
|
import { schnorr } from '@noble/curves/secp256k1';
|
||||||
import { weierstrass } from '@noble/curves/abstract/weierstrass';
|
const priv = schnorr.utils.randomPrivateKey();
|
||||||
import { hmac } from '@noble/hashes/hmac';
|
const pub = schnorr.getPublicKey(priv);
|
||||||
import { sha256 } from '@noble/hashes/sha256';
|
const msg = new TextEncoder().encode('hello');
|
||||||
import { concatBytes, randomBytes } from '@noble/hashes/utils';
|
const sig = schnorr.sign(msg, priv);
|
||||||
|
const isValid = schnorr.verify(sig, msg, pub);
|
||||||
|
console.log(isValid);
|
||||||
|
```
|
||||||
|
|
||||||
const secp256k1 = weierstrass({
|
ed25519 module has ed25519ctx / ed25519ph variants,
|
||||||
|
x25519 ECDH and [ristretto255](https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-ristretto255-decaf448).
|
||||||
|
It follows [ZIP215](https://zips.z.cash/zip-0215) and [can be used in consensus-critical applications](https://hdevalence.ca/blog/2020-10-04-its-25519am):
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { ed25519 } from '@noble/curves/ed25519';
|
||||||
|
|
||||||
|
// Variants from RFC8032: with context, prehashed
|
||||||
|
import { ed25519ctx, ed25519ph } from '@noble/curves/ed25519';
|
||||||
|
|
||||||
|
// ECDH using curve25519 aka x25519
|
||||||
|
import { x25519 } from '@noble/curves/ed25519';
|
||||||
|
const priv = 'a546e36bf0527c9d3b16154b82465edd62144c0ac1fc5a18506a2244ba449ac4';
|
||||||
|
const pub = 'e6db6867583030db3594c1a424b15f7c726624ec26b3353b10a903a6d0ab1c4c';
|
||||||
|
x25519.getSharedSecret(priv, pub) === x25519.scalarMult(priv, pub);
|
||||||
|
x25519.getPublicKey(priv) === x25519.scalarMultBase(priv);
|
||||||
|
|
||||||
|
// hash-to-curve
|
||||||
|
import { hashToCurve, encodeToCurve } from '@noble/curves/ed25519';
|
||||||
|
|
||||||
|
import { RistrettoPoint } from '@noble/curves/ed25519';
|
||||||
|
const rp = RistrettoPoint.fromHex(
|
||||||
|
'6a493210f7499cd17fecb510ae0cea23a110e8d5b901f8acadd3095c73a3b919'
|
||||||
|
);
|
||||||
|
RistrettoPoint.hashToCurve('Ristretto is traditionally a short shot of espresso coffee');
|
||||||
|
// also has add(), equals(), multiply(), toRawBytes() methods
|
||||||
|
```
|
||||||
|
|
||||||
|
ed448 module is basically the same:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { ed448, ed448ph, ed448ctx, x448 } from '@noble/curves/ed448';
|
||||||
|
import { hashToCurve, encodeToCurve } from '@noble/curves/ed448';
|
||||||
|
```
|
||||||
|
|
||||||
|
BLS12-381 pairing-friendly Barreto-Lynn-Scott elliptic curve construction allows to
|
||||||
|
construct [zk-SNARKs](https://z.cash/technology/zksnarks/) at the 128-bit security
|
||||||
|
and use aggregated, batch-verifiable
|
||||||
|
[threshold signatures](https://medium.com/snigirev.stepan/bls-signatures-better-than-schnorr-5a7fe30ea716),
|
||||||
|
using Boneh-Lynn-Shacham signature scheme.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { bls12_381 as bls } from '@noble/curves/bls12-381';
|
||||||
|
const privateKey = '67d53f170b908cabb9eb326c3c337762d59289a8fec79f7bc9254b584b73265c';
|
||||||
|
const message = '64726e3da8';
|
||||||
|
const publicKey = bls.getPublicKey(privateKey);
|
||||||
|
const signature = bls.sign(message, privateKey);
|
||||||
|
const isValid = bls.verify(signature, message, publicKey);
|
||||||
|
console.log({ publicKey, signature, isValid });
|
||||||
|
|
||||||
|
// Sign 1 msg with 3 keys
|
||||||
|
const privateKeys = [
|
||||||
|
'18f020b98eb798752a50ed0563b079c125b0db5dd0b1060d1c1b47d4a193e1e4',
|
||||||
|
'ed69a8c50cf8c9836be3b67c7eeff416612d45ba39a5c099d48fa668bf558c9c',
|
||||||
|
'16ae669f3be7a2121e17d0c68c05a8f3d6bef21ec0f2315f1d7aec12484e4cf5',
|
||||||
|
];
|
||||||
|
const messages = ['d2', '0d98', '05caf3'];
|
||||||
|
const publicKeys = privateKeys.map(bls.getPublicKey);
|
||||||
|
const signatures2 = privateKeys.map((p) => bls.sign(message, p));
|
||||||
|
const aggPubKey2 = bls.aggregatePublicKeys(publicKeys);
|
||||||
|
const aggSignature2 = bls.aggregateSignatures(signatures2);
|
||||||
|
const isValid2 = bls.verify(aggSignature2, message, aggPubKey2);
|
||||||
|
console.log({ signatures2, aggSignature2, isValid2 });
|
||||||
|
|
||||||
|
// Sign 3 msgs with 3 keys
|
||||||
|
const signatures3 = privateKeys.map((p, i) => bls.sign(messages[i], p));
|
||||||
|
const aggSignature3 = bls.aggregateSignatures(signatures3);
|
||||||
|
const isValid3 = bls.verifyBatch(aggSignature3, messages, publicKeys);
|
||||||
|
console.log({ publicKeys, signatures3, aggSignature3, isValid3 });
|
||||||
|
|
||||||
|
// Pairings
|
||||||
|
// bls.pairing(PointG1, PointG2)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Abstract API
|
||||||
|
|
||||||
|
Abstract API allows to define custom curves. All arithmetics is done with JS bigints over finite fields,
|
||||||
|
which is defined from `modular` sub-module. For scalar multiplication, we use [precomputed tables with w-ary non-adjacent form (wNAF)](https://paulmillr.com/posts/noble-secp256k1-fast-ecc/).
|
||||||
|
Precomputes are enabled for weierstrass and edwards BASE points of a curve. You could precompute any
|
||||||
|
other point (e.g. for ECDH) using `utils.precompute()` method.
|
||||||
|
|
||||||
|
There are following zero-dependency algorithms:
|
||||||
|
|
||||||
|
- [abstract/weierstrass: Short Weierstrass curve](#abstractweierstrass-short-weierstrass-curve)
|
||||||
|
- [abstract/edwards: Twisted Edwards curve](#abstractedwards-twisted-edwards-curve)
|
||||||
|
- [abstract/montgomery: Montgomery curve](#abstractmontgomery-montgomery-curve)
|
||||||
|
- [abstract/hash-to-curve: Hashing strings to curve points](#abstracthash-to-curve-hashing-strings-to-curve-points)
|
||||||
|
- [abstract/poseidon: Poseidon hash](#abstractposeidon-poseidon-hash)
|
||||||
|
- [abstract/modular: Modular arithmetics utilities](#abstractmodular-modular-arithmetics-utilities)
|
||||||
|
- [abstract/utils: General utilities](#abstractutils-general-utilities)
|
||||||
|
|
||||||
|
### abstract/weierstrass: Short Weierstrass curve
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { weierstrass } from '@noble/curves/abstract/weierstrass';
|
||||||
|
```
|
||||||
|
|
||||||
|
Short Weierstrass curve's formula is `y² = x³ + ax + b`. `weierstrass` expects arguments `a`, `b`, field `Fp`, curve order `n`, cofactor `h`
|
||||||
|
and coordinates `Gx`, `Gy` of generator point.
|
||||||
|
|
||||||
|
**`k` generation** is done deterministically, following [RFC6979](https://www.rfc-editor.org/rfc/rfc6979).
|
||||||
|
For this you will need `hmac` & `hash`, which in our implementations is provided by noble-hashes.
|
||||||
|
If you're using different hashing library, make sure to wrap it in the following interface:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export type CHash = {
|
||||||
|
(message: Uint8Array): Uint8Array;
|
||||||
|
blockLen: number;
|
||||||
|
outputLen: number;
|
||||||
|
create(): any;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
**Weierstrass points:**
|
||||||
|
|
||||||
|
1. Exported as `ProjectivePoint`
|
||||||
|
2. Represented in projective (homogeneous) coordinates: (x, y, z) ∋ (x=x/z, y=y/z)
|
||||||
|
3. Use complete exception-free formulas for addition and doubling
|
||||||
|
4. Can be decoded/encoded from/to Uint8Array / hex strings using `ProjectivePoint.fromHex` and `ProjectivePoint#toRawBytes()`
|
||||||
|
5. Have `assertValidity()` which checks for being on-curve
|
||||||
|
6. Have `toAffine()` and `x` / `y` getters which convert to 2d xy affine coordinates
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// T is usually bigint, but can be something else like complex numbers in BLS curves
|
||||||
|
export interface ProjPointType<T> extends Group<ProjPointType<T>> {
|
||||||
|
readonly px: T;
|
||||||
|
readonly py: T;
|
||||||
|
readonly pz: T;
|
||||||
|
multiply(scalar: bigint): ProjPointType<T>;
|
||||||
|
multiplyUnsafe(scalar: bigint): ProjPointType<T>;
|
||||||
|
multiplyAndAddUnsafe(Q: ProjPointType<T>, a: bigint, b: bigint): ProjPointType<T> | undefined;
|
||||||
|
toAffine(iz?: T): AffinePoint<T>;
|
||||||
|
isTorsionFree(): boolean;
|
||||||
|
clearCofactor(): ProjPointType<T>;
|
||||||
|
assertValidity(): void;
|
||||||
|
hasEvenY(): boolean;
|
||||||
|
toRawBytes(isCompressed?: boolean): Uint8Array;
|
||||||
|
toHex(isCompressed?: boolean): string;
|
||||||
|
}
|
||||||
|
// Static methods for 3d XYZ points
|
||||||
|
export interface ProjConstructor<T> extends GroupConstructor<ProjPointType<T>> {
|
||||||
|
new (x: T, y: T, z: T): ProjPointType<T>;
|
||||||
|
fromAffine(p: AffinePoint<T>): ProjPointType<T>;
|
||||||
|
fromHex(hex: Hex): ProjPointType<T>;
|
||||||
|
fromPrivateKey(privateKey: PrivKey): ProjPointType<T>;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**ECDSA signatures** are represented by `Signature` instances and can be described by the interface:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export interface SignatureType {
|
||||||
|
readonly r: bigint;
|
||||||
|
readonly s: bigint;
|
||||||
|
readonly recovery?: number;
|
||||||
|
assertValidity(): void;
|
||||||
|
addRecoveryBit(recovery: number): SignatureType;
|
||||||
|
hasHighS(): boolean;
|
||||||
|
normalizeS(): SignatureType;
|
||||||
|
recoverPublicKey(msgHash: Hex): ProjPointType<bigint>;
|
||||||
|
toCompactRawBytes(): Uint8Array;
|
||||||
|
toCompactHex(): string;
|
||||||
|
// DER-encoded
|
||||||
|
toDERRawBytes(isCompressed?: boolean): Uint8Array;
|
||||||
|
toDERHex(isCompressed?: boolean): string;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Example implementing [secq256k1](https://personaelabs.org/posts/spartan-ecdsa) (NOT secp256k1)
|
||||||
|
[cycle](https://zcash.github.io/halo2/background/curves.html#cycles-of-curves) of secp256k1 with Fp/N flipped.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { weierstrass } from '@noble/curves/abstract/weierstrass';
|
||||||
|
import { Field } from '@noble/curves/abstract/modular'; // finite field, mod arithmetics done over it
|
||||||
|
import { sha256 } from '@noble/hashes/sha256'; // 3rd-party sha256() of type utils.CHash, with blockLen/outputLen
|
||||||
|
import { hmac } from '@noble/hashes/hmac'; // 3rd-party hmac() that will accept sha256()
|
||||||
|
import { concatBytes, randomBytes } from '@noble/hashes/utils'; // 3rd-party utilities
|
||||||
|
const secq256k1 = weierstrass({
|
||||||
|
// secq256k1: cycle of secp256k1 with Fp/N flipped.
|
||||||
a: 0n,
|
a: 0n,
|
||||||
b: 7n,
|
b: 7n,
|
||||||
Fp: Fp(2n ** 256n - 2n ** 32n - 2n ** 9n - 2n ** 8n - 2n ** 7n - 2n ** 6n - 2n ** 4n - 1n),
|
Fp: Field(2n ** 256n - 432420386565659656852420866394968145599n),
|
||||||
n: 2n ** 256n - 432420386565659656852420866394968145599n,
|
n: 2n ** 256n - 2n ** 32n - 2n ** 9n - 2n ** 8n - 2n ** 7n - 2n ** 6n - 2n ** 4n - 1n,
|
||||||
Gx: 55066263022277343669578718895168534326250603453777594175500187360389116729240n,
|
Gx: 55066263022277343669578718895168534326250603453777594175500187360389116729240n,
|
||||||
Gy: 32670510020758816978083085130507043184471273380659243275938904335757337482424n,
|
Gy: 32670510020758816978083085130507043184471273380659243275938904335757337482424n,
|
||||||
hash: sha256,
|
hash: sha256,
|
||||||
hmac: (key: Uint8Array, ...msgs: Uint8Array[]) => hmac(sha256, key, concatBytes(...msgs)),
|
hmac: (key: Uint8Array, ...msgs: Uint8Array[]) => hmac(sha256, key, concatBytes(...msgs)),
|
||||||
randomBytes,
|
randomBytes,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// All curves expose same generic interface.
|
||||||
|
const priv = secq256k1.utils.randomPrivateKey();
|
||||||
|
secq256k1.getPublicKey(priv); // Convert private key to public.
|
||||||
|
const sig = secq256k1.sign(msg, priv); // Sign msg with private key.
|
||||||
|
secq256k1.verify(sig, msg, priv); // Verify if sig is correct.
|
||||||
|
|
||||||
|
const point = secq256k1.Point.BASE; // Elliptic curve Point class and BASE point static var.
|
||||||
|
point.add(point).equals(point.double()); // add(), equals(), double() methods
|
||||||
|
point.subtract(point).equals(secq256k1.Point.ZERO); // subtract() method, ZERO static var
|
||||||
|
point.negate(); // Flips point over x/y coordinate.
|
||||||
|
point.multiply(31415n); // Multiplication of Point by scalar.
|
||||||
|
|
||||||
|
point.assertValidity(); // Checks for being on-curve
|
||||||
|
point.toAffine(); // Converts to 2d affine xy coordinates
|
||||||
|
|
||||||
|
secq256k1.CURVE.n;
|
||||||
|
secq256k1.CURVE.Fp.mod();
|
||||||
|
secq256k1.CURVE.hash();
|
||||||
```
|
```
|
||||||
|
|
||||||
- To initialize new curve, you must specify its variables, order (number of points on curve), field prime (over which the modular division would be done)
|
`weierstrass()` returns `CurveFn`:
|
||||||
- All curves expose same generic interface:
|
|
||||||
- `getPublicKey()`, `sign()`, `verify()` functions
|
```ts
|
||||||
- `Point` conforming to `Group` interface with add/multiply/double/negate/add/equals methods
|
export type CurveFn = {
|
||||||
- `CURVE` object with curve variables like `Gx`, `Gy`, `Fp` (field), `n` (order)
|
CURVE: ReturnType<typeof validateOpts>;
|
||||||
- `utils` object with `randomPrivateKey()`, `mod()`, `invert()` methods (`mod CURVE.P`)
|
getPublicKey: (privateKey: PrivKey, isCompressed?: boolean) => Uint8Array;
|
||||||
- All arithmetics is done with JS bigints over finite fields, which is defined from `modular` sub-module
|
getSharedSecret: (privateA: PrivKey, publicB: Hex, isCompressed?: boolean) => Uint8Array;
|
||||||
- Many features require hashing, which is not provided. `@noble/hashes` can be used for this purpose.
|
sign: (msgHash: Hex, privKey: PrivKey, opts?: SignOpts) => SignatureType;
|
||||||
Any other library must conform to the CHash interface:
|
verify: (
|
||||||
```ts
|
signature: Hex | SignatureType,
|
||||||
export type CHash = {
|
msgHash: Hex,
|
||||||
(message: Uint8Array): Uint8Array;
|
publicKey: Hex,
|
||||||
blockLen: number;
|
opts?: { lowS?: boolean; prehash?: boolean }
|
||||||
outputLen: number;
|
) => boolean;
|
||||||
create(): any;
|
ProjectivePoint: ProjectivePointConstructor;
|
||||||
|
Signature: SignatureConstructor;
|
||||||
|
utils: {
|
||||||
|
isValidPrivateKey(privateKey: PrivKey): boolean;
|
||||||
|
randomPrivateKey: () => Uint8Array;
|
||||||
};
|
};
|
||||||
```
|
};
|
||||||
- w-ary non-adjacent form (wNAF) method with constant-time adjustments is used for point multiplication.
|
```
|
||||||
It is possible to enable precomputes for edwards & weierstrass curves.
|
|
||||||
Precomputes are calculated once (takes ~20-40ms), after that most `G` base point multiplications:
|
|
||||||
for example, `getPublicKey()`, `sign()` and similar methods - would be much faster.
|
|
||||||
Use `curve.utils.precompute()` to adjust precomputation window size
|
|
||||||
- You could use optional special params to tune performance:
|
|
||||||
- `Fp({sqrt})` square root calculation, used for point decompression
|
|
||||||
- `endo` endomorphism options for Koblitz curves
|
|
||||||
|
|
||||||
### abstract/edwards: Twisted Edwards curve
|
### abstract/edwards: Twisted Edwards curve
|
||||||
|
|
||||||
Twisted Edwards curve's formula is: ax² + y² = 1 + dx²y².
|
Twisted Edwards curve's formula is `ax² + y² = 1 + dx²y²`. You must specify `a`, `d`, field `Fp`, order `n`, cofactor `h`
|
||||||
|
and coordinates `Gx`, `Gy` of generator point.
|
||||||
|
|
||||||
- You must specify curve params `a`, `d`, field `Fp`, order `n`, cofactor `h` and coordinates `Gx`, `Gy` of generator point
|
For EdDSA signatures, `hash` param required. `adjustScalarBytes` which instructs how to change private scalars could be specified.
|
||||||
- For EdDSA signatures, params `hash` is also required. `adjustScalarBytes` which instructs how to change private scalars could be specified
|
|
||||||
|
|
||||||
```typescript
|
**Edwards points:**
|
||||||
|
|
||||||
|
1. Exported as `ExtendedPoint`
|
||||||
|
2. Represented in extended coordinates: (x, y, z, t) ∋ (x=x/z, y=y/z)
|
||||||
|
3. Use complete exception-free formulas for addition and doubling
|
||||||
|
4. Can be decoded/encoded from/to Uint8Array / hex strings using `ExtendedPoint.fromHex` and `ExtendedPoint#toRawBytes()`
|
||||||
|
5. Have `assertValidity()` which checks for being on-curve
|
||||||
|
6. Have `toAffine()` and `x` / `y` getters which convert to 2d xy affine coordinates
|
||||||
|
7. Have `isTorsionFree()`, `clearCofactor()` and `isSmallOrder()` utilities to handle torsions
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export interface ExtPointType extends Group<ExtPointType> {
|
||||||
|
readonly ex: bigint;
|
||||||
|
readonly ey: bigint;
|
||||||
|
readonly ez: bigint;
|
||||||
|
readonly et: bigint;
|
||||||
|
assertValidity(): void;
|
||||||
|
multiply(scalar: bigint): ExtPointType;
|
||||||
|
multiplyUnsafe(scalar: bigint): ExtPointType;
|
||||||
|
isSmallOrder(): boolean;
|
||||||
|
isTorsionFree(): boolean;
|
||||||
|
clearCofactor(): ExtPointType;
|
||||||
|
toAffine(iz?: bigint): AffinePoint<bigint>;
|
||||||
|
}
|
||||||
|
// Static methods of Extended Point with coordinates in X, Y, Z, T
|
||||||
|
export interface ExtPointConstructor extends GroupConstructor<ExtPointType> {
|
||||||
|
new (x: bigint, y: bigint, z: bigint, t: bigint): ExtPointType;
|
||||||
|
fromAffine(p: AffinePoint<bigint>): ExtPointType;
|
||||||
|
fromHex(hex: Hex): ExtPointType;
|
||||||
|
fromPrivateKey(privateKey: Hex): ExtPointType;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Example implementing edwards25519:
|
||||||
|
|
||||||
|
```ts
|
||||||
import { twistedEdwards } from '@noble/curves/abstract/edwards';
|
import { twistedEdwards } from '@noble/curves/abstract/edwards';
|
||||||
import { div } from '@noble/curves/abstract/modular';
|
import { div } from '@noble/curves/abstract/modular';
|
||||||
import { sha512 } from '@noble/hashes/sha512';
|
import { sha512 } from '@noble/hashes/sha512';
|
||||||
@@ -177,18 +402,13 @@ const ed25519 = twistedEdwards({
|
|||||||
hash: sha512,
|
hash: sha512,
|
||||||
randomBytes,
|
randomBytes,
|
||||||
adjustScalarBytes(bytes) {
|
adjustScalarBytes(bytes) {
|
||||||
// optional in general, mandatory in ed25519
|
// optional; but mandatory in ed25519
|
||||||
bytes[0] &= 248;
|
bytes[0] &= 248;
|
||||||
bytes[31] &= 127;
|
bytes[31] &= 127;
|
||||||
bytes[31] |= 64;
|
bytes[31] |= 64;
|
||||||
return bytes;
|
return bytes;
|
||||||
},
|
},
|
||||||
} as const);
|
} as const);
|
||||||
const key = ed25519.utils.randomPrivateKey();
|
|
||||||
const pub = ed25519.getPublicKey(key);
|
|
||||||
const msg = new TextEncoder().encode('hello world'); // strings not accepted, must be Uint8Array
|
|
||||||
const sig = ed25519.sign(msg, key);
|
|
||||||
ed25519.verify(sig, msg, pub) === true;
|
|
||||||
```
|
```
|
||||||
|
|
||||||
`twistedEdwards()` returns `CurveFn` of following type:
|
`twistedEdwards()` returns `CurveFn` of following type:
|
||||||
@@ -198,8 +418,7 @@ export type CurveFn = {
|
|||||||
CURVE: ReturnType<typeof validateOpts>;
|
CURVE: ReturnType<typeof validateOpts>;
|
||||||
getPublicKey: (privateKey: PrivKey, isCompressed?: boolean) => Uint8Array;
|
getPublicKey: (privateKey: PrivKey, isCompressed?: boolean) => Uint8Array;
|
||||||
sign: (message: Hex, privateKey: Hex) => Uint8Array;
|
sign: (message: Hex, privateKey: Hex) => Uint8Array;
|
||||||
verify: (sig: SigType, message: Hex, publicKey: PubKey) => boolean;
|
verify: (sig: SigType, message: Hex, publicKey: PubKey, context?: Hex) => boolean;
|
||||||
Point: PointConstructor;
|
|
||||||
ExtendedPoint: ExtendedPointConstructor;
|
ExtendedPoint: ExtendedPointConstructor;
|
||||||
Signature: SignatureConstructor;
|
Signature: SignatureConstructor;
|
||||||
utils: {
|
utils: {
|
||||||
@@ -217,9 +436,7 @@ export type CurveFn = {
|
|||||||
|
|
||||||
### abstract/montgomery: Montgomery curve
|
### abstract/montgomery: Montgomery curve
|
||||||
|
|
||||||
For now the module only contains methods for x-only ECDH on Curve25519 / Curve448 from RFC7748.
|
The module contains methods for x-only ECDH on Curve25519 / Curve448 from RFC7748. Proper Elliptic Curve Points are not implemented yet.
|
||||||
|
|
||||||
Proper Elliptic Curve Points are not implemented yet.
|
|
||||||
|
|
||||||
You must specify curve field, `a24` special variable, `montgomeryBits`, `nByteLength`, and coordinate `u` of generator point.
|
You must specify curve field, `a24` special variable, `montgomeryBits`, `nByteLength`, and coordinate `u` of generator point.
|
||||||
|
|
||||||
@@ -246,138 +463,51 @@ const x25519 = montgomery({
|
|||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
### abstract/weierstrass: Short Weierstrass curve
|
|
||||||
|
|
||||||
Short Weierstrass curve's formula is: y² = x³ + ax + b. Uses deterministic ECDSA from RFC6979. You can also specify `extraEntropy` in `sign()`.
|
|
||||||
|
|
||||||
- You must specify curve params: `a`, `b`, field `Fp`, order `n`, cofactor `h` and coordinates `Gx`, `Gy` of generator point
|
|
||||||
- For ECDSA, you must specify `hash`, `hmac`. It is also possible to recover keys from signatures
|
|
||||||
- For ECDH, use `getSharedSecret(privKeyA, pubKeyB)`
|
|
||||||
- Optional params are `lowS` (default value) and `endo` (endomorphism)
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
import { Fp } from '@noble/curves/abstract/modular';
|
|
||||||
import { weierstrass } from '@noble/curves/abstract/weierstrass'; // Short Weierstrass curve
|
|
||||||
import { sha256 } from '@noble/hashes/sha256';
|
|
||||||
import { hmac } from '@noble/hashes/hmac';
|
|
||||||
import { concatBytes, randomBytes } from '@noble/hashes/utils';
|
|
||||||
|
|
||||||
const secp256k1 = weierstrass({
|
|
||||||
a: 0n,
|
|
||||||
b: 7n,
|
|
||||||
Fp: Fp(2n ** 256n - 2n ** 32n - 2n ** 9n - 2n ** 8n - 2n ** 7n - 2n ** 6n - 2n ** 4n - 1n),
|
|
||||||
n: 2n ** 256n - 432420386565659656852420866394968145599n,
|
|
||||||
Gx: 55066263022277343669578718895168534326250603453777594175500187360389116729240n,
|
|
||||||
Gy: 32670510020758816978083085130507043184471273380659243275938904335757337482424n,
|
|
||||||
hash: sha256,
|
|
||||||
hmac: (k: Uint8Array, ...msgs: Uint8Array[]) => hmac(sha256, key, concatBytes(...msgs)),
|
|
||||||
randomBytes,
|
|
||||||
|
|
||||||
// Optional params
|
|
||||||
h: 1n, // Cofactor
|
|
||||||
lowS: true, // Allow only low-S signatures by default in sign() and verify()
|
|
||||||
endo: {
|
|
||||||
// Endomorphism options for Koblitz curve
|
|
||||||
// Beta param
|
|
||||||
beta: 0x7ae96a2b657c07106e64479eac3434e99cf0497512f58995c1396c28719501een,
|
|
||||||
// Split scalar k into k1, k2
|
|
||||||
splitScalar: (k: bigint) => {
|
|
||||||
// return { k1neg: true, k1: 512n, k2neg: false, k2: 448n };
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// Usage
|
|
||||||
const key = secp256k1.utils.randomPrivateKey();
|
|
||||||
const pub = secp256k1.getPublicKey(key);
|
|
||||||
const msg = randomBytes(32);
|
|
||||||
const sig = secp256k1.sign(msg, key);
|
|
||||||
secp256k1.verify(sig, msg, pub); // true
|
|
||||||
sig.recoverPublicKey(msg); // == pub
|
|
||||||
const someonesPubkey = secp256k1.getPublicKey(secp256k1.utils.randomPrivateKey());
|
|
||||||
const shared = secp256k1.getSharedSecret(key, someonesPubkey);
|
|
||||||
```
|
|
||||||
|
|
||||||
`weierstrass()` returns `CurveFn`:
|
|
||||||
|
|
||||||
```ts
|
|
||||||
export type CurveFn = {
|
|
||||||
CURVE: ReturnType<typeof validateOpts>;
|
|
||||||
getPublicKey: (privateKey: PrivKey, isCompressed?: boolean) => Uint8Array;
|
|
||||||
getSharedSecret: (privateA: PrivKey, publicB: Hex, isCompressed?: boolean) => Uint8Array;
|
|
||||||
sign: (msgHash: Hex, privKey: PrivKey, opts?: SignOpts) => SignatureType;
|
|
||||||
verify: (
|
|
||||||
signature: Hex | SignatureType,
|
|
||||||
msgHash: Hex,
|
|
||||||
publicKey: Hex,
|
|
||||||
opts?: { lowS?: boolean }
|
|
||||||
) => boolean;
|
|
||||||
Point: PointConstructor;
|
|
||||||
ProjectivePoint: ProjectivePointConstructor;
|
|
||||||
Signature: SignatureConstructor;
|
|
||||||
utils: {
|
|
||||||
isValidPrivateKey(privateKey: PrivKey): boolean;
|
|
||||||
hashToPrivateKey: (hash: Hex) => Uint8Array;
|
|
||||||
randomPrivateKey: () => Uint8Array;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
### abstract/hash-to-curve: Hashing strings to curve points
|
### abstract/hash-to-curve: Hashing strings to curve points
|
||||||
|
|
||||||
The module allows to hash arbitrary strings to elliptic curve points.
|
The module allows to hash arbitrary strings to elliptic curve points. Implements [hash-to-curve v11](https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11).
|
||||||
|
|
||||||
- `expand_message_xmd` [(spec)](https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-5.4.1) produces a uniformly random byte string using a cryptographic hash function H that outputs b bits..
|
`expand_message_xmd` [(spec)](https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-5.4.1) produces a uniformly random byte string using a cryptographic hash function H that outputs b bits.
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
function expand_message_xmd(
|
function expand_message_xmd(msg: Uint8Array, DST: Uint8Array, lenInBytes: number, H: CHash): Uint8Array;
|
||||||
msg: Uint8Array, DST: Uint8Array, lenInBytes: number, H: CHash
|
function expand_message_xof(msg: Uint8Array, DST: Uint8Array, lenInBytes: number, k: number, H: CHash): Uint8Array;
|
||||||
): 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
|
_ `msg` a byte string containing the message to hash
|
||||||
* `count` the number of elements of F to output
|
_ `count` the number of elements of F to output
|
||||||
* `options` `{DST: string, p: bigint, m: number, k: number, expand: 'xmd' | 'xof', hash: H}`
|
_ `options` `{DST: string, p: bigint, m: number, k: number, expand: 'xmd' | 'xof', hash: H}`
|
||||||
* Returns `[u_0, ..., u_(count - 1)]`, a list of field elements.
|
_ Returns `[u_0, ..., u_(count - 1)]`, a list of field elements.
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
function hash_to_field(msg: Uint8Array, count: number, options: htfOpts): bigint[][];
|
function hash_to_field(msg: Uint8Array, count: number, options: htfOpts): bigint[][];
|
||||||
type htfOpts = {
|
type htfOpts = {
|
||||||
// DST: a domain separation tag
|
DST: string; // a domain separation tag defined in section 2.2.5
|
||||||
// defined in section 2.2.5
|
// p: the characteristic of F
|
||||||
DST: string;
|
// where F is a finite field of characteristic p and order q = p^m
|
||||||
// p: the characteristic of F
|
p: bigint;
|
||||||
// where F is a finite field of characteristic p and order q = p^m
|
// m: the extension degree of F, m >= 1
|
||||||
p: bigint;
|
// where F is a finite field of characteristic p and order q = p^m
|
||||||
// m: the extension degree of F, m >= 1
|
m: number;
|
||||||
// where F is a finite field of characteristic p and order q = p^m
|
k: number; // the target security level for the suite in bits defined in section 5.1
|
||||||
m: number;
|
expand?: 'xmd' | 'xof'; // option to use a message that has already been processed by expand_message_xmd
|
||||||
// k: the target security level for the suite in bits
|
// Hash functions for: expand_message_xmd is appropriate for use with a
|
||||||
// defined in section 5.1
|
// wide range of hash functions, including SHA-2, SHA-3, BLAKE2, and others.
|
||||||
k: number;
|
// BBS+ uses blake2: https://github.com/hyperledger/aries-framework-go/issues/2247
|
||||||
// option to use a message that has already been processed by
|
// TODO: verify that hash is shake if expand==='xof' via types
|
||||||
// expand_message_xmd
|
hash: CHash;
|
||||||
expand?: 'xmd' | 'xof';
|
};
|
||||||
// Hash functions for: expand_message_xmd is appropriate for use with a
|
```
|
||||||
// wide range of hash functions, including SHA-2, SHA-3, BLAKE2, and others.
|
|
||||||
// BBS+ uses blake2: https://github.com/hyperledger/aries-framework-go/issues/2247
|
|
||||||
// TODO: verify that hash is shake if expand==='xof' via types
|
|
||||||
hash: CHash;
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
### abstract/poseidon: Poseidon hash
|
### abstract/poseidon: Poseidon hash
|
||||||
|
|
||||||
Implements [Poseidon](https://www.poseidon-hash.info) ZK-friendly hash.
|
Implements [Poseidon](https://www.poseidon-hash.info) ZK-friendly hash.
|
||||||
|
|
||||||
There are many poseidon instances with different constants. We don't provide them,
|
There are many poseidon variants with different constants.
|
||||||
but we provide ability to specify them manually. For actual usage, check out
|
We don't provide them: you should construct them manually.
|
||||||
stark curve source code.
|
The only variant provided resides in `stark` module: inspect it for proper usage.
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
import { poseidon } from '@noble/curves/abstract/poseidon';
|
import { poseidon } from '@noble/curves/abstract/poseidon';
|
||||||
@@ -395,27 +525,35 @@ type PoseidonOpts = {
|
|||||||
const instance = poseidon(opts: PoseidonOpts);
|
const instance = poseidon(opts: PoseidonOpts);
|
||||||
```
|
```
|
||||||
|
|
||||||
### abstract/modular
|
### abstract/bls
|
||||||
|
|
||||||
Modular arithmetics utilities.
|
The module abstracts BLS (Barreto-Lynn-Scott) primitives. In theory you should be able to write BLS12-377, BLS24,
|
||||||
|
and others with it.
|
||||||
|
|
||||||
```typescript
|
### abstract/modular: Modular arithmetics utilities
|
||||||
import { Fp, mod, invert, div, invertBatch, sqrt } from '@noble/curves/abstract/modular';
|
|
||||||
const fp = Fp(2n ** 255n - 19n); // Finite field over 2^255-19
|
The module also contains useful `hashToPrivateScalar` method which allows to create
|
||||||
fp.mul(591n, 932n);
|
scalars (e.g. private keys) with the modulo bias being neglible. It follows
|
||||||
fp.pow(481n, 11024858120n);
|
FIPS 186 B.4.1. Requires at least 40 bytes of input for 32-byte private key.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import * as mod from '@noble/curves/abstract/modular';
|
||||||
|
const fp = mod.Field(2n ** 255n - 19n); // Finite field over 2^255-19
|
||||||
|
fp.mul(591n, 932n); // multiplication
|
||||||
|
fp.pow(481n, 11024858120n); // exponentiation
|
||||||
|
fp.div(5n, 17n); // division: 5/17 mod 2^255-19 == 5 * invert(17)
|
||||||
|
fp.sqrt(21n); // square root
|
||||||
|
|
||||||
// Generic non-FP utils are also available
|
// Generic non-FP utils are also available
|
||||||
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
|
||||||
invert(17n, 10n); // invert(17) mod 10; modular multiplicative inverse
|
mod.invert(17n, 10n); // invert(17) mod 10; modular multiplicative inverse
|
||||||
div(5n, 17n, 10n); // 5/17 mod 10 == 5 * invert(17) mod 10; division
|
mod.invertBatch([1n, 2n, 4n], 21n); // => [1n, 11n, 16n] in one inversion
|
||||||
invertBatch([1n, 2n, 4n], 21n); // => [1n, 11n, 16n] in one inversion
|
mod.hashToPrivateScalar(sha512_of_something, secp256r1.n);
|
||||||
sqrt(21n, 73n); // √21 mod 73; square root
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### abstract/utils
|
### abstract/utils: General utilities
|
||||||
|
|
||||||
```typescript
|
```ts
|
||||||
import * as utils from '@noble/curves/abstract/utils';
|
import * as utils from '@noble/curves/abstract/utils';
|
||||||
|
|
||||||
utils.bytesToHex(Uint8Array.from([0xde, 0xad, 0xbe, 0xef]));
|
utils.bytesToHex(Uint8Array.from([0xde, 0xad, 0xbe, 0xef]));
|
||||||
@@ -428,7 +566,6 @@ utils.numberToBytesLE(123n);
|
|||||||
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);
|
||||||
utils.hashToPrivateScalar(sha512_of_something, secp256r1.n);
|
|
||||||
utils.equalBytes(Uint8Array.from([0xde]), Uint8Array.from([0xde]));
|
utils.equalBytes(Uint8Array.from([0xde]), Uint8Array.from([0xde]));
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -436,72 +573,125 @@ utils.equalBytes(Uint8Array.from([0xde]), Uint8Array.from([0xde]));
|
|||||||
|
|
||||||
The library had no prior security audit.
|
The library had no prior security audit.
|
||||||
|
|
||||||
[Timing attack](https://en.wikipedia.org/wiki/Timing_attack) considerations: _JIT-compiler_ and _Garbage Collector_ make "constant time" extremely hard to achieve in a scripting language. Which means _any other JS library can't have constant-timeness_. Even statically typed Rust, a language without GC, [makes it harder to achieve constant-time](https://www.chosenplaintext.ca/open-source/rust-timing-shield/security) for some cases. If your goal is absolute security, don't use any JS lib — including bindings to native ones. Use low-level libraries & languages. Nonetheless we're targetting algorithmic constant time.
|
[Timing attack](https://en.wikipedia.org/wiki/Timing_attack) considerations: we are using non-CT bigints. However, _JIT-compiler_ and _Garbage Collector_ make "constant time" extremely hard to achieve in a scripting language. Which means _any other JS library can't have constant-timeness_. Even statically typed Rust, a language without GC, [makes it harder to achieve constant-time](https://www.chosenplaintext.ca/open-source/rust-timing-shield/security) for some cases. If your goal is absolute security, don't use any JS lib — including bindings to native ones. Use low-level libraries & languages. Nonetheless we're targetting algorithmic constant time.
|
||||||
|
|
||||||
We consider infrastructure attacks like rogue NPM modules very important; that's why it's crucial to minimize the amount of 3rd-party dependencies & native bindings. If your app uses 500 dependencies, any dep could get hacked and you'll be downloading malware with every `npm install`. Our goal is to minimize this attack vector.
|
We consider infrastructure attacks like rogue NPM modules very important; that's why it's crucial to minimize the amount of 3rd-party dependencies & native bindings. If your app uses 500 dependencies, any dep could get hacked and you'll be downloading malware with every `npm install`. Our goal is to minimize this attack vector. As for devDependencies used by the library:
|
||||||
|
|
||||||
|
- `@scure` base, bip32, bip39 (used in tests), micro-bmark (benchmark), micro-should (testing) are developed by us
|
||||||
|
and follow the same practices such as: minimal library size, auditability, signed releases
|
||||||
|
- prettier (linter), fast-check (property-based testing),
|
||||||
|
typescript versions are locked and rarely updated. Every update is checked with `npm-diff`.
|
||||||
|
The packages are big, which makes it hard to audit their source code thoroughly and fully.
|
||||||
|
- They are only used if you clone the git repo and want to add some feature to it. End-users won't use them.
|
||||||
|
|
||||||
## Speed
|
## Speed
|
||||||
|
|
||||||
Benchmark results on Apple M2 with node v18.10:
|
Benchmark results on Apple M2 with node v19:
|
||||||
|
|
||||||
```
|
```
|
||||||
getPublicKey
|
secp256k1
|
||||||
secp256k1 x 5,241 ops/sec @ 190μs/op
|
init x 58 ops/sec @ 17ms/op
|
||||||
P256 x 7,993 ops/sec @ 125μs/op
|
getPublicKey x 5,640 ops/sec @ 177μs/op
|
||||||
P384 x 3,819 ops/sec @ 261μs/op
|
sign x 3,909 ops/sec @ 255μs/op
|
||||||
P521 x 2,074 ops/sec @ 481μs/op
|
verify x 780 ops/sec @ 1ms/op
|
||||||
ed25519 x 8,390 ops/sec @ 119μs/op
|
getSharedSecret x 465 ops/sec @ 2ms/op
|
||||||
ed448 x 3,224 ops/sec @ 310μs/op
|
recoverPublicKey x 740 ops/sec @ 1ms/op
|
||||||
sign
|
schnorr.sign x 597 ops/sec @ 1ms/op
|
||||||
secp256k1 x 3,934 ops/sec @ 254μs/op
|
schnorr.verify x 775 ops/sec @ 1ms/op
|
||||||
P256 x 5,327 ops/sec @ 187μs/op
|
|
||||||
P384 x 2,728 ops/sec @ 366μs/op
|
P256
|
||||||
P521 x 1,594 ops/sec @ 626μs/op
|
init x 31 ops/sec @ 31ms/op
|
||||||
ed25519 x 4,233 ops/sec @ 236μs/op
|
getPublicKey x 5,607 ops/sec @ 178μs/op
|
||||||
ed448 x 1,561 ops/sec @ 640μs/op
|
sign x 3,930 ops/sec @ 254μs/op
|
||||||
verify
|
verify x 540 ops/sec @ 1ms/op
|
||||||
secp256k1 x 731 ops/sec @ 1ms/op
|
|
||||||
P256 x 806 ops/sec @ 1ms/op
|
P384
|
||||||
P384 x 353 ops/sec @ 2ms/op
|
init x 15 ops/sec @ 63ms/op
|
||||||
P521 x 171 ops/sec @ 5ms/op
|
getPublicKey x 2,622 ops/sec @ 381μs/op
|
||||||
ed25519 x 860 ops/sec @ 1ms/op
|
sign x 1,913 ops/sec @ 522μs/op
|
||||||
ed448 x 313 ops/sec @ 3ms/op
|
verify x 222 ops/sec @ 4ms/op
|
||||||
getSharedSecret
|
|
||||||
secp256k1 x 445 ops/sec @ 2ms/op
|
P521
|
||||||
recoverPublicKey
|
init x 8 ops/sec @ 119ms/op
|
||||||
secp256k1 x 732 ops/sec @ 1ms/op
|
getPublicKey x 1,371 ops/sec @ 729μs/op
|
||||||
==== bls12-381 ====
|
sign x 1,090 ops/sec @ 917μs/op
|
||||||
getPublicKey x 817 ops/sec @ 1ms/op
|
verify x 118 ops/sec @ 8ms/op
|
||||||
sign x 50 ops/sec @ 19ms/op
|
|
||||||
|
ed25519
|
||||||
|
init x 47 ops/sec @ 20ms/op
|
||||||
|
getPublicKey x 9,414 ops/sec @ 106μs/op
|
||||||
|
sign x 4,516 ops/sec @ 221μs/op
|
||||||
|
verify x 912 ops/sec @ 1ms/op
|
||||||
|
|
||||||
|
ed448
|
||||||
|
init x 17 ops/sec @ 56ms/op
|
||||||
|
getPublicKey x 3,363 ops/sec @ 297μs/op
|
||||||
|
sign x 1,615 ops/sec @ 619μs/op
|
||||||
|
verify x 319 ops/sec @ 3ms/op
|
||||||
|
|
||||||
|
stark
|
||||||
|
init x 35 ops/sec @ 28ms/op
|
||||||
|
pedersen x 884 ops/sec @ 1ms/op
|
||||||
|
poseidon x 8,598 ops/sec @ 116μs/op
|
||||||
|
verify x 528 ops/sec @ 1ms/op
|
||||||
|
|
||||||
|
bls12-381
|
||||||
|
init x 32 ops/sec @ 30ms/op
|
||||||
|
getPublicKey 1-bit x 858 ops/sec @ 1ms/op
|
||||||
|
getPublicKey x 858 ops/sec @ 1ms/op
|
||||||
|
sign x 49 ops/sec @ 20ms/op
|
||||||
verify x 34 ops/sec @ 28ms/op
|
verify x 34 ops/sec @ 28ms/op
|
||||||
pairing x 89 ops/sec @ 11ms/op
|
pairing x 94 ops/sec @ 10ms/op
|
||||||
==== stark ====
|
aggregatePublicKeys/8 x 116 ops/sec @ 8ms/op
|
||||||
pedersen
|
aggregatePublicKeys/32 x 31 ops/sec @ 31ms/op
|
||||||
old x 85 ops/sec @ 11ms/op
|
aggregatePublicKeys/128 x 7 ops/sec @ 125ms/op
|
||||||
noble x 1,216 ops/sec @ 822μs/op
|
aggregateSignatures/8 x 45 ops/sec @ 22ms/op
|
||||||
verify
|
aggregateSignatures/32 x 11 ops/sec @ 84ms/op
|
||||||
old x 302 ops/sec @ 3ms/op
|
aggregateSignatures/128 x 3 ops/sec @ 332ms/opp
|
||||||
noble x 698 ops/sec @ 1ms/op
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## In the wild
|
||||||
|
|
||||||
|
Elliptic curve calculator: [paulmillr.com/ecc](https://paulmillr.com/ecc).
|
||||||
|
|
||||||
|
- secp256k1
|
||||||
|
- [btc-signer](https://github.com/paulmillr/micro-btc-signer), [eth-signer](https://github.com/paulmillr/micro-eth-signer)
|
||||||
|
- ed25519
|
||||||
|
- [sol-signer](https://github.com/paulmillr/micro-sol-signer)
|
||||||
|
- BLS12-381
|
||||||
|
- Threshold sigs demo [genthresh.com](https://genthresh.com)
|
||||||
|
- BBS signatures [github.com/Wind4Greg/BBS-Draft-Checks](https://github.com/Wind4Greg/BBS-Draft-Checks) following [draft-irtf-cfrg-bbs-signatures-latest](https://identity.foundation/bbs-signature/draft-irtf-cfrg-bbs-signatures.html)
|
||||||
|
|
||||||
## Upgrading
|
## Upgrading
|
||||||
|
|
||||||
Differences from @noble/secp256k1 1.7:
|
If you're coming from single-feature noble packages, the following changes need to be kept in mind:
|
||||||
|
|
||||||
1. Different double() formula (but same addition)
|
- 2d affine (x, y) points have been removed to reduce complexity and improve speed
|
||||||
2. Different sqrt() function
|
- Removed `number` support as a type for private keys, `bigint` is still supported
|
||||||
3. DRBG supports outputLen bigger than outputLen of hmac
|
- `mod`, `invert` are no longer present in `utils`: use `@noble/curves/abstract/modular`
|
||||||
4. Support for different hash functions
|
|
||||||
|
|
||||||
Differences from @noble/ed25519 1.7:
|
Upgrading from @noble/secp256k1 1.7:
|
||||||
|
|
||||||
1. Variable field element lengths between EDDSA/ECDH:
|
- Compressed (33-byte) public keys are now returned by default, instead of uncompressed
|
||||||
EDDSA (RFC8032) is 456 bits / 57 bytes, ECDH (RFC7748) is 448 bits / 56 bytes
|
- Methods are now synchronous. Setting `secp.utils.hmacSha256` is no longer required
|
||||||
2. Different addition formula (doubling is same)
|
- `sign()`
|
||||||
3. uvRatio differs between curves (half-expected, not only pow fn changes)
|
- `der`, `recovered` options were removed
|
||||||
4. Point decompression code is different (unexpected), now using generalized formula
|
- `canonical` was renamed to `lowS`
|
||||||
5. Domain function was no-op for ed25519, but adds some data even with empty context for ed448
|
- Return type is now `{ r: bigint, s: bigint, recovery: number }` instance of `Signature`
|
||||||
|
- `verify()`
|
||||||
|
- `strict` was renamed to `lowS`
|
||||||
|
- `recoverPublicKey()`: moved to sig instance `Signature#recoverPublicKey(msgHash)`
|
||||||
|
- `Point` was removed: use `ProjectivePoint` in xyz coordinates
|
||||||
|
- `utils`: Many methods were removed, others were moved to `schnorr` namespace
|
||||||
|
|
||||||
|
Upgrading from @noble/ed25519 1.7:
|
||||||
|
|
||||||
|
- Methods are now synchronous. Setting `secp.utils.hmacSha256` is no longer required
|
||||||
|
- ed25519ph, ed25519ctx
|
||||||
|
- `Point` was removed: use `ExtendedPoint` in xyzt coordinates
|
||||||
|
- `Signature` was removed
|
||||||
|
- `getSharedSecret` was removed: use separate x25519 sub-module
|
||||||
|
- `bigint` is no longer allowed in `getPublicKey`, `sign`, `verify`. Reason: ed25519 is LE, can lead to bugs
|
||||||
|
|
||||||
## Contributing & testing
|
## Contributing & testing
|
||||||
|
|
||||||
|
|||||||
7
benchmark/_shared.js
Normal file
7
benchmark/_shared.js
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
export function generateData(curve) {
|
||||||
|
const priv = curve.utils.randomPrivateKey();
|
||||||
|
const pub = curve.getPublicKey(priv);
|
||||||
|
const msg = curve.utils.randomPrivateKey();
|
||||||
|
const sig = curve.sign(msg, priv);
|
||||||
|
return { priv, pub, msg, sig };
|
||||||
|
}
|
||||||
52
benchmark/bls.js
Normal file
52
benchmark/bls.js
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
import { readFileSync } from 'fs';
|
||||||
|
import { mark, run } from 'micro-bmark';
|
||||||
|
import { bls12_381 as bls } from '../lib/bls12-381.js';
|
||||||
|
|
||||||
|
const G2_VECTORS = readFileSync('../test/bls12-381/bls12-381-g2-test-vectors.txt', 'utf-8')
|
||||||
|
.trim()
|
||||||
|
.split('\n')
|
||||||
|
.map((l) => l.split(':'));
|
||||||
|
|
||||||
|
run(async () => {
|
||||||
|
console.log(`\x1b[36mbls12-381\x1b[0m`);
|
||||||
|
let p1, p2, sig;
|
||||||
|
await mark('init', 1, () => {
|
||||||
|
p1 =
|
||||||
|
bls.G1.ProjectivePoint.BASE.multiply(
|
||||||
|
0x28b90deaf189015d3a325908c5e0e4bf00f84f7e639b056ff82d7e70b6eede4cn
|
||||||
|
);
|
||||||
|
p2 =
|
||||||
|
bls.G2.ProjectivePoint.BASE.multiply(
|
||||||
|
0x28b90deaf189015d3a325908c5e0e4bf00f84f7e639b056ff82d7e70b6eede4dn
|
||||||
|
);
|
||||||
|
bls.pairing(p1, p2);
|
||||||
|
});
|
||||||
|
const priv = '28b90deaf189015d3a325908c5e0e4bf00f84f7e639b056ff82d7e70b6eede4c';
|
||||||
|
sig = bls.sign('09', priv);
|
||||||
|
const pubs = G2_VECTORS.map((v) => bls.getPublicKey(v[0]));
|
||||||
|
const sigs = G2_VECTORS.map((v) => v[2]);
|
||||||
|
const pub = bls.getPublicKey(priv);
|
||||||
|
const pub512 = pubs.slice(0, 512); // .map(bls.PointG1.fromHex)
|
||||||
|
const pub32 = pub512.slice(0, 32);
|
||||||
|
const pub128 = pub512.slice(0, 128);
|
||||||
|
const pub2048 = pub512.concat(pub512, pub512, pub512);
|
||||||
|
const sig512 = sigs.slice(0, 512); // .map(bls.PointG2.fromSignature);
|
||||||
|
const sig32 = sig512.slice(0, 32);
|
||||||
|
const sig128 = sig512.slice(0, 128);
|
||||||
|
const sig2048 = sig512.concat(sig512, sig512, sig512);
|
||||||
|
await mark('getPublicKey 1-bit', 1000, () => bls.getPublicKey('2'.padStart(64, '0')));
|
||||||
|
await mark('getPublicKey', 1000, () => bls.getPublicKey(priv));
|
||||||
|
await mark('sign', 50, () => bls.sign('09', priv));
|
||||||
|
await mark('verify', 50, () => bls.verify(sig, '09', pub));
|
||||||
|
await mark('pairing', 100, () => bls.pairing(p1, p2));
|
||||||
|
await mark('aggregatePublicKeys/8', 100, () => bls.aggregatePublicKeys(pubs.slice(0, 8)));
|
||||||
|
await mark('aggregatePublicKeys/32', 50, () => bls.aggregatePublicKeys(pub32));
|
||||||
|
await mark('aggregatePublicKeys/128', 20, () => bls.aggregatePublicKeys(pub128));
|
||||||
|
await mark('aggregatePublicKeys/512', 10, () => bls.aggregatePublicKeys(pub512));
|
||||||
|
await mark('aggregatePublicKeys/2048', 5, () => bls.aggregatePublicKeys(pub2048));
|
||||||
|
await mark('aggregateSignatures/8', 100, () => bls.aggregateSignatures(sigs.slice(0, 8)));
|
||||||
|
await mark('aggregateSignatures/32', 50, () => bls.aggregateSignatures(sig32));
|
||||||
|
await mark('aggregateSignatures/128', 20, () => bls.aggregateSignatures(sig128));
|
||||||
|
await mark('aggregateSignatures/512', 10, () => bls.aggregateSignatures(sig512));
|
||||||
|
await mark('aggregateSignatures/2048', 5, () => bls.aggregateSignatures(sig2048));
|
||||||
|
});
|
||||||
23
benchmark/curves.js
Normal file
23
benchmark/curves.js
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import { run, mark, utils } from 'micro-bmark';
|
||||||
|
import { generateData } from './_shared.js';
|
||||||
|
import { P256 } from '../lib/p256.js';
|
||||||
|
import { P384 } from '../lib/p384.js';
|
||||||
|
import { P521 } from '../lib/p521.js';
|
||||||
|
import { ed25519 } from '../lib/ed25519.js';
|
||||||
|
import { ed448 } from '../lib/ed448.js';
|
||||||
|
|
||||||
|
run(async () => {
|
||||||
|
const RAM = false
|
||||||
|
for (let kv of Object.entries({ P256, P384, P521, ed25519, ed448 })) {
|
||||||
|
const [name, curve] = kv;
|
||||||
|
console.log();
|
||||||
|
console.log(`\x1b[36m${name}\x1b[0m`);
|
||||||
|
if (RAM) utils.logMem();
|
||||||
|
await mark('init', 1, () => curve.utils.precompute(8));
|
||||||
|
const d = generateData(curve);
|
||||||
|
await mark('getPublicKey', 5000, () => curve.getPublicKey(d.priv));
|
||||||
|
await mark('sign', 5000, () => curve.sign(d.msg, d.priv));
|
||||||
|
await mark('verify', 500, () => curve.verify(d.sig, d.msg, d.pub));
|
||||||
|
if (RAM) utils.logMem();
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -1,424 +0,0 @@
|
|||||||
import * as bench from 'micro-bmark';
|
|
||||||
const { run, mark } = bench; // or bench.mark
|
|
||||||
import { readFileSync } from 'fs';
|
|
||||||
|
|
||||||
// Curves
|
|
||||||
import { secp256k1 } from '../lib/secp256k1.js';
|
|
||||||
import { P256 } from '../lib/p256.js';
|
|
||||||
import { P384 } from '../lib/p384.js';
|
|
||||||
import { P521 } from '../lib/p521.js';
|
|
||||||
import { ed25519 } from '../lib/ed25519.js';
|
|
||||||
import { ed448 } from '../lib/ed448.js';
|
|
||||||
import { bls12_381 as bls } from '../lib/bls12-381.js';
|
|
||||||
|
|
||||||
// Others
|
|
||||||
import { hmac } from '@noble/hashes/hmac';
|
|
||||||
import { sha256 } from '@noble/hashes/sha256';
|
|
||||||
import { sha512 } from '@noble/hashes/sha512';
|
|
||||||
|
|
||||||
import * as old_secp from '@noble/secp256k1';
|
|
||||||
import * as old_bls from '@noble/bls12-381';
|
|
||||||
import { concatBytes, hexToBytes } from '@noble/hashes/utils';
|
|
||||||
|
|
||||||
import * as starkwareCrypto from '@starkware-industries/starkware-crypto-utils';
|
|
||||||
import * as stark from '../lib/stark.js';
|
|
||||||
|
|
||||||
old_secp.utils.sha256Sync = (...msgs) =>
|
|
||||||
sha256
|
|
||||||
.create()
|
|
||||||
.update(concatBytes(...msgs))
|
|
||||||
.digest();
|
|
||||||
old_secp.utils.hmacSha256Sync = (key, ...msgs) =>
|
|
||||||
hmac
|
|
||||||
.create(sha256, key)
|
|
||||||
.update(concatBytes(...msgs))
|
|
||||||
.digest();
|
|
||||||
import * as noble_ed25519 from '@noble/ed25519';
|
|
||||||
noble_ed25519.utils.sha512Sync = (...m) => sha512(concatBytes(...m));
|
|
||||||
|
|
||||||
// BLS
|
|
||||||
const G2_VECTORS = readFileSync('../test/bls12-381/bls12-381-g2-test-vectors.txt', 'utf-8')
|
|
||||||
.trim()
|
|
||||||
.split('\n')
|
|
||||||
.map((l) => l.split(':'));
|
|
||||||
let p1, p2, oldp1, oldp2;
|
|
||||||
// /BLS
|
|
||||||
|
|
||||||
for (let item of [secp256k1, ed25519, ed448, P256, P384, P521]) item.utils.precompute(8);
|
|
||||||
for (let item of [old_secp, noble_ed25519]) item.utils.precompute(8);
|
|
||||||
|
|
||||||
const ONLY_NOBLE = process.argv[2] === 'noble';
|
|
||||||
|
|
||||||
function generateData(namespace) {
|
|
||||||
const priv = namespace.utils.randomPrivateKey();
|
|
||||||
const pub = namespace.getPublicKey(priv);
|
|
||||||
const msg = namespace.utils.randomPrivateKey();
|
|
||||||
const sig = namespace.sign(msg, priv);
|
|
||||||
return { priv, pub, msg, sig };
|
|
||||||
}
|
|
||||||
|
|
||||||
export const CURVES = {
|
|
||||||
secp256k1: {
|
|
||||||
data: () => {
|
|
||||||
return generateData(secp256k1);
|
|
||||||
},
|
|
||||||
getPublicKey1: {
|
|
||||||
samples: 10000,
|
|
||||||
secp256k1_old: () => old_secp.getPublicKey(3n),
|
|
||||||
secp256k1: () => secp256k1.getPublicKey(3n),
|
|
||||||
},
|
|
||||||
getPublicKey255: {
|
|
||||||
samples: 10000,
|
|
||||||
secp256k1_old: () => old_secp.getPublicKey(2n ** 255n - 1n),
|
|
||||||
secp256k1: () => secp256k1.getPublicKey(2n ** 255n - 1n),
|
|
||||||
},
|
|
||||||
sign: {
|
|
||||||
samples: 5000,
|
|
||||||
secp256k1_old: ({ msg, priv }) => old_secp.signSync(msg, priv),
|
|
||||||
secp256k1: ({ msg, priv }) => secp256k1.sign(msg, priv).toCompactRawBytes(),
|
|
||||||
},
|
|
||||||
verify: {
|
|
||||||
samples: 1000,
|
|
||||||
secp256k1_old: ({ sig, msg, pub }) => {
|
|
||||||
return old_secp.verify(new old_secp.Signature(sig.r, sig.s), msg, pub);
|
|
||||||
},
|
|
||||||
secp256k1: ({ sig, msg, pub }) => secp256k1.verify(sig, msg, pub),
|
|
||||||
},
|
|
||||||
getSharedSecret: {
|
|
||||||
samples: 1000,
|
|
||||||
secp256k1_old: ({ pub, priv }) => old_secp.getSharedSecret(priv, pub),
|
|
||||||
secp256k1: ({ pub, priv }) => secp256k1.getSharedSecret(priv, pub),
|
|
||||||
},
|
|
||||||
recoverPublicKey: {
|
|
||||||
samples: 1000,
|
|
||||||
secp256k1_old: ({ sig, msg }) =>
|
|
||||||
old_secp.recoverPublicKey(msg, new old_secp.Signature(sig.r, sig.s), sig.recovery),
|
|
||||||
secp256k1: ({ sig, msg }) => sig.recoverPublicKey(msg),
|
|
||||||
},
|
|
||||||
// hashToCurve: {
|
|
||||||
// samples: 500,
|
|
||||||
// noble: () => secp256k1.Point.hashToCurve('abcd'),
|
|
||||||
// },
|
|
||||||
},
|
|
||||||
ed25519: {
|
|
||||||
data: () => {
|
|
||||||
function to32Bytes(numOrStr) {
|
|
||||||
const hex = typeof numOrStr === 'string' ? numOrStr : numOrStr.toString(16);
|
|
||||||
return hexToBytes(hex.padStart(64, '0'));
|
|
||||||
}
|
|
||||||
const priv = to32Bytes(0x9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60n);
|
|
||||||
const pub = noble_ed25519.sync.getPublicKey(priv);
|
|
||||||
const msg = to32Bytes('deadbeefdeadbeefdeadbeefdeadbeefdeadbeef');
|
|
||||||
const sig = noble_ed25519.sync.sign(msg, priv);
|
|
||||||
return { pub, priv, msg, sig };
|
|
||||||
},
|
|
||||||
getPublicKey: {
|
|
||||||
samples: 10000,
|
|
||||||
old: () => noble_ed25519.sync.getPublicKey(noble_ed25519.utils.randomPrivateKey()),
|
|
||||||
noble: () => ed25519.getPublicKey(ed25519.utils.randomPrivateKey()),
|
|
||||||
},
|
|
||||||
sign: {
|
|
||||||
samples: 5000,
|
|
||||||
old: ({ msg, priv }) => noble_ed25519.sync.sign(msg, priv),
|
|
||||||
noble: ({ msg, priv }) => ed25519.sign(msg, priv),
|
|
||||||
},
|
|
||||||
verify: {
|
|
||||||
samples: 1000,
|
|
||||||
old: ({ sig, msg, pub }) => noble_ed25519.sync.verify(sig, msg, pub),
|
|
||||||
noble: ({ sig, msg, pub }) => ed25519.verify(sig, msg, pub),
|
|
||||||
},
|
|
||||||
// hashToCurve: {
|
|
||||||
// samples: 500,
|
|
||||||
// noble: () => ed25519.Point.hashToCurve('abcd'),
|
|
||||||
// },
|
|
||||||
},
|
|
||||||
ed448: {
|
|
||||||
data: () => {
|
|
||||||
const priv = ed448.utils.randomPrivateKey();
|
|
||||||
const pub = ed448.getPublicKey(priv);
|
|
||||||
const msg = ed448.utils.randomPrivateKey();
|
|
||||||
const sig = ed448.sign(msg, priv);
|
|
||||||
return { priv, pub, msg, sig };
|
|
||||||
},
|
|
||||||
getPublicKey: {
|
|
||||||
samples: 5000,
|
|
||||||
noble: () => ed448.getPublicKey(ed448.utils.randomPrivateKey()),
|
|
||||||
},
|
|
||||||
sign: {
|
|
||||||
samples: 2500,
|
|
||||||
noble: ({ msg, priv }) => ed448.sign(msg, priv),
|
|
||||||
},
|
|
||||||
verify: {
|
|
||||||
samples: 500,
|
|
||||||
noble: ({ sig, msg, pub }) => ed448.verify(sig, msg, pub),
|
|
||||||
},
|
|
||||||
// hashToCurve: {
|
|
||||||
// samples: 500,
|
|
||||||
// noble: () => ed448.Point.hashToCurve('abcd'),
|
|
||||||
// },
|
|
||||||
},
|
|
||||||
nist: {
|
|
||||||
data: () => {
|
|
||||||
return { p256: generateData(P256), p384: generateData(P384), p521: generateData(P521) };
|
|
||||||
},
|
|
||||||
getPublicKey: {
|
|
||||||
samples: 2500,
|
|
||||||
P256: () => P256.getPublicKey(P256.utils.randomPrivateKey()),
|
|
||||||
P384: () => P384.getPublicKey(P384.utils.randomPrivateKey()),
|
|
||||||
P521: () => P521.getPublicKey(P521.utils.randomPrivateKey()),
|
|
||||||
},
|
|
||||||
sign: {
|
|
||||||
samples: 1000,
|
|
||||||
P256: ({ p256: { msg, priv } }) => P256.sign(msg, priv),
|
|
||||||
P384: ({ p384: { msg, priv } }) => P384.sign(msg, priv),
|
|
||||||
P521: ({ p521: { msg, priv } }) => P521.sign(msg, priv),
|
|
||||||
},
|
|
||||||
verify: {
|
|
||||||
samples: 250,
|
|
||||||
P256: ({ p256: { sig, msg, pub } }) => P256.verify(sig, msg, pub),
|
|
||||||
P384: ({ p384: { sig, msg, pub } }) => P384.verify(sig, msg, pub),
|
|
||||||
P521: ({ p521: { sig, msg, pub } }) => P521.verify(sig, msg, pub),
|
|
||||||
},
|
|
||||||
// hashToCurve: {
|
|
||||||
// samples: 500,
|
|
||||||
// P256: () => P256.Point.hashToCurve('abcd'),
|
|
||||||
// P384: () => P384.Point.hashToCurve('abcd'),
|
|
||||||
// P521: () => P521.Point.hashToCurve('abcd'),
|
|
||||||
// },
|
|
||||||
},
|
|
||||||
stark: {
|
|
||||||
data: () => {
|
|
||||||
const priv = '2dccce1da22003777062ee0870e9881b460a8b7eca276870f57c601f182136c';
|
|
||||||
const msg = 'c465dd6b1bbffdb05442eb17f5ca38ad1aa78a6f56bf4415bdee219114a47';
|
|
||||||
const pub = stark.getPublicKey(priv);
|
|
||||||
const sig = stark.sign(msg, priv);
|
|
||||||
|
|
||||||
const privateKey = '2dccce1da22003777062ee0870e9881b460a8b7eca276870f57c601f182136c';
|
|
||||||
const msgHash = 'c465dd6b1bbffdb05442eb17f5ca38ad1aa78a6f56bf4415bdee219114a47';
|
|
||||||
const keyPair = starkwareCrypto.default.ec.keyFromPrivate(privateKey, 'hex');
|
|
||||||
const publicKeyStark = starkwareCrypto.default.ec.keyFromPublic(
|
|
||||||
keyPair.getPublic(true, 'hex'),
|
|
||||||
'hex'
|
|
||||||
);
|
|
||||||
|
|
||||||
return { priv, sig, msg, pub, publicKeyStark, msgHash, keyPair };
|
|
||||||
},
|
|
||||||
pedersen: {
|
|
||||||
samples: 500,
|
|
||||||
old: () => {
|
|
||||||
return starkwareCrypto.default.pedersen([
|
|
||||||
'3d937c035c878245caf64531a5756109c53068da139362728feb561405371cb',
|
|
||||||
'208a0a10250e382e1e4bbe2880906c2791bf6275695e02fbbc6aeff9cd8b31a',
|
|
||||||
]);
|
|
||||||
},
|
|
||||||
noble: () => {
|
|
||||||
return stark.pedersen(
|
|
||||||
'3d937c035c878245caf64531a5756109c53068da139362728feb561405371cb',
|
|
||||||
'208a0a10250e382e1e4bbe2880906c2791bf6275695e02fbbc6aeff9cd8b31a'
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
poseidon: {
|
|
||||||
samples: 2000,
|
|
||||||
noble: () => {
|
|
||||||
return stark.poseidonHash(
|
|
||||||
0x3d937c035c878245caf64531a5756109c53068da139362728feb561405371cbn,
|
|
||||||
0x208a0a10250e382e1e4bbe2880906c2791bf6275695e02fbbc6aeff9cd8b31an
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
verify: {
|
|
||||||
samples: 500,
|
|
||||||
old: ({ publicKeyStark, msgHash, keyPair }) => {
|
|
||||||
return starkwareCrypto.default.verify(
|
|
||||||
publicKeyStark,
|
|
||||||
msgHash,
|
|
||||||
starkwareCrypto.default.sign(keyPair, msgHash)
|
|
||||||
);
|
|
||||||
},
|
|
||||||
noble: ({ priv, msg, pub }) => {
|
|
||||||
return stark.verify(stark.sign(msg, priv), msg, pub);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
'bls12-381': {
|
|
||||||
data: async () => {
|
|
||||||
const priv = '28b90deaf189015d3a325908c5e0e4bf00f84f7e639b056ff82d7e70b6eede4c';
|
|
||||||
const pubs = G2_VECTORS.map((v) => bls.getPublicKey(v[0]));
|
|
||||||
const sigs = G2_VECTORS.map((v) => v[2]);
|
|
||||||
const pub = bls.getPublicKey(priv);
|
|
||||||
const pub512 = pubs.slice(0, 512); // .map(bls.PointG1.fromHex)
|
|
||||||
const pub32 = pub512.slice(0, 32);
|
|
||||||
const pub128 = pub512.slice(0, 128);
|
|
||||||
const pub2048 = pub512.concat(pub512, pub512, pub512);
|
|
||||||
const sig512 = sigs.slice(0, 512); // .map(bls.PointG2.fromSignature);
|
|
||||||
const sig32 = sig512.slice(0, 32);
|
|
||||||
const sig128 = sig512.slice(0, 128);
|
|
||||||
const sig2048 = sig512.concat(sig512, sig512, sig512);
|
|
||||||
return {
|
|
||||||
priv,
|
|
||||||
pubs,
|
|
||||||
sigs,
|
|
||||||
pub,
|
|
||||||
pub512,
|
|
||||||
pub32,
|
|
||||||
pub128,
|
|
||||||
pub2048,
|
|
||||||
sig32,
|
|
||||||
sig128,
|
|
||||||
sig512,
|
|
||||||
sig2048,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
init: {
|
|
||||||
samples: 1,
|
|
||||||
old: () => {
|
|
||||||
oldp1 =
|
|
||||||
old_bls.PointG1.BASE.multiply(
|
|
||||||
0x28b90deaf189015d3a325908c5e0e4bf00f84f7e639b056ff82d7e70b6eede4cn
|
|
||||||
);
|
|
||||||
oldp2 =
|
|
||||||
old_bls.PointG2.BASE.multiply(
|
|
||||||
0x28b90deaf189015d3a325908c5e0e4bf00f84f7e639b056ff82d7e70b6eede4dn
|
|
||||||
);
|
|
||||||
old_bls.pairing(oldp1, oldp2);
|
|
||||||
},
|
|
||||||
noble: () => {
|
|
||||||
p1 =
|
|
||||||
bls.G1.ProjectivePoint.BASE.multiply(
|
|
||||||
0x28b90deaf189015d3a325908c5e0e4bf00f84f7e639b056ff82d7e70b6eede4cn
|
|
||||||
);
|
|
||||||
p2 =
|
|
||||||
bls.G2.ProjectivePoint.BASE.multiply(
|
|
||||||
0x28b90deaf189015d3a325908c5e0e4bf00f84f7e639b056ff82d7e70b6eede4dn
|
|
||||||
);
|
|
||||||
bls.pairing(p1, p2);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
'getPublicKey (1-bit)': {
|
|
||||||
samples: 1000,
|
|
||||||
old: () => old_bls.getPublicKey('2'.padStart(64, '0')),
|
|
||||||
noble: () => bls.getPublicKey('2'.padStart(64, '0')),
|
|
||||||
},
|
|
||||||
getPublicKey: {
|
|
||||||
samples: 1000,
|
|
||||||
old: ({ priv }) => old_bls.getPublicKey(priv),
|
|
||||||
noble: ({ priv }) => bls.getPublicKey(priv),
|
|
||||||
},
|
|
||||||
sign: {
|
|
||||||
samples: 50,
|
|
||||||
old: ({ priv }) => old_bls.sign('09', priv),
|
|
||||||
noble: ({ priv }) => bls.sign('09', priv),
|
|
||||||
},
|
|
||||||
verify: {
|
|
||||||
samples: 50,
|
|
||||||
old: ({ pub }) =>
|
|
||||||
old_bls.verify(
|
|
||||||
'8647aa9680cd0cdf065b94e818ff2bb948cc97838bcee987b9bc1b76d0a0a6e0d85db4e9d75aaedfc79d4ea2733a21ae0579014de7636dd2943d45b87c82b1c66a289006b0b9767921bb8edd3f6c5c5dec0d54cd65f61513113c50cc977849e5',
|
|
||||||
'09',
|
|
||||||
pub
|
|
||||||
),
|
|
||||||
noble: ({ pub }) =>
|
|
||||||
bls.verify(
|
|
||||||
'8647aa9680cd0cdf065b94e818ff2bb948cc97838bcee987b9bc1b76d0a0a6e0d85db4e9d75aaedfc79d4ea2733a21ae0579014de7636dd2943d45b87c82b1c66a289006b0b9767921bb8edd3f6c5c5dec0d54cd65f61513113c50cc977849e5',
|
|
||||||
'09',
|
|
||||||
pub
|
|
||||||
),
|
|
||||||
},
|
|
||||||
pairing: {
|
|
||||||
samples: 100,
|
|
||||||
old: () => old_bls.pairing(oldp1, oldp2),
|
|
||||||
noble: () => bls.pairing(p1, p2),
|
|
||||||
},
|
|
||||||
// 'hashToCurve/G1': {
|
|
||||||
// samples: 500,
|
|
||||||
// old: () => old_bls.PointG1.hashToCurve('abcd'),
|
|
||||||
// noble: () => bls.hashToCurve.G1.hashToCurve('abcd'),
|
|
||||||
// },
|
|
||||||
// 'hashToCurve/G2': {
|
|
||||||
// samples: 200,
|
|
||||||
// old: () => old_bls.PointG2.hashToCurve('abcd'),
|
|
||||||
// noble: () => bls.hashToCurve.G2.hashToCurve('abcd'),
|
|
||||||
// },
|
|
||||||
// SLOW PART
|
|
||||||
// Requires points which we cannot init before (data fn same for all)
|
|
||||||
// await mark('sign/nc', 30, () => bls.sign(msgp, priv));
|
|
||||||
// await mark('verify/nc', 30, () => bls.verify(sigp, msgp, pubp));
|
|
||||||
'aggregatePublicKeys/8': {
|
|
||||||
samples: 100,
|
|
||||||
old: ({ pubs }) => old_bls.aggregatePublicKeys(pubs.slice(0, 8)),
|
|
||||||
noble: ({ pubs }) => bls.aggregatePublicKeys(pubs.slice(0, 8)),
|
|
||||||
},
|
|
||||||
'aggregatePublicKeys/32': {
|
|
||||||
samples: 50,
|
|
||||||
old: ({ pub32 }) => old_bls.aggregatePublicKeys(pub32.map(old_bls.PointG1.fromHex)),
|
|
||||||
noble: ({ pub32 }) => bls.aggregatePublicKeys(pub32.map(bls.G1.ProjectivePoint.fromHex)),
|
|
||||||
},
|
|
||||||
'aggregatePublicKeys/128': {
|
|
||||||
samples: 20,
|
|
||||||
old: ({ pub128 }) => old_bls.aggregatePublicKeys(pub128.map(old_bls.PointG1.fromHex)),
|
|
||||||
noble: ({ pub128 }) => bls.aggregatePublicKeys(pub128.map(bls.G1.ProjectivePoint.fromHex)),
|
|
||||||
},
|
|
||||||
'aggregatePublicKeys/512': {
|
|
||||||
samples: 10,
|
|
||||||
old: ({ pub512 }) => old_bls.aggregatePublicKeys(pub512.map(old_bls.PointG1.fromHex)),
|
|
||||||
noble: ({ pub512 }) => bls.aggregatePublicKeys(pub512.map(bls.G1.ProjectivePoint.fromHex)),
|
|
||||||
},
|
|
||||||
'aggregatePublicKeys/2048': {
|
|
||||||
samples: 5,
|
|
||||||
old: ({ pub2048 }) => old_bls.aggregatePublicKeys(pub2048.map(old_bls.PointG1.fromHex)),
|
|
||||||
noble: ({ pub2048 }) => bls.aggregatePublicKeys(pub2048.map(bls.G1.ProjectivePoint.fromHex)),
|
|
||||||
},
|
|
||||||
'aggregateSignatures/8': {
|
|
||||||
samples: 50,
|
|
||||||
old: ({ sigs }) => old_bls.aggregateSignatures(sigs.slice(0, 8)),
|
|
||||||
noble: ({ sigs }) => bls.aggregateSignatures(sigs.slice(0, 8)),
|
|
||||||
},
|
|
||||||
'aggregateSignatures/32': {
|
|
||||||
samples: 10,
|
|
||||||
old: ({ sig32 }) => old_bls.aggregateSignatures(sig32.map(old_bls.PointG2.fromSignature)),
|
|
||||||
noble: ({ sig32 }) => bls.aggregateSignatures(sig32.map(bls.Signature.decode)),
|
|
||||||
},
|
|
||||||
'aggregateSignatures/128': {
|
|
||||||
samples: 5,
|
|
||||||
old: ({ sig128 }) => old_bls.aggregateSignatures(sig128.map(old_bls.PointG2.fromSignature)),
|
|
||||||
noble: ({ sig128 }) => bls.aggregateSignatures(sig128.map(bls.Signature.decode)),
|
|
||||||
},
|
|
||||||
'aggregateSignatures/512': {
|
|
||||||
samples: 3,
|
|
||||||
old: ({ sig512 }) => old_bls.aggregateSignatures(sig512.map(old_bls.PointG2.fromSignature)),
|
|
||||||
noble: ({ sig512 }) => bls.aggregateSignatures(sig512.map(bls.Signature.decode)),
|
|
||||||
},
|
|
||||||
'aggregateSignatures/2048': {
|
|
||||||
samples: 2,
|
|
||||||
old: ({ sig2048 }) => old_bls.aggregateSignatures(sig2048.map(old_bls.PointG2.fromSignature)),
|
|
||||||
noble: ({ sig2048 }) => bls.aggregateSignatures(sig2048.map(bls.Signature.decode)),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const main = () =>
|
|
||||||
run(async () => {
|
|
||||||
for (const [name, curve] of Object.entries(CURVES)) {
|
|
||||||
console.log(`==== ${name} ====`);
|
|
||||||
const data = await curve.data();
|
|
||||||
for (const [fnName, libs] of Object.entries(curve)) {
|
|
||||||
if (fnName === 'data') continue;
|
|
||||||
const samples = libs.samples;
|
|
||||||
console.log(` - ${fnName} (samples: ${samples})`);
|
|
||||||
for (const [lib, fn] of Object.entries(libs)) {
|
|
||||||
if (lib === 'samples') continue;
|
|
||||||
if (ONLY_NOBLE && lib !== 'noble') continue;
|
|
||||||
await mark(` ${lib}`, samples, () => fn(data));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Log current RAM
|
|
||||||
bench.logMem();
|
|
||||||
});
|
|
||||||
|
|
||||||
// ESM is broken.
|
|
||||||
import url from 'url';
|
|
||||||
if (import.meta.url === url.pathToFileURL(process.argv[1]).href) {
|
|
||||||
main();
|
|
||||||
}
|
|
||||||
@@ -1,26 +1,22 @@
|
|||||||
{
|
{
|
||||||
"name": "benchmark",
|
"name": "benchmark",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"description": "benchmarks",
|
"description": "benchmarks",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"bench": "node index.js"
|
"bench": "node index.js"
|
||||||
},
|
},
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
"author": "",
|
"author": "",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"micro-bmark": "0.2.1"
|
"micro-bmark": "0.3.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@noble/bls12-381": "^1.4.0",
|
"@noble/hashes": "^1.1.5",
|
||||||
"@noble/ed25519": "^1.7.1",
|
"@starkware-industries/starkware-crypto-utils": "^0.0.2",
|
||||||
"@noble/hashes": "^1.1.5",
|
"elliptic": "^6.5.4"
|
||||||
"@noble/secp256k1": "^1.7.0",
|
}
|
||||||
"@starkware-industries/starkware-crypto-utils": "^0.0.2",
|
|
||||||
"calculate-correlation": "^1.2.3",
|
|
||||||
"elliptic": "^6.5.4"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
22
benchmark/secp256k1.js
Normal file
22
benchmark/secp256k1.js
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import { run, mark, utils } from 'micro-bmark';
|
||||||
|
import { secp256k1, schnorr } from '../lib/secp256k1.js';
|
||||||
|
import { generateData } from './_shared.js';
|
||||||
|
|
||||||
|
run(async () => {
|
||||||
|
const RAM = false;
|
||||||
|
if (RAM) utils.logMem();
|
||||||
|
console.log(`\x1b[36msecp256k1\x1b[0m`);
|
||||||
|
await mark('init', 1, () => secp256k1.utils.precompute(8));
|
||||||
|
const d = generateData(secp256k1);
|
||||||
|
await mark('getPublicKey', 10000, () => secp256k1.getPublicKey(d.priv));
|
||||||
|
await mark('sign', 10000, () => secp256k1.sign(d.msg, d.priv));
|
||||||
|
await mark('verify', 1000, () => secp256k1.verify(d.sig, d.msg, d.pub));
|
||||||
|
const pub2 = secp256k1.getPublicKey(secp256k1.utils.randomPrivateKey());
|
||||||
|
await mark('getSharedSecret', 1000, () => secp256k1.getSharedSecret(d.priv, pub2));
|
||||||
|
await mark('recoverPublicKey', 1000, () => d.sig.recoverPublicKey(d.msg));
|
||||||
|
const s = schnorr.sign(d.msg, d.priv);
|
||||||
|
const spub = schnorr.getPublicKey(d.priv);
|
||||||
|
await mark('schnorr.sign', 1000, () => schnorr.sign(d.msg, d.priv));
|
||||||
|
await mark('schnorr.verify', 1000, () => schnorr.verify(s, d.msg, spub));
|
||||||
|
if (RAM) utils.logMem();
|
||||||
|
});
|
||||||
56
benchmark/stark.js
Normal file
56
benchmark/stark.js
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
import { run, mark, compare, utils } from 'micro-bmark';
|
||||||
|
import * as starkwareCrypto from '@starkware-industries/starkware-crypto-utils';
|
||||||
|
import * as stark from '../lib/stark.js';
|
||||||
|
|
||||||
|
run(async () => {
|
||||||
|
const RAM = false;
|
||||||
|
if (RAM) utils.logMem();
|
||||||
|
console.log(`\x1b[36mstark\x1b[0m`);
|
||||||
|
await mark('init', 1, () => stark.utils.precompute(8));
|
||||||
|
const d = (() => {
|
||||||
|
const priv = '2dccce1da22003777062ee0870e9881b460a8b7eca276870f57c601f182136c';
|
||||||
|
const msg = 'c465dd6b1bbffdb05442eb17f5ca38ad1aa78a6f56bf4415bdee219114a47';
|
||||||
|
const pub = stark.getPublicKey(priv);
|
||||||
|
const sig = stark.sign(msg, priv);
|
||||||
|
|
||||||
|
const privateKey = '2dccce1da22003777062ee0870e9881b460a8b7eca276870f57c601f182136c';
|
||||||
|
const msgHash = 'c465dd6b1bbffdb05442eb17f5ca38ad1aa78a6f56bf4415bdee219114a47';
|
||||||
|
const keyPair = starkwareCrypto.default.ec.keyFromPrivate(privateKey, 'hex');
|
||||||
|
const publicKeyStark = starkwareCrypto.default.ec.keyFromPublic(
|
||||||
|
keyPair.getPublic(true, 'hex'),
|
||||||
|
'hex'
|
||||||
|
);
|
||||||
|
return { priv, sig, msg, pub, publicKeyStark, msgHash, keyPair };
|
||||||
|
})();
|
||||||
|
await compare('pedersen', 500, {
|
||||||
|
old: () => {
|
||||||
|
return starkwareCrypto.default.pedersen([
|
||||||
|
'3d937c035c878245caf64531a5756109c53068da139362728feb561405371cb',
|
||||||
|
'208a0a10250e382e1e4bbe2880906c2791bf6275695e02fbbc6aeff9cd8b31a',
|
||||||
|
]);
|
||||||
|
},
|
||||||
|
noble: () => {
|
||||||
|
return stark.pedersen(
|
||||||
|
'3d937c035c878245caf64531a5756109c53068da139362728feb561405371cb',
|
||||||
|
'208a0a10250e382e1e4bbe2880906c2791bf6275695e02fbbc6aeff9cd8b31a'
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
await mark('poseidon', 10000, () => stark.poseidonHash(
|
||||||
|
0x3d937c035c878245caf64531a5756109c53068da139362728feb561405371cbn,
|
||||||
|
0x208a0a10250e382e1e4bbe2880906c2791bf6275695e02fbbc6aeff9cd8b31an
|
||||||
|
));
|
||||||
|
await compare('verify', 500, {
|
||||||
|
old: () => {
|
||||||
|
return starkwareCrypto.default.verify(
|
||||||
|
d.publicKeyStark,
|
||||||
|
d.msgHash,
|
||||||
|
starkwareCrypto.default.sign(d.keyPair, d.msgHash)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
noble: () => {
|
||||||
|
return stark.verify(stark.sign(d.msg, d.priv), d.msg, d.pub);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (RAM) utils.logMem();
|
||||||
|
});
|
||||||
178
package-lock.json
generated
Normal file
178
package-lock.json
generated
Normal file
@@ -0,0 +1,178 @@
|
|||||||
|
{
|
||||||
|
"name": "@noble/curves",
|
||||||
|
"version": "0.7.0",
|
||||||
|
"lockfileVersion": 3,
|
||||||
|
"requires": true,
|
||||||
|
"packages": {
|
||||||
|
"": {
|
||||||
|
"name": "@noble/curves",
|
||||||
|
"version": "0.7.0",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "individual",
|
||||||
|
"url": "https://paulmillr.com/funding/"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@noble/hashes": "1.2.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@scure/bip32": "~1.1.5",
|
||||||
|
"@scure/bip39": "~1.1.1",
|
||||||
|
"@types/node": "18.11.3",
|
||||||
|
"fast-check": "3.0.0",
|
||||||
|
"micro-bmark": "0.3.1",
|
||||||
|
"micro-should": "0.4.0",
|
||||||
|
"prettier": "2.8.3",
|
||||||
|
"typescript": "4.7.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@noble/hashes": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.2.0.tgz",
|
||||||
|
"integrity": "sha512-FZfhjEDbT5GRswV3C6uvLPHMiVD6lQBmpoX5+eSiPaMTXte/IKqI5dykDxzZB/WBeK/CDuQRBWarPdi3FNY2zQ==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "individual",
|
||||||
|
"url": "https://paulmillr.com/funding/"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"node_modules/@noble/secp256k1": {
|
||||||
|
"version": "1.7.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@noble/secp256k1/-/secp256k1-1.7.1.tgz",
|
||||||
|
"integrity": "sha512-hOUk6AyBFmqVrv7k5WAw/LpszxVbj9gGN4JRkIX52fdFAj1UA61KXmZDvqVEm+pOyec3+fIeZB02LYa/pWOArw==",
|
||||||
|
"dev": true,
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "individual",
|
||||||
|
"url": "https://paulmillr.com/funding/"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"node_modules/@scure/base": {
|
||||||
|
"version": "1.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.1.tgz",
|
||||||
|
"integrity": "sha512-ZxOhsSyxYwLJj3pLZCefNitxsj093tb2vq90mp2txoYeBqbcjDjqFhyM8eUjq/uFm6zJ+mUuqxlS2FkuSY1MTA==",
|
||||||
|
"dev": true,
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "individual",
|
||||||
|
"url": "https://paulmillr.com/funding/"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"node_modules/@scure/bip32": {
|
||||||
|
"version": "1.1.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.1.5.tgz",
|
||||||
|
"integrity": "sha512-XyNh1rB0SkEqd3tXcXMi+Xe1fvg+kUIcoRIEujP1Jgv7DqW2r9lg3Ah0NkFaCs9sTkQAQA8kw7xiRXzENi9Rtw==",
|
||||||
|
"dev": true,
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "individual",
|
||||||
|
"url": "https://paulmillr.com/funding/"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"dependencies": {
|
||||||
|
"@noble/hashes": "~1.2.0",
|
||||||
|
"@noble/secp256k1": "~1.7.0",
|
||||||
|
"@scure/base": "~1.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@scure/bip39": {
|
||||||
|
"version": "1.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.1.1.tgz",
|
||||||
|
"integrity": "sha512-t+wDck2rVkh65Hmv280fYdVdY25J9YeEUIgn2LG1WM6gxFkGzcksoDiUkWVpVp3Oex9xGC68JU2dSbUfwZ2jPg==",
|
||||||
|
"dev": true,
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "individual",
|
||||||
|
"url": "https://paulmillr.com/funding/"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"dependencies": {
|
||||||
|
"@noble/hashes": "~1.2.0",
|
||||||
|
"@scure/base": "~1.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/node": {
|
||||||
|
"version": "18.11.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.3.tgz",
|
||||||
|
"integrity": "sha512-fNjDQzzOsZeKZu5NATgXUPsaFaTxeRgFXoosrHivTl8RGeV733OLawXsGfEk9a8/tySyZUyiZ6E8LcjPFZ2y1A==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"node_modules/fast-check": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/fast-check/-/fast-check-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-uujtrFJEQQqnIMO52ARwzPcuV4omiL1OJBUBLE9WnNFeu0A97sREXDOmCIHY+Z6KLVcemUf09rWr0q0Xy/Y/Ew==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"pure-rand": "^5.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/fast-check"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/micro-bmark": {
|
||||||
|
"version": "0.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/micro-bmark/-/micro-bmark-0.3.1.tgz",
|
||||||
|
"integrity": "sha512-bNaKObD4yPAAPrpEqp5jO6LJ2sEFgLoFSmRjEY809mJ62+2AehI/K3+RlVpN3Oo92RHpgC2RQhj6b1Tb4dmo+w==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"node_modules/micro-should": {
|
||||||
|
"version": "0.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/micro-should/-/micro-should-0.4.0.tgz",
|
||||||
|
"integrity": "sha512-Vclj8yrngSYc9Y3dL2C+AdUlTkyx/syWc4R7LYfk4h7+icfF0DoUBGjjUIaEDzZA19RzoI+Hg8rW9IRoNGP0tQ==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"node_modules/prettier": {
|
||||||
|
"version": "2.8.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.3.tgz",
|
||||||
|
"integrity": "sha512-tJ/oJ4amDihPoufT5sM0Z1SKEuKay8LfVAMlbbhnnkvt6BUserZylqo2PN+p9KeljLr0OHa2rXHU1T8reeoTrw==",
|
||||||
|
"dev": true,
|
||||||
|
"bin": {
|
||||||
|
"prettier": "bin-prettier.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10.13.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/prettier/prettier?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/pure-rand": {
|
||||||
|
"version": "5.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-5.0.5.tgz",
|
||||||
|
"integrity": "sha512-BwQpbqxSCBJVpamI6ydzcKqyFmnd5msMWUGvzXLm1aXvusbbgkbOto/EUPM00hjveJEaJtdbhUjKSzWRhQVkaw==",
|
||||||
|
"dev": true,
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "individual",
|
||||||
|
"url": "https://github.com/sponsors/dubzzz"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/fast-check"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"node_modules/typescript": {
|
||||||
|
"version": "4.7.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.3.tgz",
|
||||||
|
"integrity": "sha512-WOkT3XYvrpXx4vMMqlD+8R8R37fZkjyLGlxavMc4iB8lrl8L0DeTcHbYgw/v0N/z9wAFsgBhcsF0ruoySS22mA==",
|
||||||
|
"dev": true,
|
||||||
|
"bin": {
|
||||||
|
"tsc": "bin/tsc",
|
||||||
|
"tsserver": "bin/tsserver"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4.2.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
175
package.json
175
package.json
@@ -1,12 +1,18 @@
|
|||||||
{
|
{
|
||||||
"name": "@noble/curves",
|
"name": "@noble/curves",
|
||||||
"version": "0.6.0",
|
"version": "0.7.0",
|
||||||
"description": "Minimal, auditable JS implementation of elliptic curve cryptography",
|
"description": "Minimal, auditable JS implementation of elliptic curve cryptography",
|
||||||
"files": [
|
"files": [
|
||||||
"lib"
|
"abstract",
|
||||||
|
"esm",
|
||||||
|
"src",
|
||||||
|
"*.js",
|
||||||
|
"*.js.map",
|
||||||
|
"*.d.ts",
|
||||||
|
"*.d.ts.map"
|
||||||
],
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"bench": "cd benchmark; node index.js",
|
"bench": "cd benchmark; node secp256k1.js; node curves.js; node stark.js; node bls.js",
|
||||||
"build": "tsc && tsc -p tsconfig.esm.json",
|
"build": "tsc && tsc -p tsconfig.esm.json",
|
||||||
"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'",
|
||||||
@@ -21,147 +27,144 @@
|
|||||||
},
|
},
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@noble/hashes": "1.1.5"
|
"@noble/hashes": "1.2.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@rollup/plugin-node-resolve": "13.3.0",
|
"@scure/bip32": "~1.1.5",
|
||||||
"@scure/base": "~1.1.1",
|
"@scure/bip39": "~1.1.1",
|
||||||
"@scure/bip32": "~1.1.1",
|
|
||||||
"@scure/bip39": "~1.1.0",
|
|
||||||
"@types/node": "18.11.3",
|
"@types/node": "18.11.3",
|
||||||
"fast-check": "3.0.0",
|
"fast-check": "3.0.0",
|
||||||
"micro-bmark": "0.2.0",
|
"micro-bmark": "0.3.1",
|
||||||
"micro-should": "0.3.0",
|
"micro-should": "0.4.0",
|
||||||
"prettier": "2.8.3",
|
"prettier": "2.8.3",
|
||||||
"rollup": "2.75.5",
|
|
||||||
"typescript": "4.7.3"
|
"typescript": "4.7.3"
|
||||||
},
|
},
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"exports": {
|
"exports": {
|
||||||
".": {
|
".": {
|
||||||
"types": "./lib/index.d.ts",
|
"types": "./index.d.ts",
|
||||||
"import": "./lib/esm/index.js",
|
"import": "./esm/index.js",
|
||||||
"default": "./lib/index.js"
|
"default": "./index.js"
|
||||||
},
|
},
|
||||||
"./abstract/edwards": {
|
"./abstract/edwards": {
|
||||||
"types": "./lib/abstract/edwards.d.ts",
|
"types": "./abstract/edwards.d.ts",
|
||||||
"import": "./lib/esm/abstract/edwards.js",
|
"import": "./esm/abstract/edwards.js",
|
||||||
"default": "./lib/abstract/edwards.js"
|
"default": "./abstract/edwards.js"
|
||||||
},
|
},
|
||||||
"./abstract/modular": {
|
"./abstract/modular": {
|
||||||
"types": "./lib/abstract/modular.d.ts",
|
"types": "./abstract/modular.d.ts",
|
||||||
"import": "./lib/esm/abstract/modular.js",
|
"import": "./esm/abstract/modular.js",
|
||||||
"default": "./lib/abstract/modular.js"
|
"default": "./abstract/modular.js"
|
||||||
},
|
},
|
||||||
"./abstract/montgomery": {
|
"./abstract/montgomery": {
|
||||||
"types": "./lib/abstract/montgomery.d.ts",
|
"types": "./abstract/montgomery.d.ts",
|
||||||
"import": "./lib/esm/abstract/montgomery.js",
|
"import": "./esm/abstract/montgomery.js",
|
||||||
"default": "./lib/abstract/montgomery.js"
|
"default": "./abstract/montgomery.js"
|
||||||
},
|
},
|
||||||
"./abstract/weierstrass": {
|
"./abstract/weierstrass": {
|
||||||
"types": "./lib/abstract/weierstrass.d.ts",
|
"types": "./abstract/weierstrass.d.ts",
|
||||||
"import": "./lib/esm/abstract/weierstrass.js",
|
"import": "./esm/abstract/weierstrass.js",
|
||||||
"default": "./lib/abstract/weierstrass.js"
|
"default": "./abstract/weierstrass.js"
|
||||||
},
|
},
|
||||||
"./abstract/bls": {
|
"./abstract/bls": {
|
||||||
"types": "./lib/abstract/bls.d.ts",
|
"types": "./abstract/bls.d.ts",
|
||||||
"import": "./lib/esm/abstract/bls.js",
|
"import": "./esm/abstract/bls.js",
|
||||||
"default": "./lib/abstract/bls.js"
|
"default": "./abstract/bls.js"
|
||||||
},
|
},
|
||||||
"./abstract/hash-to-curve": {
|
"./abstract/hash-to-curve": {
|
||||||
"types": "./lib/abstract/hash-to-curve.d.ts",
|
"types": "./abstract/hash-to-curve.d.ts",
|
||||||
"import": "./lib/esm/abstract/hash-to-curve.js",
|
"import": "./esm/abstract/hash-to-curve.js",
|
||||||
"default": "./lib/abstract/hash-to-curve.js"
|
"default": "./abstract/hash-to-curve.js"
|
||||||
},
|
},
|
||||||
"./abstract/curve": {
|
"./abstract/curve": {
|
||||||
"types": "./lib/abstract/curve.d.ts",
|
"types": "./abstract/curve.d.ts",
|
||||||
"import": "./lib/esm/abstract/curve.js",
|
"import": "./esm/abstract/curve.js",
|
||||||
"default": "./lib/abstract/curve.js"
|
"default": "./abstract/curve.js"
|
||||||
},
|
},
|
||||||
"./abstract/utils": {
|
"./abstract/utils": {
|
||||||
"types": "./lib/abstract/utils.d.ts",
|
"types": "./abstract/utils.d.ts",
|
||||||
"import": "./lib/esm/abstract/utils.js",
|
"import": "./esm/abstract/utils.js",
|
||||||
"default": "./lib/abstract/utils.js"
|
"default": "./abstract/utils.js"
|
||||||
},
|
},
|
||||||
"./abstract/poseidon": {
|
"./abstract/poseidon": {
|
||||||
"types": "./lib/abstract/poseidon.d.ts",
|
"types": "./abstract/poseidon.d.ts",
|
||||||
"import": "./lib/esm/abstract/poseidon.js",
|
"import": "./esm/abstract/poseidon.js",
|
||||||
"default": "./lib/abstract/poseidon.js"
|
"default": "./abstract/poseidon.js"
|
||||||
},
|
},
|
||||||
"./_shortw_utils": {
|
"./_shortw_utils": {
|
||||||
"types": "./lib/_shortw_utils.d.ts",
|
"types": "./_shortw_utils.d.ts",
|
||||||
"import": "./lib/esm/_shortw_utils.js",
|
"import": "./esm/_shortw_utils.js",
|
||||||
"default": "./lib/_shortw_utils.js"
|
"default": "./_shortw_utils.js"
|
||||||
},
|
},
|
||||||
"./bls12-381": {
|
"./bls12-381": {
|
||||||
"types": "./lib/bls12-381.d.ts",
|
"types": "./bls12-381.d.ts",
|
||||||
"import": "./lib/esm/bls12-381.js",
|
"import": "./esm/bls12-381.js",
|
||||||
"default": "./lib/bls12-381.js"
|
"default": "./bls12-381.js"
|
||||||
},
|
},
|
||||||
"./bn": {
|
"./bn": {
|
||||||
"types": "./lib/bn.d.ts",
|
"types": "./bn.d.ts",
|
||||||
"import": "./lib/esm/bn.js",
|
"import": "./esm/bn.js",
|
||||||
"default": "./lib/bn.js"
|
"default": "./bn.js"
|
||||||
},
|
},
|
||||||
"./ed25519": {
|
"./ed25519": {
|
||||||
"types": "./lib/ed25519.d.ts",
|
"types": "./ed25519.d.ts",
|
||||||
"import": "./lib/esm/ed25519.js",
|
"import": "./esm/ed25519.js",
|
||||||
"default": "./lib/ed25519.js"
|
"default": "./ed25519.js"
|
||||||
},
|
},
|
||||||
"./ed448": {
|
"./ed448": {
|
||||||
"types": "./lib/ed448.d.ts",
|
"types": "./ed448.d.ts",
|
||||||
"import": "./lib/esm/ed448.js",
|
"import": "./esm/ed448.js",
|
||||||
"default": "./lib/ed448.js"
|
"default": "./ed448.js"
|
||||||
},
|
},
|
||||||
"./index": {
|
"./index": {
|
||||||
"types": "./lib/index.d.ts",
|
"types": "./index.d.ts",
|
||||||
"import": "./lib/esm/index.js",
|
"import": "./esm/index.js",
|
||||||
"default": "./lib/index.js"
|
"default": "./index.js"
|
||||||
},
|
},
|
||||||
"./jubjub": {
|
"./jubjub": {
|
||||||
"types": "./lib/jubjub.d.ts",
|
"types": "./jubjub.d.ts",
|
||||||
"import": "./lib/esm/jubjub.js",
|
"import": "./esm/jubjub.js",
|
||||||
"default": "./lib/jubjub.js"
|
"default": "./jubjub.js"
|
||||||
},
|
},
|
||||||
"./p192": {
|
"./p192": {
|
||||||
"types": "./lib/p192.d.ts",
|
"types": "./p192.d.ts",
|
||||||
"import": "./lib/esm/p192.js",
|
"import": "./esm/p192.js",
|
||||||
"default": "./lib/p192.js"
|
"default": "./p192.js"
|
||||||
},
|
},
|
||||||
"./p224": {
|
"./p224": {
|
||||||
"types": "./lib/p224.d.ts",
|
"types": "./p224.d.ts",
|
||||||
"import": "./lib/esm/p224.js",
|
"import": "./esm/p224.js",
|
||||||
"default": "./lib/p224.js"
|
"default": "./p224.js"
|
||||||
},
|
},
|
||||||
"./p256": {
|
"./p256": {
|
||||||
"types": "./lib/p256.d.ts",
|
"types": "./p256.d.ts",
|
||||||
"import": "./lib/esm/p256.js",
|
"import": "./esm/p256.js",
|
||||||
"default": "./lib/p256.js"
|
"default": "./p256.js"
|
||||||
},
|
},
|
||||||
"./p384": {
|
"./p384": {
|
||||||
"types": "./lib/p384.d.ts",
|
"types": "./p384.d.ts",
|
||||||
"import": "./lib/esm/p384.js",
|
"import": "./esm/p384.js",
|
||||||
"default": "./lib/p384.js"
|
"default": "./p384.js"
|
||||||
},
|
},
|
||||||
"./p521": {
|
"./p521": {
|
||||||
"types": "./lib/p521.d.ts",
|
"types": "./p521.d.ts",
|
||||||
"import": "./lib/esm/p521.js",
|
"import": "./esm/p521.js",
|
||||||
"default": "./lib/p521.js"
|
"default": "./p521.js"
|
||||||
},
|
},
|
||||||
"./pasta": {
|
"./pasta": {
|
||||||
"types": "./lib/pasta.d.ts",
|
"types": "./pasta.d.ts",
|
||||||
"import": "./lib/esm/pasta.js",
|
"import": "./esm/pasta.js",
|
||||||
"default": "./lib/pasta.js"
|
"default": "./pasta.js"
|
||||||
},
|
},
|
||||||
"./secp256k1": {
|
"./secp256k1": {
|
||||||
"types": "./lib/secp256k1.d.ts",
|
"types": "./secp256k1.d.ts",
|
||||||
"import": "./lib/esm/secp256k1.js",
|
"import": "./esm/secp256k1.js",
|
||||||
"default": "./lib/secp256k1.js"
|
"default": "./secp256k1.js"
|
||||||
},
|
},
|
||||||
"./stark": {
|
"./stark": {
|
||||||
"types": "./lib/stark.d.ts",
|
"types": "./stark.d.ts",
|
||||||
"import": "./lib/esm/stark.js",
|
"import": "./esm/stark.js",
|
||||||
"default": "./lib/stark.js"
|
"default": "./stark.js"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"keywords": [
|
"keywords": [
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { concatBytes, randomBytes } from '@noble/hashes/utils';
|
|||||||
import { weierstrass, CurveType } from './abstract/weierstrass.js';
|
import { weierstrass, CurveType } from './abstract/weierstrass.js';
|
||||||
import { CHash } from './abstract/utils.js';
|
import { CHash } from './abstract/utils.js';
|
||||||
|
|
||||||
|
// connects noble-curves to noble-hashes
|
||||||
export function getHash(hash: CHash) {
|
export function getHash(hash: CHash) {
|
||||||
return {
|
return {
|
||||||
hash,
|
hash,
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
*/
|
*/
|
||||||
import { AffinePoint } from './curve.js';
|
import { AffinePoint } from './curve.js';
|
||||||
import { Field, hashToPrivateScalar } from './modular.js';
|
import { Field, hashToPrivateScalar } from './modular.js';
|
||||||
import { Hex, PrivKey, CHash, bitLen, bitGet, hexToBytes, bytesToHex } from './utils.js';
|
import { Hex, PrivKey, CHash, bitLen, bitGet, ensureBytes } from './utils.js';
|
||||||
import * as htf from './hash-to-curve.js';
|
import * as htf from './hash-to-curve.js';
|
||||||
import {
|
import {
|
||||||
CurvePointsType,
|
CurvePointsType,
|
||||||
@@ -67,16 +67,11 @@ export type CurveFn<Fp, Fp2, Fp6, Fp12> = {
|
|||||||
Fp2: Field<Fp2>;
|
Fp2: Field<Fp2>;
|
||||||
Fp6: Field<Fp6>;
|
Fp6: Field<Fp6>;
|
||||||
Fp12: Field<Fp12>;
|
Fp12: Field<Fp12>;
|
||||||
G1: CurvePointsRes<Fp>;
|
G1: CurvePointsRes<Fp> & ReturnType<typeof htf.createHasher<Fp>>;
|
||||||
G2: CurvePointsRes<Fp2>;
|
G2: CurvePointsRes<Fp2> & ReturnType<typeof htf.createHasher<Fp2>>;
|
||||||
Signature: SignatureCoder<Fp2>;
|
Signature: SignatureCoder<Fp2>;
|
||||||
millerLoop: (ell: [Fp2, Fp2, Fp2][], g1: [Fp, Fp]) => Fp12;
|
millerLoop: (ell: [Fp2, Fp2, Fp2][], g1: [Fp, Fp]) => Fp12;
|
||||||
calcPairingPrecomputes: (p: AffinePoint<Fp2>) => [Fp2, Fp2, Fp2][];
|
calcPairingPrecomputes: (p: AffinePoint<Fp2>) => [Fp2, Fp2, Fp2][];
|
||||||
// prettier-ignore
|
|
||||||
hashToCurve: {
|
|
||||||
G1: ReturnType<(typeof htf.hashToCurve<Fp>)>,
|
|
||||||
G2: ReturnType<(typeof htf.hashToCurve<Fp2>)>,
|
|
||||||
},
|
|
||||||
pairing: (P: ProjPointType<Fp>, Q: ProjPointType<Fp2>, withFinalExponent?: boolean) => Fp12;
|
pairing: (P: ProjPointType<Fp>, Q: ProjPointType<Fp2>, withFinalExponent?: boolean) => Fp12;
|
||||||
getPublicKey: (privateKey: PrivKey) => Uint8Array;
|
getPublicKey: (privateKey: PrivKey) => Uint8Array;
|
||||||
sign: {
|
sign: {
|
||||||
@@ -102,16 +97,14 @@ export type CurveFn<Fp, Fp2, Fp6, Fp12> = {
|
|||||||
publicKeys: (Hex | ProjPointType<Fp>)[]
|
publicKeys: (Hex | ProjPointType<Fp>)[]
|
||||||
) => boolean;
|
) => boolean;
|
||||||
utils: {
|
utils: {
|
||||||
stringToBytes: typeof htf.stringToBytes;
|
randomPrivateKey: () => Uint8Array;
|
||||||
hashToField: typeof htf.hash_to_field;
|
|
||||||
expandMessageXMD: typeof htf.expand_message_xmd;
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export function bls<Fp2, Fp6, Fp12>(
|
export function bls<Fp2, Fp6, Fp12>(
|
||||||
CURVE: CurveType<Fp, Fp2, Fp6, Fp12>
|
CURVE: CurveType<Fp, Fp2, Fp6, Fp12>
|
||||||
): CurveFn<Fp, Fp2, Fp6, Fp12> {
|
): CurveFn<Fp, Fp2, Fp6, Fp12> {
|
||||||
// Fields looks pretty specific for curve, so for now we need to pass them with options
|
// Fields looks pretty specific for curve, so for now we need to pass them with opts
|
||||||
const { Fp, Fr, Fp2, Fp6, Fp12 } = CURVE;
|
const { Fp, Fr, Fp2, Fp6, Fp12 } = CURVE;
|
||||||
const BLS_X_LEN = bitLen(CURVE.x);
|
const BLS_X_LEN = bitLen(CURVE.x);
|
||||||
const groupLen = 32; // TODO: calculate; hardcoded for now
|
const groupLen = 32; // TODO: calculate; hardcoded for now
|
||||||
@@ -180,31 +173,20 @@ export function bls<Fp2, Fp6, Fp12>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const utils = {
|
const utils = {
|
||||||
hexToBytes: hexToBytes,
|
randomPrivateKey: (): Uint8Array => {
|
||||||
bytesToHex: bytesToHex,
|
return Fr.toBytes(hashToPrivateScalar(CURVE.randomBytes(groupLen + 8), CURVE.r));
|
||||||
stringToBytes: htf.stringToBytes,
|
},
|
||||||
// TODO: do we need to export it here?
|
|
||||||
hashToField: (
|
|
||||||
msg: Uint8Array,
|
|
||||||
count: number,
|
|
||||||
options: Partial<typeof CURVE.htfDefaults> = {}
|
|
||||||
) => htf.hash_to_field(msg, count, { ...CURVE.htfDefaults, ...options }),
|
|
||||||
expandMessageXMD: (msg: Uint8Array, DST: Uint8Array, lenInBytes: number, H = CURVE.hash) =>
|
|
||||||
htf.expand_message_xmd(msg, DST, lenInBytes, H),
|
|
||||||
hashToPrivateKey: (hash: Hex): Uint8Array => Fr.toBytes(hashToPrivateScalar(hash, CURVE.r)),
|
|
||||||
randomBytes: (bytesLength: number = groupLen): Uint8Array => CURVE.randomBytes(bytesLength),
|
|
||||||
randomPrivateKey: (): Uint8Array => utils.hashToPrivateKey(utils.randomBytes(groupLen + 8)),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Point on G1 curve: (x, y)
|
// Point on G1 curve: (x, y)
|
||||||
const G1 = weierstrassPoints({
|
const G1_ = weierstrassPoints({ n: Fr.ORDER, ...CURVE.G1 });
|
||||||
n: Fr.ORDER,
|
const G1 = Object.assign(
|
||||||
...CURVE.G1,
|
G1_,
|
||||||
});
|
htf.createHasher(G1_.ProjectivePoint, CURVE.G1.mapToCurve, {
|
||||||
const G1HashToCurve = htf.hashToCurve(G1.ProjectivePoint, CURVE.G1.mapToCurve, {
|
...CURVE.htfDefaults,
|
||||||
...CURVE.htfDefaults,
|
...CURVE.G1.htfDefaults,
|
||||||
...CURVE.G1.htfDefaults,
|
})
|
||||||
});
|
);
|
||||||
|
|
||||||
// Sparse multiplication against precomputed coefficients
|
// Sparse multiplication against precomputed coefficients
|
||||||
// TODO: replace with weakmap?
|
// TODO: replace with weakmap?
|
||||||
@@ -223,15 +205,14 @@ export function bls<Fp2, Fp6, Fp12>(
|
|||||||
// }
|
// }
|
||||||
|
|
||||||
// Point on G2 curve (complex numbers): (x₁, x₂+i), (y₁, y₂+i)
|
// Point on G2 curve (complex numbers): (x₁, x₂+i), (y₁, y₂+i)
|
||||||
const G2 = weierstrassPoints({
|
const G2_ = weierstrassPoints({ n: Fr.ORDER, ...CURVE.G2 });
|
||||||
n: Fr.ORDER,
|
const G2 = Object.assign(
|
||||||
...CURVE.G2,
|
G2_,
|
||||||
});
|
htf.createHasher(G2_.ProjectivePoint as htf.H2CPointConstructor<Fp2>, CURVE.G2.mapToCurve, {
|
||||||
const C = G2.ProjectivePoint as htf.H2CPointConstructor<Fp2>; // TODO: fix
|
...CURVE.htfDefaults,
|
||||||
const G2HashToCurve = htf.hashToCurve(C, CURVE.G2.mapToCurve, {
|
...CURVE.G2.htfDefaults,
|
||||||
...CURVE.htfDefaults,
|
})
|
||||||
...CURVE.G2.htfDefaults,
|
);
|
||||||
});
|
|
||||||
|
|
||||||
const { Signature } = CURVE.G2;
|
const { Signature } = CURVE.G2;
|
||||||
|
|
||||||
@@ -260,7 +241,7 @@ export function bls<Fp2, Fp6, Fp12>(
|
|||||||
function normP2Hash(point: G2Hex, htfOpts?: htf.htfBasicOpts): G2 {
|
function normP2Hash(point: G2Hex, htfOpts?: htf.htfBasicOpts): G2 {
|
||||||
return point instanceof G2.ProjectivePoint
|
return point instanceof G2.ProjectivePoint
|
||||||
? point
|
? point
|
||||||
: (G2HashToCurve.hashToCurve(point, htfOpts) as G2);
|
: (G2.hashToCurve(ensureBytes('point', point), htfOpts) as G2);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Multiplies generator by private key.
|
// Multiplies generator by private key.
|
||||||
@@ -383,7 +364,6 @@ export function bls<Fp2, Fp6, Fp12>(
|
|||||||
Signature,
|
Signature,
|
||||||
millerLoop,
|
millerLoop,
|
||||||
calcPairingPrecomputes,
|
calcPairingPrecomputes,
|
||||||
hashToCurve: { G1: G1HashToCurve, G2: G2HashToCurve },
|
|
||||||
pairing,
|
pairing,
|
||||||
getPublicKey,
|
getPublicKey,
|
||||||
sign,
|
sign,
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
|
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
|
||||||
// Abelian group utilities
|
// Abelian group utilities
|
||||||
import { Field, validateField, nLength } from './modular.js';
|
import { Field, validateField, nLength } from './modular.js';
|
||||||
|
import { validateObject } from './utils.js';
|
||||||
const _0n = BigInt(0);
|
const _0n = BigInt(0);
|
||||||
const _1n = BigInt(1);
|
const _1n = BigInt(1);
|
||||||
|
|
||||||
@@ -24,8 +25,17 @@ export type GroupConstructor<T> = {
|
|||||||
};
|
};
|
||||||
export type Mapper<T> = (i: T[]) => T[];
|
export type Mapper<T> = (i: T[]) => T[];
|
||||||
|
|
||||||
// Elliptic curve multiplication of Point by scalar. Complicated and fragile. Uses wNAF method.
|
// Elliptic curve multiplication of Point by scalar. Fragile.
|
||||||
// Windowed method is 10% faster, but takes 2x longer to generate & consumes 2x memory.
|
// Scalars should always be less than curve order: this should be checked inside of a curve itself.
|
||||||
|
// Creates precomputation tables for fast multiplication:
|
||||||
|
// - private scalar is split by fixed size windows of W bits
|
||||||
|
// - every window point is collected from window's table & added to accumulator
|
||||||
|
// - since windows are different, same point inside tables won't be accessed more than once per calc
|
||||||
|
// - each multiplication is 'Math.ceil(CURVE_ORDER / 𝑊) + 1' point additions (fixed for any scalar)
|
||||||
|
// - +1 window is neccessary for wNAF
|
||||||
|
// - wNAF reduces table size: 2x less memory + 2x faster generation, but 10% slower multiplication
|
||||||
|
// TODO: Research returning 2d JS array of windows, instead of a single window. This would allow
|
||||||
|
// windows to be in different memory locations
|
||||||
export function wNAF<T extends Group<T>>(c: GroupConstructor<T>, bits: number) {
|
export function wNAF<T extends Group<T>>(c: GroupConstructor<T>, bits: number) {
|
||||||
const constTimeNegate = (condition: boolean, item: T): T => {
|
const constTimeNegate = (condition: boolean, item: T): T => {
|
||||||
const neg = item.negate();
|
const neg = item.negate();
|
||||||
@@ -53,8 +63,12 @@ export function wNAF<T extends Group<T>>(c: GroupConstructor<T>, bits: number) {
|
|||||||
/**
|
/**
|
||||||
* Creates a wNAF precomputation window. Used for caching.
|
* Creates a wNAF precomputation window. Used for caching.
|
||||||
* Default window size is set by `utils.precompute()` and is equal to 8.
|
* Default window size is set by `utils.precompute()` and is equal to 8.
|
||||||
* Which means we are caching 65536 points: 256 points for every bit from 0 to 256.
|
* Number of precomputed points depends on the curve size:
|
||||||
* @returns 65K precomputed points, depending on W
|
* 2^(𝑊−1) * (Math.ceil(𝑛 / 𝑊) + 1), where:
|
||||||
|
* - 𝑊 is the window size
|
||||||
|
* - 𝑛 is the bitlength of the curve order.
|
||||||
|
* For a 256-bit curve and window size 8, the number of precomputed points is 128 * 33 = 4224.
|
||||||
|
* @returns precomputed point tables flattened to a single array
|
||||||
*/
|
*/
|
||||||
precomputeWindow(elm: T, W: number): Group<T>[] {
|
precomputeWindow(elm: T, W: number): Group<T>[] {
|
||||||
const { windows, windowSize } = opts(W);
|
const { windows, windowSize } = opts(W);
|
||||||
@@ -75,14 +89,14 @@ export function wNAF<T extends Group<T>>(c: GroupConstructor<T>, bits: number) {
|
|||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implements w-ary non-adjacent form for calculating ec multiplication.
|
* Implements ec multiplication using precomputed tables and w-ary non-adjacent form.
|
||||||
* @param W window size
|
* @param W window size
|
||||||
* @param affinePoint optional 2d point to save cached precompute windows on it.
|
* @param precomputes precomputed tables
|
||||||
* @param n bits
|
* @param n scalar (we don't check here, but should be less than curve order)
|
||||||
* @returns real and fake (for const-time) points
|
* @returns real and fake (for const-time) points
|
||||||
*/
|
*/
|
||||||
wNAF(W: number, precomputes: T[], n: bigint): { p: T; f: T } {
|
wNAF(W: number, precomputes: T[], n: bigint): { p: T; f: T } {
|
||||||
// TODO: maybe check that scalar is less than group order? wNAF will fail otherwise
|
// TODO: maybe check that scalar is less than group order? wNAF behavious is undefined otherwise
|
||||||
// But need to carefully remove other checks before wNAF. ORDER == bits here
|
// But need to carefully remove other checks before wNAF. ORDER == bits here
|
||||||
const { windows, windowSize } = opts(W);
|
const { windows, windowSize } = opts(W);
|
||||||
|
|
||||||
@@ -153,7 +167,7 @@ export function wNAF<T extends Group<T>>(c: GroupConstructor<T>, bits: number) {
|
|||||||
|
|
||||||
// Generic BasicCurve interface: works even for polynomial fields (BLS): P, n, h would be ok.
|
// Generic BasicCurve interface: works even for polynomial fields (BLS): P, n, h would be ok.
|
||||||
// Though generator can be different (Fp2 / Fp6 for BLS).
|
// Though generator can be different (Fp2 / Fp6 for BLS).
|
||||||
export type AbstractCurve<T> = {
|
export type BasicCurve<T> = {
|
||||||
Fp: Field<T>; // Field over which we'll do calculations (Fp)
|
Fp: Field<T>; // Field over which we'll do calculations (Fp)
|
||||||
n: bigint; // Curve order, total count of valid points in the field
|
n: bigint; // Curve order, total count of valid points in the field
|
||||||
nBitLength?: number; // bit length of curve order
|
nBitLength?: number; // bit length of curve order
|
||||||
@@ -162,24 +176,24 @@ export type AbstractCurve<T> = {
|
|||||||
hEff?: bigint; // Number to multiply to clear cofactor
|
hEff?: bigint; // Number to multiply to clear cofactor
|
||||||
Gx: T; // base point X coordinate
|
Gx: T; // base point X coordinate
|
||||||
Gy: T; // base point Y coordinate
|
Gy: T; // base point Y coordinate
|
||||||
wrapPrivateKey?: boolean; // bls12-381 requires mod(n) instead of rejecting keys >= n
|
|
||||||
allowInfinityPoint?: boolean; // bls12-381 requires it. ZERO point is valid, but invalid pubkey
|
allowInfinityPoint?: boolean; // bls12-381 requires it. ZERO point is valid, but invalid pubkey
|
||||||
};
|
};
|
||||||
|
|
||||||
export function validateAbsOpts<FP, T>(curve: AbstractCurve<FP> & T) {
|
export function validateBasic<FP, T>(curve: BasicCurve<FP> & T) {
|
||||||
validateField(curve.Fp);
|
validateField(curve.Fp);
|
||||||
for (const i of ['n', 'h'] as const) {
|
validateObject(
|
||||||
const val = curve[i];
|
curve,
|
||||||
if (typeof val !== 'bigint') throw new Error(`Invalid curve param ${i}=${val} (${typeof val})`);
|
{
|
||||||
}
|
n: 'bigint',
|
||||||
if (!curve.Fp.isValid(curve.Gx)) throw new Error('Invalid generator X coordinate Fp element');
|
h: 'bigint',
|
||||||
if (!curve.Fp.isValid(curve.Gy)) throw new Error('Invalid generator Y coordinate Fp element');
|
Gx: 'field',
|
||||||
|
Gy: 'field',
|
||||||
for (const i of ['nBitLength', 'nByteLength'] as const) {
|
},
|
||||||
const val = curve[i];
|
{
|
||||||
if (val === undefined) continue; // Optional
|
nBitLength: 'isSafeInteger',
|
||||||
if (!Number.isSafeInteger(val)) throw new Error(`Invalid param ${i}=${val} (${typeof val})`);
|
nByteLength: 'isSafeInteger',
|
||||||
}
|
}
|
||||||
|
);
|
||||||
// Set defaults
|
// Set defaults
|
||||||
return Object.freeze({ ...nLength(curve.n, curve.nBitLength), ...curve } as const);
|
return Object.freeze({ ...nLength(curve.n, curve.nBitLength), ...curve } as const);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,23 +1,9 @@
|
|||||||
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
|
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
|
||||||
// Twisted Edwards curve. The formula is: ax² + y² = 1 + dx²y²
|
// Twisted Edwards curve. The formula is: ax² + y² = 1 + dx²y²
|
||||||
import { mod } from './modular.js';
|
import { mod } from './modular.js';
|
||||||
import {
|
import * as ut from './utils.js';
|
||||||
bytesToHex,
|
import { ensureBytes, FHash, Hex } from './utils.js';
|
||||||
bytesToNumberLE,
|
import { Group, GroupConstructor, wNAF, BasicCurve, validateBasic, AffinePoint } from './curve.js';
|
||||||
concatBytes,
|
|
||||||
ensureBytes,
|
|
||||||
FHash,
|
|
||||||
Hex,
|
|
||||||
numberToBytesLE,
|
|
||||||
} from './utils.js';
|
|
||||||
import {
|
|
||||||
Group,
|
|
||||||
GroupConstructor,
|
|
||||||
wNAF,
|
|
||||||
AbstractCurve,
|
|
||||||
validateAbsOpts,
|
|
||||||
AffinePoint,
|
|
||||||
} from './curve.js';
|
|
||||||
|
|
||||||
// Be friendly to bad ECMAScript parsers by not using bigint literals like 123n
|
// Be friendly to bad ECMAScript parsers by not using bigint literals like 123n
|
||||||
const _0n = BigInt(0);
|
const _0n = BigInt(0);
|
||||||
@@ -26,7 +12,7 @@ const _2n = BigInt(2);
|
|||||||
const _8n = BigInt(8);
|
const _8n = BigInt(8);
|
||||||
|
|
||||||
// Edwards curves must declare params a & d.
|
// Edwards curves must declare params a & d.
|
||||||
export type CurveType = AbstractCurve<bigint> & {
|
export type CurveType = BasicCurve<bigint> & {
|
||||||
a: bigint; // curve param a
|
a: bigint; // curve param a
|
||||||
d: bigint; // curve param d
|
d: bigint; // curve param d
|
||||||
hash: FHash; // Hashing
|
hash: FHash; // Hashing
|
||||||
@@ -39,19 +25,22 @@ export type CurveType = AbstractCurve<bigint> & {
|
|||||||
};
|
};
|
||||||
|
|
||||||
function validateOpts(curve: CurveType) {
|
function validateOpts(curve: CurveType) {
|
||||||
const opts = validateAbsOpts(curve);
|
const opts = validateBasic(curve);
|
||||||
if (typeof opts.hash !== 'function') throw new Error('Invalid hash function');
|
ut.validateObject(
|
||||||
for (const i of ['a', 'd'] as const) {
|
curve,
|
||||||
const val = opts[i];
|
{
|
||||||
if (typeof val !== 'bigint') throw new Error(`Invalid curve param ${i}=${val} (${typeof val})`);
|
hash: 'function',
|
||||||
}
|
a: 'bigint',
|
||||||
for (const fn of ['randomBytes'] as const) {
|
d: 'bigint',
|
||||||
if (typeof opts[fn] !== 'function') throw new Error(`Invalid ${fn} function`);
|
randomBytes: 'function',
|
||||||
}
|
},
|
||||||
for (const fn of ['adjustScalarBytes', 'domain', 'uvRatio', 'mapToCurve'] as const) {
|
{
|
||||||
if (opts[fn] === undefined) continue; // Optional
|
adjustScalarBytes: 'function',
|
||||||
if (typeof opts[fn] !== 'function') throw new Error(`Invalid ${fn} function`);
|
domain: 'function',
|
||||||
}
|
uvRatio: 'function',
|
||||||
|
mapToCurve: 'function',
|
||||||
|
}
|
||||||
|
);
|
||||||
// Set defaults
|
// Set defaults
|
||||||
return Object.freeze({ ...opts } as const);
|
return Object.freeze({ ...opts } as const);
|
||||||
}
|
}
|
||||||
@@ -75,7 +64,7 @@ export 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;
|
||||||
fromPrivateKey(privateKey: Hex): ExtPointType; // TODO: remove
|
fromPrivateKey(privateKey: Hex): ExtPointType;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type CurveFn = {
|
export type CurveFn = {
|
||||||
@@ -182,8 +171,27 @@ export function twistedEdwards(curveDef: CurveType): CurveFn {
|
|||||||
this._WINDOW_SIZE = windowSize;
|
this._WINDOW_SIZE = windowSize;
|
||||||
pointPrecomputes.delete(this);
|
pointPrecomputes.delete(this);
|
||||||
}
|
}
|
||||||
|
// Not required for fromHex(), which always creates valid points.
|
||||||
assertValidity(): void {}
|
// Could be useful for fromAffine().
|
||||||
|
assertValidity(): void {
|
||||||
|
const { a, d } = CURVE;
|
||||||
|
if (this.is0()) throw new Error('bad point: ZERO'); // TODO: optimize, with vars below?
|
||||||
|
// Equation in affine coordinates: ax² + y² = 1 + dx²y²
|
||||||
|
// Equation in projective coordinates (X/Z, Y/Z, Z): (aX² + Y²)Z² = Z⁴ + dX²Y²
|
||||||
|
const { ex: X, ey: Y, ez: Z, et: T } = this;
|
||||||
|
const X2 = modP(X * X); // X²
|
||||||
|
const Y2 = modP(Y * Y); // Y²
|
||||||
|
const Z2 = modP(Z * Z); // Z²
|
||||||
|
const Z4 = modP(Z2 * Z2); // Z⁴
|
||||||
|
const aX2 = modP(X2 * a); // aX²
|
||||||
|
const left = modP(Z2 * modP(aX2 + Y2)); // (aX² + Y²)Z²
|
||||||
|
const right = modP(Z4 + modP(d * modP(X2 * Y2))); // Z⁴ + dX²Y²
|
||||||
|
if (left !== right) throw new Error('bad point: equation left != right (1)');
|
||||||
|
// In Extended coordinates we also have T, which is x*y=T/Z: check X*Y == Z*T
|
||||||
|
const XY = modP(X * Y);
|
||||||
|
const ZT = modP(Z * T);
|
||||||
|
if (XY !== ZT) throw new Error('bad point: equation left != right (2)');
|
||||||
|
}
|
||||||
|
|
||||||
// Compare one point to another.
|
// Compare one point to another.
|
||||||
equals(other: Point): boolean {
|
equals(other: Point): boolean {
|
||||||
@@ -336,11 +344,11 @@ export function twistedEdwards(curveDef: CurveType): CurveFn {
|
|||||||
static fromHex(hex: Hex, strict = true): Point {
|
static fromHex(hex: Hex, strict = true): Point {
|
||||||
const { d, a } = CURVE;
|
const { d, a } = CURVE;
|
||||||
const len = Fp.BYTES;
|
const len = Fp.BYTES;
|
||||||
hex = ensureBytes(hex, len); // copy hex to a new array
|
hex = ensureBytes('pointHex', hex, len); // copy hex to a new array
|
||||||
const normed = hex.slice(); // copy again, we'll manipulate it
|
const normed = hex.slice(); // copy again, we'll manipulate it
|
||||||
const lastByte = hex[len - 1]; // select last byte
|
const lastByte = hex[len - 1]; // select last byte
|
||||||
normed[len - 1] = lastByte & ~0x80; // clear last bit
|
normed[len - 1] = lastByte & ~0x80; // clear last bit
|
||||||
const y = bytesToNumberLE(normed);
|
const y = ut.bytesToNumberLE(normed);
|
||||||
if (y === _0n) {
|
if (y === _0n) {
|
||||||
// y=0 is allowed
|
// y=0 is allowed
|
||||||
} else {
|
} else {
|
||||||
@@ -366,12 +374,12 @@ export function twistedEdwards(curveDef: CurveType): CurveFn {
|
|||||||
}
|
}
|
||||||
toRawBytes(): Uint8Array {
|
toRawBytes(): Uint8Array {
|
||||||
const { x, y } = this.toAffine();
|
const { x, y } = this.toAffine();
|
||||||
const bytes = numberToBytesLE(y, Fp.BYTES); // each y has 2 x values (x, -y)
|
const bytes = ut.numberToBytesLE(y, Fp.BYTES); // each y has 2 x values (x, -y)
|
||||||
bytes[bytes.length - 1] |= x & _1n ? 0x80 : 0; // when compressing, it's enough to store y
|
bytes[bytes.length - 1] |= x & _1n ? 0x80 : 0; // when compressing, it's enough to store y
|
||||||
return bytes; // and use the last byte to encode sign of x
|
return bytes; // and use the last byte to encode sign of x
|
||||||
}
|
}
|
||||||
toHex(): string {
|
toHex(): string {
|
||||||
return bytesToHex(this.toRawBytes()); // Same as toRawBytes, but returns string.
|
return ut.bytesToHex(this.toRawBytes()); // Same as toRawBytes, but returns string.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const { BASE: G, ZERO: I } = Point;
|
const { BASE: G, ZERO: I } = Point;
|
||||||
@@ -382,20 +390,16 @@ export function twistedEdwards(curveDef: CurveType): CurveFn {
|
|||||||
}
|
}
|
||||||
// Little-endian SHA512 with modulo n
|
// Little-endian SHA512 with modulo n
|
||||||
function modN_LE(hash: Uint8Array): bigint {
|
function modN_LE(hash: Uint8Array): bigint {
|
||||||
return modN(bytesToNumberLE(hash));
|
return modN(ut.bytesToNumberLE(hash));
|
||||||
}
|
|
||||||
function isHex(item: Hex, err: string) {
|
|
||||||
if (typeof item !== 'string' && !(item instanceof Uint8Array))
|
|
||||||
throw new Error(`${err} must be hex string or Uint8Array`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Convenience method that creates public key and other stuff. RFC8032 5.1.5 */
|
/** Convenience method that creates public key and other stuff. RFC8032 5.1.5 */
|
||||||
function getExtendedPublicKey(key: Hex) {
|
function getExtendedPublicKey(key: Hex) {
|
||||||
isHex(key, 'private key');
|
|
||||||
const len = nByteLength;
|
const len = nByteLength;
|
||||||
|
key = ensureBytes('private key', key, len);
|
||||||
// Hash private key with curve's hash function to produce uniformingly random input
|
// Hash private key with curve's hash function to produce uniformingly random input
|
||||||
// Check byte lengths: ensure(64, h(ensure(32, key)))
|
// Check byte lengths: ensure(64, h(ensure(32, key)))
|
||||||
const hashed = ensureBytes(cHash(ensureBytes(key, len)), 2 * len);
|
const hashed = ensureBytes('hashed private key', cHash(key), 2 * len);
|
||||||
const head = adjustScalarBytes(hashed.slice(0, len)); // clear first half bits, produce FE
|
const head = adjustScalarBytes(hashed.slice(0, len)); // clear first half bits, produce FE
|
||||||
const prefix = hashed.slice(len, 2 * len); // second half is called key prefix (5.1.6)
|
const prefix = hashed.slice(len, 2 * len); // second half is called key prefix (5.1.6)
|
||||||
const scalar = modN_LE(head); // The actual private scalar
|
const scalar = modN_LE(head); // The actual private scalar
|
||||||
@@ -411,14 +415,13 @@ export function twistedEdwards(curveDef: CurveType): CurveFn {
|
|||||||
|
|
||||||
// int('LE', SHA512(dom2(F, C) || msgs)) mod N
|
// int('LE', SHA512(dom2(F, C) || msgs)) mod N
|
||||||
function hashDomainToScalar(context: Hex = new Uint8Array(), ...msgs: Uint8Array[]) {
|
function hashDomainToScalar(context: Hex = new Uint8Array(), ...msgs: Uint8Array[]) {
|
||||||
const msg = concatBytes(...msgs);
|
const msg = ut.concatBytes(...msgs);
|
||||||
return modN_LE(cHash(domain(msg, ensureBytes(context), !!preHash)));
|
return modN_LE(cHash(domain(msg, ensureBytes('context', context), !!preHash)));
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Signs message with privateKey. RFC8032 5.1.6 */
|
/** Signs message with privateKey. RFC8032 5.1.6 */
|
||||||
function sign(msg: Hex, privKey: Hex, context?: Hex): Uint8Array {
|
function sign(msg: Hex, privKey: Hex, context?: Hex): Uint8Array {
|
||||||
isHex(msg, 'message');
|
msg = ensureBytes('message', msg);
|
||||||
msg = ensureBytes(msg);
|
|
||||||
if (preHash) msg = preHash(msg); // for ed25519ph etc.
|
if (preHash) msg = preHash(msg); // for ed25519ph etc.
|
||||||
const { prefix, scalar, pointBytes } = getExtendedPublicKey(privKey);
|
const { prefix, scalar, pointBytes } = getExtendedPublicKey(privKey);
|
||||||
const r = hashDomainToScalar(context, prefix, msg); // r = dom2(F, C) || prefix || PH(M)
|
const r = hashDomainToScalar(context, prefix, msg); // r = dom2(F, C) || prefix || PH(M)
|
||||||
@@ -426,20 +429,18 @@ export function twistedEdwards(curveDef: CurveType): CurveFn {
|
|||||||
const k = hashDomainToScalar(context, R, pointBytes, msg); // R || A || PH(M)
|
const k = hashDomainToScalar(context, R, pointBytes, msg); // R || A || PH(M)
|
||||||
const s = modN(r + k * scalar); // S = (r + k * s) mod L
|
const s = modN(r + k * scalar); // S = (r + k * s) mod L
|
||||||
assertGE0(s); // 0 <= s < l
|
assertGE0(s); // 0 <= s < l
|
||||||
const res = concatBytes(R, numberToBytesLE(s, Fp.BYTES));
|
const res = ut.concatBytes(R, ut.numberToBytesLE(s, Fp.BYTES));
|
||||||
return ensureBytes(res, nByteLength * 2); // 64-byte signature
|
return ensureBytes('result', res, nByteLength * 2); // 64-byte signature
|
||||||
}
|
}
|
||||||
|
|
||||||
function verify(sig: Hex, msg: Hex, publicKey: Hex, context?: Hex): boolean {
|
function verify(sig: Hex, msg: Hex, publicKey: Hex, context?: Hex): boolean {
|
||||||
isHex(sig, 'sig');
|
|
||||||
isHex(msg, 'message');
|
|
||||||
const len = Fp.BYTES; // Verifies EdDSA signature against message and public key. RFC8032 5.1.7.
|
const len = Fp.BYTES; // Verifies EdDSA signature against message and public key. RFC8032 5.1.7.
|
||||||
sig = ensureBytes(sig, 2 * len); // An extended group equation is checked.
|
sig = ensureBytes('signature', sig, 2 * len); // An extended group equation is checked.
|
||||||
msg = ensureBytes(msg); // ZIP215 compliant, which means not fully RFC8032 compliant.
|
msg = ensureBytes('message', msg); // ZIP215 compliant, which means not fully RFC8032 compliant.
|
||||||
if (preHash) msg = preHash(msg); // for ed25519ph, etc
|
if (preHash) msg = preHash(msg); // for ed25519ph, etc
|
||||||
const A = Point.fromHex(publicKey, false); // Check for s bounds, hex validity
|
const A = Point.fromHex(publicKey, false); // Check for s bounds, hex validity
|
||||||
const R = Point.fromHex(sig.slice(0, len), false); // 0 <= R < 2^256: ZIP215 R can be >= P
|
const R = Point.fromHex(sig.slice(0, len), false); // 0 <= R < 2^256: ZIP215 R can be >= P
|
||||||
const s = bytesToNumberLE(sig.slice(len, 2 * len)); // 0 <= s < l
|
const s = ut.bytesToNumberLE(sig.slice(len, 2 * len)); // 0 <= s < l
|
||||||
const SB = G.multiplyUnsafe(s);
|
const SB = G.multiplyUnsafe(s);
|
||||||
const k = hashDomainToScalar(context, R.toRawBytes(), A.toRawBytes(), msg);
|
const k = hashDomainToScalar(context, R.toRawBytes(), A.toRawBytes(), msg);
|
||||||
const RkA = R.add(A.multiplyUnsafe(k));
|
const RkA = R.add(A.multiplyUnsafe(k));
|
||||||
|
|||||||
@@ -1,25 +1,15 @@
|
|||||||
/*! 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, Hex, concatBytes, ensureBytes } from './utils.js';
|
import { CHash, concatBytes, utf8ToBytes, validateObject } from './utils.js';
|
||||||
|
|
||||||
export type Opts = {
|
export type Opts = {
|
||||||
// DST: a domain separation tag
|
DST: string; // DST: a domain separation tag, defined in section 2.2.5
|
||||||
// defined in section 2.2.5
|
|
||||||
DST: string;
|
|
||||||
encodeDST: string;
|
encodeDST: string;
|
||||||
// p: the characteristic of F
|
p: bigint; // characteristic of F, where F is a finite field of characteristic p and order q = p^m
|
||||||
// where F is a finite field of characteristic p and order q = p^m
|
m: number; // extension degree of F, m >= 1
|
||||||
p: bigint;
|
k: number; // k: the target security level for the suite in bits, defined in section 5.1
|
||||||
// m: the extension degree of F, m >= 1
|
expand?: 'xmd' | 'xof'; // use a message that has already been processed by expand_message_xmd
|
||||||
// where F is a finite field of characteristic p and order q = p^m
|
|
||||||
m: number;
|
|
||||||
// k: the target security level for the suite in bits
|
|
||||||
// defined in section 5.1
|
|
||||||
k: number;
|
|
||||||
// option to use a message that has already been processed by
|
|
||||||
// expand_message_xmd
|
|
||||||
expand?: 'xmd' | 'xof';
|
|
||||||
// Hash functions for: expand_message_xmd is appropriate for use with a
|
// 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.
|
// 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
|
// BBS+ uses blake2: https://github.com/hyperledger/aries-framework-go/issues/2247
|
||||||
@@ -27,29 +17,6 @@ export type Opts = {
|
|||||||
hash: CHash;
|
hash: CHash;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function validateOpts(opts: Opts) {
|
|
||||||
if (typeof opts.DST !== 'string') throw new Error('Invalid htf/DST');
|
|
||||||
if (typeof opts.p !== 'bigint') throw new Error('Invalid htf/p');
|
|
||||||
if (typeof opts.m !== 'number') throw new Error('Invalid htf/m');
|
|
||||||
if (typeof opts.k !== 'number') throw new Error('Invalid htf/k');
|
|
||||||
if (opts.expand !== 'xmd' && opts.expand !== 'xof' && opts.expand !== undefined)
|
|
||||||
throw new Error('Invalid htf/expand');
|
|
||||||
if (typeof opts.hash !== 'function' || !Number.isSafeInteger(opts.hash.outputLen))
|
|
||||||
throw new Error('Invalid htf/hash function');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Global symbols in both browsers and Node.js since v11
|
|
||||||
// See https://github.com/microsoft/TypeScript/issues/31535
|
|
||||||
declare const TextEncoder: any;
|
|
||||||
declare const TextDecoder: any;
|
|
||||||
|
|
||||||
export function stringToBytes(str: string): Uint8Array {
|
|
||||||
if (typeof str !== 'string') {
|
|
||||||
throw new TypeError(`utf8ToBytes expected string, got ${typeof str}`);
|
|
||||||
}
|
|
||||||
return new TextEncoder().encode(str);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Octet Stream to Integer (bytesToNumberBE)
|
// Octet Stream to Integer (bytesToNumberBE)
|
||||||
function os2ip(bytes: Uint8Array): bigint {
|
function os2ip(bytes: Uint8Array): bigint {
|
||||||
let result = 0n;
|
let result = 0n;
|
||||||
@@ -81,6 +48,13 @@ function strxor(a: Uint8Array, b: Uint8Array): Uint8Array {
|
|||||||
return arr;
|
return arr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isBytes(item: unknown): void {
|
||||||
|
if (!(item instanceof Uint8Array)) throw new Error('Uint8Array expected');
|
||||||
|
}
|
||||||
|
function isNum(item: unknown): void {
|
||||||
|
if (!Number.isSafeInteger(item)) throw new Error('number expected');
|
||||||
|
}
|
||||||
|
|
||||||
// Produces a uniformly random byte string using a cryptographic hash function H that outputs b bits
|
// Produces a uniformly random byte string using a cryptographic hash function H that outputs b bits
|
||||||
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-5.4.1
|
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-5.4.1
|
||||||
export function expand_message_xmd(
|
export function expand_message_xmd(
|
||||||
@@ -89,8 +63,11 @@ export function expand_message_xmd(
|
|||||||
lenInBytes: number,
|
lenInBytes: number,
|
||||||
H: CHash
|
H: CHash
|
||||||
): Uint8Array {
|
): Uint8Array {
|
||||||
|
isBytes(msg);
|
||||||
|
isBytes(DST);
|
||||||
|
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(stringToBytes('H2C-OVERSIZE-DST-'), DST));
|
if (DST.length > 255) DST = H(concatBytes(utf8ToBytes('H2C-OVERSIZE-DST-'), DST));
|
||||||
const b_in_bytes = H.outputLen;
|
const b_in_bytes = H.outputLen;
|
||||||
const r_in_bytes = H.blockLen;
|
const r_in_bytes = H.blockLen;
|
||||||
const ell = Math.ceil(lenInBytes / b_in_bytes);
|
const ell = Math.ceil(lenInBytes / b_in_bytes);
|
||||||
@@ -116,11 +93,14 @@ export function expand_message_xof(
|
|||||||
k: number,
|
k: number,
|
||||||
H: CHash
|
H: CHash
|
||||||
): Uint8Array {
|
): Uint8Array {
|
||||||
|
isBytes(msg);
|
||||||
|
isBytes(DST);
|
||||||
|
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
|
||||||
// DST = H('H2C-OVERSIZE-DST-' || a_very_long_DST, Math.ceil((lenInBytes * k) / 8));
|
// DST = H('H2C-OVERSIZE-DST-' || a_very_long_DST, Math.ceil((lenInBytes * k) / 8));
|
||||||
if (DST.length > 255) {
|
if (DST.length > 255) {
|
||||||
const dkLen = Math.ceil((2 * k) / 8);
|
const dkLen = Math.ceil((2 * k) / 8);
|
||||||
DST = H.create({ dkLen }).update(stringToBytes('H2C-OVERSIZE-DST-')).update(DST).digest();
|
DST = H.create({ dkLen }).update(utf8ToBytes('H2C-OVERSIZE-DST-')).update(DST).digest();
|
||||||
}
|
}
|
||||||
if (lenInBytes > 65535 || DST.length > 255)
|
if (lenInBytes > 65535 || DST.length > 255)
|
||||||
throw new Error('expand_message_xof: invalid lenInBytes');
|
throw new Error('expand_message_xof: invalid lenInBytes');
|
||||||
@@ -144,25 +124,27 @@ export function expand_message_xof(
|
|||||||
* @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[][] {
|
||||||
// if options is provided but incomplete, fill any missing fields with the
|
const { p, k, m, hash, expand, DST: _DST } = options;
|
||||||
// value in hftDefaults (ie hash to G2).
|
isBytes(msg);
|
||||||
const log2p = options.p.toString(2).length;
|
isNum(count);
|
||||||
const L = Math.ceil((log2p + options.k) / 8); // section 5.1 of ietf draft link above
|
if (typeof _DST !== 'string') throw new Error('DST must be valid');
|
||||||
const len_in_bytes = count * options.m * L;
|
const log2p = p.toString(2).length;
|
||||||
const DST = stringToBytes(options.DST);
|
const L = Math.ceil((log2p + k) / 8); // section 5.1 of ietf draft link above
|
||||||
let pseudo_random_bytes = msg;
|
const len_in_bytes = count * m * L;
|
||||||
if (options.expand === 'xmd') {
|
const DST = utf8ToBytes(_DST);
|
||||||
pseudo_random_bytes = expand_message_xmd(msg, DST, len_in_bytes, options.hash);
|
const pseudo_random_bytes =
|
||||||
} else if (options.expand === 'xof') {
|
expand === 'xmd'
|
||||||
pseudo_random_bytes = expand_message_xof(msg, DST, len_in_bytes, options.k, options.hash);
|
? expand_message_xmd(msg, DST, len_in_bytes, hash)
|
||||||
}
|
: expand === 'xof'
|
||||||
|
? expand_message_xof(msg, DST, len_in_bytes, k, hash)
|
||||||
|
: msg;
|
||||||
const u = new Array(count);
|
const u = new Array(count);
|
||||||
for (let i = 0; i < count; i++) {
|
for (let i = 0; i < count; i++) {
|
||||||
const e = new Array(options.m);
|
const e = new Array(m);
|
||||||
for (let j = 0; j < options.m; j++) {
|
for (let j = 0; j < m; j++) {
|
||||||
const elm_offset = L * (j + i * options.m);
|
const elm_offset = L * (j + i * m);
|
||||||
const tv = pseudo_random_bytes.subarray(elm_offset, elm_offset + L);
|
const tv = pseudo_random_bytes.subarray(elm_offset, elm_offset + L);
|
||||||
e[j] = mod(os2ip(tv), options.p);
|
e[j] = mod(os2ip(tv), p);
|
||||||
}
|
}
|
||||||
u[i] = e;
|
u[i] = e;
|
||||||
}
|
}
|
||||||
@@ -195,38 +177,40 @@ export interface H2CPointConstructor<T> extends GroupConstructor<H2CPoint<T>> {
|
|||||||
|
|
||||||
export type MapToCurve<T> = (scalar: bigint[]) => AffinePoint<T>;
|
export type MapToCurve<T> = (scalar: bigint[]) => AffinePoint<T>;
|
||||||
|
|
||||||
// Separated from initialization opts, so users won't accidentally change per-curve parameters (changing DST is ok!)
|
// Separated from initialization opts, so users won't accidentally change per-curve parameters
|
||||||
export type htfBasicOpts = {
|
// (changing DST is ok!)
|
||||||
DST: string;
|
export type htfBasicOpts = { DST: string };
|
||||||
};
|
|
||||||
|
|
||||||
export function hashToCurve<T>(
|
export function createHasher<T>(
|
||||||
Point: H2CPointConstructor<T>,
|
Point: H2CPointConstructor<T>,
|
||||||
mapToCurve: MapToCurve<T>,
|
mapToCurve: MapToCurve<T>,
|
||||||
def: Opts
|
def: Opts
|
||||||
) {
|
) {
|
||||||
validateOpts(def);
|
validateObject(def, {
|
||||||
|
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')
|
if (typeof mapToCurve !== 'function')
|
||||||
throw new Error('hashToCurve: mapToCurve() has not been defined');
|
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-11#section-3
|
||||||
hashToCurve(msg: Hex, options?: htfBasicOpts) {
|
hashToCurve(msg: Uint8Array, options?: htfBasicOpts) {
|
||||||
if (!mapToCurve) throw new Error('CURVE.mapToCurve() has not been defined');
|
|
||||||
msg = ensureBytes(msg);
|
|
||||||
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 P = Point.fromAffine(mapToCurve(u[0]))
|
const u0 = Point.fromAffine(mapToCurve(u[0]));
|
||||||
.add(Point.fromAffine(mapToCurve(u[1])))
|
const u1 = Point.fromAffine(mapToCurve(u[1]));
|
||||||
.clearCofactor();
|
const P = u0.add(u1).clearCofactor();
|
||||||
P.assertValidity();
|
P.assertValidity();
|
||||||
return P;
|
return P;
|
||||||
},
|
},
|
||||||
|
|
||||||
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-16#section-3
|
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-16#section-3
|
||||||
encodeToCurve(msg: Hex, options?: htfBasicOpts) {
|
encodeToCurve(msg: Uint8Array, options?: htfBasicOpts) {
|
||||||
if (!mapToCurve) throw new Error('CURVE.mapToCurve() has not been defined');
|
|
||||||
msg = ensureBytes(msg);
|
|
||||||
const u = hash_to_field(msg, 1, { ...def, DST: def.encodeDST, ...options } as Opts);
|
const u = hash_to_field(msg, 1, { ...def, DST: def.encodeDST, ...options } as Opts);
|
||||||
const P = Point.fromAffine(mapToCurve(u[0])).clearCofactor();
|
const P = Point.fromAffine(mapToCurve(u[0])).clearCofactor();
|
||||||
P.assertValidity();
|
P.assertValidity();
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import {
|
|||||||
bytesToNumberBE,
|
bytesToNumberBE,
|
||||||
bytesToNumberLE,
|
bytesToNumberLE,
|
||||||
ensureBytes,
|
ensureBytes,
|
||||||
|
validateObject,
|
||||||
} from './utils.js';
|
} from './utils.js';
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
const _0n = BigInt(0), _1n = BigInt(1), _2n = BigInt(2), _3n = BigInt(3);
|
const _0n = BigInt(0), _1n = BigInt(1), _2n = BigInt(2), _3n = BigInt(3);
|
||||||
@@ -40,7 +41,6 @@ export function pow(num: bigint, power: bigint, modulo: bigint): bigint {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Does x ^ (2 ^ power) mod p. pow2(30, 4) == 30 ^ (2 ^ 4)
|
// Does x ^ (2 ^ power) mod p. pow2(30, 4) == 30 ^ (2 ^ 4)
|
||||||
// TODO: Fp version?
|
|
||||||
export function pow2(x: bigint, power: bigint, modulo: bigint): bigint {
|
export function pow2(x: bigint, power: bigint, modulo: bigint): bigint {
|
||||||
let res = x;
|
let res = x;
|
||||||
while (power-- > _0n) {
|
while (power-- > _0n) {
|
||||||
@@ -249,18 +249,17 @@ const FIELD_FIELDS = [
|
|||||||
'addN', 'subN', 'mulN', 'sqrN'
|
'addN', 'subN', 'mulN', 'sqrN'
|
||||||
] as const;
|
] as const;
|
||||||
export function validateField<T>(field: Field<T>) {
|
export function validateField<T>(field: Field<T>) {
|
||||||
for (const i of ['ORDER', 'MASK'] as const) {
|
const initial = {
|
||||||
if (typeof field[i] !== 'bigint')
|
ORDER: 'bigint',
|
||||||
throw new Error(`Invalid field param ${i}=${field[i]} (${typeof field[i]})`);
|
MASK: 'bigint',
|
||||||
}
|
BYTES: 'isSafeInteger',
|
||||||
for (const i of ['BYTES', 'BITS'] as const) {
|
BITS: 'isSafeInteger',
|
||||||
if (typeof field[i] !== 'number')
|
} as Record<string, string>;
|
||||||
throw new Error(`Invalid field param ${i}=${field[i]} (${typeof field[i]})`);
|
const opts = FIELD_FIELDS.reduce((map, val: string) => {
|
||||||
}
|
map[val] = 'function';
|
||||||
for (const i of FIELD_FIELDS) {
|
return map;
|
||||||
if (typeof field[i] !== 'function')
|
}, initial);
|
||||||
throw new Error(`Invalid field param ${i}=${field[i]} (${typeof field[i]})`);
|
return validateObject(field, opts);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generic field functions
|
// Generic field functions
|
||||||
@@ -408,7 +407,7 @@ export function hashToPrivateScalar(
|
|||||||
groupOrder: bigint,
|
groupOrder: bigint,
|
||||||
isLE = false
|
isLE = false
|
||||||
): bigint {
|
): bigint {
|
||||||
hash = ensureBytes(hash);
|
hash = ensureBytes('privateHash', hash);
|
||||||
const hashLen = hash.length;
|
const hashLen = hash.length;
|
||||||
const minLen = nLength(groupOrder).nByteLength + 8;
|
const minLen = nLength(groupOrder).nByteLength + 8;
|
||||||
if (minLen < 24 || hashLen < minLen || hashLen > 1024)
|
if (minLen < 24 || hashLen < minLen || hashLen > 1024)
|
||||||
|
|||||||
@@ -1,14 +1,13 @@
|
|||||||
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
|
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
|
||||||
import { mod, pow } from './modular.js';
|
import { mod, pow } from './modular.js';
|
||||||
import { ensureBytes, numberToBytesLE, bytesToNumberLE } from './utils.js';
|
import { bytesToNumberLE, ensureBytes, numberToBytesLE, validateObject } from './utils.js';
|
||||||
|
|
||||||
const _0n = BigInt(0);
|
const _0n = BigInt(0);
|
||||||
const _1n = BigInt(1);
|
const _1n = BigInt(1);
|
||||||
type Hex = string | Uint8Array;
|
type Hex = string | Uint8Array;
|
||||||
|
|
||||||
export type CurveType = {
|
export type CurveType = {
|
||||||
// Field over which we'll do calculations. Verify with:
|
P: bigint; // finite field prime
|
||||||
P: bigint;
|
|
||||||
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;
|
||||||
@@ -27,24 +26,20 @@ export type CurveFn = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
function validateOpts(curve: CurveType) {
|
function validateOpts(curve: CurveType) {
|
||||||
for (const i of ['a24'] as const) {
|
validateObject(
|
||||||
if (typeof curve[i] !== 'bigint')
|
curve,
|
||||||
throw new Error(`Invalid curve param ${i}=${curve[i]} (${typeof curve[i]})`);
|
{
|
||||||
}
|
a24: 'bigint',
|
||||||
for (const i of ['montgomeryBits', 'nByteLength'] as const) {
|
},
|
||||||
if (curve[i] === undefined) continue; // Optional
|
{
|
||||||
if (!Number.isSafeInteger(curve[i]))
|
montgomeryBits: 'isSafeInteger',
|
||||||
throw new Error(`Invalid curve param ${i}=${curve[i]} (${typeof curve[i]})`);
|
nByteLength: 'isSafeInteger',
|
||||||
}
|
adjustScalarBytes: 'function',
|
||||||
for (const fn of ['adjustScalarBytes', 'domain', 'powPminus2'] as const) {
|
domain: 'function',
|
||||||
if (curve[fn] === undefined) continue; // Optional
|
powPminus2: 'function',
|
||||||
if (typeof curve[fn] !== 'function') throw new Error(`Invalid ${fn} function`);
|
Gu: 'string',
|
||||||
}
|
}
|
||||||
for (const i of ['Gu'] as const) {
|
);
|
||||||
if (curve[i] === undefined) continue; // Optional
|
|
||||||
if (typeof curve[i] !== 'string')
|
|
||||||
throw new Error(`Invalid curve param ${i}=${curve[i]} (${typeof curve[i]})`);
|
|
||||||
}
|
|
||||||
// Set defaults
|
// Set defaults
|
||||||
return Object.freeze({ ...curve } as const);
|
return Object.freeze({ ...curve } as const);
|
||||||
}
|
}
|
||||||
@@ -61,27 +56,7 @@ export function montgomery(curveDef: CurveType): CurveFn {
|
|||||||
const adjustScalarBytes = CURVE.adjustScalarBytes || ((bytes: Uint8Array) => bytes);
|
const adjustScalarBytes = CURVE.adjustScalarBytes || ((bytes: Uint8Array) => bytes);
|
||||||
const powPminus2 = CURVE.powPminus2 || ((x: bigint) => pow(x, P - BigInt(2), P));
|
const powPminus2 = CURVE.powPminus2 || ((x: bigint) => pow(x, P - BigInt(2), P));
|
||||||
|
|
||||||
/**
|
// cswap from RFC7748. But it is not from RFC7748!
|
||||||
* Checks for num to be in range:
|
|
||||||
* For strict == true: `0 < num < max`.
|
|
||||||
* For strict == false: `0 <= num < max`.
|
|
||||||
* Converts non-float safe numbers to bigints.
|
|
||||||
*/
|
|
||||||
function normalizeScalar(num: bigint, max: bigint, strict = true): bigint {
|
|
||||||
if (!max) throw new TypeError('Specify max value');
|
|
||||||
if (typeof num === 'number' && Number.isSafeInteger(num)) num = BigInt(num);
|
|
||||||
if (typeof num === 'bigint' && num < max) {
|
|
||||||
if (strict) {
|
|
||||||
if (_0n < num) return num;
|
|
||||||
} else {
|
|
||||||
if (_0n <= num) return num;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
throw new TypeError('Expected valid scalar: 0 < scalar < max');
|
|
||||||
}
|
|
||||||
|
|
||||||
// cswap from RFC7748
|
|
||||||
// NOTE: cswap is not from RFC7748!
|
|
||||||
/*
|
/*
|
||||||
cswap(swap, x_2, x_3):
|
cswap(swap, x_2, x_3):
|
||||||
dummy = mask(swap) AND (x_2 XOR x_3)
|
dummy = mask(swap) AND (x_2 XOR x_3)
|
||||||
@@ -98,6 +73,11 @@ export function montgomery(curveDef: CurveType): CurveFn {
|
|||||||
return [x_2, x_3];
|
return [x_2, x_3];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function assertFieldElement(n: bigint): bigint {
|
||||||
|
if (typeof n === 'bigint' && _0n <= n && n < P) return n;
|
||||||
|
throw new Error('Expected valid scalar 0 < scalar < CURVE.P');
|
||||||
|
}
|
||||||
|
|
||||||
// x25519 from 4
|
// x25519 from 4
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
@@ -106,11 +86,10 @@ export function montgomery(curveDef: CurveType): CurveFn {
|
|||||||
* @returns new Point on Montgomery curve
|
* @returns new Point on Montgomery curve
|
||||||
*/
|
*/
|
||||||
function montgomeryLadder(pointU: bigint, scalar: bigint): bigint {
|
function montgomeryLadder(pointU: bigint, scalar: bigint): bigint {
|
||||||
const { P } = CURVE;
|
const u = assertFieldElement(pointU);
|
||||||
const u = normalizeScalar(pointU, P);
|
|
||||||
// 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 = normalizeScalar(scalar, P);
|
const k = assertFieldElement(scalar);
|
||||||
// The constant a24 is (486662 - 2) / 4 = 121665 for curve25519/X25519
|
// The constant a24 is (486662 - 2) / 4 = 121665 for curve25519/X25519
|
||||||
const a24 = CURVE.a24;
|
const a24 = CURVE.a24;
|
||||||
const x_1 = u;
|
const x_1 = u;
|
||||||
@@ -166,28 +145,21 @@ export function montgomery(curveDef: CurveType): CurveFn {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function decodeUCoordinate(uEnc: Hex): bigint {
|
function decodeUCoordinate(uEnc: Hex): bigint {
|
||||||
const u = ensureBytes(uEnc, montgomeryBytes);
|
|
||||||
// Section 5: When receiving such an array, implementations of X25519
|
// Section 5: When receiving such an array, implementations of X25519
|
||||||
// MUST mask the most significant bit in the final byte.
|
// MUST mask the most significant bit in the final byte.
|
||||||
// This is very ugly way, but it works because fieldLen-1 is outside of bounds for X448, so this becomes NOOP
|
// This is very ugly way, but it works because fieldLen-1 is outside of bounds for X448, so this becomes NOOP
|
||||||
// fieldLen - scalaryBytes = 1 for X448 and = 0 for X25519
|
// fieldLen - scalaryBytes = 1 for X448 and = 0 for X25519
|
||||||
u[fieldLen - 1] &= 127; // 0b0111_1111
|
const u = ensureBytes('u coordinate', uEnc, montgomeryBytes);
|
||||||
|
// u[fieldLen-1] crashes QuickJS (TypeError: out-of-bound numeric index)
|
||||||
|
if (fieldLen === montgomeryBytes) u[fieldLen - 1] &= 127; // 0b0111_1111
|
||||||
return bytesToNumberLE(u);
|
return bytesToNumberLE(u);
|
||||||
}
|
}
|
||||||
|
|
||||||
function decodeScalar(n: Hex): bigint {
|
function decodeScalar(n: Hex): bigint {
|
||||||
const bytes = ensureBytes(n);
|
const bytes = ensureBytes('scalar', n);
|
||||||
if (bytes.length !== montgomeryBytes && bytes.length !== fieldLen)
|
if (bytes.length !== montgomeryBytes && bytes.length !== fieldLen)
|
||||||
throw new Error(`Expected ${montgomeryBytes} or ${fieldLen} bytes, got ${bytes.length}`);
|
throw new Error(`Expected ${montgomeryBytes} or ${fieldLen} bytes, got ${bytes.length}`);
|
||||||
return bytesToNumberLE(adjustScalarBytes(bytes));
|
return bytesToNumberLE(adjustScalarBytes(bytes));
|
||||||
}
|
}
|
||||||
/**
|
|
||||||
* Computes shared secret between private key "scalar" and public key's "u" (x) coordinate.
|
|
||||||
* We can get 'y' coordinate from 'u',
|
|
||||||
* but Point.fromHex also wants 'x' coordinate oddity flag,
|
|
||||||
* and we cannot get 'x' without knowing 'v'.
|
|
||||||
* Need to add generic conversion between twisted edwards and complimentary curve for JubJub.
|
|
||||||
*/
|
|
||||||
function scalarMult(scalar: Hex, u: Hex): Uint8Array {
|
function scalarMult(scalar: Hex, u: Hex): Uint8Array {
|
||||||
const pointU = decodeUCoordinate(u);
|
const pointU = decodeUCoordinate(u);
|
||||||
const _scalar = decodeScalar(scalar);
|
const _scalar = decodeScalar(scalar);
|
||||||
@@ -197,12 +169,7 @@ export function montgomery(curveDef: CurveType): CurveFn {
|
|||||||
if (pu === _0n) throw new Error('Invalid private or public key received');
|
if (pu === _0n) throw new Error('Invalid private or public key received');
|
||||||
return encodeUCoordinate(pu);
|
return encodeUCoordinate(pu);
|
||||||
}
|
}
|
||||||
/**
|
// Computes public key from private. By doing scalar multiplication of base point.
|
||||||
* Computes public key from private.
|
|
||||||
* Executes scalar multiplication of curve's base point by scalar.
|
|
||||||
* @param scalar private key
|
|
||||||
* @returns new public key
|
|
||||||
*/
|
|
||||||
function scalarMultBase(scalar: Hex): Uint8Array {
|
function scalarMultBase(scalar: Hex): Uint8Array {
|
||||||
return scalarMult(scalar, CURVE.Gu);
|
return scalarMult(scalar, CURVE.Gu);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
|
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
|
||||||
// Poseidon Hash: https://eprint.iacr.org/2019/458.pdf, https://www.poseidon-hash.info
|
// Poseidon Hash: https://eprint.iacr.org/2019/458.pdf, https://www.poseidon-hash.info
|
||||||
import { Field, validateField, FpPow } from './modular.js';
|
import { Field, FpPow, validateField } from './modular.js';
|
||||||
// We don't provide any constants, since different implementations use different constants.
|
// We don't provide any constants, since different implementations use different constants.
|
||||||
// For reference constants see './test/poseidon.test.js'.
|
// For reference constants see './test/poseidon.test.js'.
|
||||||
export type PoseidonOpts = {
|
export type PoseidonOpts = {
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ export type FHash = (message: Uint8Array | string) => Uint8Array;
|
|||||||
|
|
||||||
const hexes = Array.from({ length: 256 }, (v, i) => i.toString(16).padStart(2, '0'));
|
const hexes = Array.from({ length: 256 }, (v, i) => i.toString(16).padStart(2, '0'));
|
||||||
export function bytesToHex(bytes: Uint8Array): string {
|
export function bytesToHex(bytes: Uint8Array): string {
|
||||||
if (!u8a(bytes)) throw new Error('Expected Uint8Array');
|
if (!u8a(bytes)) throw new Error('Uint8Array expected');
|
||||||
// pre-caching improves the speed 6x
|
// pre-caching improves the speed 6x
|
||||||
let hex = '';
|
let hex = '';
|
||||||
for (let i = 0; i < bytes.length; i++) {
|
for (let i = 0; i < bytes.length; i++) {
|
||||||
@@ -33,21 +33,21 @@ export function numberToHexUnpadded(num: number | bigint): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function hexToNumber(hex: string): bigint {
|
export function hexToNumber(hex: string): bigint {
|
||||||
if (typeof hex !== 'string') throw new Error('hexToNumber: expected string, got ' + typeof hex);
|
if (typeof hex !== 'string') throw new Error('hex string expected, got ' + typeof hex);
|
||||||
// Big Endian
|
// Big Endian
|
||||||
return BigInt(`0x${hex}`);
|
return BigInt(hex === '' ? '0' : `0x${hex}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Caching slows it down 2-3x
|
// Caching slows it down 2-3x
|
||||||
export function hexToBytes(hex: string): Uint8Array {
|
export function hexToBytes(hex: string): Uint8Array {
|
||||||
if (typeof hex !== 'string') throw new Error('hexToBytes: expected string, got ' + typeof hex);
|
if (typeof hex !== 'string') throw new Error('hex string expected, got ' + typeof hex);
|
||||||
if (hex.length % 2) throw new Error('hexToBytes: received invalid unpadded hex ' + hex.length);
|
if (hex.length % 2) throw new Error('hex string is invalid: unpadded ' + hex.length);
|
||||||
const array = new Uint8Array(hex.length / 2);
|
const array = new Uint8Array(hex.length / 2);
|
||||||
for (let i = 0; i < array.length; i++) {
|
for (let i = 0; i < array.length; i++) {
|
||||||
const j = i * 2;
|
const j = i * 2;
|
||||||
const hexByte = hex.slice(j, j + 2);
|
const hexByte = hex.slice(j, j + 2);
|
||||||
const byte = Number.parseInt(hexByte, 16);
|
const byte = Number.parseInt(hexByte, 16);
|
||||||
if (Number.isNaN(byte) || byte < 0) throw new Error('Invalid byte sequence');
|
if (Number.isNaN(byte) || byte < 0) throw new Error('invalid byte sequence');
|
||||||
array[i] = byte;
|
array[i] = byte;
|
||||||
}
|
}
|
||||||
return array;
|
return array;
|
||||||
@@ -58,7 +58,7 @@ export function bytesToNumberBE(bytes: Uint8Array): bigint {
|
|||||||
return hexToNumber(bytesToHex(bytes));
|
return hexToNumber(bytesToHex(bytes));
|
||||||
}
|
}
|
||||||
export function bytesToNumberLE(bytes: Uint8Array): bigint {
|
export function bytesToNumberLE(bytes: Uint8Array): bigint {
|
||||||
if (!u8a(bytes)) throw new Error('Expected Uint8Array');
|
if (!u8a(bytes)) throw new Error('Uint8Array expected');
|
||||||
return hexToNumber(bytesToHex(Uint8Array.from(bytes).reverse()));
|
return hexToNumber(bytesToHex(Uint8Array.from(bytes).reverse()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -66,33 +66,39 @@ export const numberToBytesBE = (n: bigint, len: number) =>
|
|||||||
hexToBytes(n.toString(16).padStart(len * 2, '0'));
|
hexToBytes(n.toString(16).padStart(len * 2, '0'));
|
||||||
export const numberToBytesLE = (n: bigint, len: number) => numberToBytesBE(n, len).reverse();
|
export const numberToBytesLE = (n: bigint, len: number) => numberToBytesBE(n, len).reverse();
|
||||||
// Returns variable number bytes (minimal bigint encoding?)
|
// Returns variable number bytes (minimal bigint encoding?)
|
||||||
export const numberToVarBytesBE = (n: bigint) => {
|
export const numberToVarBytesBE = (n: bigint) => hexToBytes(numberToHexUnpadded(n));
|
||||||
let hex = n.toString(16);
|
|
||||||
if (hex.length & 1) hex = '0' + hex;
|
|
||||||
return hexToBytes(hex);
|
|
||||||
};
|
|
||||||
|
|
||||||
export function ensureBytes(hex: Hex, expectedLength?: number): Uint8Array {
|
export function ensureBytes(title: string, hex: Hex, expectedLength?: number): Uint8Array {
|
||||||
// Uint8Array.from() instead of hash.slice() because node.js Buffer
|
let res: Uint8Array;
|
||||||
// is instance of Uint8Array, and its slice() creates **mutable** copy
|
if (typeof hex === 'string') {
|
||||||
const bytes = u8a(hex) ? Uint8Array.from(hex) : hexToBytes(hex);
|
try {
|
||||||
if (typeof expectedLength === 'number' && bytes.length !== expectedLength)
|
res = hexToBytes(hex);
|
||||||
throw new Error(`Expected ${expectedLength} bytes`);
|
} catch (e) {
|
||||||
return bytes;
|
throw new Error(`${title} must be valid hex string, got "${hex}". Cause: ${e}`);
|
||||||
|
}
|
||||||
|
} else if (u8a(hex)) {
|
||||||
|
// Uint8Array.from() instead of hash.slice() because node.js Buffer
|
||||||
|
// is instance of Uint8Array, and its slice() creates **mutable** copy
|
||||||
|
res = Uint8Array.from(hex);
|
||||||
|
} else {
|
||||||
|
throw new Error(`${title} must be hex string or Uint8Array`);
|
||||||
|
}
|
||||||
|
const len = res.length;
|
||||||
|
if (typeof expectedLength === 'number' && len !== expectedLength)
|
||||||
|
throw new Error(`${title} expected ${expectedLength} bytes, got ${len}`);
|
||||||
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copies several Uint8Arrays into one.
|
// Copies several Uint8Arrays into one.
|
||||||
export function concatBytes(...arrays: Uint8Array[]): Uint8Array {
|
export function concatBytes(...arrs: Uint8Array[]): Uint8Array {
|
||||||
if (!arrays.every((b) => u8a(b))) throw new Error('Uint8Array list expected');
|
const r = new Uint8Array(arrs.reduce((sum, a) => sum + a.length, 0));
|
||||||
if (arrays.length === 1) return arrays[0];
|
let pad = 0; // walk through each item, ensure they have proper type
|
||||||
const length = arrays.reduce((a, arr) => a + arr.length, 0);
|
arrs.forEach((a) => {
|
||||||
const result = new Uint8Array(length);
|
if (!u8a(a)) throw new Error('Uint8Array expected');
|
||||||
for (let i = 0, pad = 0; i < arrays.length; i++) {
|
r.set(a, pad);
|
||||||
const arr = arrays[i];
|
pad += a.length;
|
||||||
result.set(arr, pad);
|
});
|
||||||
pad += arr.length;
|
return r;
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function equalBytes(b1: Uint8Array, b2: Uint8Array) {
|
export function equalBytes(b1: Uint8Array, b2: Uint8Array) {
|
||||||
@@ -102,6 +108,16 @@ export function equalBytes(b1: Uint8Array, b2: Uint8Array) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Global symbols in both browsers and Node.js since v11
|
||||||
|
// See https://github.com/microsoft/TypeScript/issues/31535
|
||||||
|
declare const TextEncoder: any;
|
||||||
|
export function utf8ToBytes(str: string): Uint8Array {
|
||||||
|
if (typeof str !== 'string') {
|
||||||
|
throw new Error(`utf8ToBytes expected string, got ${typeof str}`);
|
||||||
|
}
|
||||||
|
return new TextEncoder().encode(str);
|
||||||
|
}
|
||||||
|
|
||||||
// Bit operations
|
// Bit operations
|
||||||
|
|
||||||
// Amount of bits inside bigint (Same as n.toString(2).length)
|
// Amount of bits inside bigint (Same as n.toString(2).length)
|
||||||
@@ -119,3 +135,112 @@ export const bitSet = (n: bigint, pos: number, value: boolean) =>
|
|||||||
// Return mask for N bits (Same as BigInt(`0b${Array(i).fill('1').join('')}`))
|
// Return mask for N bits (Same as BigInt(`0b${Array(i).fill('1').join('')}`))
|
||||||
// Not using ** operator with bigints for old engines.
|
// Not using ** operator with bigints for old engines.
|
||||||
export const bitMask = (n: number) => (_2n << BigInt(n - 1)) - _1n;
|
export const bitMask = (n: number) => (_2n << BigInt(n - 1)) - _1n;
|
||||||
|
|
||||||
|
// DRBG
|
||||||
|
|
||||||
|
const u8n = (data?: any) => new Uint8Array(data); // creates Uint8Array
|
||||||
|
const u8fr = (arr: any) => Uint8Array.from(arr); // another shortcut
|
||||||
|
type Pred<T> = (v: Uint8Array) => T | undefined;
|
||||||
|
/**
|
||||||
|
* Minimal HMAC-DRBG from NIST 800-90 for RFC6979 sigs.
|
||||||
|
* @returns function that will call DRBG until 2nd arg returns something meaningful
|
||||||
|
* @example
|
||||||
|
* const drbg = createHmacDRBG<Key>(32, 32, hmac);
|
||||||
|
* drbg(seed, bytesToKey); // bytesToKey must return Key or undefined
|
||||||
|
*/
|
||||||
|
export function createHmacDrbg<T>(
|
||||||
|
hashLen: number,
|
||||||
|
qByteLen: number,
|
||||||
|
hmacFn: (key: Uint8Array, ...messages: Uint8Array[]) => Uint8Array
|
||||||
|
): (seed: Uint8Array, predicate: Pred<T>) => T {
|
||||||
|
if (typeof hashLen !== 'number' || hashLen < 2) throw new Error('hashLen must be a number');
|
||||||
|
if (typeof qByteLen !== 'number' || qByteLen < 2) throw new Error('qByteLen must be a number');
|
||||||
|
if (typeof hmacFn !== 'function') throw new Error('hmacFn must be a function');
|
||||||
|
// Step B, Step C: set hashLen to 8*ceil(hlen/8)
|
||||||
|
let v = u8n(hashLen); // Minimal non-full-spec HMAC-DRBG from NIST 800-90 for RFC6979 sigs.
|
||||||
|
let k = u8n(hashLen); // Steps B and C of RFC6979 3.2: set hashLen, in our case always same
|
||||||
|
let i = 0; // Iterations counter, will throw when over 1000
|
||||||
|
const reset = () => {
|
||||||
|
v.fill(1);
|
||||||
|
k.fill(0);
|
||||||
|
i = 0;
|
||||||
|
};
|
||||||
|
const h = (...b: Uint8Array[]) => hmacFn(k, v, ...b); // hmac(k)(v, ...values)
|
||||||
|
const reseed = (seed = u8n()) => {
|
||||||
|
// HMAC-DRBG reseed() function. Steps D-G
|
||||||
|
k = h(u8fr([0x00]), seed); // k = hmac(k || v || 0x00 || seed)
|
||||||
|
v = h(); // v = hmac(k || v)
|
||||||
|
if (seed.length === 0) return;
|
||||||
|
k = h(u8fr([0x01]), seed); // k = hmac(k || v || 0x01 || seed)
|
||||||
|
v = h(); // v = hmac(k || v)
|
||||||
|
};
|
||||||
|
const gen = () => {
|
||||||
|
// HMAC-DRBG generate() function
|
||||||
|
if (i++ >= 1000) throw new Error('drbg: tried 1000 values');
|
||||||
|
let len = 0;
|
||||||
|
const out: Uint8Array[] = [];
|
||||||
|
while (len < qByteLen) {
|
||||||
|
v = h();
|
||||||
|
const sl = v.slice();
|
||||||
|
out.push(sl);
|
||||||
|
len += v.length;
|
||||||
|
}
|
||||||
|
return concatBytes(...out);
|
||||||
|
};
|
||||||
|
const genUntil = (seed: Uint8Array, pred: Pred<T>): T => {
|
||||||
|
reset();
|
||||||
|
reseed(seed); // Steps D-G
|
||||||
|
let res: T | undefined = undefined; // Step H: grind until k is in [1..n-1]
|
||||||
|
while (!(res = pred(gen()))) reseed();
|
||||||
|
reset();
|
||||||
|
return res;
|
||||||
|
};
|
||||||
|
return genUntil;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validating curves and fields
|
||||||
|
|
||||||
|
const validatorFns = {
|
||||||
|
bigint: (val: any) => typeof val === 'bigint',
|
||||||
|
function: (val: any) => typeof val === 'function',
|
||||||
|
boolean: (val: any) => typeof val === 'boolean',
|
||||||
|
string: (val: any) => typeof val === 'string',
|
||||||
|
isSafeInteger: (val: any) => Number.isSafeInteger(val),
|
||||||
|
array: (val: any) => Array.isArray(val),
|
||||||
|
field: (val: any, object: any) => (object as any).Fp.isValid(val),
|
||||||
|
hash: (val: any) => typeof val === 'function' && Number.isSafeInteger(val.outputLen),
|
||||||
|
} as const;
|
||||||
|
type Validator = keyof typeof validatorFns;
|
||||||
|
type ValMap<T extends Record<string, any>> = { [K in keyof T]?: Validator };
|
||||||
|
// type Record<K extends string | number | symbol, T> = { [P in K]: T; }
|
||||||
|
|
||||||
|
export function validateObject<T extends Record<string, any>>(
|
||||||
|
object: T,
|
||||||
|
validators: ValMap<T>,
|
||||||
|
optValidators: ValMap<T> = {}
|
||||||
|
) {
|
||||||
|
const checkField = (fieldName: keyof T, type: Validator, isOptional: boolean) => {
|
||||||
|
const checkVal = validatorFns[type];
|
||||||
|
if (typeof checkVal !== 'function')
|
||||||
|
throw new Error(`Invalid validator "${type}", expected function`);
|
||||||
|
|
||||||
|
const val = object[fieldName as keyof typeof object];
|
||||||
|
if (isOptional && val === undefined) return;
|
||||||
|
if (!checkVal(val, object)) {
|
||||||
|
throw new Error(
|
||||||
|
`Invalid param ${String(fieldName)}=${val} (${typeof val}), expected ${type}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
for (const [fieldName, type] of Object.entries(validators)) checkField(fieldName, type!, false);
|
||||||
|
for (const [fieldName, type] of Object.entries(optValidators)) checkField(fieldName, type!, true);
|
||||||
|
return object;
|
||||||
|
}
|
||||||
|
// validate type tests
|
||||||
|
// const o: { a: number; b: number; c: number } = { a: 1, b: 5, c: 6 };
|
||||||
|
// const z0 = validateObject(o, { a: 'isSafeInteger' }, { c: 'bigint' }); // Ok!
|
||||||
|
// // Should fail type-check
|
||||||
|
// const z1 = validateObject(o, { a: 'tmp' }, { c: 'zz' });
|
||||||
|
// const z2 = validateObject(o, { a: 'isSafeInteger' }, { c: 'zz' });
|
||||||
|
// const z3 = validateObject(o, { test: 'boolean', z: 'bug' });
|
||||||
|
// const z4 = validateObject(o, { a: 'boolean', z: 'bug' });
|
||||||
|
|||||||
@@ -2,15 +2,8 @@
|
|||||||
// Short Weierstrass curve. The formula is: y² = x³ + ax + b
|
// Short Weierstrass curve. The formula is: y² = x³ + ax + b
|
||||||
import * as mod from './modular.js';
|
import * as mod from './modular.js';
|
||||||
import * as ut from './utils.js';
|
import * as ut from './utils.js';
|
||||||
import { Hex, PrivKey, ensureBytes, CHash } from './utils.js';
|
import { CHash, Hex, PrivKey, ensureBytes } from './utils.js';
|
||||||
import {
|
import { Group, GroupConstructor, wNAF, BasicCurve, validateBasic, AffinePoint } from './curve.js';
|
||||||
Group,
|
|
||||||
GroupConstructor,
|
|
||||||
wNAF,
|
|
||||||
AbstractCurve,
|
|
||||||
validateAbsOpts,
|
|
||||||
AffinePoint,
|
|
||||||
} from './curve.js';
|
|
||||||
|
|
||||||
export type { AffinePoint };
|
export type { AffinePoint };
|
||||||
type HmacFnSync = (key: Uint8Array, ...messages: Uint8Array[]) => Uint8Array;
|
type HmacFnSync = (key: Uint8Array, ...messages: Uint8Array[]) => Uint8Array;
|
||||||
@@ -18,18 +11,15 @@ type EndomorphismOpts = {
|
|||||||
beta: bigint;
|
beta: bigint;
|
||||||
splitScalar: (k: bigint) => { k1neg: boolean; k1: bigint; k2neg: boolean; k2: bigint };
|
splitScalar: (k: bigint) => { k1neg: boolean; k1: bigint; k2neg: boolean; k2: bigint };
|
||||||
};
|
};
|
||||||
export type BasicCurve<T> = AbstractCurve<T> & {
|
export type BasicWCurve<T> = BasicCurve<T> & {
|
||||||
// Params: a, b
|
// Params: a, b
|
||||||
a: T;
|
a: T;
|
||||||
b: T;
|
b: T;
|
||||||
|
|
||||||
// Optional params
|
// Optional params
|
||||||
// Executed before privkey validation. Useful for P521 with var-length priv key
|
allowedPrivateKeyLengths?: readonly number[]; // for P521
|
||||||
normalizePrivateKey?: (key: PrivKey) => PrivKey;
|
wrapPrivateKey?: boolean; // bls12-381 requires mod(n) instead of rejecting keys >= n
|
||||||
// Whether to execute modular division on a private key, useful for bls curves with cofactor > 1
|
endo?: EndomorphismOpts; // Endomorphism options for Koblitz curves
|
||||||
wrapPrivateKey?: boolean;
|
|
||||||
// Endomorphism options for Koblitz curves
|
|
||||||
endo?: EndomorphismOpts;
|
|
||||||
// When a cofactor != 1, there can be an effective methods to:
|
// When a cofactor != 1, there can be an effective methods to:
|
||||||
// 1. Determine whether a point is torsion-free
|
// 1. Determine whether a point is torsion-free
|
||||||
isTorsionFree?: (c: ProjConstructor<T>, point: ProjPointType<T>) => boolean;
|
isTorsionFree?: (c: ProjConstructor<T>, point: ProjPointType<T>) => boolean;
|
||||||
@@ -69,9 +59,6 @@ export interface ProjPointType<T> extends Group<ProjPointType<T>> {
|
|||||||
readonly py: T;
|
readonly py: T;
|
||||||
readonly pz: T;
|
readonly pz: T;
|
||||||
multiply(scalar: bigint): ProjPointType<T>;
|
multiply(scalar: bigint): ProjPointType<T>;
|
||||||
multiplyUnsafe(scalar: bigint): ProjPointType<T>;
|
|
||||||
multiplyAndAddUnsafe(Q: ProjPointType<T>, a: bigint, b: bigint): ProjPointType<T> | undefined;
|
|
||||||
_setWindowSize(windowSize: number): void;
|
|
||||||
toAffine(iz?: T): AffinePoint<T>;
|
toAffine(iz?: T): AffinePoint<T>;
|
||||||
isTorsionFree(): boolean;
|
isTorsionFree(): boolean;
|
||||||
clearCofactor(): ProjPointType<T>;
|
clearCofactor(): ProjPointType<T>;
|
||||||
@@ -79,6 +66,10 @@ export interface ProjPointType<T> extends Group<ProjPointType<T>> {
|
|||||||
hasEvenY(): boolean;
|
hasEvenY(): boolean;
|
||||||
toRawBytes(isCompressed?: boolean): Uint8Array;
|
toRawBytes(isCompressed?: boolean): Uint8Array;
|
||||||
toHex(isCompressed?: boolean): string;
|
toHex(isCompressed?: boolean): string;
|
||||||
|
|
||||||
|
multiplyUnsafe(scalar: bigint): ProjPointType<T>;
|
||||||
|
multiplyAndAddUnsafe(Q: ProjPointType<T>, a: bigint, b: bigint): ProjPointType<T> | undefined;
|
||||||
|
_setWindowSize(windowSize: number): void;
|
||||||
}
|
}
|
||||||
// Static methods for 3d XYZ points
|
// Static methods for 3d XYZ points
|
||||||
export interface ProjConstructor<T> extends GroupConstructor<ProjPointType<T>> {
|
export interface ProjConstructor<T> extends GroupConstructor<ProjPointType<T>> {
|
||||||
@@ -89,26 +80,33 @@ export interface ProjConstructor<T> extends GroupConstructor<ProjPointType<T>> {
|
|||||||
normalizeZ(points: ProjPointType<T>[]): ProjPointType<T>[];
|
normalizeZ(points: ProjPointType<T>[]): ProjPointType<T>[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export type CurvePointsType<T> = BasicCurve<T> & {
|
export type CurvePointsType<T> = BasicWCurve<T> & {
|
||||||
// Bytes
|
// Bytes
|
||||||
fromBytes: (bytes: Uint8Array) => AffinePoint<T>;
|
fromBytes: (bytes: Uint8Array) => AffinePoint<T>;
|
||||||
toBytes: (c: ProjConstructor<T>, point: ProjPointType<T>, compressed: boolean) => Uint8Array;
|
toBytes: (c: ProjConstructor<T>, point: ProjPointType<T>, compressed: boolean) => Uint8Array;
|
||||||
};
|
};
|
||||||
|
|
||||||
function validatePointOpts<T>(curve: CurvePointsType<T>) {
|
function validatePointOpts<T>(curve: CurvePointsType<T>) {
|
||||||
const opts = validateAbsOpts(curve);
|
const opts = validateBasic(curve);
|
||||||
const Fp = opts.Fp;
|
ut.validateObject(
|
||||||
for (const i of ['a', 'b'] as const) {
|
opts,
|
||||||
if (!Fp.isValid(curve[i]))
|
{
|
||||||
throw new Error(`Invalid curve param ${i}=${opts[i]} (${typeof opts[i]})`);
|
a: 'field',
|
||||||
}
|
b: 'field',
|
||||||
for (const i of ['isTorsionFree', 'clearCofactor'] as const) {
|
fromBytes: 'function',
|
||||||
if (curve[i] === undefined) continue; // Optional
|
toBytes: 'function',
|
||||||
if (typeof curve[i] !== 'function') throw new Error(`Invalid ${i} function`);
|
},
|
||||||
}
|
{
|
||||||
const endo = opts.endo;
|
allowedPrivateKeyLengths: 'array',
|
||||||
|
wrapPrivateKey: 'boolean',
|
||||||
|
isTorsionFree: 'function',
|
||||||
|
clearCofactor: 'function',
|
||||||
|
allowInfinityPoint: 'boolean',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const { endo, Fp, a } = opts;
|
||||||
if (endo) {
|
if (endo) {
|
||||||
if (!Fp.eql(opts.a, Fp.ZERO)) {
|
if (!Fp.eql(a, Fp.ZERO)) {
|
||||||
throw new Error('Endomorphism can only be defined for Koblitz curves that have a=0');
|
throw new Error('Endomorphism can only be defined for Koblitz curves that have a=0');
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
@@ -119,9 +117,6 @@ function validatePointOpts<T>(curve: CurvePointsType<T>) {
|
|||||||
throw new Error('Expected endomorphism with beta: bigint and splitScalar: function');
|
throw new Error('Expected endomorphism with beta: bigint and splitScalar: function');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (typeof opts.fromBytes !== 'function') throw new Error('Invalid fromBytes function');
|
|
||||||
if (typeof opts.toBytes !== 'function') throw new Error('Invalid fromBytes function');
|
|
||||||
// Set defaults
|
|
||||||
return Object.freeze({ ...opts } as const);
|
return Object.freeze({ ...opts } as const);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -207,32 +202,27 @@ export function weierstrassPoints<T>(opts: CurvePointsType<T>) {
|
|||||||
function assertGE(num: bigint) {
|
function assertGE(num: bigint) {
|
||||||
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 a private key is valid and converts it to bigint form.
|
// Supports options CURVE.normalizePrivateKey and CURVE.wrapPrivateKey.
|
||||||
* Supports two options, that are passed when CURVE is initialized:
|
|
||||||
* - `normalizePrivateKey()` executed before all checks
|
|
||||||
* - `wrapPrivateKey` when true, executed after most checks, but before `0 < key < n`
|
|
||||||
*/
|
|
||||||
function normalizePrivateKey(key: PrivKey): bigint {
|
function normalizePrivateKey(key: PrivKey): bigint {
|
||||||
const { normalizePrivateKey: custom, nByteLength: groupLen, wrapPrivateKey, n } = CURVE;
|
const { allowedPrivateKeyLengths: lengths, nByteLength, wrapPrivateKey, n } = CURVE;
|
||||||
if (typeof custom === 'function') key = custom(key);
|
if (lengths && typeof key !== 'bigint') {
|
||||||
let num: bigint;
|
if (key instanceof Uint8Array) key = ut.bytesToHex(key);
|
||||||
if (typeof key === 'bigint') {
|
// Normalize to hex string, pad. E.g. P521 would norm 130-132 char hex to 132-char bytes
|
||||||
// Curve order check is done below
|
if (typeof key !== 'string' || !lengths.includes(key.length)) throw new Error('Invalid key');
|
||||||
num = key;
|
key = key.padStart(nByteLength * 2, '0');
|
||||||
} else if (typeof key === 'string') {
|
|
||||||
if (key.length !== 2 * groupLen) throw new Error(`must be ${groupLen} bytes`);
|
|
||||||
// Validates individual octets
|
|
||||||
num = ut.bytesToNumberBE(ensureBytes(key));
|
|
||||||
} else if (key instanceof Uint8Array) {
|
|
||||||
if (key.length !== groupLen) throw new Error(`must be ${groupLen} bytes`);
|
|
||||||
num = ut.bytesToNumberBE(key);
|
|
||||||
} else {
|
|
||||||
throw new Error('private key must be bytes, hex or bigint, not ' + typeof key);
|
|
||||||
}
|
}
|
||||||
// Useful for curves with cofactor != 1
|
let num: bigint;
|
||||||
if (wrapPrivateKey) num = mod.mod(num, n);
|
try {
|
||||||
assertGE(num);
|
num =
|
||||||
|
typeof key === 'bigint'
|
||||||
|
? key
|
||||||
|
: ut.bytesToNumberBE(ensureBytes('private key', key, nByteLength));
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(`private key must be ${nByteLength} bytes, hex or bigint, not ${typeof key}`);
|
||||||
|
}
|
||||||
|
if (wrapPrivateKey) num = mod.mod(num, n); // disabled by default, enabled for BLS
|
||||||
|
assertGE(num); // num in range [1..N-1]
|
||||||
return num;
|
return num;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -255,6 +245,8 @@ export function weierstrassPoints<T>(opts: CurvePointsType<T>) {
|
|||||||
if (pz == null || !Fp.isValid(pz)) throw new Error('z required');
|
if (pz == null || !Fp.isValid(pz)) throw new Error('z required');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Does not validate if the point is on-curve.
|
||||||
|
// Use fromHex instead, or call assertValidity() later.
|
||||||
static fromAffine(p: AffinePoint<T>): Point {
|
static fromAffine(p: AffinePoint<T>): Point {
|
||||||
const { x, y } = p || {};
|
const { x, y } = p || {};
|
||||||
if (!p || !Fp.isValid(x) || !Fp.isValid(y)) throw new Error('invalid affine point');
|
if (!p || !Fp.isValid(x) || !Fp.isValid(y)) throw new Error('invalid affine point');
|
||||||
@@ -288,7 +280,7 @@ export function weierstrassPoints<T>(opts: CurvePointsType<T>) {
|
|||||||
* @param hex short/long ECDSA hex
|
* @param hex short/long ECDSA hex
|
||||||
*/
|
*/
|
||||||
static fromHex(hex: Hex): Point {
|
static fromHex(hex: Hex): Point {
|
||||||
const P = Point.fromAffine(CURVE.fromBytes(ensureBytes(hex)));
|
const P = Point.fromAffine(CURVE.fromBytes(ensureBytes('pointHex', hex)));
|
||||||
P.assertValidity();
|
P.assertValidity();
|
||||||
return P;
|
return P;
|
||||||
}
|
}
|
||||||
@@ -612,25 +604,30 @@ type SignatureLike = { r: bigint; s: bigint };
|
|||||||
|
|
||||||
export type PubKey = Hex | ProjPointType<bigint>;
|
export type PubKey = Hex | ProjPointType<bigint>;
|
||||||
|
|
||||||
export type CurveType = BasicCurve<bigint> & {
|
export type CurveType = BasicWCurve<bigint> & {
|
||||||
// Default options
|
hash: CHash; // CHash not FHash because we need outputLen for DRBG
|
||||||
lowS?: boolean;
|
|
||||||
// Hashes
|
|
||||||
hash: CHash; // Because we need outputLen for DRBG
|
|
||||||
hmac: HmacFnSync;
|
hmac: HmacFnSync;
|
||||||
randomBytes: (bytesLength?: number) => Uint8Array;
|
randomBytes: (bytesLength?: number) => Uint8Array;
|
||||||
// truncateHash?: (hash: Uint8Array, truncateOnly?: boolean) => Uint8Array;
|
lowS?: boolean;
|
||||||
bits2int?: (bytes: Uint8Array) => bigint;
|
bits2int?: (bytes: Uint8Array) => bigint;
|
||||||
bits2int_modN?: (bytes: Uint8Array) => bigint;
|
bits2int_modN?: (bytes: Uint8Array) => bigint;
|
||||||
};
|
};
|
||||||
|
|
||||||
function validateOpts(curve: CurveType) {
|
function validateOpts(curve: CurveType) {
|
||||||
const opts = validateAbsOpts(curve);
|
const opts = validateBasic(curve);
|
||||||
if (typeof opts.hash !== 'function' || !Number.isSafeInteger(opts.hash.outputLen))
|
ut.validateObject(
|
||||||
throw new Error('Invalid hash function');
|
opts,
|
||||||
if (typeof opts.hmac !== 'function') throw new Error('Invalid hmac function');
|
{
|
||||||
if (typeof opts.randomBytes !== 'function') throw new Error('Invalid randomBytes function');
|
hash: 'hash',
|
||||||
// Set defaults
|
hmac: 'function',
|
||||||
|
randomBytes: 'function',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
bits2int: 'function',
|
||||||
|
bits2int_modN: 'function',
|
||||||
|
lowS: 'boolean',
|
||||||
|
}
|
||||||
|
);
|
||||||
return Object.freeze({ lowS: true, ...opts } as const);
|
return Object.freeze({ lowS: true, ...opts } as const);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -643,66 +640,14 @@ export type CurveFn = {
|
|||||||
ProjectivePoint: ProjConstructor<bigint>;
|
ProjectivePoint: ProjConstructor<bigint>;
|
||||||
Signature: SignatureConstructor;
|
Signature: SignatureConstructor;
|
||||||
utils: {
|
utils: {
|
||||||
_normalizePrivateKey: (key: PrivKey) => bigint;
|
normPrivateKeyToScalar: (key: PrivKey) => bigint;
|
||||||
isValidPrivateKey(privateKey: PrivKey): boolean;
|
isValidPrivateKey(privateKey: PrivKey): boolean;
|
||||||
hashToPrivateKey: (hash: Hex) => Uint8Array;
|
hashToPrivateKey: (hash: Hex) => Uint8Array;
|
||||||
randomPrivateKey: () => Uint8Array;
|
randomPrivateKey: () => Uint8Array;
|
||||||
|
precompute: (windowSize?: number, point?: ProjPointType<bigint>) => ProjPointType<bigint>;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const u8n = (data?: any) => new Uint8Array(data); // creates Uint8Array
|
|
||||||
const u8fr = (arr: any) => Uint8Array.from(arr); // another shortcut
|
|
||||||
// Minimal HMAC-DRBG from NIST 800-90 for RFC6979 sigs.
|
|
||||||
type Pred<T> = (v: Uint8Array) => T | undefined;
|
|
||||||
function hmacDrbg<T>(
|
|
||||||
hashLen: number,
|
|
||||||
qByteLen: number,
|
|
||||||
hmacFn: HmacFnSync
|
|
||||||
): (seed: Uint8Array, predicate: Pred<T>) => T {
|
|
||||||
if (typeof hashLen !== 'number' || hashLen < 2) throw new Error('hashLen must be a number');
|
|
||||||
if (typeof qByteLen !== 'number' || qByteLen < 2) throw new Error('qByteLen must be a number');
|
|
||||||
if (typeof hmacFn !== 'function') throw new Error('hmacFn must be a function');
|
|
||||||
// Step B, Step C: set hashLen to 8*ceil(hlen/8)
|
|
||||||
let v = u8n(hashLen); // Minimal non-full-spec HMAC-DRBG from NIST 800-90 for RFC6979 sigs.
|
|
||||||
let k = u8n(hashLen); // Steps B and C of RFC6979 3.2: set hashLen, in our case always same
|
|
||||||
let i = 0; // Iterations counter, will throw when over 1000
|
|
||||||
const reset = () => {
|
|
||||||
v.fill(1);
|
|
||||||
k.fill(0);
|
|
||||||
i = 0;
|
|
||||||
};
|
|
||||||
const h = (...b: Uint8Array[]) => hmacFn(k, v, ...b); // hmac(k)(v, ...values)
|
|
||||||
const reseed = (seed = u8n()) => {
|
|
||||||
// HMAC-DRBG reseed() function. Steps D-G
|
|
||||||
k = h(u8fr([0x00]), seed); // k = hmac(k || v || 0x00 || seed)
|
|
||||||
v = h(); // v = hmac(k || v)
|
|
||||||
if (seed.length === 0) return;
|
|
||||||
k = h(u8fr([0x01]), seed); // k = hmac(k || v || 0x01 || seed)
|
|
||||||
v = h(); // v = hmac(k || v)
|
|
||||||
};
|
|
||||||
const gen = () => {
|
|
||||||
// HMAC-DRBG generate() function
|
|
||||||
if (i++ >= 1000) throw new Error('drbg: tried 1000 values');
|
|
||||||
let len = 0;
|
|
||||||
const out: Uint8Array[] = [];
|
|
||||||
while (len < qByteLen) {
|
|
||||||
v = h();
|
|
||||||
const sl = v.slice();
|
|
||||||
out.push(sl);
|
|
||||||
len += v.length;
|
|
||||||
}
|
|
||||||
return ut.concatBytes(...out);
|
|
||||||
};
|
|
||||||
const genUntil = (seed: Uint8Array, pred: Pred<T>): T => {
|
|
||||||
reset();
|
|
||||||
reseed(seed); // Steps D-G
|
|
||||||
let res: T | undefined = undefined; // Step H: grind until k is in [1..n-1]
|
|
||||||
while (!(res = pred(gen()))) reseed();
|
|
||||||
reset();
|
|
||||||
return res;
|
|
||||||
};
|
|
||||||
return genUntil;
|
|
||||||
}
|
|
||||||
export function weierstrass(curveDef: CurveType): CurveFn {
|
export function weierstrass(curveDef: CurveType): CurveFn {
|
||||||
const CURVE = validateOpts(curveDef) as ReturnType<typeof validateOpts>;
|
const CURVE = validateOpts(curveDef) as ReturnType<typeof validateOpts>;
|
||||||
const CURVE_ORDER = CURVE.n;
|
const CURVE_ORDER = CURVE.n;
|
||||||
@@ -759,7 +704,7 @@ export function weierstrass(curveDef: CurveType): CurveFn {
|
|||||||
return { x, y };
|
return { x, y };
|
||||||
} else {
|
} else {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Point.fromHex: received invalid point. Expected ${compressedLen} compressed bytes or ${uncompressedLen} uncompressed bytes, not ${len}`
|
`Point of length ${len} was invalid. Expected ${compressedLen} compressed bytes or ${uncompressedLen} uncompressed bytes`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -788,24 +733,22 @@ export function weierstrass(curveDef: CurveType): CurveFn {
|
|||||||
|
|
||||||
// pair (bytes of r, bytes of s)
|
// pair (bytes of r, bytes of s)
|
||||||
static fromCompact(hex: Hex) {
|
static fromCompact(hex: Hex) {
|
||||||
const gl = CURVE.nByteLength;
|
const l = CURVE.nByteLength;
|
||||||
hex = ensureBytes(hex, gl * 2);
|
hex = ensureBytes('compactSignature', hex, l * 2);
|
||||||
return new Signature(slcNum(hex, 0, gl), slcNum(hex, gl, 2 * gl));
|
return new Signature(slcNum(hex, 0, l), slcNum(hex, l, 2 * l));
|
||||||
}
|
}
|
||||||
|
|
||||||
// DER encoded ECDSA signature
|
// DER encoded ECDSA signature
|
||||||
// https://bitcoin.stackexchange.com/questions/57644/what-are-the-parts-of-a-bitcoin-transaction-input-script
|
// https://bitcoin.stackexchange.com/questions/57644/what-are-the-parts-of-a-bitcoin-transaction-input-script
|
||||||
static fromDER(hex: Hex) {
|
static fromDER(hex: Hex) {
|
||||||
if (typeof hex !== 'string' && !(hex instanceof Uint8Array))
|
const { r, s } = DER.toSig(ensureBytes('DER', hex));
|
||||||
throw new Error(`Signature.fromDER: Expected string or Uint8Array`);
|
|
||||||
const { r, s } = DER.toSig(ensureBytes(hex));
|
|
||||||
return new Signature(r, s);
|
return new Signature(r, s);
|
||||||
}
|
}
|
||||||
|
|
||||||
assertValidity(): void {
|
assertValidity(): void {
|
||||||
// can use assertGE here
|
// can use assertGE here
|
||||||
if (!isWithinCurveOrder(this.r)) throw new Error('r must be 0 < r < n');
|
if (!isWithinCurveOrder(this.r)) throw new Error('r must be 0 < r < CURVE.n');
|
||||||
if (!isWithinCurveOrder(this.s)) throw new Error('s must be 0 < s < n');
|
if (!isWithinCurveOrder(this.s)) throw new Error('s must be 0 < s < CURVE.n');
|
||||||
}
|
}
|
||||||
|
|
||||||
addRecoveryBit(recovery: number) {
|
addRecoveryBit(recovery: number) {
|
||||||
@@ -813,18 +756,17 @@ export function weierstrass(curveDef: CurveType): CurveFn {
|
|||||||
}
|
}
|
||||||
|
|
||||||
recoverPublicKey(msgHash: Hex): typeof Point.BASE {
|
recoverPublicKey(msgHash: Hex): typeof Point.BASE {
|
||||||
const { n: N } = CURVE; // ECDSA public key recovery secg.org/sec1-v2.pdf 4.1.6
|
|
||||||
const { r, s, recovery: rec } = this;
|
const { r, s, recovery: rec } = this;
|
||||||
const h = bits2int_modN(ensureBytes(msgHash)); // Truncate hash
|
const h = bits2int_modN(ensureBytes('msgHash', msgHash)); // Truncate hash
|
||||||
if (rec == null || ![0, 1, 2, 3].includes(rec)) throw new Error('recovery id invalid');
|
if (rec == null || ![0, 1, 2, 3].includes(rec)) throw new Error('recovery id invalid');
|
||||||
const radj = rec === 2 || rec === 3 ? r + N : r;
|
const radj = rec === 2 || rec === 3 ? r + CURVE.n : r;
|
||||||
if (radj >= Fp.ORDER) throw new Error('recovery id 2 or 3 invalid');
|
if (radj >= Fp.ORDER) throw new Error('recovery id 2 or 3 invalid');
|
||||||
const prefix = (rec & 1) === 0 ? '02' : '03';
|
const prefix = (rec & 1) === 0 ? '02' : '03';
|
||||||
const R = Point.fromHex(prefix + numToNByteStr(radj));
|
const R = Point.fromHex(prefix + numToNByteStr(radj));
|
||||||
const ir = invN(radj); // r^-1
|
const ir = invN(radj); // r^-1
|
||||||
const u1 = modN(-h * ir); // -hr^-1
|
const u1 = modN(-h * ir); // -hr^-1
|
||||||
const u2 = modN(s * ir); // sr^-1
|
const u2 = modN(s * ir); // sr^-1
|
||||||
const Q = Point.BASE.multiplyAndAddUnsafe(R, u1, u2); // (sr^-1)R-(hr^-1)G = -(hr^-1)G + (sr^-1)
|
const Q = Point.BASE.multiplyAndAddUnsafe(R, u1, u2); // (sr^-1)R-(hr^-1)G = -(hr^-1)G + (sr^-1)
|
||||||
if (!Q) throw new Error('point at infinify'); // unsafe is fine: no priv data leaked
|
if (!Q) throw new Error('point at infinify'); // unsafe is fine: no priv data leaked
|
||||||
Q.assertValidity();
|
Q.assertValidity();
|
||||||
return Q;
|
return Q;
|
||||||
@@ -865,7 +807,7 @@ export function weierstrass(curveDef: CurveType): CurveFn {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
_normalizePrivateKey: normalizePrivateKey,
|
normPrivateKeyToScalar: normalizePrivateKey,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts some bytes to a valid private key. Needs at least (nBitLength+64) bytes.
|
* Converts some bytes to a valid private key. Needs at least (nBitLength+64) bytes.
|
||||||
@@ -942,8 +884,8 @@ export function weierstrass(curveDef: CurveType): CurveFn {
|
|||||||
function (bytes: Uint8Array): bigint {
|
function (bytes: Uint8Array): bigint {
|
||||||
// For curves with nBitLength % 8 !== 0: bits2octets(bits2octets(m)) !== bits2octets(m)
|
// For curves with nBitLength % 8 !== 0: bits2octets(bits2octets(m)) !== bits2octets(m)
|
||||||
// for some cases, since bytes.length * 8 is not actual bitLength.
|
// for some cases, since bytes.length * 8 is not actual bitLength.
|
||||||
const delta = bytes.length * 8 - CURVE.nBitLength; // truncate to nBitLength leftmost bits
|
|
||||||
const num = ut.bytesToNumberBE(bytes); // check for == u8 done here
|
const num = ut.bytesToNumberBE(bytes); // check for == u8 done here
|
||||||
|
const delta = bytes.length * 8 - CURVE.nBitLength; // truncate to nBitLength leftmost bits
|
||||||
return delta > 0 ? num >> BigInt(delta) : num;
|
return delta > 0 ? num >> BigInt(delta) : num;
|
||||||
};
|
};
|
||||||
const bits2int_modN =
|
const bits2int_modN =
|
||||||
@@ -954,9 +896,10 @@ 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);
|
||||||
function int2octets(num: bigint): Uint8Array {
|
function int2octets(num: bigint): Uint8Array {
|
||||||
if (typeof num !== 'bigint') throw new Error('Expected bigint');
|
if (typeof num !== 'bigint') throw new Error('bigint expected');
|
||||||
if (!(_0n <= num && num < ORDER_MASK))
|
if (!(_0n <= num && num < ORDER_MASK))
|
||||||
throw new Error(`Expected number < 2^${CURVE.nBitLength}`);
|
// n in [0..ORDER_MASK-1]
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
@@ -967,32 +910,25 @@ export function weierstrass(curveDef: CurveType): CurveFn {
|
|||||||
// NOTE: we cannot assume here that msgHash has same amount of bytes as curve order, this will be wrong at least for P521.
|
// NOTE: we cannot assume here that msgHash has same amount of bytes as curve order, this will be wrong at least for P521.
|
||||||
// Also it can be bigger for P224 + SHA256
|
// Also it can be bigger for P224 + SHA256
|
||||||
function prepSig(msgHash: Hex, privateKey: PrivKey, opts = defaultSigOpts) {
|
function prepSig(msgHash: Hex, privateKey: PrivKey, opts = defaultSigOpts) {
|
||||||
if (msgHash == null) throw new Error(`sign: expected valid message hash, not "${msgHash}"`);
|
|
||||||
if (['recovered', 'canonical'].some((k) => k in opts))
|
if (['recovered', 'canonical'].some((k) => k in opts))
|
||||||
// Ban legacy options
|
|
||||||
throw new Error('sign() legacy options not supported');
|
throw new Error('sign() legacy options not supported');
|
||||||
|
const { hash, randomBytes } = CURVE;
|
||||||
let { lowS, prehash, extraEntropy: ent } = opts; // generates low-s sigs by default
|
let { lowS, prehash, extraEntropy: ent } = opts; // generates low-s sigs by default
|
||||||
if (prehash) msgHash = CURVE.hash(ensureBytes(msgHash));
|
if (lowS == null) lowS = true; // RFC6979 3.2: we skip step A, because we already provide hash
|
||||||
if (lowS == null) lowS = true; // RFC6979 3.2: we skip step A, because
|
msgHash = ensureBytes('msgHash', msgHash);
|
||||||
// Step A is ignored, since we already provide hash instead of msg
|
if (prehash) msgHash = ensureBytes('prehashed msgHash', hash(msgHash));
|
||||||
|
|
||||||
// NOTE: instead of bits2int, we calling here truncateHash, since we need
|
// We can't later call bits2octets, since nested bits2int is broken for curves
|
||||||
// custom truncation for stark. For other curves it is essentially same as calling bits2int + mod
|
// with nBitLength % 8 !== 0. Because of that, we unwrap it here as int2octets call.
|
||||||
// However, we cannot later call bits2octets (which is truncateHash + int2octets), since nested bits2int is broken
|
// const bits2octets = (bits) => int2octets(bits2int_modN(bits))
|
||||||
// for curves where nBitLength % 8 !== 0, so we unwrap it here as int2octets call.
|
const h1int = bits2int_modN(msgHash);
|
||||||
// const bits2octets = (bits)=>int2octets(bytesToNumberBE(truncateHash(bits)))
|
const d = normalizePrivateKey(privateKey); // validate private key, convert to bigint
|
||||||
const h1int = bits2int_modN(ensureBytes(msgHash));
|
const seedArgs = [int2octets(d), int2octets(h1int)];
|
||||||
const h1octets = int2octets(h1int);
|
// extraEntropy. RFC6979 3.6: additional k' (optional).
|
||||||
|
|
||||||
const d = normalizePrivateKey(privateKey);
|
|
||||||
// K = HMAC_K(V || 0x00 || int2octets(x) || bits2octets(h1) || k')
|
|
||||||
const seedArgs = [int2octets(d), h1octets];
|
|
||||||
if (ent != null) {
|
if (ent != null) {
|
||||||
// RFC6979 3.6: additional k' (optional)
|
// K = HMAC_K(V || 0x00 || int2octets(x) || bits2octets(h1) || k')
|
||||||
if (ent === true) ent = CURVE.randomBytes(Fp.BYTES);
|
const e = ent === true ? randomBytes(Fp.BYTES) : ent; // generate random bytes OR pass as-is
|
||||||
const e = ensureBytes(ent);
|
seedArgs.push(ensureBytes('extraEntropy', e, Fp.BYTES)); // check for being of size BYTES
|
||||||
if (e.length !== Fp.BYTES) throw new Error(`sign: Expected ${Fp.BYTES} bytes of extra data`);
|
|
||||||
seedArgs.push(e);
|
|
||||||
}
|
}
|
||||||
const seed = ut.concatBytes(...seedArgs); // Step D of RFC6979 3.2
|
const seed = ut.concatBytes(...seedArgs); // Step D of RFC6979 3.2
|
||||||
const m = h1int; // NOTE: no need to call bits2int second time here, it is inside truncateHash!
|
const m = h1int; // NOTE: no need to call bits2int second time here, it is inside truncateHash!
|
||||||
@@ -1005,7 +941,16 @@ 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;
|
||||||
const s = modN(ik * modN(m + modN(d * r))); // s = k^-1(m + rd) mod n
|
// X blinding according to https://tches.iacr.org/index.php/TCHES/article/view/7337/6509
|
||||||
|
// b * m + b * r * d ∈ [0,q−1] exposed via side-channel, but d (private scalar) is not.
|
||||||
|
// NOTE: there is still probable some leak in multiplication, since it is not constant-time
|
||||||
|
const b = ut.bytesToNumberBE(utils.randomPrivateKey()); // random scalar, b ∈ [1,q−1]
|
||||||
|
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;
|
||||||
@@ -1032,8 +977,8 @@ export function weierstrass(curveDef: CurveType): CurveFn {
|
|||||||
*/
|
*/
|
||||||
function sign(msgHash: Hex, privKey: PrivKey, opts = defaultSigOpts): Signature {
|
function sign(msgHash: Hex, privKey: PrivKey, opts = defaultSigOpts): Signature {
|
||||||
const { seed, k2sig } = prepSig(msgHash, privKey, opts); // Steps A, D of RFC6979 3.2.
|
const { seed, k2sig } = prepSig(msgHash, privKey, opts); // Steps A, D of RFC6979 3.2.
|
||||||
const genUntil = hmacDrbg<Signature>(CURVE.hash.outputLen, CURVE.nByteLength, CURVE.hmac);
|
const drbg = ut.createHmacDrbg<Signature>(CURVE.hash.outputLen, CURVE.nByteLength, CURVE.hmac);
|
||||||
return genUntil(seed, k2sig); // Steps B, C, D, E, F, G
|
return drbg(seed, k2sig); // Steps B, C, D, E, F, G
|
||||||
}
|
}
|
||||||
|
|
||||||
// Enable precomputes. Slows down first publicKey computation by 20ms.
|
// Enable precomputes. Slows down first publicKey computation by 20ms.
|
||||||
@@ -1054,35 +999,43 @@ export function weierstrass(curveDef: CurveType): CurveFn {
|
|||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
function verify(
|
function verify(
|
||||||
signature: Hex | { r: bigint; s: bigint },
|
signature: Hex | SignatureLike,
|
||||||
msgHash: Hex,
|
msgHash: Hex,
|
||||||
publicKey: Hex,
|
publicKey: Hex,
|
||||||
opts = defaultVerOpts
|
opts = defaultVerOpts
|
||||||
): boolean {
|
): boolean {
|
||||||
let P: ProjPointType<bigint>;
|
const sg = signature;
|
||||||
|
msgHash = ensureBytes('msgHash', msgHash);
|
||||||
|
publicKey = ensureBytes('publicKey', publicKey);
|
||||||
|
if ('strict' in opts) throw new Error('options.strict was renamed to lowS');
|
||||||
|
const { lowS, prehash } = opts;
|
||||||
|
|
||||||
let _sig: Signature | undefined = undefined;
|
let _sig: Signature | undefined = undefined;
|
||||||
if (publicKey instanceof Point) throw new Error('publicKey must be hex');
|
let P: ProjPointType<bigint>;
|
||||||
try {
|
try {
|
||||||
if (signature && typeof signature === 'object' && !(signature instanceof Uint8Array)) {
|
if (typeof sg === 'string' || sg instanceof Uint8Array) {
|
||||||
const { r, s } = signature;
|
|
||||||
_sig = new Signature(r, s); // assertValidity() is executed on creation
|
|
||||||
} else {
|
|
||||||
// Signature can be represented in 2 ways: compact (2*nByteLength) & DER (variable-length).
|
// Signature can be represented in 2 ways: compact (2*nByteLength) & DER (variable-length).
|
||||||
// Since DER can also be 2*nByteLength bytes, we check for it first.
|
// Since DER can also be 2*nByteLength bytes, we check for it first.
|
||||||
try {
|
try {
|
||||||
_sig = Signature.fromDER(signature as Hex);
|
_sig = Signature.fromDER(sg);
|
||||||
} catch (derError) {
|
} catch (derError) {
|
||||||
if (!(derError instanceof DER.Err)) throw derError;
|
if (!(derError instanceof DER.Err)) throw derError;
|
||||||
_sig = Signature.fromCompact(signature as Hex);
|
_sig = Signature.fromCompact(sg);
|
||||||
}
|
}
|
||||||
|
} else if (typeof sg === 'object' && typeof sg.r === 'bigint' && typeof sg.s === 'bigint') {
|
||||||
|
const { r, s } = sg;
|
||||||
|
_sig = new Signature(r, s);
|
||||||
|
} else {
|
||||||
|
throw new Error('PARSE');
|
||||||
}
|
}
|
||||||
msgHash = ensureBytes(msgHash);
|
|
||||||
P = Point.fromHex(publicKey);
|
P = Point.fromHex(publicKey);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
if ((error as Error).message === 'PARSE')
|
||||||
|
throw new Error(`signature must be Signature instance, Uint8Array or hex string`);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (opts.lowS && _sig.hasHighS()) return false;
|
if (lowS && _sig.hasHighS()) return false;
|
||||||
if (opts.prehash) msgHash = CURVE.hash(msgHash);
|
if (prehash) msgHash = CURVE.hash(msgHash);
|
||||||
const { r, s } = _sig;
|
const { r, s } = _sig;
|
||||||
const h = bits2int_modN(msgHash); // Cannot use fields methods, since it is group element
|
const h = bits2int_modN(msgHash); // Cannot use fields methods, since it is group element
|
||||||
const is = invN(s); // s^-1
|
const is = invN(s); // s^-1
|
||||||
@@ -1099,7 +1052,6 @@ export function weierstrass(curveDef: CurveType): CurveFn {
|
|||||||
getSharedSecret,
|
getSharedSecret,
|
||||||
sign,
|
sign,
|
||||||
verify,
|
verify,
|
||||||
// Point,
|
|
||||||
ProjectivePoint: Point,
|
ProjectivePoint: Point,
|
||||||
Signature,
|
Signature,
|
||||||
utils,
|
utils,
|
||||||
|
|||||||
@@ -1,9 +1,43 @@
|
|||||||
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
|
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
|
||||||
|
|
||||||
// The pairing-friendly Barreto-Lynn-Scott elliptic curve construction allows to:
|
// bls12-381 pairing-friendly Barreto-Lynn-Scott elliptic curve construction allows to:
|
||||||
// - Construct zk-SNARKs at the 128-bit security
|
// - Construct zk-SNARKs at the 128-bit security
|
||||||
// - Use threshold signatures, which allows a user to sign lots of messages with one signature and verify them swiftly in a batch, using Boneh-Lynn-Shacham signature scheme.
|
// - Use threshold signatures, which allows a user to sign lots of messages with one signature and
|
||||||
// Differences from @noble/bls12-381 1.4:
|
// verify them swiftly in a batch, using Boneh-Lynn-Shacham signature scheme.
|
||||||
|
//
|
||||||
|
// The library uses G1 for public keys and G2 for signatures. Support for G1 signatures is planned.
|
||||||
|
// Compatible with Algorand, Chia, Dfinity, Ethereum, FIL, Zcash. Matches specs
|
||||||
|
// [pairing-curves-10](https://tools.ietf.org/html/draft-irtf-cfrg-pairing-friendly-curves-10),
|
||||||
|
// [bls-sigs-04](https://tools.ietf.org/html/draft-irtf-cfrg-bls-signature-04),
|
||||||
|
// [hash-to-curve-12](https://tools.ietf.org/html/draft-irtf-cfrg-hash-to-curve-12).
|
||||||
|
//
|
||||||
|
// ### Summary
|
||||||
|
// 1. BLS Relies on Bilinear Pairing (expensive)
|
||||||
|
// 2. Private Keys: 32 bytes
|
||||||
|
// 3. Public Keys: 48 bytes: 381 bit affine x coordinate, encoded into 48 big-endian bytes.
|
||||||
|
// 4. Signatures: 96 bytes: two 381 bit integers (affine x coordinate), encoded into two 48 big-endian byte arrays.
|
||||||
|
// - The signature is a point on the G2 subgroup, which is defined over a finite field
|
||||||
|
// with elements twice as big as the G1 curve (G2 is over Fp2 rather than Fp. Fp2 is analogous to the complex numbers).
|
||||||
|
// 5. The 12 stands for the Embedding degree.
|
||||||
|
//
|
||||||
|
// ### Formulas
|
||||||
|
// - `P = pk x G` - public keys
|
||||||
|
// - `S = pk x H(m)` - signing
|
||||||
|
// - `e(P, H(m)) == e(G, S)` - verification using pairings
|
||||||
|
// - `e(G, S) = e(G, SUM(n)(Si)) = MUL(n)(e(G, Si))` - signature aggregation
|
||||||
|
// Filecoin uses little endian byte arrays for private keys -
|
||||||
|
// so ensure to reverse byte order if you'll use it with FIL.
|
||||||
|
//
|
||||||
|
// ### Resources
|
||||||
|
// - [BLS12-381 for the rest of us](https://hackmd.io/@benjaminion/bls12-381)
|
||||||
|
// - [Key concepts of pairings](https://medium.com/@alonmuroch_65570/bls-signatures-part-2-key-concepts-of-pairings-27a8a9533d0c)
|
||||||
|
// - Pairing over bls12-381:
|
||||||
|
// [part 1](https://research.nccgroup.com/2020/07/06/pairing-over-bls12-381-part-1-fields/),
|
||||||
|
// [part 2](https://research.nccgroup.com/2020/07/13/pairing-over-bls12-381-part-2-curves/),
|
||||||
|
// [part 3](https://research.nccgroup.com/2020/08/13/pairing-over-bls12-381-part-3-pairing/)
|
||||||
|
// - [Estimating the bit security of pairing-friendly curves](https://research.nccgroup.com/2022/02/03/estimating-the-bit-security-of-pairing-friendly-curves/)
|
||||||
|
//
|
||||||
|
// ### Differences from @noble/bls12-381 1.4
|
||||||
// - PointG1 -> G1.Point
|
// - PointG1 -> G1.Point
|
||||||
// - PointG2 -> G2.Point
|
// - PointG2 -> G2.Point
|
||||||
// - PointG2.fromSignature -> Signature.decode
|
// - PointG2.fromSignature -> Signature.decode
|
||||||
@@ -910,7 +944,7 @@ function G2psi2(c: ProjConstructor<Fp2>, P: ProjPointType<Fp2>) {
|
|||||||
// p = 0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab
|
// p = 0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab
|
||||||
// m = 2 (or 1 for G1 see section 8.8.1)
|
// m = 2 (or 1 for G1 see section 8.8.1)
|
||||||
// k = 128
|
// k = 128
|
||||||
const htfDefaults = {
|
const htfDefaults = Object.freeze({
|
||||||
// DST: a domain separation tag
|
// DST: a domain separation tag
|
||||||
// defined in section 2.2.5
|
// defined in section 2.2.5
|
||||||
// Use utils.getDSTLabel(), utils.setDSTLabel(value)
|
// Use utils.getDSTLabel(), utils.setDSTLabel(value)
|
||||||
@@ -932,7 +966,7 @@ const htfDefaults = {
|
|||||||
// wide range of hash functions, including SHA-2, SHA-3, BLAKE2, and others.
|
// 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
|
// BBS+ uses blake2: https://github.com/hyperledger/aries-framework-go/issues/2247
|
||||||
hash: sha256,
|
hash: sha256,
|
||||||
} as const;
|
} as const);
|
||||||
|
|
||||||
// Encoding utils
|
// Encoding utils
|
||||||
// Point on G1 curve: (x, y)
|
// Point on G1 curve: (x, y)
|
||||||
@@ -1186,7 +1220,7 @@ export const bls12_381: CurveFn<Fp, Fp2, Fp6, Fp12> = bls({
|
|||||||
Signature: {
|
Signature: {
|
||||||
// TODO: Optimize, it's very slow because of sqrt.
|
// TODO: Optimize, it's very slow because of sqrt.
|
||||||
decode(hex: Hex): ProjPointType<Fp2> {
|
decode(hex: Hex): ProjPointType<Fp2> {
|
||||||
hex = ensureBytes(hex);
|
hex = ensureBytes('signatureHex', hex);
|
||||||
const P = Fp.ORDER;
|
const P = Fp.ORDER;
|
||||||
const half = hex.length / 2;
|
const half = hex.length / 2;
|
||||||
if (half !== 48 && half !== 96)
|
if (half !== 48 && half !== 96)
|
||||||
@@ -1213,7 +1247,6 @@ export const bls12_381: CurveFn<Fp, Fp2, Fp6, Fp12> = bls({
|
|||||||
const isZero = y1 === 0n && (y0 * 2n) / P !== aflag1;
|
const isZero = y1 === 0n && (y0 * 2n) / P !== aflag1;
|
||||||
if (isGreater || isZero) y = Fp2.neg(y);
|
if (isGreater || isZero) y = Fp2.neg(y);
|
||||||
const point = bls12_381.G2.ProjectivePoint.fromAffine({ x, y });
|
const point = bls12_381.G2.ProjectivePoint.fromAffine({ x, y });
|
||||||
// console.log('Signature.decode', point);
|
|
||||||
point.assertValidity();
|
point.assertValidity();
|
||||||
return point;
|
return point;
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -5,12 +5,12 @@ import { twistedEdwards, ExtPointType } from './abstract/edwards.js';
|
|||||||
import { montgomery } from './abstract/montgomery.js';
|
import { montgomery } from './abstract/montgomery.js';
|
||||||
import { mod, pow2, isNegativeLE, Fp as Field, FpSqrtEven } from './abstract/modular.js';
|
import { mod, pow2, isNegativeLE, Fp as Field, FpSqrtEven } from './abstract/modular.js';
|
||||||
import {
|
import {
|
||||||
ensureBytes,
|
|
||||||
equalBytes,
|
equalBytes,
|
||||||
bytesToHex,
|
bytesToHex,
|
||||||
bytesToNumberLE,
|
bytesToNumberLE,
|
||||||
numberToBytesLE,
|
numberToBytesLE,
|
||||||
Hex,
|
Hex,
|
||||||
|
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';
|
||||||
|
|
||||||
@@ -223,7 +223,7 @@ function map_to_curve_elligator2_edwards25519(u: bigint) {
|
|||||||
const inv = Fp.invertBatch([xd, yd]); // batch division
|
const inv = Fp.invertBatch([xd, yd]); // batch division
|
||||||
return { x: Fp.mul(xn, inv[0]), y: Fp.mul(yn, inv[1]) }; // 13. return (xn, xd, yn, yd)
|
return { x: Fp.mul(xn, inv[0]), y: Fp.mul(yn, inv[1]) }; // 13. return (xn, xd, yn, yd)
|
||||||
}
|
}
|
||||||
const { hashToCurve, encodeToCurve } = htf.hashToCurve(
|
const { hashToCurve, encodeToCurve } = htf.createHasher(
|
||||||
ed25519.ExtendedPoint,
|
ed25519.ExtendedPoint,
|
||||||
(scalars: bigint[]) => map_to_curve_elligator2_edwards25519(scalars[0]),
|
(scalars: bigint[]) => map_to_curve_elligator2_edwards25519(scalars[0]),
|
||||||
{
|
{
|
||||||
@@ -316,7 +316,7 @@ export class RistrettoPoint {
|
|||||||
* @param hex 64-bit output of a hash function
|
* @param hex 64-bit output of a hash function
|
||||||
*/
|
*/
|
||||||
static hashToCurve(hex: Hex): RistrettoPoint {
|
static hashToCurve(hex: Hex): RistrettoPoint {
|
||||||
hex = ensureBytes(hex, 64);
|
hex = ensureBytes('ristrettoHash', hex, 64);
|
||||||
const r1 = bytes255ToNumberLE(hex.slice(0, 32));
|
const r1 = bytes255ToNumberLE(hex.slice(0, 32));
|
||||||
const R1 = calcElligatorRistrettoMap(r1);
|
const R1 = calcElligatorRistrettoMap(r1);
|
||||||
const r2 = bytes255ToNumberLE(hex.slice(32, 64));
|
const r2 = bytes255ToNumberLE(hex.slice(32, 64));
|
||||||
@@ -330,7 +330,7 @@ export class RistrettoPoint {
|
|||||||
* @param hex Ristretto-encoded 32 bytes. Not every 32-byte string is valid ristretto encoding
|
* @param hex Ristretto-encoded 32 bytes. Not every 32-byte string is valid ristretto encoding
|
||||||
*/
|
*/
|
||||||
static fromHex(hex: Hex): RistrettoPoint {
|
static fromHex(hex: Hex): RistrettoPoint {
|
||||||
hex = ensureBytes(hex, 32);
|
hex = ensureBytes('ristrettoHex', hex, 32);
|
||||||
const { a, d } = ed25519.CURVE;
|
const { a, d } = ed25519.CURVE;
|
||||||
const P = ed25519.CURVE.Fp.ORDER;
|
const P = ed25519.CURVE.Fp.ORDER;
|
||||||
const mod = ed25519.CURVE.Fp.create;
|
const mod = ed25519.CURVE.Fp.create;
|
||||||
|
|||||||
@@ -225,7 +225,7 @@ function map_to_curve_elligator2_edwards448(u: bigint) {
|
|||||||
return { x: Fp.mul(xEn, inv[0]), y: Fp.mul(yEn, inv[1]) }; // 38. return (xEn, xEd, yEn, yEd)
|
return { x: Fp.mul(xEn, inv[0]), y: Fp.mul(yEn, inv[1]) }; // 38. return (xEn, xEd, yEn, yEd)
|
||||||
}
|
}
|
||||||
|
|
||||||
const { hashToCurve, encodeToCurve } = htf.hashToCurve(
|
const { hashToCurve, encodeToCurve } = htf.createHasher(
|
||||||
ed448.ExtendedPoint,
|
ed448.ExtendedPoint,
|
||||||
(scalars: bigint[]) => map_to_curve_elligator2_edwards448(scalars[0]),
|
(scalars: bigint[]) => map_to_curve_elligator2_edwards448(scalars[0]),
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ export const P224 = createCurve(
|
|||||||
// Params: a, b
|
// Params: a, b
|
||||||
a: BigInt('0xfffffffffffffffffffffffffffffffefffffffffffffffffffffffe'),
|
a: BigInt('0xfffffffffffffffffffffffffffffffefffffffffffffffffffffffe'),
|
||||||
b: BigInt('0xb4050a850c04b3abf54132565044b0b7d7bfd8ba270b39432355ffb4'),
|
b: BigInt('0xb4050a850c04b3abf54132565044b0b7d7bfd8ba270b39432355ffb4'),
|
||||||
// Field over which we'll do calculations; 2n**224n - 2n**96n + 1n
|
// Field over which we'll do calculations;
|
||||||
Fp: Fp(BigInt('0xffffffffffffffffffffffffffffffff000000000000000000000001')),
|
Fp: Fp(BigInt('0xffffffffffffffffffffffffffffffff000000000000000000000001')),
|
||||||
// Curve order, total count of valid points in the field
|
// Curve order, total count of valid points in the field
|
||||||
n: BigInt('0xffffffffffffffffffffffffffff16a2e0b8f03e13dd29455c5c2a3d'),
|
n: BigInt('0xffffffffffffffffffffffffffff16a2e0b8f03e13dd29455c5c2a3d'),
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ export const P256 = createCurve(
|
|||||||
);
|
);
|
||||||
export const secp256r1 = P256;
|
export const secp256r1 = P256;
|
||||||
|
|
||||||
const { hashToCurve, encodeToCurve } = htf.hashToCurve(
|
const { hashToCurve, encodeToCurve } = htf.createHasher(
|
||||||
secp256r1.ProjectivePoint,
|
secp256r1.ProjectivePoint,
|
||||||
(scalars: bigint[]) => mapSWU(scalars[0]),
|
(scalars: bigint[]) => mapSWU(scalars[0]),
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ export const P384 = createCurve({
|
|||||||
);
|
);
|
||||||
export const secp384r1 = P384;
|
export const secp384r1 = P384;
|
||||||
|
|
||||||
const { hashToCurve, encodeToCurve } = htf.hashToCurve(
|
const { hashToCurve, encodeToCurve } = htf.createHasher(
|
||||||
secp384r1.ProjectivePoint,
|
secp384r1.ProjectivePoint,
|
||||||
(scalars: bigint[]) => mapSWU(scalars[0]),
|
(scalars: bigint[]) => mapSWU(scalars[0]),
|
||||||
{
|
{
|
||||||
|
|||||||
14
src/p521.ts
14
src/p521.ts
@@ -1,7 +1,6 @@
|
|||||||
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
|
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
|
||||||
import { createCurve } from './_shortw_utils.js';
|
import { createCurve } from './_shortw_utils.js';
|
||||||
import { sha512 } from '@noble/hashes/sha512';
|
import { sha512 } from '@noble/hashes/sha512';
|
||||||
import { bytesToHex, PrivKey } from './abstract/utils.js';
|
|
||||||
import { Fp as Field } from './abstract/modular.js';
|
import { Fp as Field } from './abstract/modular.js';
|
||||||
import { mapToCurveSimpleSWU } from './abstract/weierstrass.js';
|
import { mapToCurveSimpleSWU } from './abstract/weierstrass.js';
|
||||||
import * as htf from './abstract/hash-to-curve.js';
|
import * as htf from './abstract/hash-to-curve.js';
|
||||||
@@ -38,20 +37,11 @@ export const P521 = createCurve({
|
|||||||
Gy: BigInt('0x011839296a789a3bc0045c8a5fb42c7d1bd998f54449579b446817afbd17273e662c97ee72995ef42640c550b9013fad0761353c7086a272c24088be94769fd16650'),
|
Gy: BigInt('0x011839296a789a3bc0045c8a5fb42c7d1bd998f54449579b446817afbd17273e662c97ee72995ef42640c550b9013fad0761353c7086a272c24088be94769fd16650'),
|
||||||
h: BigInt(1),
|
h: BigInt(1),
|
||||||
lowS: false,
|
lowS: false,
|
||||||
// P521 keys could be 130, 131, 132 bytes. We normalize to 132 bytes.
|
allowedPrivateKeyLengths: [130, 131, 132] // P521 keys are variable-length. Normalize to 132b
|
||||||
// Does not replace validation; invalid keys would still be rejected.
|
|
||||||
normalizePrivateKey(key: PrivKey) {
|
|
||||||
if (typeof key === 'bigint') return key;
|
|
||||||
if (key instanceof Uint8Array) key = bytesToHex(key);
|
|
||||||
if (typeof key !== 'string' || !([130, 131, 132].includes(key.length))) {
|
|
||||||
throw new Error('Invalid key');
|
|
||||||
}
|
|
||||||
return key.padStart(66 * 2, '0'); // ensure it's always 132 bytes
|
|
||||||
},
|
|
||||||
} as const, sha512);
|
} as const, sha512);
|
||||||
export const secp521r1 = P521;
|
export const secp521r1 = P521;
|
||||||
|
|
||||||
const { hashToCurve, encodeToCurve } = htf.hashToCurve(
|
const { hashToCurve, encodeToCurve } = htf.createHasher(
|
||||||
secp521r1.ProjectivePoint,
|
secp521r1.ProjectivePoint,
|
||||||
(scalars: bigint[]) => mapSWU(scalars[0]),
|
(scalars: bigint[]) => mapSWU(scalars[0]),
|
||||||
{
|
{
|
||||||
|
|||||||
163
src/secp256k1.ts
163
src/secp256k1.ts
@@ -1,26 +1,12 @@
|
|||||||
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
|
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
|
||||||
import { sha256 } from '@noble/hashes/sha256';
|
import { sha256 } from '@noble/hashes/sha256';
|
||||||
import { Fp as Field, mod, pow2 } from './abstract/modular.js';
|
|
||||||
import { createCurve } from './_shortw_utils.js';
|
|
||||||
import { ProjPointType as PointType, mapToCurveSimpleSWU } from './abstract/weierstrass.js';
|
|
||||||
import {
|
|
||||||
ensureBytes,
|
|
||||||
concatBytes,
|
|
||||||
Hex,
|
|
||||||
bytesToNumberBE as bytesToNum,
|
|
||||||
PrivKey,
|
|
||||||
numberToBytesBE,
|
|
||||||
} from './abstract/utils.js';
|
|
||||||
import { randomBytes } from '@noble/hashes/utils';
|
import { randomBytes } from '@noble/hashes/utils';
|
||||||
|
import { Fp as Field, mod, pow2 } from './abstract/modular.js';
|
||||||
|
import { ProjPointType as PointType, mapToCurveSimpleSWU } from './abstract/weierstrass.js';
|
||||||
|
import type { Hex, PrivKey } from './abstract/utils.js';
|
||||||
|
import { bytesToNumberBE, concatBytes, ensureBytes, numberToBytesBE } from './abstract/utils.js';
|
||||||
import * as htf from './abstract/hash-to-curve.js';
|
import * as htf from './abstract/hash-to-curve.js';
|
||||||
|
import { createCurve } from './_shortw_utils.js';
|
||||||
/**
|
|
||||||
* secp256k1 belongs to Koblitz curves: it has efficiently computable endomorphism.
|
|
||||||
* Endomorphism uses 2x less RAM, speeds up precomputation by 2x and ECDH / key recovery by 20%.
|
|
||||||
* Should always be used for Projective's double-and-add multiplication.
|
|
||||||
* For affines cached multiplication, it trades off 1/2 init time & 1/3 ram for 20% perf hit.
|
|
||||||
* https://gist.github.com/paulmillr/eb670806793e84df628a7c434a873066
|
|
||||||
*/
|
|
||||||
|
|
||||||
const secp256k1P = BigInt('0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f');
|
const secp256k1P = BigInt('0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f');
|
||||||
const secp256k1N = BigInt('0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141');
|
const secp256k1N = BigInt('0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141');
|
||||||
@@ -61,23 +47,22 @@ type Fp = bigint;
|
|||||||
|
|
||||||
export const secp256k1 = createCurve(
|
export const secp256k1 = createCurve(
|
||||||
{
|
{
|
||||||
// Params: a, b
|
a: BigInt(0), // equation params: a, b
|
||||||
// Seem to be rigid https://bitcointalk.org/index.php?topic=289795.msg3183975#msg3183975
|
b: BigInt(7), // Seem to be rigid: bitcointalk.org/index.php?topic=289795.msg3183975#msg3183975
|
||||||
a: BigInt(0),
|
Fp, // Field's prime: 2n**256n - 2n**32n - 2n**9n - 2n**8n - 2n**7n - 2n**6n - 2n**4n - 1n
|
||||||
b: BigInt(7),
|
n: secp256k1N, // Curve order, total count of valid points in the field
|
||||||
// Field over which we'll do calculations;
|
|
||||||
// 2n**256n - 2n**32n - 2n**9n - 2n**8n - 2n**7n - 2n**6n - 2n**4n - 1n
|
|
||||||
Fp,
|
|
||||||
// Curve order, total count of valid points in the field
|
|
||||||
n: secp256k1N,
|
|
||||||
// Base point (x, y) aka generator point
|
// Base point (x, y) aka generator point
|
||||||
Gx: BigInt('55066263022277343669578718895168534326250603453777594175500187360389116729240'),
|
Gx: BigInt('55066263022277343669578718895168534326250603453777594175500187360389116729240'),
|
||||||
Gy: BigInt('32670510020758816978083085130507043184471273380659243275938904335757337482424'),
|
Gy: BigInt('32670510020758816978083085130507043184471273380659243275938904335757337482424'),
|
||||||
h: BigInt(1),
|
h: BigInt(1), // Cofactor
|
||||||
// Alllow only low-S signatures by default in sign() and verify()
|
lowS: true, // Allow only low-S signatures by default in sign() and verify()
|
||||||
lowS: true,
|
/**
|
||||||
|
* secp256k1 belongs to Koblitz curves: it has efficiently computable endomorphism.
|
||||||
|
* Endomorphism uses 2x less RAM, speeds up precomputation by 2x and ECDH / key recovery by 20%.
|
||||||
|
* For precomputed wNAF it trades off 1/2 init time & 1/3 ram for 20% perf hit.
|
||||||
|
* Explanation: https://gist.github.com/paulmillr/eb670806793e84df628a7c434a873066
|
||||||
|
*/
|
||||||
endo: {
|
endo: {
|
||||||
// Params taken from https://gist.github.com/paulmillr/eb670806793e84df628a7c434a873066
|
|
||||||
beta: BigInt('0x7ae96a2b657c07106e64479eac3434e99cf0497512f58995c1396c28719501ee'),
|
beta: BigInt('0x7ae96a2b657c07106e64479eac3434e99cf0497512f58995c1396c28719501ee'),
|
||||||
splitScalar: (k: bigint) => {
|
splitScalar: (k: bigint) => {
|
||||||
const n = secp256k1N;
|
const n = secp256k1N;
|
||||||
@@ -105,19 +90,11 @@ export const secp256k1 = createCurve(
|
|||||||
sha256
|
sha256
|
||||||
);
|
);
|
||||||
|
|
||||||
// Schnorr signatures are superior to ECDSA from above.
|
// Schnorr signatures are superior to ECDSA from above. Below is Schnorr-specific BIP0340 code.
|
||||||
// Below is Schnorr-specific code as per BIP0340.
|
|
||||||
// https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki
|
// https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki
|
||||||
const _0n = BigInt(0);
|
const _0n = BigInt(0);
|
||||||
const fe = (x: bigint) => typeof x === 'bigint' && _0n < x && x < secp256k1P;
|
const fe = (x: bigint) => typeof x === 'bigint' && _0n < x && x < secp256k1P;
|
||||||
const ge = (x: bigint) => typeof x === 'bigint' && _0n < x && x < secp256k1N;
|
const ge = (x: bigint) => typeof x === 'bigint' && _0n < x && x < secp256k1N;
|
||||||
|
|
||||||
const TAGS = {
|
|
||||||
challenge: 'BIP0340/challenge',
|
|
||||||
aux: 'BIP0340/aux',
|
|
||||||
nonce: 'BIP0340/nonce',
|
|
||||||
} as const;
|
|
||||||
|
|
||||||
/** An object mapping tags to their tagged hash prefix of [SHA256(tag) | SHA256(tag)] */
|
/** An object mapping tags to their tagged hash prefix of [SHA256(tag) | SHA256(tag)] */
|
||||||
const TAGGED_HASH_PREFIXES: { [tag: string]: Uint8Array } = {};
|
const TAGGED_HASH_PREFIXES: { [tag: string]: Uint8Array } = {};
|
||||||
function taggedHash(tag: string, ...messages: Uint8Array[]): Uint8Array {
|
function taggedHash(tag: string, ...messages: Uint8Array[]): Uint8Array {
|
||||||
@@ -130,57 +107,57 @@ function taggedHash(tag: string, ...messages: Uint8Array[]): Uint8Array {
|
|||||||
return sha256(concatBytes(tagP, ...messages));
|
return sha256(concatBytes(tagP, ...messages));
|
||||||
}
|
}
|
||||||
|
|
||||||
const toRawX = (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 modN = (x: bigint) => mod(x, secp256k1N);
|
const modN = (x: bigint) => mod(x, secp256k1N);
|
||||||
const _Point = secp256k1.ProjectivePoint;
|
const Point = secp256k1.ProjectivePoint;
|
||||||
const Gmul = (priv: PrivKey) => _Point.fromPrivateKey(priv);
|
|
||||||
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);
|
||||||
function schnorrGetScalar(priv: bigint) {
|
function schnorrGetExtPubKey(priv: PrivKey) {
|
||||||
// Let d' = int(sk)
|
const d = secp256k1.utils.normPrivateKeyToScalar(priv);
|
||||||
// Fail if d' = 0 or d' ≥ n
|
const point = Point.fromPrivateKey(d); // P = d'⋅G; 0 < d' < n check is done inside
|
||||||
// Let P = d'⋅G
|
const scalar = point.hasEvenY() ? d : modN(-d); // d = d' if has_even_y(P), otherwise d = n-d'
|
||||||
// Let d = d' if has_even_y(P), otherwise let d = n - d' .
|
return { point, scalar, bytes: pointToBytes(point) };
|
||||||
const point = Gmul(priv);
|
|
||||||
const scalar = point.hasEvenY() ? priv : modN(-priv);
|
|
||||||
return { point, scalar, x: toRawX(point) };
|
|
||||||
}
|
}
|
||||||
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 c = mod(x * x * x + BigInt(7), secp256k1P); // Let c = x³ + 7 mod p.
|
const xx = modP(x * x);
|
||||||
|
const c = modP(xx * x + BigInt(7)); // Let c = x³ + 7 mod p.
|
||||||
let y = sqrtMod(c); // Let y = c^(p+1)/4 mod p.
|
let y = sqrtMod(c); // Let y = c^(p+1)/4 mod p.
|
||||||
if (y % 2n !== 0n) y = mod(-y, secp256k1P); // Return the unique point P such that x(P) = x and
|
if (y % 2n !== 0n) y = modP(-y); // Return the unique point P such that x(P) = x and
|
||||||
const p = new _Point(x, y, _1n); // y(P) = y if y mod 2 = 0 or y(P) = p-y otherwise.
|
const p = new Point(x, y, _1n); // y(P) = y if y mod 2 = 0 or y(P) = p-y otherwise.
|
||||||
p.assertValidity();
|
p.assertValidity();
|
||||||
return p;
|
return p;
|
||||||
}
|
}
|
||||||
function challenge(...args: Uint8Array[]): bigint {
|
function challenge(...args: Uint8Array[]): bigint {
|
||||||
return modN(bytesToNum(taggedHash(TAGS.challenge, ...args)));
|
return modN(bytesToNumberBE(taggedHash('BIP0340/challenge', ...args)));
|
||||||
}
|
}
|
||||||
function schnorrGetPublicKey(privateKey: PrivKey): Uint8Array {
|
|
||||||
return toRawX(Gmul(privateKey)); // Let d' = int(sk). Fail if d' = 0 or d' ≥ n. Return bytes(d'⋅G)
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Synchronously creates Schnorr signature. Improved security: verifies itself before
|
|
||||||
* producing an output.
|
|
||||||
* @param msg message (not message hash)
|
|
||||||
* @param privateKey private key
|
|
||||||
* @param auxRand random bytes that would be added to k. Bad RNG won't break it.
|
|
||||||
*/
|
|
||||||
function schnorrSign(message: Hex, privateKey: Hex, auxRand: Hex = randomBytes(32)): Uint8Array {
|
|
||||||
if (message == null) throw new Error(`sign: Expected valid message, not "${message}"`);
|
|
||||||
const m = ensureBytes(message);
|
|
||||||
// checks for isWithinCurveOrder
|
|
||||||
|
|
||||||
const { x: px, scalar: d } = schnorrGetScalar(bytesToNum(ensureBytes(privateKey, 32)));
|
/**
|
||||||
const a = ensureBytes(auxRand, 32); // Auxiliary random data a: a 32-byte array
|
* Schnorr public key is just `x` coordinate of Point as per BIP340.
|
||||||
// TODO: replace with proper xor?
|
*/
|
||||||
const t = numTo32b(d ^ bytesToNum(taggedHash(TAGS.aux, a))); // Let t be the byte-wise xor of bytes(d) and hash/aux(a)
|
function schnorrGetPublicKey(privateKey: Hex): Uint8Array {
|
||||||
const rand = taggedHash(TAGS.nonce, t, px, m); // Let rand = hash/nonce(t || bytes(P) || m)
|
return schnorrGetExtPubKey(privateKey).bytes; // d'=int(sk). Fail if d'=0 or d'≥n. Ret bytes(d'⋅G)
|
||||||
const k_ = modN(bytesToNum(rand)); // Let k' = int(rand) mod n
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates Schnorr signature as per BIP340. Verifies itself before returning anything.
|
||||||
|
* auxRand is optional and is not the sole source of k generation: bad CSPRNG won't be dangerous.
|
||||||
|
*/
|
||||||
|
function schnorrSign(
|
||||||
|
message: Hex,
|
||||||
|
privateKey: PrivKey,
|
||||||
|
auxRand: Hex = randomBytes(32)
|
||||||
|
): Uint8Array {
|
||||||
|
const m = ensureBytes('message', message);
|
||||||
|
const { bytes: px, scalar: d } = schnorrGetExtPubKey(privateKey); // checks for isWithinCurveOrder
|
||||||
|
const a = ensureBytes('auxRand', auxRand, 32); // Auxiliary random data a: a 32-byte array
|
||||||
|
const t = numTo32b(d ^ bytesToNumberBE(taggedHash('BIP0340/aux', a))); // Let t be the byte-wise xor of bytes(d) and hash/aux(a)
|
||||||
|
const rand = taggedHash('BIP0340/nonce', t, px, m); // Let rand = hash/nonce(t || bytes(P) || m)
|
||||||
|
const k_ = modN(bytesToNumberBE(rand)); // Let k' = int(rand) mod n
|
||||||
if (k_ === _0n) throw new Error('sign failed: k is zero'); // Fail if k' = 0.
|
if (k_ === _0n) throw new Error('sign failed: k is zero'); // Fail if k' = 0.
|
||||||
const { point: R, x: rx, scalar: k } = schnorrGetScalar(k_); // Let R = k'⋅G.
|
const { point: R, bytes: rx, scalar: k } = schnorrGetExtPubKey(k_); // Let R = k'⋅G.
|
||||||
const e = challenge(rx, px, m); // Let e = int(hash/challenge(bytes(R) || bytes(P) || m)) mod n.
|
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(numTo32b(R.px), 0);
|
||||||
@@ -191,18 +168,19 @@ function schnorrSign(message: Hex, privateKey: Hex, auxRand: Hex = randomBytes(3
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Verifies Schnorr signature synchronously.
|
* Verifies Schnorr signature.
|
||||||
*/
|
*/
|
||||||
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 m = ensureBytes('message', message);
|
||||||
|
const pub = ensureBytes('publicKey', publicKey, 32);
|
||||||
try {
|
try {
|
||||||
const P = lift_x(bytesToNum(ensureBytes(publicKey, 32))); // P = lift_x(int(pk)); fail if that fails
|
const P = lift_x(bytesToNumberBE(pub)); // P = lift_x(int(pk)); fail if that fails
|
||||||
const sig = ensureBytes(signature, 64);
|
const r = bytesToNumberBE(sig.subarray(0, 32)); // Let r = int(sig[0:32]); fail if r ≥ p.
|
||||||
const r = bytesToNum(sig.subarray(0, 32)); // Let r = int(sig[0:32]); fail if r ≥ p.
|
|
||||||
if (!fe(r)) return false;
|
if (!fe(r)) return false;
|
||||||
const s = bytesToNum(sig.subarray(32, 64)); // Let s = int(sig[32:64]); fail if s ≥ n.
|
const s = bytesToNumberBE(sig.subarray(32, 64)); // Let s = int(sig[32:64]); fail if s ≥ n.
|
||||||
if (!ge(s)) return false;
|
if (!ge(s)) return false;
|
||||||
const m = ensureBytes(message);
|
const e = challenge(numTo32b(r), pointToBytes(P), m); // int(challenge(bytes(r)||bytes(P)||m))%n
|
||||||
const e = challenge(numTo32b(r), toRawX(P), m); // int(challenge(bytes(r)||bytes(P)||m)) mod n
|
|
||||||
const R = GmulAdd(P, s, modN(-e)); // R = s⋅G - e⋅P
|
const R = GmulAdd(P, s, modN(-e)); // R = s⋅G - e⋅P
|
||||||
if (!R || !R.hasEvenY() || R.toAffine().x !== r) return false; // -eP == (n-e)P
|
if (!R || !R.hasEvenY() || R.toAffine().x !== r) return false; // -eP == (n-e)P
|
||||||
return true; // Fail if is_infinite(R) / not has_even_y(R) / x(R) ≠ r.
|
return true; // Fail if is_infinite(R) / not has_even_y(R) / x(R) ≠ r.
|
||||||
@@ -212,11 +190,19 @@ function schnorrVerify(signature: Hex, message: Hex, publicKey: Hex): boolean {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const schnorr = {
|
export const schnorr = {
|
||||||
// Schnorr's pubkey is just `x` of Point (BIP340)
|
|
||||||
getPublicKey: schnorrGetPublicKey,
|
getPublicKey: schnorrGetPublicKey,
|
||||||
sign: schnorrSign,
|
sign: schnorrSign,
|
||||||
verify: schnorrVerify,
|
verify: schnorrVerify,
|
||||||
utils: { lift_x, int: bytesToNum, taggedHash },
|
utils: {
|
||||||
|
randomPrivateKey: secp256k1.utils.randomPrivateKey,
|
||||||
|
getExtendedPublicKey: schnorrGetExtPubKey,
|
||||||
|
lift_x,
|
||||||
|
pointToBytes,
|
||||||
|
numberToBytesBE,
|
||||||
|
bytesToNumberBE,
|
||||||
|
taggedHash,
|
||||||
|
mod,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const isoMap = htf.isogenyMap(
|
const isoMap = htf.isogenyMap(
|
||||||
@@ -256,7 +242,7 @@ const mapSWU = mapToCurveSimpleSWU(Fp, {
|
|||||||
B: BigInt('1771'),
|
B: BigInt('1771'),
|
||||||
Z: Fp.create(BigInt('-11')),
|
Z: Fp.create(BigInt('-11')),
|
||||||
});
|
});
|
||||||
const { hashToCurve, encodeToCurve } = htf.hashToCurve(
|
export const { hashToCurve, encodeToCurve } = htf.createHasher(
|
||||||
secp256k1.ProjectivePoint,
|
secp256k1.ProjectivePoint,
|
||||||
(scalars: bigint[]) => {
|
(scalars: bigint[]) => {
|
||||||
const { x, y } = mapSWU(Fp.create(scalars[0]));
|
const { x, y } = mapSWU(Fp.create(scalars[0]));
|
||||||
@@ -272,4 +258,3 @@ const { hashToCurve, encodeToCurve } = htf.hashToCurve(
|
|||||||
hash: sha256,
|
hash: sha256,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
export { hashToCurve, encodeToCurve };
|
|
||||||
|
|||||||
@@ -97,7 +97,7 @@ function ensureBytes0x(hex: Hex): Uint8Array {
|
|||||||
function normalizePrivateKey(privKey: Hex) {
|
function normalizePrivateKey(privKey: Hex) {
|
||||||
return cutils.bytesToHex(ensureBytes0x(privKey)).padStart(64, '0');
|
return cutils.bytesToHex(ensureBytes0x(privKey)).padStart(64, '0');
|
||||||
}
|
}
|
||||||
function getPublicKey0x(privKey: Hex, isCompressed?: boolean) {
|
function getPublicKey0x(privKey: Hex, isCompressed = false) {
|
||||||
return starkCurve.getPublicKey(normalizePrivateKey(privKey), isCompressed);
|
return starkCurve.getPublicKey(normalizePrivateKey(privKey), isCompressed);
|
||||||
}
|
}
|
||||||
function getSharedSecret0x(privKeyA: Hex, pubKeyB: Hex) {
|
function getSharedSecret0x(privKeyA: Hex, pubKeyB: Hex) {
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import { secp256r1 } from '../lib/esm/p256.js';
|
|||||||
import { secp384r1 } from '../lib/esm/p384.js';
|
import { secp384r1 } from '../lib/esm/p384.js';
|
||||||
import { secp521r1 } from '../lib/esm/p521.js';
|
import { secp521r1 } from '../lib/esm/p521.js';
|
||||||
import { secp256k1 } from '../lib/esm/secp256k1.js';
|
import { secp256k1 } from '../lib/esm/secp256k1.js';
|
||||||
import { ed25519, ed25519ctx, ed25519ph } from '../lib/esm/ed25519.js';
|
import { ed25519, ed25519ctx, ed25519ph, x25519 } from '../lib/esm/ed25519.js';
|
||||||
import { ed448, ed448ph } from '../lib/esm/ed448.js';
|
import { ed448, ed448ph } from '../lib/esm/ed448.js';
|
||||||
import { starkCurve } from '../lib/esm/stark.js';
|
import { starkCurve } from '../lib/esm/stark.js';
|
||||||
import { pallas, vesta } from '../lib/esm/pasta.js';
|
import { pallas, vesta } from '../lib/esm/pasta.js';
|
||||||
@@ -239,6 +239,11 @@ for (const c in FIELDS) {
|
|||||||
deepStrictEqual(isSquare(a), true);
|
deepStrictEqual(isSquare(a), true);
|
||||||
deepStrictEqual(Fp.eql(Fp.sqr(root), a), true, 'sqrt(a)^2 == a');
|
deepStrictEqual(Fp.eql(Fp.sqr(root), a), true, 'sqrt(a)^2 == a');
|
||||||
deepStrictEqual(Fp.eql(Fp.sqr(Fp.neg(root)), a), true, '(-sqrt(a))^2 == a');
|
deepStrictEqual(Fp.eql(Fp.sqr(Fp.neg(root)), a), true, '(-sqrt(a))^2 == a');
|
||||||
|
// Returns odd/even element
|
||||||
|
deepStrictEqual(Fp.isOdd(mod.FpSqrtOdd(Fp, a)), true);
|
||||||
|
deepStrictEqual(Fp.isOdd(mod.FpSqrtEven(Fp, a)), false);
|
||||||
|
deepStrictEqual(Fp.eql(Fp.sqr(mod.FpSqrtOdd(Fp, a)), a), true);
|
||||||
|
deepStrictEqual(Fp.eql(Fp.sqr(mod.FpSqrtEven(Fp, a)), a), true);
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -261,6 +266,9 @@ for (const c in FIELDS) {
|
|||||||
if (Fp.eql(a, Fp.ZERO)) return; // No division by zero
|
if (Fp.eql(a, Fp.ZERO)) return; // No division by zero
|
||||||
deepStrictEqual(Fp.div(a, Fp.ONE), a);
|
deepStrictEqual(Fp.div(a, Fp.ONE), a);
|
||||||
deepStrictEqual(Fp.div(a, a), Fp.ONE);
|
deepStrictEqual(Fp.div(a, a), Fp.ONE);
|
||||||
|
// FpDiv tests
|
||||||
|
deepStrictEqual(mod.FpDiv(Fp, a, Fp.ONE), a);
|
||||||
|
deepStrictEqual(mod.FpDiv(Fp, a, a), Fp.ONE);
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -269,6 +277,7 @@ for (const c in FIELDS) {
|
|||||||
fc.property(FC_BIGINT, (num) => {
|
fc.property(FC_BIGINT, (num) => {
|
||||||
const a = create(num);
|
const a = create(num);
|
||||||
deepStrictEqual(Fp.div(Fp.ZERO, a), Fp.ZERO);
|
deepStrictEqual(Fp.div(Fp.ZERO, a), Fp.ZERO);
|
||||||
|
deepStrictEqual(mod.FpDiv(Fp, Fp.ZERO, a), Fp.ZERO);
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -279,6 +288,10 @@ for (const c in FIELDS) {
|
|||||||
const b = create(num2);
|
const b = create(num2);
|
||||||
const c = create(num3);
|
const c = create(num3);
|
||||||
deepStrictEqual(Fp.div(Fp.add(a, b), c), Fp.add(Fp.div(a, c), Fp.div(b, c)));
|
deepStrictEqual(Fp.div(Fp.add(a, b), c), Fp.add(Fp.div(a, c), Fp.div(b, c)));
|
||||||
|
deepStrictEqual(
|
||||||
|
mod.FpDiv(Fp, Fp.add(a, b), c),
|
||||||
|
Fp.add(mod.FpDiv(Fp, a, c), mod.FpDiv(Fp, b, c))
|
||||||
|
);
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -436,9 +449,16 @@ for (const name in CURVES) {
|
|||||||
throws(() => G[1][op](0n), '0n');
|
throws(() => G[1][op](0n), '0n');
|
||||||
G[1][op](G[2]);
|
G[1][op](G[2]);
|
||||||
throws(() => G[1][op](CURVE_ORDER), 'CURVE_ORDER');
|
throws(() => G[1][op](CURVE_ORDER), 'CURVE_ORDER');
|
||||||
|
throws(() => G[1][op](-123n), '-123n');
|
||||||
|
throws(() => G[1][op](123), '123');
|
||||||
throws(() => G[1][op](123.456), '123.456');
|
throws(() => G[1][op](123.456), '123.456');
|
||||||
throws(() => G[1][op](true), 'true');
|
throws(() => G[1][op](true), 'true');
|
||||||
|
throws(() => G[1][op](false), 'false');
|
||||||
|
throws(() => G[1][op](null), 'null');
|
||||||
|
throws(() => G[1][op](undefined), 'undefined');
|
||||||
throws(() => G[1][op]('1'), "'1'");
|
throws(() => G[1][op]('1'), "'1'");
|
||||||
|
throws(() => G[1][op]({ x: 1n, y: 1n }), '{ x: 1n, y: 1n }');
|
||||||
|
throws(() => G[1][op]({ x: 1n, y: 1n, z: 1n }), '{ x: 1n, y: 1n, z: 1n }');
|
||||||
throws(
|
throws(
|
||||||
() => G[1][op]({ x: 1n, y: 1n, z: 1n, t: 1n }),
|
() => G[1][op]({ x: 1n, y: 1n, z: 1n, t: 1n }),
|
||||||
'{ x: 1n, y: 1n, z: 1n, t: 1n }'
|
'{ x: 1n, y: 1n, z: 1n, t: 1n }'
|
||||||
@@ -514,8 +534,22 @@ for (const name in CURVES) {
|
|||||||
should('fromHex(toHex()) roundtrip', () => {
|
should('fromHex(toHex()) roundtrip', () => {
|
||||||
fc.assert(
|
fc.assert(
|
||||||
fc.property(FC_BIGINT, (x) => {
|
fc.property(FC_BIGINT, (x) => {
|
||||||
const hex = p.BASE.multiply(x).toHex();
|
const point = p.BASE.multiply(x);
|
||||||
|
const hex = point.toHex();
|
||||||
|
const bytes = point.toRawBytes();
|
||||||
deepStrictEqual(p.fromHex(hex).toHex(), hex);
|
deepStrictEqual(p.fromHex(hex).toHex(), hex);
|
||||||
|
deepStrictEqual(p.fromHex(bytes).toHex(), hex);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
should('fromHex(toHex(compressed=true)) roundtrip', () => {
|
||||||
|
fc.assert(
|
||||||
|
fc.property(FC_BIGINT, (x) => {
|
||||||
|
const point = p.BASE.multiply(x);
|
||||||
|
const hex = point.toHex(true);
|
||||||
|
const bytes = point.toRawBytes(true);
|
||||||
|
deepStrictEqual(p.fromHex(hex).toHex(true), hex);
|
||||||
|
deepStrictEqual(p.fromHex(bytes).toHex(true), hex);
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -527,10 +561,13 @@ for (const name in CURVES) {
|
|||||||
should('.getPublicKey() type check', () => {
|
should('.getPublicKey() type check', () => {
|
||||||
throws(() => C.getPublicKey(0), '0');
|
throws(() => C.getPublicKey(0), '0');
|
||||||
throws(() => C.getPublicKey(0n), '0n');
|
throws(() => C.getPublicKey(0n), '0n');
|
||||||
throws(() => C.getPublicKey(false), 'false');
|
throws(() => C.getPublicKey(-123n), '-123n');
|
||||||
throws(() => C.getPublicKey(123), '123');
|
throws(() => C.getPublicKey(123), '123');
|
||||||
throws(() => C.getPublicKey(123.456), '123.456');
|
throws(() => C.getPublicKey(123.456), '123.456');
|
||||||
throws(() => C.getPublicKey(true), 'true');
|
throws(() => C.getPublicKey(true), 'true');
|
||||||
|
throws(() => C.getPublicKey(false), 'false');
|
||||||
|
throws(() => C.getPublicKey(null), 'null');
|
||||||
|
throws(() => C.getPublicKey(undefined), 'undefined');
|
||||||
throws(() => C.getPublicKey(''), "''");
|
throws(() => C.getPublicKey(''), "''");
|
||||||
// NOTE: passes because of disabled hex padding checks for starknet, maybe enable?
|
// NOTE: passes because of disabled hex padding checks for starknet, maybe enable?
|
||||||
// throws(() => C.getPublicKey('1'), "'1'");
|
// throws(() => C.getPublicKey('1'), "'1'");
|
||||||
@@ -551,39 +588,96 @@ for (const name in CURVES) {
|
|||||||
deepStrictEqual(
|
deepStrictEqual(
|
||||||
C.verify(sig, msg, pub),
|
C.verify(sig, msg, pub),
|
||||||
true,
|
true,
|
||||||
'priv=${toHex(priv)},pub=${toHex(pub)},msg=${msg}'
|
`priv=${toHex(priv)},pub=${toHex(pub)},msg=${msg}`
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
{ numRuns: NUM_RUNS }
|
{ numRuns: NUM_RUNS }
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
should('.verify() should verify empty signatures', () => {
|
||||||
|
const msg = new Uint8Array([]);
|
||||||
|
const priv = C.utils.randomPrivateKey();
|
||||||
|
const pub = C.getPublicKey(priv);
|
||||||
|
const sig = C.sign(msg, priv);
|
||||||
|
deepStrictEqual(
|
||||||
|
C.verify(sig, msg, pub),
|
||||||
|
true,
|
||||||
|
'priv=${toHex(priv)},pub=${toHex(pub)},msg=${msg}'
|
||||||
|
);
|
||||||
|
});
|
||||||
should('.sign() edge cases', () => {
|
should('.sign() edge cases', () => {
|
||||||
throws(() => C.sign());
|
throws(() => C.sign());
|
||||||
throws(() => C.sign(''));
|
throws(() => C.sign(''));
|
||||||
|
throws(() => C.sign('', ''));
|
||||||
|
throws(() => C.sign(new Uint8Array(), new Uint8Array()));
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('verify()', () => {
|
describe('verify()', () => {
|
||||||
|
const msg = '01'.repeat(32);
|
||||||
should('true for proper signatures', () => {
|
should('true for proper signatures', () => {
|
||||||
const msg = '01'.repeat(32);
|
|
||||||
const priv = C.utils.randomPrivateKey();
|
const priv = C.utils.randomPrivateKey();
|
||||||
const sig = C.sign(msg, priv);
|
const sig = C.sign(msg, priv);
|
||||||
const pub = C.getPublicKey(priv);
|
const pub = C.getPublicKey(priv);
|
||||||
deepStrictEqual(C.verify(sig, msg, pub), true);
|
deepStrictEqual(C.verify(sig, msg, pub), true);
|
||||||
});
|
});
|
||||||
should('false for wrong messages', () => {
|
should('false for wrong messages', () => {
|
||||||
const msg = '01'.repeat(32);
|
|
||||||
const priv = C.utils.randomPrivateKey();
|
const priv = C.utils.randomPrivateKey();
|
||||||
const sig = C.sign(msg, priv);
|
const sig = C.sign(msg, priv);
|
||||||
const pub = C.getPublicKey(priv);
|
const pub = C.getPublicKey(priv);
|
||||||
deepStrictEqual(C.verify(sig, '11'.repeat(32), pub), false);
|
deepStrictEqual(C.verify(sig, '11'.repeat(32), pub), false);
|
||||||
});
|
});
|
||||||
should('false for wrong keys', () => {
|
should('false for wrong keys', () => {
|
||||||
const msg = '01'.repeat(32);
|
|
||||||
const priv = C.utils.randomPrivateKey();
|
const priv = C.utils.randomPrivateKey();
|
||||||
const sig = C.sign(msg, priv);
|
const sig = C.sign(msg, priv);
|
||||||
deepStrictEqual(C.verify(sig, msg, C.getPublicKey(C.utils.randomPrivateKey())), false);
|
deepStrictEqual(C.verify(sig, msg, C.getPublicKey(C.utils.randomPrivateKey())), false);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
if (C.Signature) {
|
||||||
|
should('Signature serialization roundtrip', () =>
|
||||||
|
fc.assert(
|
||||||
|
fc.property(fc.hexaString({ minLength: 64, maxLength: 64 }), (msg) => {
|
||||||
|
const priv = C.utils.randomPrivateKey();
|
||||||
|
const sig = C.sign(msg, priv);
|
||||||
|
const sigRS = (sig) => ({ s: sig.s, r: sig.r });
|
||||||
|
// Compact
|
||||||
|
deepStrictEqual(sigRS(C.Signature.fromCompact(sig.toCompactHex())), sigRS(sig));
|
||||||
|
deepStrictEqual(sigRS(C.Signature.fromCompact(sig.toCompactRawBytes())), sigRS(sig));
|
||||||
|
// DER
|
||||||
|
deepStrictEqual(sigRS(C.Signature.fromDER(sig.toDERHex())), sigRS(sig));
|
||||||
|
deepStrictEqual(sigRS(C.Signature.fromDER(sig.toDERRawBytes())), sigRS(sig));
|
||||||
|
}),
|
||||||
|
{ numRuns: NUM_RUNS }
|
||||||
|
)
|
||||||
|
);
|
||||||
|
should('Signature.addRecoveryBit/Signature.recoveryPublicKey', () =>
|
||||||
|
fc.assert(
|
||||||
|
fc.property(fc.hexaString({ minLength: 64, maxLength: 64 }), (msg) => {
|
||||||
|
const priv = C.utils.randomPrivateKey();
|
||||||
|
const pub = C.getPublicKey(priv);
|
||||||
|
const sig = C.sign(msg, priv);
|
||||||
|
deepStrictEqual(sig.recoverPublicKey(msg).toRawBytes(), pub);
|
||||||
|
const sig2 = C.Signature.fromCompact(sig.toCompactHex());
|
||||||
|
throws(() => sig2.recoverPublicKey(msg));
|
||||||
|
const sig3 = sig2.addRecoveryBit(sig.recovery);
|
||||||
|
deepStrictEqual(sig3.recoverPublicKey(msg).toRawBytes(), pub);
|
||||||
|
}),
|
||||||
|
{ numRuns: NUM_RUNS }
|
||||||
|
)
|
||||||
|
);
|
||||||
|
should('Signature.normalizeS', () =>
|
||||||
|
fc.assert(
|
||||||
|
fc.property(fc.hexaString({ minLength: 64, maxLength: 64 }), (msg) => {
|
||||||
|
const priv = C.utils.randomPrivateKey();
|
||||||
|
const pub = C.getPublicKey(priv);
|
||||||
|
const sig = C.sign(msg, priv);
|
||||||
|
const sig2 = sig.normalizeS();
|
||||||
|
deepStrictEqual(sig2.hasHighS(), false);
|
||||||
|
}),
|
||||||
|
{ numRuns: NUM_RUNS }
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// NOTE: fails for ed, because of empty message. Since we convert it to scalar,
|
// NOTE: fails for ed, because of empty message. Since we convert it to scalar,
|
||||||
// need to check what other implementations do. Empty message != new Uint8Array([0]), but what scalar should be in that case?
|
// need to check what other implementations do. Empty message != new Uint8Array([0]), but what scalar should be in that case?
|
||||||
// should('should not verify signature with wrong message', () => {
|
// should('should not verify signature with wrong message', () => {
|
||||||
@@ -641,6 +735,16 @@ should('secp224k1 sqrt bug', () => {
|
|||||||
deepStrictEqual(Fp.sqr(sqrtMinus1), Fp.create(-1n));
|
deepStrictEqual(Fp.sqr(sqrtMinus1), Fp.create(-1n));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
should('bigInt private keys', () => {
|
||||||
|
// Doesn't support bigints anymore
|
||||||
|
throws(() => ed25519.sign('', 123n));
|
||||||
|
throws(() => ed25519.getPublicKey(123n));
|
||||||
|
throws(() => x25519.getPublicKey(123n));
|
||||||
|
// Weierstrass still supports
|
||||||
|
secp256k1.getPublicKey(123n);
|
||||||
|
secp256k1.sign('', 123n);
|
||||||
|
});
|
||||||
|
|
||||||
// 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) {
|
||||||
|
|||||||
@@ -1,18 +1,14 @@
|
|||||||
import { bls12_381 } from '../lib/esm/bls12-381.js';
|
|
||||||
import { describe, should } from 'micro-should';
|
|
||||||
import { deepStrictEqual, notDeepStrictEqual, throws } from 'assert';
|
import { deepStrictEqual, notDeepStrictEqual, throws } from 'assert';
|
||||||
import { sha512 } from '@noble/hashes/sha512';
|
|
||||||
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 { wNAF } from '../lib/esm/abstract/curve.js';
|
||||||
|
import { bytesToHex, utf8ToBytes } from '../lib/esm/abstract/utils.js';
|
||||||
|
import { hash_to_field } from '../lib/esm/abstract/hash-to-curve.js';
|
||||||
|
import { bls12_381 as bls } from '../lib/esm/bls12-381.js';
|
||||||
|
|
||||||
import zkVectors from './bls12-381/zkcrypto/converted.json' assert { type: 'json' };
|
import zkVectors from './bls12-381/zkcrypto/converted.json' assert { type: 'json' };
|
||||||
import pairingVectors from './bls12-381/go_pairing_vectors/pairing.json' assert { type: 'json' };
|
import pairingVectors from './bls12-381/go_pairing_vectors/pairing.json' assert { type: 'json' };
|
||||||
import { wNAF } from '../lib/esm/abstract/curve.js';
|
|
||||||
const bls = bls12_381;
|
|
||||||
const { Fp2 } = bls;
|
|
||||||
const G1Point = bls.G1.ProjectivePoint;
|
|
||||||
const G2Point = bls.G2.ProjectivePoint;
|
|
||||||
const G1Aff = (x, y) => G1Point.fromAffine({ x, y });
|
|
||||||
|
|
||||||
const G2_VECTORS = readFileSync('./test/bls12-381/bls12-381-g2-test-vectors.txt', 'utf-8')
|
const G2_VECTORS = readFileSync('./test/bls12-381/bls12-381-g2-test-vectors.txt', 'utf-8')
|
||||||
.trim()
|
.trim()
|
||||||
.split('\n')
|
.split('\n')
|
||||||
@@ -28,7 +24,10 @@ const SCALAR_VECTORS = readFileSync('./test/bls12-381/bls12-381-scalar-test-vect
|
|||||||
const NUM_RUNS = Number(process.env.RUNS_COUNT || 10); // reduce to 1 to shorten test time
|
const NUM_RUNS = Number(process.env.RUNS_COUNT || 10); // reduce to 1 to shorten test time
|
||||||
fc.configureGlobal({ numRuns: NUM_RUNS });
|
fc.configureGlobal({ numRuns: NUM_RUNS });
|
||||||
|
|
||||||
// @ts-ignore
|
const { Fp2 } = bls;
|
||||||
|
const G1Point = bls.G1.ProjectivePoint;
|
||||||
|
const G2Point = bls.G2.ProjectivePoint;
|
||||||
|
const G1Aff = (x, y) => G1Point.fromAffine({ x, y });
|
||||||
const CURVE_ORDER = bls.CURVE.r;
|
const CURVE_ORDER = bls.CURVE.r;
|
||||||
|
|
||||||
const FC_MSG = fc.hexaString({ minLength: 64, maxLength: 64 });
|
const FC_MSG = fc.hexaString({ minLength: 64, maxLength: 64 });
|
||||||
@@ -851,7 +850,7 @@ describe('bls12-381/basic', () => {
|
|||||||
for (let vector of G2_VECTORS) {
|
for (let vector of G2_VECTORS) {
|
||||||
const [priv, msg, expected] = vector;
|
const [priv, msg, expected] = vector;
|
||||||
const sig = bls.sign(msg, priv);
|
const sig = bls.sign(msg, priv);
|
||||||
deepStrictEqual(bls.utils.bytesToHex(sig), expected);
|
deepStrictEqual(bytesToHex(sig), expected);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
should(`produce correct scalars (${SCALAR_VECTORS.length} vectors)`, () => {
|
should(`produce correct scalars (${SCALAR_VECTORS.length} vectors)`, () => {
|
||||||
@@ -863,8 +862,8 @@ describe('bls12-381/basic', () => {
|
|||||||
for (let vector of SCALAR_VECTORS) {
|
for (let vector of SCALAR_VECTORS) {
|
||||||
const [okmAscii, expectedHex] = vector;
|
const [okmAscii, expectedHex] = vector;
|
||||||
const expected = BigInt('0x' + expectedHex);
|
const expected = BigInt('0x' + expectedHex);
|
||||||
const okm = new Uint8Array(okmAscii.split('').map((c) => c.charCodeAt(0)));
|
const okm = utf8ToBytes(okmAscii);
|
||||||
const scalars = bls.utils.hashToField(okm, 1, options);
|
const scalars = hash_to_field(okm, 1, Object.assign({}, bls.CURVE.htfDefaults, options));
|
||||||
deepStrictEqual(scalars[0][0], expected);
|
deepStrictEqual(scalars[0][0], expected);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -973,25 +972,25 @@ describe('hash-to-curve', () => {
|
|||||||
// Point G1
|
// Point G1
|
||||||
const VECTORS_G1 = [
|
const VECTORS_G1 = [
|
||||||
{
|
{
|
||||||
msg: bls.utils.stringToBytes(''),
|
msg: utf8ToBytes(''),
|
||||||
expected:
|
expected:
|
||||||
'0576730ab036cbac1d95b38dca905586f28d0a59048db4e8778782d89bff856ddef89277ead5a21e2975c4a6e3d8c79e' +
|
'0576730ab036cbac1d95b38dca905586f28d0a59048db4e8778782d89bff856ddef89277ead5a21e2975c4a6e3d8c79e' +
|
||||||
'1273e568bebf1864393c517f999b87c1eaa1b8432f95aea8160cd981b5b05d8cd4a7cf00103b6ef87f728e4b547dd7ae',
|
'1273e568bebf1864393c517f999b87c1eaa1b8432f95aea8160cd981b5b05d8cd4a7cf00103b6ef87f728e4b547dd7ae',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
msg: bls.utils.stringToBytes('abc'),
|
msg: utf8ToBytes('abc'),
|
||||||
expected:
|
expected:
|
||||||
'061daf0cc00d8912dac1d4cf5a7c32fca97f8b3bf3f805121888e5eb89f77f9a9f406569027ac6d0e61b1229f42c43d6' +
|
'061daf0cc00d8912dac1d4cf5a7c32fca97f8b3bf3f805121888e5eb89f77f9a9f406569027ac6d0e61b1229f42c43d6' +
|
||||||
'0de1601e5ba02cb637c1d35266f5700acee9850796dc88e860d022d7b9e7e3dce5950952e97861e5bb16d215c87f030d',
|
'0de1601e5ba02cb637c1d35266f5700acee9850796dc88e860d022d7b9e7e3dce5950952e97861e5bb16d215c87f030d',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
msg: bls.utils.stringToBytes('abcdef0123456789'),
|
msg: utf8ToBytes('abcdef0123456789'),
|
||||||
expected:
|
expected:
|
||||||
'0fb3455436843e76079c7cf3dfef75e5a104dfe257a29a850c145568d500ad31ccfe79be9ae0ea31a722548070cf98cd' +
|
'0fb3455436843e76079c7cf3dfef75e5a104dfe257a29a850c145568d500ad31ccfe79be9ae0ea31a722548070cf98cd' +
|
||||||
'177989f7e2c751658df1b26943ee829d3ebcf131d8f805571712f3a7527ee5334ecff8a97fc2a50cea86f5e6212e9a57',
|
'177989f7e2c751658df1b26943ee829d3ebcf131d8f805571712f3a7527ee5334ecff8a97fc2a50cea86f5e6212e9a57',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
msg: bls.utils.stringToBytes(
|
msg: utf8ToBytes(
|
||||||
'a512_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'
|
'a512_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'
|
||||||
),
|
),
|
||||||
expected:
|
expected:
|
||||||
@@ -1002,7 +1001,7 @@ describe('hash-to-curve', () => {
|
|||||||
for (let i = 0; i < VECTORS_G1.length; i++) {
|
for (let i = 0; i < VECTORS_G1.length; i++) {
|
||||||
const t = VECTORS_G1[i];
|
const t = VECTORS_G1[i];
|
||||||
should(`hashToCurve/G1 Killic (${i})`, () => {
|
should(`hashToCurve/G1 Killic (${i})`, () => {
|
||||||
const p = bls.hashToCurve.G1.hashToCurve(t.msg, {
|
const p = bls.G1.hashToCurve(t.msg, {
|
||||||
DST: 'BLS12381G1_XMD:SHA-256_SSWU_RO_TESTGEN',
|
DST: 'BLS12381G1_XMD:SHA-256_SSWU_RO_TESTGEN',
|
||||||
});
|
});
|
||||||
deepStrictEqual(p.toHex(false), t.expected);
|
deepStrictEqual(p.toHex(false), t.expected);
|
||||||
@@ -1011,25 +1010,25 @@ describe('hash-to-curve', () => {
|
|||||||
|
|
||||||
const VECTORS_ENCODE_G1 = [
|
const VECTORS_ENCODE_G1 = [
|
||||||
{
|
{
|
||||||
msg: bls.utils.stringToBytes(''),
|
msg: utf8ToBytes(''),
|
||||||
expected:
|
expected:
|
||||||
'1223effdbb2d38152495a864d78eee14cb0992d89a241707abb03819a91a6d2fd65854ab9a69e9aacb0cbebfd490732c' +
|
'1223effdbb2d38152495a864d78eee14cb0992d89a241707abb03819a91a6d2fd65854ab9a69e9aacb0cbebfd490732c' +
|
||||||
'0f925d61e0b235ecd945cbf0309291878df0d06e5d80d6b84aa4ff3e00633b26f9a7cb3523ef737d90e6d71e8b98b2d5',
|
'0f925d61e0b235ecd945cbf0309291878df0d06e5d80d6b84aa4ff3e00633b26f9a7cb3523ef737d90e6d71e8b98b2d5',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
msg: bls.utils.stringToBytes('abc'),
|
msg: utf8ToBytes('abc'),
|
||||||
expected:
|
expected:
|
||||||
'179d3fd0b4fb1da43aad06cea1fb3f828806ddb1b1fa9424b1e3944dfdbab6e763c42636404017da03099af0dcca0fd6' +
|
'179d3fd0b4fb1da43aad06cea1fb3f828806ddb1b1fa9424b1e3944dfdbab6e763c42636404017da03099af0dcca0fd6' +
|
||||||
'0d037cb1c6d495c0f5f22b061d23f1be3d7fe64d3c6820cfcd99b6b36fa69f7b4c1f4addba2ae7aa46fb25901ab483e4',
|
'0d037cb1c6d495c0f5f22b061d23f1be3d7fe64d3c6820cfcd99b6b36fa69f7b4c1f4addba2ae7aa46fb25901ab483e4',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
msg: bls.utils.stringToBytes('abcdef0123456789'),
|
msg: utf8ToBytes('abcdef0123456789'),
|
||||||
expected:
|
expected:
|
||||||
'15aa66c77eded1209db694e8b1ba49daf8b686733afaa7b68c683d0b01788dfb0617a2e2d04c0856db4981921d3004af' +
|
'15aa66c77eded1209db694e8b1ba49daf8b686733afaa7b68c683d0b01788dfb0617a2e2d04c0856db4981921d3004af' +
|
||||||
'0952bb2f61739dd1d201dd0a79d74cda3285403d47655ee886afe860593a8a4e51c5b77a22d2133e3a4280eaaaa8b788',
|
'0952bb2f61739dd1d201dd0a79d74cda3285403d47655ee886afe860593a8a4e51c5b77a22d2133e3a4280eaaaa8b788',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
msg: bls.utils.stringToBytes(
|
msg: utf8ToBytes(
|
||||||
'a512_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'
|
'a512_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'
|
||||||
),
|
),
|
||||||
expected:
|
expected:
|
||||||
@@ -1040,7 +1039,7 @@ describe('hash-to-curve', () => {
|
|||||||
for (let i = 0; i < VECTORS_ENCODE_G1.length; i++) {
|
for (let i = 0; i < VECTORS_ENCODE_G1.length; i++) {
|
||||||
const t = VECTORS_ENCODE_G1[i];
|
const t = VECTORS_ENCODE_G1[i];
|
||||||
should(`hashToCurve/G1 (Killic, encodeToCurve) (${i})`, () => {
|
should(`hashToCurve/G1 (Killic, encodeToCurve) (${i})`, () => {
|
||||||
const p = bls.hashToCurve.G1.encodeToCurve(t.msg, {
|
const p = bls.G1.encodeToCurve(t.msg, {
|
||||||
DST: 'BLS12381G1_XMD:SHA-256_SSWU_NU_TESTGEN',
|
DST: 'BLS12381G1_XMD:SHA-256_SSWU_NU_TESTGEN',
|
||||||
});
|
});
|
||||||
deepStrictEqual(p.toHex(false), t.expected);
|
deepStrictEqual(p.toHex(false), t.expected);
|
||||||
@@ -1049,7 +1048,7 @@ describe('hash-to-curve', () => {
|
|||||||
// Point G2
|
// Point G2
|
||||||
const VECTORS_G2 = [
|
const VECTORS_G2 = [
|
||||||
{
|
{
|
||||||
msg: bls.utils.stringToBytes(''),
|
msg: utf8ToBytes(''),
|
||||||
expected:
|
expected:
|
||||||
'0fbdae26f9f9586a46d4b0b70390d09064ef2afe5c99348438a3c7d9756471e015cb534204c1b6824617a85024c772dc' +
|
'0fbdae26f9f9586a46d4b0b70390d09064ef2afe5c99348438a3c7d9756471e015cb534204c1b6824617a85024c772dc' +
|
||||||
'0a650bd36ae7455cb3fe5d8bb1310594551456f5c6593aec9ee0c03d2f6cb693bd2c5e99d4e23cbaec767609314f51d3' +
|
'0a650bd36ae7455cb3fe5d8bb1310594551456f5c6593aec9ee0c03d2f6cb693bd2c5e99d4e23cbaec767609314f51d3' +
|
||||||
@@ -1057,7 +1056,7 @@ describe('hash-to-curve', () => {
|
|||||||
'0d8d49e7737d8f9fc5cef7c4b8817633103faf2613016cb86a1f3fc29968fe2413e232d9208d2d74a89bf7a48ac36f83',
|
'0d8d49e7737d8f9fc5cef7c4b8817633103faf2613016cb86a1f3fc29968fe2413e232d9208d2d74a89bf7a48ac36f83',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
msg: bls.utils.stringToBytes('abc'),
|
msg: utf8ToBytes('abc'),
|
||||||
expected:
|
expected:
|
||||||
'03578447618463deb106b60e609c6f7cc446dc6035f84a72801ba17c94cd800583b493b948eff0033f09086fdd7f6175' +
|
'03578447618463deb106b60e609c6f7cc446dc6035f84a72801ba17c94cd800583b493b948eff0033f09086fdd7f6175' +
|
||||||
'1953ce6d4267939c7360756d9cca8eb34aac4633ef35369a7dc249445069888e7d1b3f9d2e75fbd468fbcbba7110ea02' +
|
'1953ce6d4267939c7360756d9cca8eb34aac4633ef35369a7dc249445069888e7d1b3f9d2e75fbd468fbcbba7110ea02' +
|
||||||
@@ -1065,7 +1064,7 @@ describe('hash-to-curve', () => {
|
|||||||
'0882ab045b8fe4d7d557ebb59a63a35ac9f3d312581b509af0f8eaa2960cbc5e1e36bb969b6e22980b5cbdd0787fcf4e',
|
'0882ab045b8fe4d7d557ebb59a63a35ac9f3d312581b509af0f8eaa2960cbc5e1e36bb969b6e22980b5cbdd0787fcf4e',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
msg: bls.utils.stringToBytes('abcdef0123456789'),
|
msg: utf8ToBytes('abcdef0123456789'),
|
||||||
expected:
|
expected:
|
||||||
'195fad48982e186ce3c5c82133aefc9b26d55979b6f530992a8849d4263ec5d57f7a181553c8799bcc83da44847bdc8d' +
|
'195fad48982e186ce3c5c82133aefc9b26d55979b6f530992a8849d4263ec5d57f7a181553c8799bcc83da44847bdc8d' +
|
||||||
'17b461fc3b96a30c2408958cbfa5f5927b6063a8ad199d5ebf2d7cdeffa9c20c85487204804fab53f950b2f87db365aa' +
|
'17b461fc3b96a30c2408958cbfa5f5927b6063a8ad199d5ebf2d7cdeffa9c20c85487204804fab53f950b2f87db365aa' +
|
||||||
@@ -1073,7 +1072,7 @@ describe('hash-to-curve', () => {
|
|||||||
'174a3473a3af2d0302b9065e895ca4adba4ece6ce0b41148ba597001abb152f852dd9a96fb45c9de0a43d944746f833e',
|
'174a3473a3af2d0302b9065e895ca4adba4ece6ce0b41148ba597001abb152f852dd9a96fb45c9de0a43d944746f833e',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
msg: bls.utils.stringToBytes(
|
msg: utf8ToBytes(
|
||||||
'a512_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'
|
'a512_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'
|
||||||
),
|
),
|
||||||
expected:
|
expected:
|
||||||
@@ -1086,7 +1085,7 @@ describe('hash-to-curve', () => {
|
|||||||
for (let i = 0; i < VECTORS_G2.length; i++) {
|
for (let i = 0; i < VECTORS_G2.length; i++) {
|
||||||
const t = VECTORS_G2[i];
|
const t = VECTORS_G2[i];
|
||||||
should(`hashToCurve/G2 Killic (${i})`, () => {
|
should(`hashToCurve/G2 Killic (${i})`, () => {
|
||||||
const p = bls.hashToCurve.G2.hashToCurve(t.msg, {
|
const p = bls.G2.hashToCurve(t.msg, {
|
||||||
DST: 'BLS12381G2_XMD:SHA-256_SSWU_RO_TESTGEN',
|
DST: 'BLS12381G2_XMD:SHA-256_SSWU_RO_TESTGEN',
|
||||||
});
|
});
|
||||||
deepStrictEqual(p.toHex(false), t.expected);
|
deepStrictEqual(p.toHex(false), t.expected);
|
||||||
@@ -1095,7 +1094,7 @@ describe('hash-to-curve', () => {
|
|||||||
|
|
||||||
const VECTORS_ENCODE_G2 = [
|
const VECTORS_ENCODE_G2 = [
|
||||||
{
|
{
|
||||||
msg: bls.utils.stringToBytes(''),
|
msg: utf8ToBytes(''),
|
||||||
expected:
|
expected:
|
||||||
'0d4333b77becbf9f9dfa3ca928002233d1ecc854b1447e5a71f751c9042d000f42db91c1d6649a5e0ad22bd7bf7398b8' +
|
'0d4333b77becbf9f9dfa3ca928002233d1ecc854b1447e5a71f751c9042d000f42db91c1d6649a5e0ad22bd7bf7398b8' +
|
||||||
'027e4bfada0b47f9f07e04aec463c7371e68f2fd0c738cd517932ea3801a35acf09db018deda57387b0f270f7a219e4d' +
|
'027e4bfada0b47f9f07e04aec463c7371e68f2fd0c738cd517932ea3801a35acf09db018deda57387b0f270f7a219e4d' +
|
||||||
@@ -1103,7 +1102,7 @@ describe('hash-to-curve', () => {
|
|||||||
'053674cba9ef516ddc218fedb37324e6c47de27f88ab7ef123b006127d738293c0277187f7e2f80a299a24d84ed03da7',
|
'053674cba9ef516ddc218fedb37324e6c47de27f88ab7ef123b006127d738293c0277187f7e2f80a299a24d84ed03da7',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
msg: bls.utils.stringToBytes('abc'),
|
msg: utf8ToBytes('abc'),
|
||||||
expected:
|
expected:
|
||||||
'18f0f87b40af67c056915dbaf48534c592524e82c1c2b50c3734d02c0172c80df780a60b5683759298a3303c5d942778' +
|
'18f0f87b40af67c056915dbaf48534c592524e82c1c2b50c3734d02c0172c80df780a60b5683759298a3303c5d942778' +
|
||||||
'09349f1cb5b2e55489dcd45a38545343451cc30a1681c57acd4fb0a6db125f8352c09f4a67eb7d1d8242cb7d3405f97b' +
|
'09349f1cb5b2e55489dcd45a38545343451cc30a1681c57acd4fb0a6db125f8352c09f4a67eb7d1d8242cb7d3405f97b' +
|
||||||
@@ -1111,7 +1110,7 @@ describe('hash-to-curve', () => {
|
|||||||
'02f2d9deb2c7742512f5b8230bf0fd83ea42279d7d39779543c1a43b61c885982b611f6a7a24b514995e8a098496b811',
|
'02f2d9deb2c7742512f5b8230bf0fd83ea42279d7d39779543c1a43b61c885982b611f6a7a24b514995e8a098496b811',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
msg: bls.utils.stringToBytes('abcdef0123456789'),
|
msg: utf8ToBytes('abcdef0123456789'),
|
||||||
expected:
|
expected:
|
||||||
'19808ec5930a53c7cf5912ccce1cc33f1b3dcff24a53ce1cc4cba41fd6996dbed4843ccdd2eaf6a0cd801e562718d163' +
|
'19808ec5930a53c7cf5912ccce1cc33f1b3dcff24a53ce1cc4cba41fd6996dbed4843ccdd2eaf6a0cd801e562718d163' +
|
||||||
'149fe43777d34f0d25430dea463889bd9393bdfb4932946db23671727081c629ebb98a89604f3433fba1c67d356a4af7' +
|
'149fe43777d34f0d25430dea463889bd9393bdfb4932946db23671727081c629ebb98a89604f3433fba1c67d356a4af7' +
|
||||||
@@ -1119,7 +1118,7 @@ describe('hash-to-curve', () => {
|
|||||||
'04c0d6793a766233b2982087b5f4a254f261003ccb3262ea7c50903eecef3e871d1502c293f9e063d7d293f6384f4551',
|
'04c0d6793a766233b2982087b5f4a254f261003ccb3262ea7c50903eecef3e871d1502c293f9e063d7d293f6384f4551',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
msg: bls.utils.stringToBytes(
|
msg: utf8ToBytes(
|
||||||
'a512_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'
|
'a512_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'
|
||||||
),
|
),
|
||||||
expected:
|
expected:
|
||||||
@@ -1132,7 +1131,7 @@ describe('hash-to-curve', () => {
|
|||||||
for (let i = 0; i < VECTORS_ENCODE_G2.length; i++) {
|
for (let i = 0; i < VECTORS_ENCODE_G2.length; i++) {
|
||||||
const t = VECTORS_ENCODE_G2[i];
|
const t = VECTORS_ENCODE_G2[i];
|
||||||
should(`hashToCurve/G2 (Killic, encodeToCurve) (${i})`, () => {
|
should(`hashToCurve/G2 (Killic, encodeToCurve) (${i})`, () => {
|
||||||
const p = bls.hashToCurve.G2.encodeToCurve(t.msg, {
|
const p = bls.G2.encodeToCurve(t.msg, {
|
||||||
DST: 'BLS12381G2_XMD:SHA-256_SSWU_NU_TESTGEN',
|
DST: 'BLS12381G2_XMD:SHA-256_SSWU_NU_TESTGEN',
|
||||||
});
|
});
|
||||||
deepStrictEqual(p.toHex(false), t.expected);
|
deepStrictEqual(p.toHex(false), t.expected);
|
||||||
@@ -1265,7 +1264,7 @@ describe('bls12-381 deterministic', () => {
|
|||||||
should('Killic based/Pairing', () => {
|
should('Killic based/Pairing', () => {
|
||||||
const t = bls.pairing(G1Point.BASE, G2Point.BASE);
|
const t = bls.pairing(G1Point.BASE, G2Point.BASE);
|
||||||
deepStrictEqual(
|
deepStrictEqual(
|
||||||
bls.utils.bytesToHex(Fp12.toBytes(t)),
|
bytesToHex(Fp12.toBytes(t)),
|
||||||
killicHex([
|
killicHex([
|
||||||
'0f41e58663bf08cf068672cbd01a7ec73baca4d72ca93544deff686bfd6df543d48eaa24afe47e1efde449383b676631',
|
'0f41e58663bf08cf068672cbd01a7ec73baca4d72ca93544deff686bfd6df543d48eaa24afe47e1efde449383b676631',
|
||||||
'04c581234d086a9902249b64728ffd21a189e87935a954051c7cdba7b3872629a4fafc05066245cb9108f0242d0fe3ef',
|
'04c581234d086a9902249b64728ffd21a189e87935a954051c7cdba7b3872629a4fafc05066245cb9108f0242d0fe3ef',
|
||||||
@@ -1287,7 +1286,7 @@ describe('bls12-381 deterministic', () => {
|
|||||||
let p2 = G2Point.BASE;
|
let p2 = G2Point.BASE;
|
||||||
for (let v of pairingVectors) {
|
for (let v of pairingVectors) {
|
||||||
deepStrictEqual(
|
deepStrictEqual(
|
||||||
bls.utils.bytesToHex(Fp12.toBytes(bls.pairing(p1, p2))),
|
bytesToHex(Fp12.toBytes(bls.pairing(p1, p2))),
|
||||||
// Reverse order
|
// Reverse order
|
||||||
v.match(/.{96}/g).reverse().join('')
|
v.match(/.{96}/g).reverse().join('')
|
||||||
);
|
);
|
||||||
|
|||||||
290
test/ed25519-addons.test.js
Normal file
290
test/ed25519-addons.test.js
Normal file
@@ -0,0 +1,290 @@
|
|||||||
|
import { sha512 } from '@noble/hashes/sha512';
|
||||||
|
import { hexToBytes, bytesToHex, randomBytes } from '@noble/hashes/utils';
|
||||||
|
import { deepStrictEqual, strictEqual, throws } from 'assert';
|
||||||
|
import { describe, should } from 'micro-should';
|
||||||
|
import { numberToBytesLE } from '../lib/esm/abstract/utils.js';
|
||||||
|
import { default as x25519vectors } from './wycheproof/x25519_test.json' assert { type: 'json' };
|
||||||
|
import { ed25519ctx, ed25519ph, RistrettoPoint, x25519 } from '../lib/esm/ed25519.js';
|
||||||
|
|
||||||
|
// const ed = ed25519;
|
||||||
|
const hex = bytesToHex;
|
||||||
|
// const Point = ed.ExtendedPoint;
|
||||||
|
|
||||||
|
const VECTORS_RFC8032_CTX = [
|
||||||
|
{
|
||||||
|
secretKey: '0305334e381af78f141cb666f6199f57bc3495335a256a95bd2a55bf546663f6',
|
||||||
|
publicKey: 'dfc9425e4f968f7f0c29f0259cf5f9aed6851c2bb4ad8bfb860cfee0ab248292',
|
||||||
|
message: 'f726936d19c800494e3fdaff20b276a8',
|
||||||
|
context: '666f6f',
|
||||||
|
signature:
|
||||||
|
'55a4cc2f70a54e04288c5f4cd1e45a7b' +
|
||||||
|
'b520b36292911876cada7323198dd87a' +
|
||||||
|
'8b36950b95130022907a7fb7c4e9b2d5' +
|
||||||
|
'f6cca685a587b4b21f4b888e4e7edb0d',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
secretKey: '0305334e381af78f141cb666f6199f57bc3495335a256a95bd2a55bf546663f6',
|
||||||
|
publicKey: 'dfc9425e4f968f7f0c29f0259cf5f9aed6851c2bb4ad8bfb860cfee0ab248292',
|
||||||
|
message: 'f726936d19c800494e3fdaff20b276a8',
|
||||||
|
context: '626172',
|
||||||
|
signature:
|
||||||
|
'fc60d5872fc46b3aa69f8b5b4351d580' +
|
||||||
|
'8f92bcc044606db097abab6dbcb1aee3' +
|
||||||
|
'216c48e8b3b66431b5b186d1d28f8ee1' +
|
||||||
|
'5a5ca2df6668346291c2043d4eb3e90d',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
secretKey: '0305334e381af78f141cb666f6199f57bc3495335a256a95bd2a55bf546663f6',
|
||||||
|
publicKey: 'dfc9425e4f968f7f0c29f0259cf5f9aed6851c2bb4ad8bfb860cfee0ab248292',
|
||||||
|
message: '508e9e6882b979fea900f62adceaca35',
|
||||||
|
context: '666f6f',
|
||||||
|
signature:
|
||||||
|
'8b70c1cc8310e1de20ac53ce28ae6e72' +
|
||||||
|
'07f33c3295e03bb5c0732a1d20dc6490' +
|
||||||
|
'8922a8b052cf99b7c4fe107a5abb5b2c' +
|
||||||
|
'4085ae75890d02df26269d8945f84b0b',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
secretKey: 'ab9c2853ce297ddab85c993b3ae14bcad39b2c682beabc27d6d4eb20711d6560',
|
||||||
|
publicKey: '0f1d1274943b91415889152e893d80e93275a1fc0b65fd71b4b0dda10ad7d772',
|
||||||
|
message: 'f726936d19c800494e3fdaff20b276a8',
|
||||||
|
context: '666f6f',
|
||||||
|
signature:
|
||||||
|
'21655b5f1aa965996b3f97b3c849eafb' +
|
||||||
|
'a922a0a62992f73b3d1b73106a84ad85' +
|
||||||
|
'e9b86a7b6005ea868337ff2d20a7f5fb' +
|
||||||
|
'd4cd10b0be49a68da2b2e0dc0ad8960f',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
describe('RFC8032ctx', () => {
|
||||||
|
for (let i = 0; i < VECTORS_RFC8032_CTX.length; i++) {
|
||||||
|
const v = VECTORS_RFC8032_CTX[i];
|
||||||
|
should(`${i}`, () => {
|
||||||
|
deepStrictEqual(hex(ed25519ctx.getPublicKey(v.secretKey)), v.publicKey);
|
||||||
|
deepStrictEqual(hex(ed25519ctx.sign(v.message, v.secretKey, v.context)), v.signature);
|
||||||
|
deepStrictEqual(ed25519ctx.verify(v.signature, v.message, v.publicKey, v.context), true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const VECTORS_RFC8032_PH = [
|
||||||
|
{
|
||||||
|
secretKey: '833fe62409237b9d62ec77587520911e9a759cec1d19755b7da901b96dca3d42',
|
||||||
|
publicKey: 'ec172b93ad5e563bf4932c70e1245034c35467ef2efd4d64ebf819683467e2bf',
|
||||||
|
message: '616263',
|
||||||
|
signature:
|
||||||
|
'98a70222f0b8121aa9d30f813d683f80' +
|
||||||
|
'9e462b469c7ff87639499bb94e6dae41' +
|
||||||
|
'31f85042463c2a355a2003d062adf5aa' +
|
||||||
|
'a10b8c61e636062aaad11c2a26083406',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
describe('RFC8032ph', () => {
|
||||||
|
for (let i = 0; i < VECTORS_RFC8032_PH.length; i++) {
|
||||||
|
const v = VECTORS_RFC8032_PH[i];
|
||||||
|
should(`${i}`, () => {
|
||||||
|
deepStrictEqual(hex(ed25519ph.getPublicKey(v.secretKey)), v.publicKey);
|
||||||
|
deepStrictEqual(hex(ed25519ph.sign(v.message, v.secretKey)), v.signature);
|
||||||
|
deepStrictEqual(ed25519ph.verify(v.signature, v.message, v.publicKey), true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// x25519
|
||||||
|
should('X25519 base point', () => {
|
||||||
|
const { y } = ed25519ph.ExtendedPoint.BASE;
|
||||||
|
const { Fp } = ed25519ph.CURVE;
|
||||||
|
const u = Fp.create((y + 1n) * Fp.inv(1n - y));
|
||||||
|
deepStrictEqual(hex(numberToBytesLE(u, 32)), x25519.Gu);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('RFC7748', () => {
|
||||||
|
const rfc7748Mul = [
|
||||||
|
{
|
||||||
|
scalar: 'a546e36bf0527c9d3b16154b82465edd62144c0ac1fc5a18506a2244ba449ac4',
|
||||||
|
u: 'e6db6867583030db3594c1a424b15f7c726624ec26b3353b10a903a6d0ab1c4c',
|
||||||
|
outputU: 'c3da55379de9c6908e94ea4df28d084f32eccf03491c71f754b4075577a28552',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
scalar: '4b66e9d4d1b4673c5ad22691957d6af5c11b6421e0ea01d42ca4169e7918ba0d',
|
||||||
|
u: 'e5210f12786811d3f4b7959d0538ae2c31dbe7106fc03c3efc4cd549c715a493',
|
||||||
|
outputU: '95cbde9476e8907d7aade45cb4b873f88b595a68799fa152e6f8f7647aac7957',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
for (let i = 0; i < rfc7748Mul.length; i++) {
|
||||||
|
const v = rfc7748Mul[i];
|
||||||
|
should(`scalarMult (${i})`, () => {
|
||||||
|
deepStrictEqual(hex(x25519.scalarMult(v.scalar, v.u)), v.outputU);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const rfc7748Iter = [
|
||||||
|
{ scalar: '422c8e7a6227d7bca1350b3e2bb7279f7897b87bb6854b783c60e80311ae3079', iters: 1 },
|
||||||
|
{ scalar: '684cf59ba83309552800ef566f2f4d3c1c3887c49360e3875f2eb94d99532c51', iters: 1000 },
|
||||||
|
// { scalar: '7c3911e0ab2586fd864497297e575e6f3bc601c0883c30df5f4dd2d24f665424', iters: 1000000 },
|
||||||
|
];
|
||||||
|
for (let i = 0; i < rfc7748Iter.length; i++) {
|
||||||
|
const { scalar, iters } = rfc7748Iter[i];
|
||||||
|
should(`scalarMult iteration (${i})`, () => {
|
||||||
|
let k = x25519.Gu;
|
||||||
|
for (let i = 0, u = k; i < iters; i++) [k, u] = [x25519.scalarMult(k, u), k];
|
||||||
|
deepStrictEqual(hex(k), scalar);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
should('getSharedKey', () => {
|
||||||
|
const alicePrivate = '77076d0a7318a57d3c16c17251b26645df4c2f87ebc0992ab177fba51db92c2a';
|
||||||
|
const alicePublic = '8520f0098930a754748b7ddcb43ef75a0dbf3a0d26381af4eba4a98eaa9b4e6a';
|
||||||
|
const bobPrivate = '5dab087e624a8a4b79e17f8b83800ee66f3bb1292618b6fd1c2f8b27ff88e0eb';
|
||||||
|
const bobPublic = 'de9edb7d7b7dc1b4d35b61c2ece435373f8343c85b78674dadfc7e146f882b4f';
|
||||||
|
const shared = '4a5d9d5ba4ce2de1728e3bf480350f25e07e21c947d19e3376f09b3c1e161742';
|
||||||
|
deepStrictEqual(alicePublic, hex(x25519.getPublicKey(alicePrivate)));
|
||||||
|
deepStrictEqual(bobPublic, hex(x25519.getPublicKey(bobPrivate)));
|
||||||
|
deepStrictEqual(hex(x25519.scalarMult(alicePrivate, bobPublic)), shared);
|
||||||
|
deepStrictEqual(hex(x25519.scalarMult(bobPrivate, alicePublic)), shared);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('Wycheproof', () => {
|
||||||
|
const group = x25519vectors.testGroups[0];
|
||||||
|
should(`X25519`, () => {
|
||||||
|
for (let i = 0; i < group.tests.length; i++) {
|
||||||
|
const v = group.tests[i];
|
||||||
|
const comment = `(${i}, ${v.result}) ${v.comment}`;
|
||||||
|
if (v.result === 'valid' || v.result === 'acceptable') {
|
||||||
|
try {
|
||||||
|
const shared = hex(x25519.scalarMult(v.private, v.public));
|
||||||
|
deepStrictEqual(shared, v.shared, comment);
|
||||||
|
} catch (e) {
|
||||||
|
// We are more strict
|
||||||
|
if (e.message.includes('Expected valid scalar')) return;
|
||||||
|
if (e.message.includes('Invalid private or public key received')) return;
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
} else if (v.result === 'invalid') {
|
||||||
|
let failed = false;
|
||||||
|
try {
|
||||||
|
x25519.scalarMult(v.private, v.public);
|
||||||
|
} catch (error) {
|
||||||
|
failed = true;
|
||||||
|
}
|
||||||
|
deepStrictEqual(failed, true, comment);
|
||||||
|
} else throw new Error('unknown test result');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function utf8ToBytes(str) {
|
||||||
|
if (typeof str !== 'string') {
|
||||||
|
throw new Error(`utf8ToBytes expected string, got ${typeof str}`);
|
||||||
|
}
|
||||||
|
return new TextEncoder().encode(str);
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('ristretto255', () => {
|
||||||
|
should('follow the byte encodings of small multiples', () => {
|
||||||
|
const encodingsOfSmallMultiples = [
|
||||||
|
// This is the identity point
|
||||||
|
'0000000000000000000000000000000000000000000000000000000000000000',
|
||||||
|
// This is the basepoint
|
||||||
|
'e2f2ae0a6abc4e71a884a961c500515f58e30b6aa582dd8db6a65945e08d2d76',
|
||||||
|
// These are small multiples of the basepoint
|
||||||
|
'6a493210f7499cd17fecb510ae0cea23a110e8d5b901f8acadd3095c73a3b919',
|
||||||
|
'94741f5d5d52755ece4f23f044ee27d5d1ea1e2bd196b462166b16152a9d0259',
|
||||||
|
'da80862773358b466ffadfe0b3293ab3d9fd53c5ea6c955358f568322daf6a57',
|
||||||
|
'e882b131016b52c1d3337080187cf768423efccbb517bb495ab812c4160ff44e',
|
||||||
|
'f64746d3c92b13050ed8d80236a7f0007c3b3f962f5ba793d19a601ebb1df403',
|
||||||
|
'44f53520926ec81fbd5a387845beb7df85a96a24ece18738bdcfa6a7822a176d',
|
||||||
|
'903293d8f2287ebe10e2374dc1a53e0bc887e592699f02d077d5263cdd55601c',
|
||||||
|
'02622ace8f7303a31cafc63f8fc48fdc16e1c8c8d234b2f0d6685282a9076031',
|
||||||
|
'20706fd788b2720a1ed2a5dad4952b01f413bcf0e7564de8cdc816689e2db95f',
|
||||||
|
'bce83f8ba5dd2fa572864c24ba1810f9522bc6004afe95877ac73241cafdab42',
|
||||||
|
'e4549ee16b9aa03099ca208c67adafcafa4c3f3e4e5303de6026e3ca8ff84460',
|
||||||
|
'aa52e000df2e16f55fb1032fc33bc42742dad6bd5a8fc0be0167436c5948501f',
|
||||||
|
'46376b80f409b29dc2b5f6f0c52591990896e5716f41477cd30085ab7f10301e',
|
||||||
|
'e0c418f7c8d9c4cdd7395b93ea124f3ad99021bb681dfc3302a9d99a2e53e64e',
|
||||||
|
];
|
||||||
|
let B = RistrettoPoint.BASE;
|
||||||
|
let P = RistrettoPoint.ZERO;
|
||||||
|
for (const encoded of encodingsOfSmallMultiples) {
|
||||||
|
deepStrictEqual(P.toHex(), encoded);
|
||||||
|
deepStrictEqual(RistrettoPoint.fromHex(encoded).toHex(), encoded);
|
||||||
|
P = P.add(B);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
should('not convert bad bytes encoding', () => {
|
||||||
|
const badEncodings = [
|
||||||
|
// These are all bad because they're non-canonical field encodings.
|
||||||
|
'00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff',
|
||||||
|
'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f',
|
||||||
|
'f3ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f',
|
||||||
|
'edffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f',
|
||||||
|
// These are all bad because they're negative field elements.
|
||||||
|
'0100000000000000000000000000000000000000000000000000000000000000',
|
||||||
|
'01ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f',
|
||||||
|
'ed57ffd8c914fb201471d1c3d245ce3c746fcbe63a3679d51b6a516ebebe0e20',
|
||||||
|
'c34c4e1826e5d403b78e246e88aa051c36ccf0aafebffe137d148a2bf9104562',
|
||||||
|
'c940e5a4404157cfb1628b108db051a8d439e1a421394ec4ebccb9ec92a8ac78',
|
||||||
|
'47cfc5497c53dc8e61c91d17fd626ffb1c49e2bca94eed052281b510b1117a24',
|
||||||
|
'f1c6165d33367351b0da8f6e4511010c68174a03b6581212c71c0e1d026c3c72',
|
||||||
|
'87260f7a2f12495118360f02c26a470f450dadf34a413d21042b43b9d93e1309',
|
||||||
|
// These are all bad because they give a nonsquare x².
|
||||||
|
'26948d35ca62e643e26a83177332e6b6afeb9d08e4268b650f1f5bbd8d81d371',
|
||||||
|
'4eac077a713c57b4f4397629a4145982c661f48044dd3f96427d40b147d9742f',
|
||||||
|
'de6a7b00deadc788eb6b6c8d20c0ae96c2f2019078fa604fee5b87d6e989ad7b',
|
||||||
|
'bcab477be20861e01e4a0e295284146a510150d9817763caf1a6f4b422d67042',
|
||||||
|
'2a292df7e32cababbd9de088d1d1abec9fc0440f637ed2fba145094dc14bea08',
|
||||||
|
'f4a9e534fc0d216c44b218fa0c42d99635a0127ee2e53c712f70609649fdff22',
|
||||||
|
'8268436f8c4126196cf64b3c7ddbda90746a378625f9813dd9b8457077256731',
|
||||||
|
'2810e5cbc2cc4d4eece54f61c6f69758e289aa7ab440b3cbeaa21995c2f4232b',
|
||||||
|
// These are all bad because they give a negative xy value.
|
||||||
|
'3eb858e78f5a7254d8c9731174a94f76755fd3941c0ac93735c07ba14579630e',
|
||||||
|
'a45fdc55c76448c049a1ab33f17023edfb2be3581e9c7aade8a6125215e04220',
|
||||||
|
'd483fe813c6ba647ebbfd3ec41adca1c6130c2beeee9d9bf065c8d151c5f396e',
|
||||||
|
'8a2e1d30050198c65a54483123960ccc38aef6848e1ec8f5f780e8523769ba32',
|
||||||
|
'32888462f8b486c68ad7dd9610be5192bbeaf3b443951ac1a8118419d9fa097b',
|
||||||
|
'227142501b9d4355ccba290404bde41575b037693cef1f438c47f8fbf35d1165',
|
||||||
|
'5c37cc491da847cfeb9281d407efc41e15144c876e0170b499a96a22ed31e01e',
|
||||||
|
'445425117cb8c90edcbc7c1cc0e74f747f2c1efa5630a967c64f287792a48a4b',
|
||||||
|
// This is s = -1, which causes y = 0.
|
||||||
|
'ecffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f',
|
||||||
|
];
|
||||||
|
for (const badBytes of badEncodings) {
|
||||||
|
const b = hexToBytes(badBytes);
|
||||||
|
throws(() => RistrettoPoint.fromHex(b), badBytes);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
should('create right points from uniform hash', () => {
|
||||||
|
const labels = [
|
||||||
|
'Ristretto is traditionally a short shot of espresso coffee',
|
||||||
|
'made with the normal amount of ground coffee but extracted with',
|
||||||
|
'about half the amount of water in the same amount of time',
|
||||||
|
'by using a finer grind.',
|
||||||
|
'This produces a concentrated shot of coffee per volume.',
|
||||||
|
'Just pulling a normal shot short will produce a weaker shot',
|
||||||
|
'and is not a Ristretto as some believe.',
|
||||||
|
];
|
||||||
|
const encodedHashToPoints = [
|
||||||
|
'3066f82a1a747d45120d1740f14358531a8f04bbffe6a819f86dfe50f44a0a46',
|
||||||
|
'f26e5b6f7d362d2d2a94c5d0e7602cb4773c95a2e5c31a64f133189fa76ed61b',
|
||||||
|
'006ccd2a9e6867e6a2c5cea83d3302cc9de128dd2a9a57dd8ee7b9d7ffe02826',
|
||||||
|
'f8f0c87cf237953c5890aec3998169005dae3eca1fbb04548c635953c817f92a',
|
||||||
|
'ae81e7dedf20a497e10c304a765c1767a42d6e06029758d2d7e8ef7cc4c41179',
|
||||||
|
'e2705652ff9f5e44d3e841bf1c251cf7dddb77d140870d1ab2ed64f1a9ce8628',
|
||||||
|
'80bd07262511cdde4863f8a7434cef696750681cb9510eea557088f76d9e5065',
|
||||||
|
];
|
||||||
|
|
||||||
|
for (let i = 0; i < labels.length; i++) {
|
||||||
|
const hash = sha512(utf8ToBytes(labels[i]));
|
||||||
|
const point = RistrettoPoint.hashToCurve(hash);
|
||||||
|
deepStrictEqual(point.toHex(), encodedHashToPoints[i]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// ESM is broken.
|
||||||
|
import url from 'url';
|
||||||
|
if (import.meta.url === url.pathToFileURL(process.argv[1]).href) {
|
||||||
|
should.run();
|
||||||
|
}
|
||||||
1
test/ed25519.helpers.js
Normal file
1
test/ed25519.helpers.js
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export { ed25519, ED25519_TORSION_SUBGROUP } from '../lib/esm/ed25519.js';
|
||||||
@@ -1,21 +1,11 @@
|
|||||||
import { deepEqual, deepStrictEqual, strictEqual, throws } from 'assert';
|
import { deepStrictEqual, strictEqual, throws } from 'assert';
|
||||||
import { describe, should } from 'micro-should';
|
|
||||||
import * as fc from 'fast-check';
|
|
||||||
import {
|
|
||||||
ed25519,
|
|
||||||
ed25519ctx,
|
|
||||||
ed25519ph,
|
|
||||||
x25519,
|
|
||||||
RistrettoPoint,
|
|
||||||
ED25519_TORSION_SUBGROUP,
|
|
||||||
} from '../lib/esm/ed25519.js';
|
|
||||||
import { readFileSync } from 'fs';
|
import { readFileSync } from 'fs';
|
||||||
import { default as zip215 } from './ed25519/zip215.json' assert { type: 'json' };
|
|
||||||
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 * as fc from 'fast-check';
|
||||||
import { sha512 } from '@noble/hashes/sha512';
|
import { describe, should } from 'micro-should';
|
||||||
|
import { ed25519, ED25519_TORSION_SUBGROUP } from './ed25519.helpers.js';
|
||||||
import { default as ed25519vectors } from './wycheproof/eddsa_test.json' assert { type: 'json' };
|
import { default as ed25519vectors } from './wycheproof/eddsa_test.json' assert { type: 'json' };
|
||||||
import { default as x25519vectors } from './wycheproof/x25519_test.json' assert { type: 'json' };
|
import { default as zip215 } from './ed25519/zip215.json' assert { type: 'json' };
|
||||||
|
|
||||||
describe('ed25519', () => {
|
describe('ed25519', () => {
|
||||||
const ed = ed25519;
|
const ed = ed25519;
|
||||||
@@ -292,104 +282,6 @@ describe('ed25519', () => {
|
|||||||
// // const signature = await ristretto25519.sign(MESSAGE, PRIVATE_KEY);
|
// // const signature = await ristretto25519.sign(MESSAGE, PRIVATE_KEY);
|
||||||
// // expect(await ristretto25519.verify(signature, WRONG_MESSAGE, publicKey)).toBe(false);
|
// // expect(await ristretto25519.verify(signature, WRONG_MESSAGE, publicKey)).toBe(false);
|
||||||
// // });
|
// // });
|
||||||
should('ristretto255/should follow the byte encodings of small multiples', () => {
|
|
||||||
const encodingsOfSmallMultiples = [
|
|
||||||
// This is the identity point
|
|
||||||
'0000000000000000000000000000000000000000000000000000000000000000',
|
|
||||||
// This is the basepoint
|
|
||||||
'e2f2ae0a6abc4e71a884a961c500515f58e30b6aa582dd8db6a65945e08d2d76',
|
|
||||||
// These are small multiples of the basepoint
|
|
||||||
'6a493210f7499cd17fecb510ae0cea23a110e8d5b901f8acadd3095c73a3b919',
|
|
||||||
'94741f5d5d52755ece4f23f044ee27d5d1ea1e2bd196b462166b16152a9d0259',
|
|
||||||
'da80862773358b466ffadfe0b3293ab3d9fd53c5ea6c955358f568322daf6a57',
|
|
||||||
'e882b131016b52c1d3337080187cf768423efccbb517bb495ab812c4160ff44e',
|
|
||||||
'f64746d3c92b13050ed8d80236a7f0007c3b3f962f5ba793d19a601ebb1df403',
|
|
||||||
'44f53520926ec81fbd5a387845beb7df85a96a24ece18738bdcfa6a7822a176d',
|
|
||||||
'903293d8f2287ebe10e2374dc1a53e0bc887e592699f02d077d5263cdd55601c',
|
|
||||||
'02622ace8f7303a31cafc63f8fc48fdc16e1c8c8d234b2f0d6685282a9076031',
|
|
||||||
'20706fd788b2720a1ed2a5dad4952b01f413bcf0e7564de8cdc816689e2db95f',
|
|
||||||
'bce83f8ba5dd2fa572864c24ba1810f9522bc6004afe95877ac73241cafdab42',
|
|
||||||
'e4549ee16b9aa03099ca208c67adafcafa4c3f3e4e5303de6026e3ca8ff84460',
|
|
||||||
'aa52e000df2e16f55fb1032fc33bc42742dad6bd5a8fc0be0167436c5948501f',
|
|
||||||
'46376b80f409b29dc2b5f6f0c52591990896e5716f41477cd30085ab7f10301e',
|
|
||||||
'e0c418f7c8d9c4cdd7395b93ea124f3ad99021bb681dfc3302a9d99a2e53e64e',
|
|
||||||
];
|
|
||||||
let B = RistrettoPoint.BASE;
|
|
||||||
let P = RistrettoPoint.ZERO;
|
|
||||||
for (const encoded of encodingsOfSmallMultiples) {
|
|
||||||
deepStrictEqual(P.toHex(), encoded);
|
|
||||||
deepStrictEqual(RistrettoPoint.fromHex(encoded).toHex(), encoded);
|
|
||||||
P = P.add(B);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
should('ristretto255/should not convert bad bytes encoding', () => {
|
|
||||||
const badEncodings = [
|
|
||||||
// These are all bad because they're non-canonical field encodings.
|
|
||||||
'00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff',
|
|
||||||
'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f',
|
|
||||||
'f3ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f',
|
|
||||||
'edffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f',
|
|
||||||
// These are all bad because they're negative field elements.
|
|
||||||
'0100000000000000000000000000000000000000000000000000000000000000',
|
|
||||||
'01ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f',
|
|
||||||
'ed57ffd8c914fb201471d1c3d245ce3c746fcbe63a3679d51b6a516ebebe0e20',
|
|
||||||
'c34c4e1826e5d403b78e246e88aa051c36ccf0aafebffe137d148a2bf9104562',
|
|
||||||
'c940e5a4404157cfb1628b108db051a8d439e1a421394ec4ebccb9ec92a8ac78',
|
|
||||||
'47cfc5497c53dc8e61c91d17fd626ffb1c49e2bca94eed052281b510b1117a24',
|
|
||||||
'f1c6165d33367351b0da8f6e4511010c68174a03b6581212c71c0e1d026c3c72',
|
|
||||||
'87260f7a2f12495118360f02c26a470f450dadf34a413d21042b43b9d93e1309',
|
|
||||||
// These are all bad because they give a nonsquare x².
|
|
||||||
'26948d35ca62e643e26a83177332e6b6afeb9d08e4268b650f1f5bbd8d81d371',
|
|
||||||
'4eac077a713c57b4f4397629a4145982c661f48044dd3f96427d40b147d9742f',
|
|
||||||
'de6a7b00deadc788eb6b6c8d20c0ae96c2f2019078fa604fee5b87d6e989ad7b',
|
|
||||||
'bcab477be20861e01e4a0e295284146a510150d9817763caf1a6f4b422d67042',
|
|
||||||
'2a292df7e32cababbd9de088d1d1abec9fc0440f637ed2fba145094dc14bea08',
|
|
||||||
'f4a9e534fc0d216c44b218fa0c42d99635a0127ee2e53c712f70609649fdff22',
|
|
||||||
'8268436f8c4126196cf64b3c7ddbda90746a378625f9813dd9b8457077256731',
|
|
||||||
'2810e5cbc2cc4d4eece54f61c6f69758e289aa7ab440b3cbeaa21995c2f4232b',
|
|
||||||
// These are all bad because they give a negative xy value.
|
|
||||||
'3eb858e78f5a7254d8c9731174a94f76755fd3941c0ac93735c07ba14579630e',
|
|
||||||
'a45fdc55c76448c049a1ab33f17023edfb2be3581e9c7aade8a6125215e04220',
|
|
||||||
'd483fe813c6ba647ebbfd3ec41adca1c6130c2beeee9d9bf065c8d151c5f396e',
|
|
||||||
'8a2e1d30050198c65a54483123960ccc38aef6848e1ec8f5f780e8523769ba32',
|
|
||||||
'32888462f8b486c68ad7dd9610be5192bbeaf3b443951ac1a8118419d9fa097b',
|
|
||||||
'227142501b9d4355ccba290404bde41575b037693cef1f438c47f8fbf35d1165',
|
|
||||||
'5c37cc491da847cfeb9281d407efc41e15144c876e0170b499a96a22ed31e01e',
|
|
||||||
'445425117cb8c90edcbc7c1cc0e74f747f2c1efa5630a967c64f287792a48a4b',
|
|
||||||
// This is s = -1, which causes y = 0.
|
|
||||||
'ecffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f',
|
|
||||||
];
|
|
||||||
for (const badBytes of badEncodings) {
|
|
||||||
const b = hexToBytes(badBytes);
|
|
||||||
throws(() => RistrettoPoint.fromHex(b), badBytes);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
should('ristretto255/should create right points from uniform hash', () => {
|
|
||||||
const labels = [
|
|
||||||
'Ristretto is traditionally a short shot of espresso coffee',
|
|
||||||
'made with the normal amount of ground coffee but extracted with',
|
|
||||||
'about half the amount of water in the same amount of time',
|
|
||||||
'by using a finer grind.',
|
|
||||||
'This produces a concentrated shot of coffee per volume.',
|
|
||||||
'Just pulling a normal shot short will produce a weaker shot',
|
|
||||||
'and is not a Ristretto as some believe.',
|
|
||||||
];
|
|
||||||
const encodedHashToPoints = [
|
|
||||||
'3066f82a1a747d45120d1740f14358531a8f04bbffe6a819f86dfe50f44a0a46',
|
|
||||||
'f26e5b6f7d362d2d2a94c5d0e7602cb4773c95a2e5c31a64f133189fa76ed61b',
|
|
||||||
'006ccd2a9e6867e6a2c5cea83d3302cc9de128dd2a9a57dd8ee7b9d7ffe02826',
|
|
||||||
'f8f0c87cf237953c5890aec3998169005dae3eca1fbb04548c635953c817f92a',
|
|
||||||
'ae81e7dedf20a497e10c304a765c1767a42d6e06029758d2d7e8ef7cc4c41179',
|
|
||||||
'e2705652ff9f5e44d3e841bf1c251cf7dddb77d140870d1ab2ed64f1a9ce8628',
|
|
||||||
'80bd07262511cdde4863f8a7434cef696750681cb9510eea557088f76d9e5065',
|
|
||||||
];
|
|
||||||
|
|
||||||
for (let i = 0; i < labels.length; i++) {
|
|
||||||
const hash = sha512(utf8ToBytes(labels[i]));
|
|
||||||
const point = RistrettoPoint.hashToCurve(hash);
|
|
||||||
deepStrictEqual(point.toHex(), encodedHashToPoints[i]);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
should('input immutability: sign/verify are immutable', () => {
|
should('input immutability: sign/verify are immutable', () => {
|
||||||
const privateKey = ed.utils.randomPrivateKey();
|
const privateKey = ed.utils.randomPrivateKey();
|
||||||
@@ -432,51 +324,6 @@ describe('ed25519', () => {
|
|||||||
throws(() => ed.verify(sig, 'deadbeef', Point.BASE));
|
throws(() => ed.verify(sig, 'deadbeef', Point.BASE));
|
||||||
});
|
});
|
||||||
|
|
||||||
const rfc7748Mul = [
|
|
||||||
{
|
|
||||||
scalar: 'a546e36bf0527c9d3b16154b82465edd62144c0ac1fc5a18506a2244ba449ac4',
|
|
||||||
u: 'e6db6867583030db3594c1a424b15f7c726624ec26b3353b10a903a6d0ab1c4c',
|
|
||||||
outputU: 'c3da55379de9c6908e94ea4df28d084f32eccf03491c71f754b4075577a28552',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
scalar: '4b66e9d4d1b4673c5ad22691957d6af5c11b6421e0ea01d42ca4169e7918ba0d',
|
|
||||||
u: 'e5210f12786811d3f4b7959d0538ae2c31dbe7106fc03c3efc4cd549c715a493',
|
|
||||||
outputU: '95cbde9476e8907d7aade45cb4b873f88b595a68799fa152e6f8f7647aac7957',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
for (let i = 0; i < rfc7748Mul.length; i++) {
|
|
||||||
const v = rfc7748Mul[i];
|
|
||||||
should(`RFC7748: scalarMult (${i})`, () => {
|
|
||||||
deepStrictEqual(hex(x25519.scalarMult(v.scalar, v.u)), v.outputU);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const rfc7748Iter = [
|
|
||||||
{ scalar: '422c8e7a6227d7bca1350b3e2bb7279f7897b87bb6854b783c60e80311ae3079', iters: 1 },
|
|
||||||
{ scalar: '684cf59ba83309552800ef566f2f4d3c1c3887c49360e3875f2eb94d99532c51', iters: 1000 },
|
|
||||||
// { scalar: '7c3911e0ab2586fd864497297e575e6f3bc601c0883c30df5f4dd2d24f665424', iters: 1000000 },
|
|
||||||
];
|
|
||||||
for (let i = 0; i < rfc7748Iter.length; i++) {
|
|
||||||
const { scalar, iters } = rfc7748Iter[i];
|
|
||||||
should(`RFC7748: scalarMult iteration (${i})`, () => {
|
|
||||||
let k = x25519.Gu;
|
|
||||||
for (let i = 0, u = k; i < iters; i++) [k, u] = [x25519.scalarMult(k, u), k];
|
|
||||||
deepStrictEqual(hex(k), scalar);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
should('RFC7748 getSharedKey', () => {
|
|
||||||
const alicePrivate = '77076d0a7318a57d3c16c17251b26645df4c2f87ebc0992ab177fba51db92c2a';
|
|
||||||
const alicePublic = '8520f0098930a754748b7ddcb43ef75a0dbf3a0d26381af4eba4a98eaa9b4e6a';
|
|
||||||
const bobPrivate = '5dab087e624a8a4b79e17f8b83800ee66f3bb1292618b6fd1c2f8b27ff88e0eb';
|
|
||||||
const bobPublic = 'de9edb7d7b7dc1b4d35b61c2ece435373f8343c85b78674dadfc7e146f882b4f';
|
|
||||||
const shared = '4a5d9d5ba4ce2de1728e3bf480350f25e07e21c947d19e3376f09b3c1e161742';
|
|
||||||
deepStrictEqual(alicePublic, hex(x25519.getPublicKey(alicePrivate)));
|
|
||||||
deepStrictEqual(bobPublic, hex(x25519.getPublicKey(bobPrivate)));
|
|
||||||
deepStrictEqual(hex(x25519.scalarMult(alicePrivate, bobPublic)), shared);
|
|
||||||
deepStrictEqual(hex(x25519.scalarMult(bobPrivate, alicePublic)), shared);
|
|
||||||
});
|
|
||||||
|
|
||||||
// should('X25519/getSharedSecret() should be commutative', () => {
|
// should('X25519/getSharedSecret() should be commutative', () => {
|
||||||
// for (let i = 0; i < 512; i++) {
|
// for (let i = 0; i < 512; i++) {
|
||||||
// const asec = ed.utils.randomPrivateKey();
|
// const asec = ed.utils.randomPrivateKey();
|
||||||
@@ -499,35 +346,6 @@ describe('ed25519', () => {
|
|||||||
// );
|
// );
|
||||||
// });
|
// });
|
||||||
|
|
||||||
{
|
|
||||||
const group = x25519vectors.testGroups[0];
|
|
||||||
should(`Wycheproof/X25519`, () => {
|
|
||||||
for (let i = 0; i < group.tests.length; i++) {
|
|
||||||
const v = group.tests[i];
|
|
||||||
const comment = `(${i}, ${v.result}) ${v.comment}`;
|
|
||||||
if (v.result === 'valid' || v.result === 'acceptable') {
|
|
||||||
try {
|
|
||||||
const shared = hex(x25519.scalarMult(v.private, v.public));
|
|
||||||
deepStrictEqual(shared, v.shared, comment);
|
|
||||||
} catch (e) {
|
|
||||||
// We are more strict
|
|
||||||
if (e.message.includes('Expected valid scalar')) return;
|
|
||||||
if (e.message.includes('Invalid private or public key received')) return;
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
} else if (v.result === 'invalid') {
|
|
||||||
let failed = false;
|
|
||||||
try {
|
|
||||||
x25519.scalarMult(v.private, v.public);
|
|
||||||
} catch (error) {
|
|
||||||
failed = true;
|
|
||||||
}
|
|
||||||
deepStrictEqual(failed, true, comment);
|
|
||||||
} else throw new Error('unknown test result');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
should(`Wycheproof/ED25519`, () => {
|
should(`Wycheproof/ED25519`, () => {
|
||||||
for (let g = 0; g < ed25519vectors.testGroups.length; g++) {
|
for (let g = 0; g < ed25519vectors.testGroups.length; g++) {
|
||||||
const group = ed25519vectors.testGroups[g];
|
const group = ed25519vectors.testGroups[g];
|
||||||
@@ -559,91 +377,6 @@ describe('ed25519', () => {
|
|||||||
deepStrictEqual(ed.verify(signature, message, publicKey), true);
|
deepStrictEqual(ed.verify(signature, message, publicKey), true);
|
||||||
});
|
});
|
||||||
|
|
||||||
const VECTORS_RFC8032_CTX = [
|
|
||||||
{
|
|
||||||
secretKey: '0305334e381af78f141cb666f6199f57bc3495335a256a95bd2a55bf546663f6',
|
|
||||||
publicKey: 'dfc9425e4f968f7f0c29f0259cf5f9aed6851c2bb4ad8bfb860cfee0ab248292',
|
|
||||||
message: 'f726936d19c800494e3fdaff20b276a8',
|
|
||||||
context: '666f6f',
|
|
||||||
signature:
|
|
||||||
'55a4cc2f70a54e04288c5f4cd1e45a7b' +
|
|
||||||
'b520b36292911876cada7323198dd87a' +
|
|
||||||
'8b36950b95130022907a7fb7c4e9b2d5' +
|
|
||||||
'f6cca685a587b4b21f4b888e4e7edb0d',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
secretKey: '0305334e381af78f141cb666f6199f57bc3495335a256a95bd2a55bf546663f6',
|
|
||||||
publicKey: 'dfc9425e4f968f7f0c29f0259cf5f9aed6851c2bb4ad8bfb860cfee0ab248292',
|
|
||||||
message: 'f726936d19c800494e3fdaff20b276a8',
|
|
||||||
context: '626172',
|
|
||||||
signature:
|
|
||||||
'fc60d5872fc46b3aa69f8b5b4351d580' +
|
|
||||||
'8f92bcc044606db097abab6dbcb1aee3' +
|
|
||||||
'216c48e8b3b66431b5b186d1d28f8ee1' +
|
|
||||||
'5a5ca2df6668346291c2043d4eb3e90d',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
secretKey: '0305334e381af78f141cb666f6199f57bc3495335a256a95bd2a55bf546663f6',
|
|
||||||
publicKey: 'dfc9425e4f968f7f0c29f0259cf5f9aed6851c2bb4ad8bfb860cfee0ab248292',
|
|
||||||
message: '508e9e6882b979fea900f62adceaca35',
|
|
||||||
context: '666f6f',
|
|
||||||
signature:
|
|
||||||
'8b70c1cc8310e1de20ac53ce28ae6e72' +
|
|
||||||
'07f33c3295e03bb5c0732a1d20dc6490' +
|
|
||||||
'8922a8b052cf99b7c4fe107a5abb5b2c' +
|
|
||||||
'4085ae75890d02df26269d8945f84b0b',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
secretKey: 'ab9c2853ce297ddab85c993b3ae14bcad39b2c682beabc27d6d4eb20711d6560',
|
|
||||||
publicKey: '0f1d1274943b91415889152e893d80e93275a1fc0b65fd71b4b0dda10ad7d772',
|
|
||||||
message: 'f726936d19c800494e3fdaff20b276a8',
|
|
||||||
context: '666f6f',
|
|
||||||
signature:
|
|
||||||
'21655b5f1aa965996b3f97b3c849eafb' +
|
|
||||||
'a922a0a62992f73b3d1b73106a84ad85' +
|
|
||||||
'e9b86a7b6005ea868337ff2d20a7f5fb' +
|
|
||||||
'd4cd10b0be49a68da2b2e0dc0ad8960f',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
for (let i = 0; i < VECTORS_RFC8032_CTX.length; i++) {
|
|
||||||
const v = VECTORS_RFC8032_CTX[i];
|
|
||||||
should(`RFC8032ctx/${i}`, () => {
|
|
||||||
deepStrictEqual(hex(ed25519ctx.getPublicKey(v.secretKey)), v.publicKey);
|
|
||||||
deepStrictEqual(hex(ed25519ctx.sign(v.message, v.secretKey, v.context)), v.signature);
|
|
||||||
deepStrictEqual(ed25519ctx.verify(v.signature, v.message, v.publicKey, v.context), true);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const VECTORS_RFC8032_PH = [
|
|
||||||
{
|
|
||||||
secretKey: '833fe62409237b9d62ec77587520911e9a759cec1d19755b7da901b96dca3d42',
|
|
||||||
publicKey: 'ec172b93ad5e563bf4932c70e1245034c35467ef2efd4d64ebf819683467e2bf',
|
|
||||||
message: '616263',
|
|
||||||
signature:
|
|
||||||
'98a70222f0b8121aa9d30f813d683f80' +
|
|
||||||
'9e462b469c7ff87639499bb94e6dae41' +
|
|
||||||
'31f85042463c2a355a2003d062adf5aa' +
|
|
||||||
'a10b8c61e636062aaad11c2a26083406',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
for (let i = 0; i < VECTORS_RFC8032_PH.length; i++) {
|
|
||||||
const v = VECTORS_RFC8032_PH[i];
|
|
||||||
should(`RFC8032ph/${i}`, () => {
|
|
||||||
deepStrictEqual(hex(ed25519ph.getPublicKey(v.secretKey)), v.publicKey);
|
|
||||||
deepStrictEqual(hex(ed25519ph.sign(v.message, v.secretKey)), v.signature);
|
|
||||||
deepStrictEqual(ed25519ph.verify(v.signature, v.message, v.publicKey), true);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
should('X25519 base point', () => {
|
|
||||||
const { y } = ed25519.ExtendedPoint.BASE;
|
|
||||||
const { Fp } = ed25519.CURVE;
|
|
||||||
const u = Fp.create((y + 1n) * Fp.inv(1n - y));
|
|
||||||
deepStrictEqual(hex(numberToBytesLE(u, 32)), x25519.Gu);
|
|
||||||
});
|
|
||||||
|
|
||||||
should('isTorsionFree()', () => {
|
should('isTorsionFree()', () => {
|
||||||
const orig = ed.utils.getExtendedPublicKey(ed.utils.randomPrivateKey()).point;
|
const orig = ed.utils.getExtendedPublicKey(ed.utils.randomPrivateKey()).point;
|
||||||
for (const hex of ED25519_TORSION_SUBGROUP.slice(1)) {
|
for (const hex of ED25519_TORSION_SUBGROUP.slice(1)) {
|
||||||
@@ -656,6 +389,15 @@ describe('ed25519', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
should('ed25519 bug', () => {
|
||||||
|
const t = 81718630521762619991978402609047527194981150691135404693881672112315521837062n;
|
||||||
|
const point = ed25519.ExtendedPoint.fromAffine({ x: t, y: t });
|
||||||
|
throws(() => point.assertValidity());
|
||||||
|
// Otherwise (without assertValidity):
|
||||||
|
// const point2 = point.double();
|
||||||
|
// point2.toAffine(); // crash!
|
||||||
|
});
|
||||||
|
|
||||||
// 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) {
|
||||||
|
|||||||
@@ -12,11 +12,8 @@ import * as ed25519 from '../lib/esm/ed25519.js';
|
|||||||
import * as ed448 from '../lib/esm/ed448.js';
|
import * as ed448 from '../lib/esm/ed448.js';
|
||||||
import * as secp256k1 from '../lib/esm/secp256k1.js';
|
import * as secp256k1 from '../lib/esm/secp256k1.js';
|
||||||
import { bls12_381 } from '../lib/esm/bls12-381.js';
|
import { bls12_381 } from '../lib/esm/bls12-381.js';
|
||||||
import {
|
import { expand_message_xmd, expand_message_xof } from '../lib/esm/abstract/hash-to-curve.js';
|
||||||
stringToBytes,
|
import { utf8ToBytes } from '../lib/esm/abstract/utils.js';
|
||||||
expand_message_xmd,
|
|
||||||
expand_message_xof,
|
|
||||||
} from '../lib/esm/abstract/hash-to-curve.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' };
|
||||||
@@ -56,9 +53,9 @@ function testExpandXMD(hash, vectors) {
|
|||||||
const t = vectors.tests[i];
|
const t = vectors.tests[i];
|
||||||
should(`${vectors.hash}/${vectors.DST.length}/${i}`, () => {
|
should(`${vectors.hash}/${vectors.DST.length}/${i}`, () => {
|
||||||
const p = expand_message_xmd(
|
const p = expand_message_xmd(
|
||||||
stringToBytes(t.msg),
|
utf8ToBytes(t.msg),
|
||||||
stringToBytes(vectors.DST),
|
utf8ToBytes(vectors.DST),
|
||||||
t.len_in_bytes,
|
Number.parseInt(t.len_in_bytes),
|
||||||
hash
|
hash
|
||||||
);
|
);
|
||||||
deepStrictEqual(bytesToHex(p), t.uniform_bytes);
|
deepStrictEqual(bytesToHex(p), t.uniform_bytes);
|
||||||
@@ -79,9 +76,9 @@ function testExpandXOF(hash, vectors) {
|
|||||||
const t = vectors.tests[i];
|
const t = vectors.tests[i];
|
||||||
should(`${i}`, () => {
|
should(`${i}`, () => {
|
||||||
const p = expand_message_xof(
|
const p = expand_message_xof(
|
||||||
stringToBytes(t.msg),
|
utf8ToBytes(t.msg),
|
||||||
stringToBytes(vectors.DST),
|
utf8ToBytes(vectors.DST),
|
||||||
+t.len_in_bytes,
|
Number.parseInt(t.len_in_bytes),
|
||||||
vectors.k,
|
vectors.k,
|
||||||
hash
|
hash
|
||||||
);
|
);
|
||||||
@@ -112,7 +109,7 @@ function testCurve(curve, ro, nu) {
|
|||||||
const t = ro.vectors[i];
|
const t = ro.vectors[i];
|
||||||
should(`(${i})`, () => {
|
should(`(${i})`, () => {
|
||||||
const p = curve
|
const p = curve
|
||||||
.hashToCurve(stringToBytes(t.msg), {
|
.hashToCurve(utf8ToBytes(t.msg), {
|
||||||
DST: ro.dst,
|
DST: ro.dst,
|
||||||
})
|
})
|
||||||
.toAffine();
|
.toAffine();
|
||||||
@@ -126,7 +123,7 @@ function testCurve(curve, ro, nu) {
|
|||||||
const t = nu.vectors[i];
|
const t = nu.vectors[i];
|
||||||
should(`(${i})`, () => {
|
should(`(${i})`, () => {
|
||||||
const p = curve
|
const p = curve
|
||||||
.encodeToCurve(stringToBytes(t.msg), {
|
.encodeToCurve(utf8ToBytes(t.msg), {
|
||||||
DST: nu.dst,
|
DST: nu.dst,
|
||||||
})
|
})
|
||||||
.toAffine();
|
.toAffine();
|
||||||
@@ -140,8 +137,8 @@ function testCurve(curve, ro, nu) {
|
|||||||
testCurve(secp256r1, p256_ro, p256_nu);
|
testCurve(secp256r1, p256_ro, p256_nu);
|
||||||
testCurve(secp384r1, p384_ro, p384_nu);
|
testCurve(secp384r1, p384_ro, p384_nu);
|
||||||
testCurve(secp521r1, p521_ro, p521_nu);
|
testCurve(secp521r1, p521_ro, p521_nu);
|
||||||
testCurve(bls12_381.hashToCurve.G1, g1_ro, g1_nu);
|
testCurve(bls12_381.G1, g1_ro, g1_nu);
|
||||||
testCurve(bls12_381.hashToCurve.G2, g2_ro, g2_nu);
|
testCurve(bls12_381.G2, g2_ro, g2_nu);
|
||||||
testCurve(secp256k1, secp256k1_ro, secp256k1_nu);
|
testCurve(secp256k1, secp256k1_ro, secp256k1_nu);
|
||||||
testCurve(ed25519, ed25519_ro, ed25519_nu);
|
testCurve(ed25519, ed25519_ro, ed25519_nu);
|
||||||
testCurve(ed448, ed448_ro, ed448_nu);
|
testCurve(ed448, ed448_ro, ed448_nu);
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import './nist.test.js';
|
|||||||
import './ed448.test.js';
|
import './ed448.test.js';
|
||||||
import './ed25519.test.js';
|
import './ed25519.test.js';
|
||||||
import './secp256k1.test.js';
|
import './secp256k1.test.js';
|
||||||
|
import './secp256k1-schnorr.test.js';
|
||||||
import './stark/index.test.js';
|
import './stark/index.test.js';
|
||||||
import './jubjub.test.js';
|
import './jubjub.test.js';
|
||||||
import './bls12-381.test.js';
|
import './bls12-381.test.js';
|
||||||
|
|||||||
@@ -86,7 +86,8 @@ describe('wycheproof ECDH', () => {
|
|||||||
try {
|
try {
|
||||||
const pub = CURVE.ProjectivePoint.fromHex(test.public);
|
const pub = CURVE.ProjectivePoint.fromHex(test.public);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e.message.includes('Point.fromHex: received invalid point.')) continue;
|
// Our strict validation filter doesn't let weird-length DER vectors
|
||||||
|
if (e.message.startsWith('Point of length')) continue;
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
const shared = CURVE.getSharedSecret(test.private, test.public);
|
const shared = CURVE.getSharedSecret(test.private, test.public);
|
||||||
@@ -140,7 +141,8 @@ describe('wycheproof ECDH', () => {
|
|||||||
try {
|
try {
|
||||||
const pub = curve.ProjectivePoint.fromHex(test.public);
|
const pub = curve.ProjectivePoint.fromHex(test.public);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e.message.includes('Point.fromHex: received invalid point.')) continue;
|
// Our strict validation filter doesn't let weird-length DER vectors
|
||||||
|
if (e.message.includes('Point of length')) continue;
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
const shared = curve.getSharedSecret(test.private, test.public);
|
const shared = curve.getSharedSecret(test.private, test.public);
|
||||||
@@ -194,7 +196,6 @@ const WYCHEPROOF_ECDSA = {
|
|||||||
secp256k1: {
|
secp256k1: {
|
||||||
curve: secp256k1,
|
curve: secp256k1,
|
||||||
hashes: {
|
hashes: {
|
||||||
// TODO: debug why fails, can be bug
|
|
||||||
sha256: {
|
sha256: {
|
||||||
hash: sha256,
|
hash: sha256,
|
||||||
tests: [secp256k1_sha256_test],
|
tests: [secp256k1_sha256_test],
|
||||||
|
|||||||
34
test/secp256k1-schnorr.test.js
Normal file
34
test/secp256k1-schnorr.test.js
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import { deepStrictEqual, throws } from 'assert';
|
||||||
|
import { readFileSync } from 'fs';
|
||||||
|
import { should, describe } from 'micro-should';
|
||||||
|
import { bytesToHex as hex } from '@noble/hashes/utils';
|
||||||
|
import { schnorr } from '../lib/esm/secp256k1.js';
|
||||||
|
const schCsv = readFileSync('./test/vectors/schnorr.csv', 'utf-8');
|
||||||
|
|
||||||
|
describe('schnorr.sign()', () => {
|
||||||
|
// index,secret key,public key,aux_rand,message,signature,verification result,comment
|
||||||
|
const vectors = schCsv
|
||||||
|
.split('\n')
|
||||||
|
.map((line) => line.split(','))
|
||||||
|
.slice(1, -1);
|
||||||
|
for (let vec of vectors) {
|
||||||
|
const [index, sec, pub, rnd, msg, expSig, passes, comment] = vec;
|
||||||
|
should(`${comment || 'vector ' + index}`, () => {
|
||||||
|
if (sec) {
|
||||||
|
deepStrictEqual(hex(schnorr.getPublicKey(sec)), pub.toLowerCase());
|
||||||
|
const sig = schnorr.sign(msg, sec, rnd);
|
||||||
|
deepStrictEqual(hex(sig), expSig.toLowerCase());
|
||||||
|
deepStrictEqual(schnorr.verify(sig, msg, pub), true);
|
||||||
|
} else {
|
||||||
|
const passed = schnorr.verify(expSig, msg, pub);
|
||||||
|
deepStrictEqual(passed, passes === 'TRUE');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// ESM is broken.
|
||||||
|
import url from 'url';
|
||||||
|
if (import.meta.url === url.pathToFileURL(process.argv[1]).href) {
|
||||||
|
should.run();
|
||||||
|
}
|
||||||
14
test/secp256k1.helpers.js
Normal file
14
test/secp256k1.helpers.js
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
// @ts-ignore
|
||||||
|
export { secp256k1 as secp } from '../lib/esm/secp256k1.js';
|
||||||
|
import { secp256k1 as _secp } from '../lib/esm/secp256k1.js';
|
||||||
|
export { bytesToNumberBE, numberToBytesBE } from '../lib/esm/abstract/utils.js';
|
||||||
|
export { mod } from '../lib/esm/abstract/modular.js';
|
||||||
|
export const sigFromDER = (der) => {
|
||||||
|
return _secp.Signature.fromDER(der);
|
||||||
|
};
|
||||||
|
export const sigToDER = (sig) => sig.toDERHex();
|
||||||
|
export const selectHash = (secp) => secp.CURVE.hash;
|
||||||
|
export const normVerifySig = (s) => _secp.Signature.fromDER(s);
|
||||||
|
// export const bytesToNumberBE = secp256k1.utils.bytesToNumberBE;
|
||||||
|
// export const numberToBytesBE = secp256k1.utils.numberToBytesBE;
|
||||||
|
// export const mod = mod_;
|
||||||
@@ -1,22 +1,21 @@
|
|||||||
|
import { hexToBytes, bytesToHex as hex } from '@noble/hashes/utils';
|
||||||
|
import { deepStrictEqual, throws } from 'assert';
|
||||||
import * as fc from 'fast-check';
|
import * as fc from 'fast-check';
|
||||||
import { secp256k1, schnorr } from '../lib/esm/secp256k1.js';
|
|
||||||
import { Fp } from '../lib/esm/abstract/modular.js';
|
|
||||||
import { bytesToNumberBE, numberToBytesBE } from '../lib/esm/abstract/utils.js';
|
|
||||||
import { readFileSync } from 'fs';
|
import { readFileSync } from 'fs';
|
||||||
|
import { should, describe } from 'micro-should';
|
||||||
|
// prettier-ignore
|
||||||
|
import {
|
||||||
|
secp, sigFromDER, sigToDER, selectHash, normVerifySig, mod, bytesToNumberBE, numberToBytesBE
|
||||||
|
} from './secp256k1.helpers.js';
|
||||||
|
|
||||||
import { default as ecdsa } from './vectors/ecdsa.json' assert { type: 'json' };
|
import { default as ecdsa } from './vectors/ecdsa.json' assert { type: 'json' };
|
||||||
import { default as ecdh } from './vectors/ecdh.json' assert { type: 'json' };
|
import { default as ecdh } from './vectors/ecdh.json' assert { type: 'json' };
|
||||||
import { default as privates } from './vectors/privates.json' assert { type: 'json' };
|
import { default as privates } from './vectors/privates.json' assert { type: 'json' };
|
||||||
import { default as points } from './vectors/points.json' assert { type: 'json' };
|
import { default as points } from './vectors/points.json' assert { type: 'json' };
|
||||||
import { default as wp } from './vectors/wychenproof.json' assert { type: 'json' };
|
import { default as wp } from './vectors/wychenproof.json' assert { type: 'json' };
|
||||||
import { should, describe } from 'micro-should';
|
|
||||||
import { deepStrictEqual, throws } from 'assert';
|
|
||||||
import { hexToBytes, bytesToHex } from '@noble/hashes/utils';
|
|
||||||
|
|
||||||
const hex = bytesToHex;
|
|
||||||
const secp = secp256k1;
|
|
||||||
const Point = secp.ProjectivePoint;
|
const Point = secp.ProjectivePoint;
|
||||||
const privatesTxt = readFileSync('./test/vectors/privates-2.txt', 'utf-8');
|
const privatesTxt = readFileSync('./test/vectors/privates-2.txt', 'utf-8');
|
||||||
const schCsv = readFileSync('./test/vectors/schnorr.csv', 'utf-8');
|
|
||||||
|
|
||||||
const FC_BIGINT = fc.bigInt(1n + 1n, secp.CURVE.n - 1n);
|
const FC_BIGINT = fc.bigInt(1n + 1n, secp.CURVE.n - 1n);
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
@@ -193,7 +192,7 @@ describe('secp256k1', () => {
|
|||||||
fc.assert(
|
fc.assert(
|
||||||
fc.property(FC_BIGINT, FC_BIGINT, (r, s) => {
|
fc.property(FC_BIGINT, FC_BIGINT, (r, s) => {
|
||||||
const sig = new secp.Signature(r, s);
|
const sig = new secp.Signature(r, s);
|
||||||
deepStrictEqual(secp.Signature.fromDER(sig.toDERHex()), sig);
|
deepStrictEqual(sigFromDER(sigToDER(sig)), sig);
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -241,9 +240,9 @@ describe('secp256k1', () => {
|
|||||||
);
|
);
|
||||||
for (const [msg, exp] of CASES) {
|
for (const [msg, exp] of CASES) {
|
||||||
const res = secp.sign(msg, privKey, { extraEntropy: undefined });
|
const res = secp.sign(msg, privKey, { extraEntropy: undefined });
|
||||||
deepStrictEqual(res.toDERHex(), exp);
|
deepStrictEqual(sigToDER(res), exp);
|
||||||
const rs = secp.Signature.fromDER(res.toDERHex()).toCompactHex();
|
const rs = sigFromDER(sigToDER(res)).toCompactHex();
|
||||||
deepStrictEqual(secp.Signature.fromCompact(rs).toDERHex(), exp);
|
deepStrictEqual(sigToDER(secp.Signature.fromCompact(rs)), exp);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
should('handle {extraData} option', () => {
|
should('handle {extraData} option', () => {
|
||||||
@@ -342,7 +341,7 @@ describe('secp256k1', () => {
|
|||||||
const s = 115792089237316195423570985008687907852837564279074904382605163141518161494334n;
|
const s = 115792089237316195423570985008687907852837564279074904382605163141518161494334n;
|
||||||
const pub = new Point(x, y, 1n).toRawBytes();
|
const pub = new Point(x, y, 1n).toRawBytes();
|
||||||
const sig = new secp.Signature(r, s);
|
const sig = new secp.Signature(r, s);
|
||||||
deepStrictEqual(secp.verify(sig, msg, pub, { strict: false }), true);
|
deepStrictEqual(secp.verify(sig, msg, pub, { lowS: false }), true);
|
||||||
});
|
});
|
||||||
should('not verify invalid deterministic signatures with RFC 6979', () => {
|
should('not verify invalid deterministic signatures with RFC 6979', () => {
|
||||||
for (const vector of ecdsa.invalid.verify) {
|
for (const vector of ecdsa.invalid.verify) {
|
||||||
@@ -351,29 +350,6 @@ describe('secp256k1', () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('schnorr.sign()', () => {
|
|
||||||
// index,secret key,public key,aux_rand,message,signature,verification result,comment
|
|
||||||
const vectors = schCsv
|
|
||||||
.split('\n')
|
|
||||||
.map((line) => line.split(','))
|
|
||||||
.slice(1, -1);
|
|
||||||
for (let vec of vectors) {
|
|
||||||
const [index, sec, pub, rnd, msg, expSig, passes, comment] = vec;
|
|
||||||
should(`${comment || 'vector ' + index}`, () => {
|
|
||||||
if (sec) {
|
|
||||||
deepStrictEqual(hex(schnorr.getPublicKey(sec)), pub.toLowerCase());
|
|
||||||
const sig = schnorr.sign(msg, sec, rnd);
|
|
||||||
deepStrictEqual(hex(sig), expSig.toLowerCase());
|
|
||||||
deepStrictEqual(schnorr.verify(sig, msg, pub), true);
|
|
||||||
} else {
|
|
||||||
const passed = schnorr.verify(expSig, msg, pub);
|
|
||||||
deepStrictEqual(passed, passes === 'TRUE');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('recoverPublicKey()', () => {
|
describe('recoverPublicKey()', () => {
|
||||||
should('recover public key from recovery bit', () => {
|
should('recover public key from recovery bit', () => {
|
||||||
const message = '00000000000000000000000000000000000000000000000000000000deadbeef';
|
const message = '00000000000000000000000000000000000000000000000000000000deadbeef';
|
||||||
@@ -404,7 +380,7 @@ describe('secp256k1', () => {
|
|||||||
should('handle RFC 6979 vectors', () => {
|
should('handle RFC 6979 vectors', () => {
|
||||||
for (const vector of ecdsa.valid) {
|
for (const vector of ecdsa.valid) {
|
||||||
let usig = secp.sign(vector.m, vector.d);
|
let usig = secp.sign(vector.m, vector.d);
|
||||||
let sig = usig.toDERHex();
|
let sig = sigToDER(usig);
|
||||||
const vpub = secp.getPublicKey(vector.d);
|
const vpub = secp.getPublicKey(vector.d);
|
||||||
const recovered = usig.recoverPublicKey(vector.m);
|
const recovered = usig.recoverPublicKey(vector.m);
|
||||||
deepStrictEqual(recovered.toHex(), hex(vpub));
|
deepStrictEqual(recovered.toHex(), hex(vpub));
|
||||||
@@ -459,52 +435,46 @@ describe('secp256k1', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('tweak utilities (legacy)', () => {
|
describe('tweak utilities (legacy)', () => {
|
||||||
const Fn = Fp(secp.CURVE.n);
|
const normal = secp.utils.normPrivateKeyToScalar;
|
||||||
const normal = secp.utils._normalizePrivateKey;
|
|
||||||
const tweakUtils = {
|
const tweakUtils = {
|
||||||
privateAdd: (privateKey, tweak) => {
|
privateAdd: (privateKey, tweak) => {
|
||||||
const p = normal(privateKey);
|
return numberToBytesBE(mod(normal(privateKey) + normal(tweak), secp.CURVE.n), 32);
|
||||||
const t = normal(tweak);
|
|
||||||
return numberToBytesBE(Fn.create(p + t), 32);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
privateNegate: (privateKey) => {
|
privateNegate: (privateKey) => {
|
||||||
return numberToBytesBE(Fn.neg(normal(privateKey)), 32);
|
return numberToBytesBE(mod(-normal(privateKey), secp.CURVE.n), 32);
|
||||||
},
|
},
|
||||||
|
|
||||||
pointAddScalar: (p, tweak, isCompressed) => {
|
pointAddScalar: (p, tweak, isCompressed) => {
|
||||||
const P = Point.fromHex(p);
|
const tweaked = Point.fromHex(p).add(Point.fromPrivateKey(tweak));
|
||||||
const t = normal(tweak);
|
if (tweaked.equals(Point.ZERO)) throw new Error('Tweaked point at infinity');
|
||||||
const Q = Point.BASE.multiplyAndAddUnsafe(P, t, 1n);
|
return tweaked.toRawBytes(isCompressed);
|
||||||
if (!Q) throw new Error('Tweaked point at infinity');
|
|
||||||
return Q.toRawBytes(isCompressed);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
pointMultiply: (p, tweak, isCompressed) => {
|
pointMultiply: (p, tweak, isCompressed) => {
|
||||||
const P = Point.fromHex(p);
|
if (typeof tweak === 'string') tweak = hexToBytes(tweak);
|
||||||
const h = typeof tweak === 'string' ? tweak : bytesToHex(tweak);
|
const t = bytesToNumberBE(tweak);
|
||||||
const t = BigInt(`0x${h}`);
|
return Point.fromHex(p).multiply(t).toRawBytes(isCompressed);
|
||||||
return P.multiply(t).toRawBytes(isCompressed);
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
should('privateAdd()', () => {
|
should('privateAdd()', () => {
|
||||||
for (const vector of privates.valid.add) {
|
for (const vector of privates.valid.add) {
|
||||||
const { a, b, expected } = vector;
|
const { a, b, expected } = vector;
|
||||||
deepStrictEqual(bytesToHex(tweakUtils.privateAdd(a, b)), expected);
|
deepStrictEqual(hex(tweakUtils.privateAdd(a, b)), expected);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
should('privateNegate()', () => {
|
should('privateNegate()', () => {
|
||||||
for (const vector of privates.valid.negate) {
|
for (const vector of privates.valid.negate) {
|
||||||
const { a, expected } = vector;
|
const { a, expected } = vector;
|
||||||
deepStrictEqual(bytesToHex(tweakUtils.privateNegate(a)), expected);
|
deepStrictEqual(hex(tweakUtils.privateNegate(a)), expected);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
should('pointAddScalar()', () => {
|
should('pointAddScalar()', () => {
|
||||||
for (const vector of points.valid.pointAddScalar) {
|
for (const vector of points.valid.pointAddScalar) {
|
||||||
const { description, P, d, expected } = vector;
|
const { description, P, d, expected } = vector;
|
||||||
const compressed = !!expected && expected.length === 66; // compressed === 33 bytes
|
const compressed = !!expected && expected.length === 66; // compressed === 33 bytes
|
||||||
deepStrictEqual(bytesToHex(tweakUtils.pointAddScalar(P, d, compressed)), expected);
|
deepStrictEqual(hex(tweakUtils.pointAddScalar(P, d, compressed)), expected);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
should('pointAddScalar() invalid', () => {
|
should('pointAddScalar() invalid', () => {
|
||||||
@@ -516,7 +486,7 @@ describe('secp256k1', () => {
|
|||||||
should('pointMultiply()', () => {
|
should('pointMultiply()', () => {
|
||||||
for (const vector of points.valid.pointMultiply) {
|
for (const vector of points.valid.pointMultiply) {
|
||||||
const { P, d, expected } = vector;
|
const { P, d, expected } = vector;
|
||||||
deepStrictEqual(bytesToHex(tweakUtils.pointMultiply(P, d, true)), expected);
|
deepStrictEqual(hex(tweakUtils.pointMultiply(P, d, true)), expected);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
should('pointMultiply() invalid', () => {
|
should('pointMultiply() invalid', () => {
|
||||||
@@ -532,10 +502,12 @@ describe('secp256k1', () => {
|
|||||||
// const pubKey = Point.fromHex().toRawBytes();
|
// const pubKey = Point.fromHex().toRawBytes();
|
||||||
const pubKey = group.key.uncompressed;
|
const pubKey = group.key.uncompressed;
|
||||||
for (let test of group.tests) {
|
for (let test of group.tests) {
|
||||||
const m = secp.CURVE.hash(hexToBytes(test.msg));
|
const h = selectHash(secp);
|
||||||
|
|
||||||
|
const m = h(hexToBytes(test.msg));
|
||||||
if (test.result === 'valid' || test.result === 'acceptable') {
|
if (test.result === 'valid' || test.result === 'acceptable') {
|
||||||
const verified = secp.verify(test.sig, m, pubKey);
|
const verified = secp.verify(normVerifySig(test.sig), m, pubKey);
|
||||||
if (secp.Signature.fromDER(test.sig).hasHighS()) {
|
if (sigFromDER(test.sig).hasHighS()) {
|
||||||
deepStrictEqual(verified, false);
|
deepStrictEqual(verified, false);
|
||||||
} else {
|
} else {
|
||||||
deepStrictEqual(verified, true);
|
deepStrictEqual(verified, true);
|
||||||
|
|||||||
@@ -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 { hex, utf8 } from '@scure/base';
|
import { utf8ToBytes } from '@noble/hashes/utils.js';
|
||||||
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 '../../lib/esm/stark.js';
|
||||||
@@ -9,7 +9,7 @@ import { default as precomputedKeys } from './fixtures/keys_precomputed.json' as
|
|||||||
|
|
||||||
describe('starknet', () => {
|
describe('starknet', () => {
|
||||||
should('custom keccak', () => {
|
should('custom keccak', () => {
|
||||||
const value = starknet.keccak(utf8.decode('hello'));
|
const value = starknet.keccak(utf8ToBytes('hello'));
|
||||||
deepStrictEqual(value, 0x8aff950685c2ed4bc3174f3472287b56d9517b9c948127319a09a7a36deac8n);
|
deepStrictEqual(value, 0x8aff950685c2ed4bc3174f3472287b56d9517b9c948127319a09a7a36deac8n);
|
||||||
deepStrictEqual(value < 2n ** 250n, true);
|
deepStrictEqual(value < 2n ** 250n, true);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"outDir": "lib/esm",
|
"outDir": "esm",
|
||||||
"target": "es2020",
|
"target": "es2020",
|
||||||
"module": "es6",
|
"module": "es6",
|
||||||
"moduleResolution": "node16",
|
"moduleResolution": "node16",
|
||||||
"noUnusedLocals": true,
|
"noUnusedLocals": true,
|
||||||
|
"sourceMap": true,
|
||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
"paths": {
|
"paths": {
|
||||||
"@noble/hashes/crypto": [ "src/crypto" ]
|
"@noble/hashes/crypto": [ "src/crypto" ]
|
||||||
|
|||||||
@@ -2,9 +2,11 @@
|
|||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"declaration": true,
|
"declaration": true,
|
||||||
"outDir": "lib",
|
"declarationMap": true,
|
||||||
|
"outDir": ".",
|
||||||
"target": "es2020",
|
"target": "es2020",
|
||||||
"lib": ["es2020"], // Set explicitly to remove DOM
|
"lib": ["es2020"], // Set explicitly to remove DOM
|
||||||
|
"sourceMap": true,
|
||||||
"module": "commonjs",
|
"module": "commonjs",
|
||||||
"moduleResolution": "node",
|
"moduleResolution": "node",
|
||||||
"noUnusedLocals": true,
|
"noUnusedLocals": true,
|
||||||
|
|||||||
Reference in New Issue
Block a user