Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
debb9d9709 | ||
|
|
d2c6459756 | ||
|
|
47533b6336 | ||
|
|
00b73b68d3 | ||
|
|
cef4b52d12 | ||
|
|
47ce547dcf | ||
|
|
e2a7594eae | ||
|
|
823149ecd9 | ||
|
|
e57aec63d8 | ||
|
|
837aca98c9 | ||
|
|
dbb16b0e5e | ||
|
|
e14af67254 | ||
|
|
4780850748 | ||
|
|
3374a70f47 |
80
README.md
80
README.md
@@ -22,8 +22,7 @@ Package consists of two parts:
|
|||||||
|
|
||||||
Curves incorporate work from previous noble packages
|
Curves incorporate work from previous 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)),
|
||||||
[bls12-381](https://github.com/paulmillr/noble-bls12-381)),
|
|
||||||
which had security audits and were developed from 2019 to 2022.
|
which had security audits and were developed from 2019 to 2022.
|
||||||
Check out [Upgrading](#upgrading) section if you've used them before.
|
Check out [Upgrading](#upgrading) section if you've used them before.
|
||||||
|
|
||||||
@@ -31,14 +30,14 @@ Check out [Upgrading](#upgrading) section if you've used them before.
|
|||||||
|
|
||||||
> **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
|
- 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
|
||||||
@@ -48,23 +47,7 @@ Use NPM in node.js / browser, or include single file from
|
|||||||
|
|
||||||
> 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.
|
The library does not have an entry point. It allows you to select specific primitives and drop everything else. If you only want to use secp256k1, just use the library with rollup or other bundlers. This is done to make your bundles tiny. All curves:
|
||||||
|
|
||||||
```ts
|
|
||||||
// Common.js and ECMAScript Modules (ESM)
|
|
||||||
import { secp256k1 } from '@noble/curves/secp256k1';
|
|
||||||
|
|
||||||
const key = secp256k1.utils.randomPrivateKey();
|
|
||||||
const pub = secp256k1.getPublicKey(key);
|
|
||||||
const msg = new Uint8Array(32).fill(1);
|
|
||||||
const sig = secp256k1.sign(msg, key);
|
|
||||||
secp256k1.verify(sig, msg, pub) === true;
|
|
||||||
sig.recoverPublicKey(msg) === pub;
|
|
||||||
const someonesPub = secp256k1.getPublicKey(secp256k1.utils.randomPrivateKey());
|
|
||||||
const shared = secp256k1.getSharedSecret(key, someonesPub);
|
|
||||||
```
|
|
||||||
|
|
||||||
All curves:
|
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
import { secp256k1 } from '@noble/curves/secp256k1';
|
import { secp256k1 } from '@noble/curves/secp256k1';
|
||||||
@@ -80,7 +63,25 @@ 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.
|
Every curve can be used in the following way:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { secp256k1 } from '@noble/curves/secp256k1'; // Common.js and ECMAScript Modules (ESM)
|
||||||
|
|
||||||
|
const key = secp256k1.utils.randomPrivateKey();
|
||||||
|
const pub = secp256k1.getPublicKey(key);
|
||||||
|
const msg = new Uint8Array(32).fill(1);
|
||||||
|
const sig = secp256k1.sign(msg, key);
|
||||||
|
// weierstrass curves should use extraEntropy: https://moderncrypto.org/mail-archive/curves/2017/000925.html
|
||||||
|
const sigImprovedSecurity = secp256k1.sign(msg, key, { extraEntropy: true });
|
||||||
|
secp256k1.verify(sig, msg, pub) === true;
|
||||||
|
// secp, p*, pasta curves allow pub recovery
|
||||||
|
sig.recoverPublicKey(msg) === pub;
|
||||||
|
const someonesPub = secp256k1.getPublicKey(secp256k1.utils.randomPrivateKey());
|
||||||
|
const shared = secp256k1.getSharedSecret(key, someonesPub);
|
||||||
|
```
|
||||||
|
|
||||||
|
To define a custom curve, check out docs below.
|
||||||
|
|
||||||
## API
|
## API
|
||||||
|
|
||||||
@@ -109,17 +110,20 @@ import * as utils from '@noble/curves/abstract/utils';
|
|||||||
They allow to define a new curve in a few lines of code:
|
They allow to define a new curve in a few lines of code:
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
import { Fp } from '@noble/curves/abstract/modular';
|
import { Field } from '@noble/curves/abstract/modular';
|
||||||
import { weierstrass } from '@noble/curves/abstract/weierstrass';
|
import { weierstrass } from '@noble/curves/abstract/weierstrass';
|
||||||
import { hmac } from '@noble/hashes/hmac';
|
import { hmac } from '@noble/hashes/hmac';
|
||||||
import { sha256 } from '@noble/hashes/sha256';
|
import { sha256 } from '@noble/hashes/sha256';
|
||||||
import { concatBytes, randomBytes } from '@noble/hashes/utils';
|
import { concatBytes, randomBytes } from '@noble/hashes/utils';
|
||||||
|
|
||||||
const secp256k1 = weierstrass({
|
// secq (NOT secp) 256k1: cycle of secp256k1 with Fp/N flipped.
|
||||||
|
// https://zcash.github.io/halo2/background/curves.html#cycles-of-curves
|
||||||
|
// https://personaelabs.org/posts/spartan-ecdsa
|
||||||
|
const secq256k1 = weierstrass({
|
||||||
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,
|
||||||
@@ -331,19 +335,26 @@ The module allows to hash arbitrary strings to elliptic curve points.
|
|||||||
|
|
||||||
```ts
|
```ts
|
||||||
function expand_message_xmd(
|
function expand_message_xmd(
|
||||||
msg: Uint8Array, DST: Uint8Array, lenInBytes: number, H: CHash
|
msg: Uint8Array,
|
||||||
|
DST: Uint8Array,
|
||||||
|
lenInBytes: number,
|
||||||
|
H: CHash
|
||||||
): Uint8Array;
|
): Uint8Array;
|
||||||
function expand_message_xof(
|
function expand_message_xof(
|
||||||
msg: Uint8Array, DST: Uint8Array, lenInBytes: number, k: number, H: CHash
|
msg: Uint8Array,
|
||||||
|
DST: Uint8Array,
|
||||||
|
lenInBytes: number,
|
||||||
|
k: number,
|
||||||
|
H: CHash
|
||||||
): Uint8Array;
|
): 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[][];
|
||||||
@@ -532,6 +543,7 @@ Upgrading from @noble/ed25519 1.7:
|
|||||||
- `Point` was removed: use `ExtendedPoint` in xyzt coordinates
|
- `Point` was removed: use `ExtendedPoint` in xyzt coordinates
|
||||||
- `Signature` was removed
|
- `Signature` was removed
|
||||||
- `getSharedSecret` was removed: use separate x25519 sub-module
|
- `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
|
||||||
|
|
||||||
|
|||||||
179
package-lock.json
generated
Normal file
179
package-lock.json
generated
Normal file
@@ -0,0 +1,179 @@
|
|||||||
|
{
|
||||||
|
"name": "@noble/curves",
|
||||||
|
"version": "0.6.2",
|
||||||
|
"lockfileVersion": 3,
|
||||||
|
"requires": true,
|
||||||
|
"packages": {
|
||||||
|
"": {
|
||||||
|
"name": "@noble/curves",
|
||||||
|
"version": "0.6.2",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "individual",
|
||||||
|
"url": "https://paulmillr.com/funding/"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@noble/hashes": "1.2.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@scure/base": "~1.1.1",
|
||||||
|
"@scure/bip32": "~1.1.5",
|
||||||
|
"@scure/bip39": "~1.1.1",
|
||||||
|
"@types/node": "18.11.3",
|
||||||
|
"fast-check": "3.0.0",
|
||||||
|
"micro-bmark": "0.3.0",
|
||||||
|
"micro-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.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/micro-bmark/-/micro-bmark-0.3.0.tgz",
|
||||||
|
"integrity": "sha512-rYu+AtUq8lC3zPCoxkOOtwhgJoMpCDGe0/BXUCkj6+H9f/U/TunH/n/qkN98yh04dCCtDV8Aj9uYO3+DKxYrcw==",
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
10
package.json
10
package.json
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@noble/curves",
|
"name": "@noble/curves",
|
||||||
"version": "0.6.1",
|
"version": "0.6.3",
|
||||||
"description": "Minimal, auditable JS implementation of elliptic curve cryptography",
|
"description": "Minimal, auditable JS implementation of elliptic curve cryptography",
|
||||||
"files": [
|
"files": [
|
||||||
"lib"
|
"lib"
|
||||||
@@ -21,19 +21,17 @@
|
|||||||
},
|
},
|
||||||
"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/base": "~1.1.1",
|
"@scure/base": "~1.1.1",
|
||||||
"@scure/bip32": "~1.1.1",
|
"@scure/bip32": "~1.1.5",
|
||||||
"@scure/bip39": "~1.1.0",
|
"@scure/bip39": "~1.1.1",
|
||||||
"@types/node": "18.11.3",
|
"@types/node": "18.11.3",
|
||||||
"fast-check": "3.0.0",
|
"fast-check": "3.0.0",
|
||||||
"micro-bmark": "0.3.0",
|
"micro-bmark": "0.3.0",
|
||||||
"micro-should": "0.4.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",
|
||||||
|
|||||||
@@ -25,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();
|
||||||
@@ -54,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);
|
||||||
@@ -76,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);
|
||||||
|
|
||||||
|
|||||||
@@ -171,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 {
|
||||||
|
|||||||
@@ -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, Hex, concatBytes, ensureBytes, 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,17 +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
|
// Global symbols in both browsers and Node.js since v11
|
||||||
// See https://github.com/microsoft/TypeScript/issues/31535
|
// See https://github.com/microsoft/TypeScript/issues/31535
|
||||||
declare const TextEncoder: any;
|
declare const TextEncoder: any;
|
||||||
@@ -195,20 +174,26 @@ 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 hashToCurve<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
|
||||||
|
|||||||
@@ -150,7 +150,8 @@ export function montgomery(curveDef: CurveType): CurveFn {
|
|||||||
// 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
|
||||||
const u = ensureBytes(uEnc, montgomeryBytes);
|
const u = ensureBytes(uEnc, montgomeryBytes);
|
||||||
u[fieldLen - 1] &= 127; // 0b0111_1111
|
// 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 {
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ 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('string expected, got ' + typeof hex);
|
if (typeof hex !== 'string') throw new Error('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
|
||||||
@@ -114,31 +114,47 @@ export const bitSet = (n: bigint, pos: number, value: boolean) =>
|
|||||||
// 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;
|
||||||
|
|
||||||
type ValMap = Record<string, string>;
|
const validatorFns = {
|
||||||
export function validateObject(object: object, validators: ValMap, optValidators: ValMap = {}) {
|
bigint: (val: any) => typeof val === 'bigint',
|
||||||
const validatorFns: Record<string, (val: any) => boolean> = {
|
function: (val: any) => typeof val === 'function',
|
||||||
bigint: (val) => typeof val === 'bigint',
|
boolean: (val: any) => typeof val === 'boolean',
|
||||||
function: (val) => typeof val === 'function',
|
string: (val: any) => typeof val === 'string',
|
||||||
boolean: (val) => typeof val === 'boolean',
|
isSafeInteger: (val: any) => Number.isSafeInteger(val),
|
||||||
string: (val) => typeof val === 'string',
|
array: (val: any) => Array.isArray(val),
|
||||||
isSafeInteger: (val) => Number.isSafeInteger(val),
|
field: (val: any, object: any) => (object as any).Fp.isValid(val),
|
||||||
array: (val) => Array.isArray(val),
|
hash: (val: any) => typeof val === 'function' && Number.isSafeInteger(val.outputLen),
|
||||||
field: (val) => (object as any).Fp.isValid(val),
|
} as const;
|
||||||
hash: (val) => typeof val === 'function' && Number.isSafeInteger(val.outputLen),
|
type Validator = keyof typeof validatorFns;
|
||||||
};
|
type ValMap<T extends Record<string, any>> = { [K in keyof T]?: Validator };
|
||||||
// type Key = keyof typeof validators;
|
// type Record<K extends string | number | symbol, T> = { [P in K]: T; }
|
||||||
const checkField = (fieldName: string, type: string, isOptional: boolean) => {
|
|
||||||
|
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];
|
const checkVal = validatorFns[type];
|
||||||
if (typeof checkVal !== 'function')
|
if (typeof checkVal !== 'function')
|
||||||
throw new Error(`Invalid validator "${type}", expected function`);
|
throw new Error(`Invalid validator "${type}", expected function`);
|
||||||
|
|
||||||
const val = object[fieldName as keyof typeof object];
|
const val = object[fieldName as keyof typeof object];
|
||||||
if (isOptional && val === undefined) return;
|
if (isOptional && val === undefined) return;
|
||||||
if (!checkVal(val)) {
|
if (!checkVal(val, object)) {
|
||||||
throw new Error(`Invalid param ${fieldName}=${val} (${typeof val}), expected ${type}`);
|
throw new Error(
|
||||||
|
`Invalid param ${String(fieldName)}=${val} (${typeof val}), expected ${type}`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
for (let [fieldName, type] of Object.entries(validators)) checkField(fieldName, type, false);
|
for (const [fieldName, type] of Object.entries(validators)) checkField(fieldName, type!, false);
|
||||||
for (let [fieldName, type] of Object.entries(optValidators)) checkField(fieldName, type, true);
|
for (const [fieldName, type] of Object.entries(optValidators)) checkField(fieldName, type!, true);
|
||||||
return object;
|
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' });
|
||||||
|
|||||||
@@ -100,6 +100,7 @@ function validatePointOpts<T>(curve: CurvePointsType<T>) {
|
|||||||
wrapPrivateKey: 'boolean',
|
wrapPrivateKey: 'boolean',
|
||||||
isTorsionFree: 'function',
|
isTorsionFree: 'function',
|
||||||
clearCofactor: 'function',
|
clearCofactor: 'function',
|
||||||
|
allowInfinityPoint: 'boolean',
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
const { endo, Fp, a } = opts;
|
const { endo, Fp, a } = opts;
|
||||||
@@ -240,6 +241,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');
|
||||||
|
|||||||
@@ -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))
|
||||||
|
);
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -521,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);
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -561,12 +588,23 @@ 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(''));
|
||||||
@@ -594,6 +632,52 @@ for (const name in CURVES) {
|
|||||||
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', () => {
|
||||||
@@ -651,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) {
|
||||||
|
|||||||
@@ -656,6 +656,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) {
|
||||||
|
|||||||
Reference in New Issue
Block a user