52 Commits
1.0.0 ... 1.1.0

Author SHA1 Message Date
Paul Miller
62e806cfaf Release 1.1.0. 2023-06-03 14:31:43 +02:00
Paul Miller
6a72821185 readme 2023-06-03 14:27:05 +02:00
Paul Miller
8cee1f559f Bump noble-hashes to 1.3.1 2023-06-03 14:23:18 +02:00
Paul Miller
6f10632ac0 Add build directory that allows to test tree-shaking 2023-06-02 17:16:56 +02:00
Paul Miller
b281167e8d Fix utf8ToBytes in firefox extension context 2023-06-02 15:57:46 +02:00
Paul Miller
c6b4aadafb utils: harmonize with noble-hashes 2023-06-02 15:35:37 +02:00
Paul Miller
aade023e48 pkg.json: Adjust funding field 2023-05-27 16:10:58 +02:00
Paul Miller
2e04d96ce9 readme 2023-05-26 13:27:41 +02:00
Paul Miller
79dd7d3426 readme 2023-05-20 12:34:51 +02:00
Paul Miller
ff5b231e31 secp256k1 & other implementations: reduce bundle size by 20% by using PURE.
PURE annotation helps bundlers during tree-shaking and eliminates dead code.

* secp256k1: 75.4kb => 62.3kb

* ed25519: 67.5kb => 51.1kb

* ed448: 55.1kb => 44.0kb

* p256: 67.8kb => 59.8kb

* p384: 75.4kb => 67.4kb

* p521: 75.8kb => 67.8kb
2023-05-20 10:49:50 +02:00
Paul Miller
648fd2cc07 benchmark: curves should bench ed25519 first 2023-05-19 09:58:30 +02:00
Paul Miller
f67134ca86 benchmark: add msm to bls 2023-05-19 09:58:13 +02:00
Paul Miller
6d0678b076 readme 2023-05-14 06:54:17 +02:00
Paul Miller
53ebde19ea readme 2023-05-14 06:48:22 +02:00
Paul Miller
a7755332c8 readme 2023-05-14 06:40:09 +02:00
Paul Miller
5f0007ab24 readme 2023-05-13 01:31:55 +02:00
Paul Miller
1ee5a5c07f CI: auto-publish to NPM on GH release 2023-05-12 20:53:24 +02:00
Paul Miller
708c0e14d5 readme 2023-05-12 19:03:17 +02:00
Paul Miller
624d7c9910 Merge pull request #46 from sublimator/nd-sort-few-typos-things-editor-nagging-about-2023-05-09
docs(modular): sort few typos/things editor nagging about
2023-05-09 18:01:35 +02:00
Nicholas Dudfield
665ef2dd93 docs(modular): sort few typos/things editor nagging about 2023-05-09 07:35:33 +07:00
Paul Miller
acc1f26acf readme 2023-05-07 23:32:41 +02:00
Paul Miller
3c4a25263e readme 2023-05-06 22:05:33 +02:00
Paul Miller
e887d516ab readme 2023-05-06 21:20:38 +02:00
Paul Miller
90e87f7ab1 weierstrass: adjust SWUFpSqrtRatio to not use exp operator 2023-05-06 14:38:53 +02:00
Paul Miller
5edafbac97 Merge pull request #42 from sublimator/patch-1
ed25519: fix ristrettoHash size typo in hashToCurve
2023-05-05 17:48:08 +02:00
Nicholas Dudfield
554c94509e ed25519: fix ristrettoHash size typo in hashToCurve 2023-05-05 18:17:40 +07:00
Paul Miller
7c11a021c0 Drop v16 from ci, it will be out in 4 months 2023-05-05 03:54:16 +02:00
Paul Miller
531b6a3a48 Adjust CI 2023-05-05 03:53:35 +02:00
Paul Miller
fb5cd9df39 README 2023-05-05 03:52:49 +02:00
Paul Miller
53a6d636d4 Merge pull request #38 from legobeat/ci-node-version-matrix
ci: test nodejs v16/v18/v20
2023-05-05 03:50:00 +02:00
Paul Miller
42de620010 edwards: make zip215 false Strongly Binding Signature (SBS) secure. gh-40 2023-05-05 03:37:13 +02:00
Paul Miller
6621053c7d edwards: ensure Point.fromHex fails when x=0 and first x bit is 1. gh-40 2023-05-05 01:39:53 +02:00
Paul Miller
9bee88888f weierstrass: improve return type of sign(). Clarify comments. 2023-05-03 18:28:35 +02:00
legobt
103ba5f0a7 ci: test nodejs v16/v18/v20 2023-05-02 11:59:54 +09:00
Paul Miller
d5de5d2659 README: add more projects using curves 2023-04-28 02:46:07 +02:00
Paul Miller
217cf8c654 readme: more resources 2023-04-27 01:58:29 +02:00
Paul Miller
8e307d8f89 readme 2023-04-27 01:01:42 +02:00
Paul Miller
8c0018d57f readme 2023-04-27 00:54:41 +02:00
Paul Miller
ca7f202839 Add secp256k1 compatibility layer URL to readme 2023-04-27 00:16:29 +02:00
Paul Miller
816077ac0a README 2023-04-24 13:00:43 +02:00
Paul Miller
bc03a07043 readme 2023-04-23 20:31:29 +02:00
Paul Miller
63653255e1 ed448: rename to edwardsToMontgomeryPub 2023-04-23 20:29:17 +02:00
Paul Miller
895ee3a1a4 bls: refactor slightly 2023-04-23 20:29:03 +02:00
Paul Miller
16b31b9087 edwards: use bitmask instead of exp 2023-04-23 20:28:47 +02:00
Paul Miller
213796db4b ed25519: rename to edwardsToMontgomeryPub 2023-04-23 20:28:28 +02:00
Paul Miller
049d3bce54 CI: node.js 20 2023-04-23 20:17:45 +02:00
Paul Miller
b2a04c2393 Merge pull request #32 from mirceanis/31-fix-edwardsToMontgomery
ed25519: fix edwardsToMontgomery formula; implement edwardsToMontgomeryPriv
2023-04-23 20:16:22 +02:00
Paul Miller
cb5e9a6e96 Update benchmarks 2023-04-22 03:20:11 +02:00
Paul Miller
36af62357f test: adjust ed and secp tests a bit 2023-04-22 02:24:41 +02:00
Mircea Nistor
88291eba33 ed25519: fix edwardsToMontgomery formula; implement edwardsToMontgomeryPriv; add tests 2023-04-20 13:37:21 +02:00
Paul Miller
848a1b0226 nist tests: add endomorphism test 2023-04-14 19:53:20 +02:00
Paul Miller
972e549dde bls: no bigint literals 2023-04-13 17:18:39 +02:00
28 changed files with 753 additions and 432 deletions

View File

@@ -3,15 +3,18 @@ name: Node CI
on: [push, pull_request] on: [push, pull_request]
jobs: jobs:
test: test:
name: v18 @ ubuntu-latest name: v${{ matrix.node }} @ ubuntu-latest
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy:
matrix:
node: [18, 20]
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- name: Use Node.js ${{ matrix.node }} - name: Use Node.js ${{ matrix.node }}
uses: actions/setup-node@v3 uses: actions/setup-node@v3
with: with:
node-version: 18 node-version: ${{ matrix.node }}
- run: npm install - run: npm install
- run: npm run build --if-present - run: npm run build --if-present
- run: npm run lint --if-present
- run: npm test - run: npm test
- run: npm run lint --if-present

23
.github/workflows/publish-npm.yml vendored Normal file
View File

@@ -0,0 +1,23 @@
name: Publish Package to npm
on:
release:
types: [created]
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write
steps:
- uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3
- uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c # v3
with:
node-version: 20
registry-url: 'https://registry.npmjs.org'
cache: npm
- run: npm install -g npm
- run: npm ci
- run: npm run build
- run: npm publish --provenance --access public
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_PUBLISH_TOKEN }}

269
README.md
View File

@@ -8,6 +8,7 @@ Audited & minimal JS implementation of elliptic curve cryptography.
- 🔍 Unique tests ensure correctness: property-based, cross-library and Wycheproof vectors, fuzzing - 🔍 Unique tests ensure correctness: property-based, cross-library and Wycheproof vectors, fuzzing
- ➰ 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
- 🔖 SUF-CMA and SBS (non-repudiation) for ed25519, ed448 and others
- #⃣ Hash-to-curve - #⃣ Hash-to-curve
for encoding or hashing an arbitrary string to an elliptic curve point for encoding or hashing an arbitrary string to an elliptic curve point
- 🧜‍♂️ Poseidon ZK-friendly hash - 🧜‍♂️ Poseidon ZK-friendly hash
@@ -31,41 +32,34 @@ packages. See [Resources](#resources) for articles and real-world software that
## Usage ## Usage
Browser, deno and node.js are supported:
> npm install @noble/curves > npm install @noble/curves
For [Deno](https://deno.land), use it with We support all major platforms and runtimes.
[npm specifier](https://deno.land/manual@v1.28.0/node/npm_specifiers). For [Deno](https://deno.land), ensure to use [npm specifier](https://deno.land/manual@v1.28.0/node/npm_specifiers).
In browser, you could also include the single file from For React Native, you may need a [polyfill for crypto.getRandomValues](https://github.com/LinusU/react-native-get-random-values).
[GitHub's releases page](https://github.com/paulmillr/noble-curves/releases). If you don't like NPM, a standalone [noble-curves.js](https://github.com/paulmillr/noble-curves/releases) is also available.
The library is tree-shaking-friendly and does NOT expose root entry point as The library is tree-shaking-friendly and does not expose root entry point as
`import c from '@noble/curves'`. Instead, you need to import specific primitives. `@noble/curves`. Instead, you need to import specific primitives.
This is done to ensure small size of your apps. This is done to ensure small size of your apps.
Package consists of two parts: The package consists of two parts:
1. [Implementations](#implementations), utilizing one dependency [noble-hashes](https://github.com/paulmillr/noble-hashes), * [Implementations](#implementations), utilizing one dependency [noble-hashes](https://github.com/paulmillr/noble-hashes),
providing ready-to-use: providing ready-to-use:
- NIST curves secp256r1 / p256, secp384r1 / p384, secp521r1 / p521 - NIST curves secp256r1 / p256, secp384r1 / p384, secp521r1 / p521
- SECG curve secp256k1 - SECG curve secp256k1
- ed25519 / curve25519 / x25519 / ristretto255, - ed25519 / curve25519 / x25519 / ristretto255, edwards448 / curve448 / x448
edwards448 / curve448 / x448
implementing
[RFC7748](https://www.rfc-editor.org/rfc/rfc7748) /
[RFC8032](https://www.rfc-editor.org/rfc/rfc8032) /
[FIPS 186-5](https://csrc.nist.gov/publications/detail/fips/186/5/final) /
[ZIP215](https://zips.z.cash/zip-0215) standards
- pairing-friendly curves bls12-381, bn254 - pairing-friendly curves bls12-381, bn254
- [pasta](https://electriccoin.co/blog/the-pasta-curves-for-halo-2-and-beyond/) curves - [pasta](https://electriccoin.co/blog/the-pasta-curves-for-halo-2-and-beyond/) curves
2. [Abstract](#abstract-api), zero-dependency EC algorithms 2. [Abstract](#abstract-api), zero-dependency elliptic curve algorithms
### Implementations ### Implementations
Each curve can be used in the following way: #### Generic example for all curves, secp256k1
```ts ```ts
// Each curve has similar methods
import { secp256k1 } from '@noble/curves/secp256k1'; // ESM and Common.js import { secp256k1 } from '@noble/curves/secp256k1'; // ESM and Common.js
// import { secp256k1 } from 'npm:@noble/curves@1.2.0/secp256k1'; // Deno // import { secp256k1 } from 'npm:@noble/curves@1.2.0/secp256k1'; // Deno
const priv = secp256k1.utils.randomPrivateKey(); const priv = secp256k1.utils.randomPrivateKey();
@@ -79,7 +73,7 @@ const privHex = '46c930bc7bb4db7f55da20798697421b98c4175a52c630294d75a84b9c12623
const pub2 = secp256k1.getPublicKey(privHex); const pub2 = secp256k1.getPublicKey(privHex);
``` ```
All curves: #### All imports
```typescript ```typescript
import { secp256k1, schnorr } from '@noble/curves/secp256k1'; import { secp256k1, schnorr } from '@noble/curves/secp256k1';
@@ -94,7 +88,7 @@ import { bn254 } from '@noble/curves/bn254';
import { jubjub } from '@noble/curves/jubjub'; import { jubjub } from '@noble/curves/jubjub';
``` ```
Recovering public keys from weierstrass ECDSA signatures; using ECDH: #### ECDSA public key recovery & ECDH
```ts ```ts
// extraEntropy https://moderncrypto.org/mail-archive/curves/2017/000925.html // extraEntropy https://moderncrypto.org/mail-archive/curves/2017/000925.html
@@ -104,8 +98,7 @@ const someonesPub = secp256k1.getPublicKey(secp256k1.utils.randomPrivateKey());
const shared = secp256k1.getSharedSecret(priv, someonesPub); // ECDH const shared = secp256k1.getSharedSecret(priv, someonesPub); // ECDH
``` ```
Schnorr signatures over secp256k1 following #### Schnorr signatures over secp256k1 (BIP340)
[BIP340](https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki):
```ts ```ts
import { schnorr } from '@noble/curves/secp256k1'; import { schnorr } from '@noble/curves/secp256k1';
@@ -116,12 +109,7 @@ const sig = schnorr.sign(msg, priv);
const isValid = schnorr.verify(sig, msg, pub); const isValid = schnorr.verify(sig, msg, pub);
``` ```
ed25519 module has ed25519ctx / ed25519ph variants, #### ed25519, X25519, ristretto255
x25519 ECDH and [ristretto255](https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-ristretto255-decaf448).
Default `verify` behavior 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).
`zip215: false` option switches verification criteria to RFC8032 / FIPS 186-5.
```ts ```ts
import { ed25519 } from '@noble/curves/ed25519'; import { ed25519 } from '@noble/curves/ed25519';
@@ -131,7 +119,19 @@ const msg = new TextEncoder().encode('hello');
const sig = ed25519.sign(msg, priv); const sig = ed25519.sign(msg, priv);
ed25519.verify(sig, msg, pub); // Default mode: follows ZIP215 ed25519.verify(sig, msg, pub); // Default mode: follows ZIP215
ed25519.verify(sig, msg, pub, { zip215: false }); // RFC8032 / FIPS 186-5 ed25519.verify(sig, msg, pub, { zip215: false }); // RFC8032 / FIPS 186-5
```
Default `verify` behavior 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).
It has SUF-CMA (strong unforgeability under chosen message attacks).
`zip215: false` option switches verification criteria to strict
[RFC8032](https://www.rfc-editor.org/rfc/rfc8032) / [FIPS 186-5](https://csrc.nist.gov/publications/detail/fips/186/5/final)
and additionally provides non-repudiation with SBS [(Strongly Binding Signatures)](https://eprint.iacr.org/2020/1244).
X25519 follows [RFC7748](https://www.rfc-editor.org/rfc/rfc7748).
ristretto255 follows [irtf draft](https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-ristretto255-decaf448).
```ts
// Variants from RFC8032: with context, prehashed // Variants from RFC8032: with context, prehashed
import { ed25519ctx, ed25519ph } from '@noble/curves/ed25519'; import { ed25519ctx, ed25519ph } from '@noble/curves/ed25519';
@@ -141,11 +141,15 @@ const priv = 'a546e36bf0527c9d3b16154b82465edd62144c0ac1fc5a18506a2244ba449ac4';
const pub = 'e6db6867583030db3594c1a424b15f7c726624ec26b3353b10a903a6d0ab1c4c'; const pub = 'e6db6867583030db3594c1a424b15f7c726624ec26b3353b10a903a6d0ab1c4c';
x25519.getSharedSecret(priv, pub) === x25519.scalarMult(priv, pub); // aliases x25519.getSharedSecret(priv, pub) === x25519.scalarMult(priv, pub); // aliases
x25519.getPublicKey(priv) === x25519.scalarMultBase(priv); x25519.getPublicKey(priv) === x25519.scalarMultBase(priv);
x25519.getPublicKey(x25519.utils.randomPrivateKey());
// hash-to-curve // ed25519 => x25519 conversion
import { hashToCurve, encodeToCurve } from '@noble/curves/ed25519'; import { edwardsToMontgomeryPub, edwardsToMontgomeryPriv } from '@noble/curves/ed25519';
edwardsToMontgomeryPub(ed25519.getPublicKey(ed25519.utils.randomPrivateKey()));
edwardsToMontgomeryPriv(ed25519.utils.randomPrivateKey());
import { RistrettoPoint } from '@noble/curves/ed25519'; // hash-to-curve, ristretto255
import { hashToCurve, encodeToCurve, RistrettoPoint } from '@noble/curves/ed25519';
const rp = RistrettoPoint.fromHex( const rp = RistrettoPoint.fromHex(
'6a493210f7499cd17fecb510ae0cea23a110e8d5b901f8acadd3095c73a3b919' '6a493210f7499cd17fecb510ae0cea23a110e8d5b901f8acadd3095c73a3b919'
); );
@@ -153,19 +157,36 @@ RistrettoPoint.hashToCurve('Ristretto is traditionally a short shot of espresso
// also has add(), equals(), multiply(), toRawBytes() methods // also has add(), equals(), multiply(), toRawBytes() methods
``` ```
ed448 is similar: #### ed448, X448
```ts ```ts
import { ed448, ed448ph, ed448ctx, x448 } from '@noble/curves/ed448'; import { ed448 } from '@noble/curves/ed448';
import { hashToCurve, encodeToCurve } from '@noble/curves/ed448'; const priv = ed448.utils.randomPrivateKey();
ed448.getPublicKey(ed448.utils.randomPrivateKey()); const pub = ed448.getPublicKey(priv);
const msg = new TextEncoder().encode('whatsup');
const sig = ed448.sign(msg, priv);
ed448.verify(sig, msg, pub);
import { ed448ph, ed448ctx, x448, hashToCurve, encodeToCurve } from '@noble/curves/ed448';
x448.getSharedSecret(priv, pub) === x448.scalarMult(priv, pub); // aliases
x448.getPublicKey(priv) === x448.scalarMultBase(priv);
``` ```
Every curve has `CURVE` object that contains its parameters, field, and others: Same RFC7748 / RFC8032 are followed.
#### bls12-381
See [abstract/bls](#abstractbls-barreto-lynn-scott-curves).
#### Accessing a curve's variables
```ts ```ts
import { secp256k1 } from '@noble/curves/secp256k1'; // ESM and Common.js import { secp256k1 } from '@noble/curves/secp256k1';
console.log(secp256k1.CURVE.p, secp256k1.CURVE.n, secp256k1.CURVE.a, secp256k1.CURVE.b); // Every curve has `CURVE` object that contains its parameters, field, and others
console.log(secp256k1.CURVE.p); // field modulus
console.log(secp256k1.CURVE.n); // curve order
console.log(secp256k1.CURVE.a, secp256k1.CURVE.b); // equation params
console.log(secp256k1.CURVE.Gx, secp256k1.CURVE.Gy); // base point coordinates
``` ```
## Abstract API ## Abstract API
@@ -734,9 +755,9 @@ utils.equalBytes(Uint8Array.from([0xde]), Uint8Array.from([0xde]));
## Security ## Security
1. The library has been audited during Jan-Feb 2023 by an independent security firm [Trail of Bits](https://www.trailofbits.com): 1. The library has been audited in Feb 2023 by an independent security firm [Trail of Bits](https://www.trailofbits.com):
[PDF](https://github.com/trailofbits/publications/blob/master/reviews/2023-01-ryanshea-noblecurveslibrary-securityreview.pdf). [PDF](https://github.com/trailofbits/publications/blob/master/reviews/2023-01-ryanshea-noblecurveslibrary-securityreview.pdf).
The audit has been funded by Ryan Shea. Audit scope was abstract modules `curve`, `hash-to-curve`, `modular`, `poseidon`, `utils`, `weierstrass`, and top-level modules `_shortw_utils` and `secp256k1`. See [changes since audit](https://github.com/paulmillr/noble-curves/compare/0.7.3..main). The audit has been funded by [Ryan Shea](https://www.shea.io). Audit scope was abstract modules `curve`, `hash-to-curve`, `modular`, `poseidon`, `utils`, `weierstrass`, and top-level modules `_shortw_utils` and `secp256k1`. See [changes since audit](https://github.com/paulmillr/noble-curves/compare/0.7.3..main).
2. The library has been fuzzed by [Guido Vranken's cryptofuzz](https://github.com/guidovranken/cryptofuzz). You can run the fuzzer by yourself to check it. 2. The library has been fuzzed by [Guido Vranken's cryptofuzz](https://github.com/guidovranken/cryptofuzz). You can run the fuzzer by yourself to check it.
3. [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. 3. [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.
@@ -749,81 +770,89 @@ We consider infrastructure attacks like rogue NPM modules very important; that's
The packages are big, which makes it hard to audit their source code thoroughly and fully. 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. - They are only used if you clone the git repo and want to add some feature to it. End-users won't use them.
As for key generation, we're deferring to built-in
[crypto.getRandomValues](https://developer.mozilla.org/en-US/docs/Web/API/Crypto/getRandomValues)
which is considered cryptographically secure (CSPRNG).
## Speed ## Speed
Benchmark results on Apple M2 with node v19: Benchmark results on Apple M2 with node v20:
``` ```
secp256k1 secp256k1
init x 58 ops/sec @ 17ms/op init x 68 ops/sec @ 14ms/op
getPublicKey x 5,640 ops/sec @ 177μs/op getPublicKey x 6,750 ops/sec @ 148μs/op
sign x 4,471 ops/sec @ 223μs/op sign x 5,206 ops/sec @ 192μs/op
verify x 780 ops/sec @ 1ms/op verify x 880 ops/sec @ 1ms/op
getSharedSecret x 465 ops/sec @ 2ms/op getSharedSecret x 536 ops/sec @ 1ms/op
recoverPublicKey x 740 ops/sec @ 1ms/op recoverPublicKey x 852 ops/sec @ 1ms/op
schnorr.sign x 597 ops/sec @ 1ms/op schnorr.sign x 685 ops/sec @ 1ms/op
schnorr.verify x 775 ops/sec @ 1ms/op schnorr.verify x 908 ops/sec @ 1ms/op
P256 p256
init x 31 ops/sec @ 31ms/op init x 38 ops/sec @ 26ms/op
getPublicKey x 5,607 ops/sec @ 178μs/op getPublicKey x 6,530 ops/sec @ 153μs/op
sign x 4,583 ops/sec @ 218μs/op sign x 5,074 ops/sec @ 197μs/op
verify x 540 ops/sec @ 1ms/op verify x 626 ops/sec @ 1ms/op
P384 p384
init x 15 ops/sec @ 63ms/op init x 17 ops/sec @ 57ms/op
getPublicKey x 2,622 ops/sec @ 381μs/op getPublicKey x 2,883 ops/sec @ 346μs/op
sign x 2,106 ops/sec @ 474μs/op sign x 2,358 ops/sec @ 424μs/op
verify x 222 ops/sec @ 4ms/op verify x 245 ops/sec @ 4ms/op
P521 p521
init x 8 ops/sec @ 119ms/op init x 9 ops/sec @ 109ms/op
getPublicKey x 1,371 ops/sec @ 729μs/op getPublicKey x 1,516 ops/sec @ 659μs/op
sign x 1,164 ops/sec @ 858μs/op sign x 1,271 ops/sec @ 786μs/op
verify x 118 ops/sec @ 8ms/op verify x 123 ops/sec @ 8ms/op
ed25519 ed25519
init x 47 ops/sec @ 20ms/op init x 54 ops/sec @ 18ms/op
getPublicKey x 9,414 ops/sec @ 106μs/op getPublicKey x 10,269 ops/sec @ 97μs/op
sign x 4,516 ops/sec @ 221μs/op sign x 5,110 ops/sec @ 195μs/op
verify x 912 ops/sec @ 1ms/op verify x 1,049 ops/sec @ 952μs/op
ed448 ed448
init x 17 ops/sec @ 56ms/op init x 19 ops/sec @ 51ms/op
getPublicKey x 3,363 ops/sec @ 297μs/op getPublicKey x 3,775 ops/sec @ 264μs/op
sign x 1,615 ops/sec @ 619μs/op sign x 1,771 ops/sec @ 564μs/op
verify x 319 ops/sec @ 3ms/op verify x 351 ops/sec @ 2ms/op
ecdh ecdh
├─x25519 x 1,337 ops/sec @ 747μs/op ├─x25519 x 1,466 ops/sec @ 682μs/op
├─secp256k1 x 461 ops/sec @ 2ms/op ├─secp256k1 x 539 ops/sec @ 1ms/op
├─P256 x 441 ops/sec @ 2ms/op ├─p256 x 511 ops/sec @ 1ms/op
├─P384 x 179 ops/sec @ 5ms/op ├─p384 x 199 ops/sec @ 5ms/op
├─P521 x 93 ops/sec @ 10ms/op ├─p521 x 103 ops/sec @ 9ms/op
└─x448 x 496 ops/sec @ 2ms/op └─x448 x 548 ops/sec @ 1ms/op
bls12-381 bls12-381
init x 32 ops/sec @ 30ms/op init x 36 ops/sec @ 27ms/op
getPublicKey 1-bit x 858 ops/sec @ 1ms/op getPublicKey 1-bit x 973 ops/sec @ 1ms/op
getPublicKey x 858 ops/sec @ 1ms/op getPublicKey x 970 ops/sec @ 1ms/op
sign x 49 ops/sec @ 20ms/op sign x 55 ops/sec @ 17ms/op
verify x 34 ops/sec @ 28ms/op verify x 39 ops/sec @ 25ms/op
pairing x 94 ops/sec @ 10ms/op pairing x 106 ops/sec @ 9ms/op
aggregatePublicKeys/8 x 116 ops/sec @ 8ms/op aggregatePublicKeys/8 x 129 ops/sec @ 7ms/op
aggregatePublicKeys/32 x 31 ops/sec @ 31ms/op aggregatePublicKeys/32 x 34 ops/sec @ 28ms/op
aggregatePublicKeys/128 x 7 ops/sec @ 125ms/op aggregatePublicKeys/128 x 8 ops/sec @ 112ms/op
aggregateSignatures/8 x 45 ops/sec @ 22ms/op aggregatePublicKeys/512 x 2 ops/sec @ 446ms/op
aggregateSignatures/32 x 11 ops/sec @ 84ms/op aggregatePublicKeys/2048 x 0 ops/sec @ 1778ms/op
aggregateSignatures/128 x 3 ops/sec @ 332ms/opp aggregateSignatures/8 x 50 ops/sec @ 19ms/op
aggregateSignatures/32 x 13 ops/sec @ 74ms/op
aggregateSignatures/128 x 3 ops/sec @ 296ms/op
aggregateSignatures/512 x 0 ops/sec @ 1180ms/op
aggregateSignatures/2048 x 0 ops/sec @ 4715ms/op
hash-to-curve hash-to-curve
hash_to_field x 850,340 ops/sec @ 1μs/op hash_to_field x 91,600 ops/sec @ 10μs/op
secp256k1 x 2,143 ops/sec @ 466μs/op secp256k1 x 2,373 ops/sec @ 421μs/op
P256 x 3,861 ops/sec @ 258μs/op p256 x 4,310 ops/sec @ 231μs/op
P384 x 1,526 ops/sec @ 655μs/op p384 x 1,664 ops/sec @ 600μs/op
P521 x 748 ops/sec @ 1ms/op p521 x 807 ops/sec @ 1ms/op
ed25519 x 2,772 ops/sec @ 360μs/op ed25519 x 3,088 ops/sec @ 323μs/op
ed448 x 1,146 ops/sec @ 871μs/op ed448 x 1,247 ops/sec @ 801μs/op
``` ```
## Contributing & testing ## Contributing & testing
@@ -836,9 +865,11 @@ ed448 x 1,146 ops/sec @ 871μs/op
## Upgrading ## Upgrading
Previously, the library was split into single-feature packages Previously, the library was split into single-feature packages
noble-secp256k1 and noble-ed25519. curves can be thought as a continuation of their noble-secp256k1, noble-ed25519 and noble-bls12-381.
original work. The libraries now changed their direction towards providing
minimal 4kb implementations of cryptography and are not as feature-complete. Curves continue their original work. The single-feature packages changed their
direction towards providing minimal 4kb implementations of cryptography,
which means they have less features.
Upgrading from @noble/secp256k1 2.0 or @noble/ed25519 2.0: no changes, libraries are compatible. Upgrading from @noble/secp256k1 2.0 or @noble/ed25519 2.0: no changes, libraries are compatible.
@@ -878,6 +909,7 @@ Upgrading from [@noble/ed25519](https://github.com/paulmillr/noble-ed25519) 1.7:
- `utils` were split into `utils` (same api as in noble-curves) and - `utils` were split into `utils` (same api as in noble-curves) and
`etc` (`sha512Sync` and others) `etc` (`sha512Sync` and others)
- `getSharedSecret` was moved to `x25519` module - `getSharedSecret` was moved to `x25519` module
- `toX25519` has been moved to `edwardsToMontgomeryPub` and `edwardsToMontgomeryPriv` methods
Upgrading from [@noble/bls12-381](https://github.com/paulmillr/noble-bls12-381): Upgrading from [@noble/bls12-381](https://github.com/paulmillr/noble-bls12-381):
@@ -888,10 +920,13 @@ Upgrading from [@noble/bls12-381](https://github.com/paulmillr/noble-bls12-381):
## Resources ## Resources
Useful articles about the library or its primitives: Useful documentation and articles about the library or its primitives:
- [Learning fast elliptic-curve cryptography](https://paulmillr.com/posts/noble-secp256k1-fast-ecc/) - [Learning fast elliptic-curve cryptography](https://paulmillr.com/posts/noble-secp256k1-fast-ecc/)
- [Taming the many EdDSAs](https://csrc.nist.gov/csrc/media/Presentations/2023/crclub-2023-03-08/images-media/20230308-crypto-club-slides--taming-the-many-EdDSAs.pdf)
that describes concepts of Strong UnForgeability under Chosen Message Attacks and Strongly Binding Signatures
- Pairings and BLS - Pairings and BLS
- [BLS signatures for busy people](https://gist.github.com/paulmillr/18b802ad219b1aee34d773d08ec26ca2)
- [BLS12-381 for the rest of us](https://hackmd.io/@benjaminion/bls12-381) - [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) - [Key concepts of pairings](https://medium.com/@alonmuroch_65570/bls-signatures-part-2-key-concepts-of-pairings-27a8a9533d0c)
- Pairing over bls12-381: - Pairing over bls12-381:
@@ -900,17 +935,31 @@ Useful articles about the library or its primitives:
[part 3](https://research.nccgroup.com/2020/08/13/pairing-over-bls12-381-part-3-pairing/) [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/) - [Estimating the bit security of pairing-friendly curves](https://research.nccgroup.com/2022/02/03/estimating-the-bit-security-of-pairing-friendly-curves/)
Real-world software that uses curves: Online demos:
- [Elliptic Curve Calculator](https://paulmillr.com/noble): add / multiply points, sign messages
- [BLS threshold signatures](https://genthresh.com)
Projects using noble-curves:
- [Elliptic Curve Calculator](https://paulmillr.com/noble) online demo: add / multiply points, sign messages
- Signers for web3 projects:
[btc-signer](https://github.com/paulmillr/scure-btc-signer), [eth-signer](https://github.com/paulmillr/micro-eth-signer),
[sol-signer](https://github.com/paulmillr/micro-sol-signer) for Solana
- [scure-bip32](https://github.com/paulmillr/scure-bip32) and separate [bip32](https://github.com/bitcoinjs/bip32) HDkey libraries - [scure-bip32](https://github.com/paulmillr/scure-bip32) and separate [bip32](https://github.com/bitcoinjs/bip32) HDkey libraries
- Ethereum libraries:
- [ethereum-cryptography](https://github.com/ethereum/js-ethereum-cryptography)
- [@ethereumjs](https://github.com/ethereumjs/ethereumjs-monorepo)
- [micro-eth-signer](https://github.com/paulmillr/micro-eth-signer)
- [ethers](https://github.com/ethers-io/ethers.js) (old noble-secp256k1 for now)
- [viem.sh](https://viem.sh)
- [metamask's eth-sig-util](https://github.com/MetaMask/eth-sig-util)
- [gridplus lattice sdk](https://github.com/GridPlus/lattice-eth2-utils)
- Bitcoin libraries: [scure-btc-signer](https://github.com/paulmillr/scure-btc-signer)
- Solana libraries: [micro-sol-signer](https://github.com/paulmillr/micro-sol-signer), [solana-web3.js](https://github.com/solana-labs/solana-web3.js)
- [polkadot.js](https://github.com/polkadot-js/common), [micro-starknet](https://github.com/paulmillr/micro-starknet)
- [protonmail](https://github.com/ProtonMail/WebClients) (old noble-ed25519 for now)
- [did-jwt](https://github.com/decentralized-identity/did-jwt), [hpke-js](https://github.com/dajiaji/hpke-js), [nostr-tools](https://github.com/nbd-wtf/nostr-tools)
- [ed25519-keygen](https://github.com/paulmillr/ed25519-keygen) SSH, PGP, TOR key generation - [ed25519-keygen](https://github.com/paulmillr/ed25519-keygen) SSH, PGP, TOR key generation
- [micro-starknet](https://github.com/paulmillr/micro-starknet) stark-friendly elliptic curve algorithms. - [secp256k1 compatibility layer](https://github.com/ethereum/js-ethereum-cryptography/blob/2.0.0/src/secp256k1-compat.ts)
- BLS threshold sigs demo [genthresh.com](https://genthresh.com) for users who want to switch from secp256k1-node or tiny-secp256k1. Allows to see which methods map to corresponding noble code.
- BLS 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) - [BLS BBS signatures](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)
- [KZG trusted setup ceremony](https://github.com/dsrvlabs/czg-keremony) - [KZG trusted setup ceremony](https://github.com/dsrvlabs/czg-keremony)
## License ## License

View File

@@ -39,6 +39,21 @@ run(async () => {
await mark('sign', 50, () => bls.sign('09', priv)); await mark('sign', 50, () => bls.sign('09', priv));
await mark('verify', 50, () => bls.verify(sig, '09', pub)); await mark('verify', 50, () => bls.verify(sig, '09', pub));
await mark('pairing', 100, () => bls.pairing(p1, p2)); await mark('pairing', 100, () => bls.pairing(p1, p2));
const scalars1 = Array(4096).fill(0).map(i => 2n ** 235n - BigInt(i));
const scalars2 = Array(4096).fill(0).map(i => 2n ** 241n + BigInt(i));
const points = scalars1.map(s => bls.G1.ProjectivePoint.BASE.multiply(s));
await mark('MSM 4096 scalars x points', 1, () => {
// naive approach, not using multi-scalar-multiplication
let sum = bls.G1.ProjectivePoint.ZERO;
for (let i = 0; i < 4096; i++) {
const scalar = scalars2[i];
const G1 = points[i];
const mutliplied = G1.multiplyUnsafe(scalar);
sum = sum.add(mutliplied);
}
});
await mark('aggregatePublicKeys/8', 100, () => bls.aggregatePublicKeys(pubs.slice(0, 8))); await mark('aggregatePublicKeys/8', 100, () => bls.aggregatePublicKeys(pubs.slice(0, 8)));
await mark('aggregatePublicKeys/32', 50, () => bls.aggregatePublicKeys(pub32)); await mark('aggregatePublicKeys/32', 50, () => bls.aggregatePublicKeys(pub32));
await mark('aggregatePublicKeys/128', 20, () => bls.aggregatePublicKeys(pub128)); await mark('aggregatePublicKeys/128', 20, () => bls.aggregatePublicKeys(pub128));

View File

@@ -8,7 +8,7 @@ import { ed448 } from '../ed448.js';
run(async () => { run(async () => {
const RAM = false const RAM = false
for (let kv of Object.entries({ p256, p384, p521, ed25519, ed448 })) { for (let kv of Object.entries({ ed25519, ed448, p256, p384, p521 })) {
const [name, curve] = kv; const [name, curve] = kv;
console.log(); console.log();
console.log(`\x1b[36m${name}\x1b[0m`); console.log(`\x1b[36m${name}\x1b[0m`);

7
build/README.md Normal file
View File

@@ -0,0 +1,7 @@
# build
The directory is used to build a single file `noble-curves.js` which contains everything.
The output file uses iife wrapper and can be used in browsers as-is.
Don't use it unless you can't use NPM/ESM, which support tree shaking.

11
build/input.js Normal file
View File

@@ -0,0 +1,11 @@
import { bytesToHex, concatBytes, hexToBytes } from '@noble/curves/abstract/utils';
export { secp256k1 } from '@noble/curves/secp256k1';
export { ed25519, x25519 } from '@noble/curves/ed25519';
export { ed448, x448 } from '@noble/curves/ed448';
export { p256 } from '@noble/curves/p256';
export { p384 } from '@noble/curves/p384';
export { p521 } from '@noble/curves/p521';
export { bls12_381 } from '@noble/curves/bls12-381';
export const utils = { bytesToHex, concatBytes, hexToBytes };

18
build/package.json Normal file
View File

@@ -0,0 +1,18 @@
{
"name": "build",
"private": true,
"version": "1.0.0",
"description": "Used to build a single file",
"main": "input.js",
"keywords": [],
"type": "module",
"author": "",
"license": "MIT",
"devDependencies": {
"@noble/curves": "..",
"esbuild": "0.17.19"
},
"scripts": {
"build": "npx esbuild --bundle input.js --outfile=noble-curves.js --global-name=nobleCurves"
}
}

29
package-lock.json generated
View File

@@ -1,21 +1,15 @@
{ {
"name": "@noble/curves", "name": "@noble/curves",
"version": "1.0.0", "version": "1.1.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "@noble/curves", "name": "@noble/curves",
"version": "1.0.0", "version": "1.1.0",
"funding": [
{
"type": "individual",
"url": "https://paulmillr.com/funding/"
}
],
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@noble/hashes": "1.3.0" "@noble/hashes": "1.3.1"
}, },
"devDependencies": { "devDependencies": {
"fast-check": "3.0.0", "fast-check": "3.0.0",
@@ -23,18 +17,21 @@
"micro-should": "0.4.0", "micro-should": "0.4.0",
"prettier": "2.8.4", "prettier": "2.8.4",
"typescript": "5.0.2" "typescript": "5.0.2"
},
"funding": {
"url": "https://paulmillr.com/funding/"
} }
}, },
"node_modules/@noble/hashes": { "node_modules/@noble/hashes": {
"version": "1.3.0", "version": "1.3.1",
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.0.tgz", "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.1.tgz",
"integrity": "sha512-ilHEACi9DwqJB0pw7kv+Apvh50jiiSyR/cQ3y4W7lOR5mhvn/50FLUfsnfJz0BDZtl/RR16kXvptiv6q1msYZg==", "integrity": "sha512-EbqwksQwz9xDRGfDST86whPBgM65E0OH/pCgqW0GBVzO22bNE+NuIbeTb714+IfSjU3aRk47EUvXIb5bTsenKA==",
"funding": [ "engines": {
{ "node": ">= 16"
"type": "individual", },
"funding": {
"url": "https://paulmillr.com/funding/" "url": "https://paulmillr.com/funding/"
} }
]
}, },
"node_modules/fast-check": { "node_modules/fast-check": {
"version": "3.0.0", "version": "3.0.0",

View File

@@ -1,6 +1,6 @@
{ {
"name": "@noble/curves", "name": "@noble/curves",
"version": "1.0.0", "version": "1.1.0",
"description": "Audited & minimal JS implementation of elliptic curve cryptography", "description": "Audited & minimal JS implementation of elliptic curve cryptography",
"files": [ "files": [
"abstract", "abstract",
@@ -28,7 +28,7 @@
}, },
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@noble/hashes": "1.3.0" "@noble/hashes": "1.3.1"
}, },
"devDependencies": { "devDependencies": {
"fast-check": "3.0.0", "fast-check": "3.0.0",
@@ -164,6 +164,8 @@
"secp256k1", "secp256k1",
"ed25519", "ed25519",
"ed448", "ed448",
"x25519",
"ed25519",
"bls12-381", "bls12-381",
"bn254", "bn254",
"pasta", "pasta",
@@ -174,10 +176,5 @@
"eddsa", "eddsa",
"schnorr" "schnorr"
], ],
"funding": [ "funding": "https://paulmillr.com/funding/"
{
"type": "individual",
"url": "https://paulmillr.com/funding/"
}
]
} }

View File

@@ -75,8 +75,13 @@ export interface ExtPointConstructor extends GroupConstructor<ExtPointType> {
export type CurveFn = { export type CurveFn = {
CURVE: ReturnType<typeof validateOpts>; CURVE: ReturnType<typeof validateOpts>;
getPublicKey: (privateKey: Hex) => Uint8Array; getPublicKey: (privateKey: Hex) => Uint8Array;
sign: (message: Hex, privateKey: Hex) => Uint8Array; sign: (message: Hex, privateKey: Hex, options?: { context?: Hex }) => Uint8Array;
verify: (sig: Hex, message: Hex, publicKey: Hex) => boolean; verify: (
sig: Hex,
message: Hex,
publicKey: Hex,
options?: { context?: Hex; zip215: boolean }
) => boolean;
ExtendedPoint: ExtPointConstructor; ExtendedPoint: ExtPointConstructor;
utils: { utils: {
randomPrivateKey: () => Uint8Array; randomPrivateKey: () => Uint8Array;
@@ -102,7 +107,7 @@ export function twistedEdwards(curveDef: CurveType): CurveFn {
nByteLength, nByteLength,
h: cofactor, h: cofactor,
} = CURVE; } = CURVE;
const MASK = _2n ** BigInt(nByteLength * 8); const MASK = _2n << (BigInt(nByteLength * 8) - _1n);
const modP = Fp.create; // Function overrides const modP = Fp.create; // Function overrides
// sqrt(u/v) // sqrt(u/v)
@@ -379,7 +384,10 @@ export function twistedEdwards(curveDef: CurveType): CurveFn {
let { isValid, value: x } = uvRatio(u, v); // √(u/v) let { isValid, value: x } = uvRatio(u, v); // √(u/v)
if (!isValid) throw new Error('Point.fromHex: invalid y coordinate'); if (!isValid) throw new Error('Point.fromHex: invalid y coordinate');
const isXOdd = (x & _1n) === _1n; // There are 2 square roots. Use x_0 bit to select proper const isXOdd = (x & _1n) === _1n; // There are 2 square roots. Use x_0 bit to select proper
const isLastByteOdd = (lastByte & 0x80) !== 0; // if x=0 and x_0 = 1, fail const isLastByteOdd = (lastByte & 0x80) !== 0; // x_0, last bit
if (!zip215 && x === _0n && isLastByteOdd)
// if x=0 and x_0 = 1, fail
throw new Error('Point.fromHex: x=0 and x_0=1');
if (isLastByteOdd !== isXOdd) x = modP(-x); // if x_0 != x mod 2, set x = p-x if (isLastByteOdd !== isXOdd) x = modP(-x); // if x_0 != x mod 2, set x = p-x
return Point.fromAffine({ x, y }); return Point.fromAffine({ x, y });
} }
@@ -466,6 +474,7 @@ export function twistedEdwards(curveDef: CurveType): CurveFn {
} catch (error) { } catch (error) {
return false; return false;
} }
if (!zip215 && A.isSmallOrder()) return false;
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));

View File

@@ -22,10 +22,10 @@ export function mod(a: bigint, b: bigint): bigint {
return result >= _0n ? result : b + result; return result >= _0n ? result : b + result;
} }
/** /**
* Efficiently exponentiate num to power and do modular division. * Efficiently raise num to power and do modular division.
* Unsafe in some contexts: uses ladder, so can expose bigint bits. * Unsafe in some contexts: uses ladder, so can expose bigint bits.
* @example * @example
* powMod(2n, 6n, 11n) // 64n % 11n == 9n * pow(2n, 6n, 11n) // 64n % 11n == 9n
*/ */
// TODO: use field version && remove // TODO: use field version && remove
export function pow(num: bigint, power: bigint, modulo: bigint): bigint { export function pow(num: bigint, power: bigint, modulo: bigint): bigint {
@@ -55,7 +55,7 @@ export function invert(number: bigint, modulo: bigint): bigint {
if (number === _0n || modulo <= _0n) { if (number === _0n || modulo <= _0n) {
throw new Error(`invert: expected positive integers, got n=${number} mod=${modulo}`); throw new Error(`invert: expected positive integers, got n=${number} mod=${modulo}`);
} }
// Eucledian GCD https://brilliant.org/wiki/extended-euclidean-algorithm/ // Euclidean GCD https://brilliant.org/wiki/extended-euclidean-algorithm/
// Fermat's little theorem "CT-like" version inv(n) = n^(m-2) mod m is 30x slower. // Fermat's little theorem "CT-like" version inv(n) = n^(m-2) mod m is 30x slower.
let a = mod(number, modulo); let a = mod(number, modulo);
let b = modulo; let b = modulo;
@@ -198,10 +198,6 @@ export function FpSqrt(P: bigint) {
// Little-endian check for first LE bit (last BE bit); // Little-endian check for first LE bit (last BE bit);
export const isNegativeLE = (num: bigint, modulo: bigint) => (mod(num, modulo) & _1n) === _1n; export const isNegativeLE = (num: bigint, modulo: bigint) => (mod(num, modulo) & _1n) === _1n;
// Currently completly inconsistent naming:
// - readable: add, mul, sqr, sqrt, inv, div, pow, eq, sub
// - unreadable mess: addition, multiply, square, squareRoot, inversion, divide, power, equals, subtract
// Field is not always over prime, Fp2 for example has ORDER(q)=p^m // Field is not always over prime, Fp2 for example has ORDER(q)=p^m
export interface IField<T> { export interface IField<T> {
ORDER: bigint; ORDER: bigint;
@@ -406,10 +402,12 @@ export function FpSqrtEven<T>(Fp: IField<T>, elm: T) {
/** /**
* FIPS 186 B.4.1-compliant "constant-time" private key generation utility. * FIPS 186 B.4.1-compliant "constant-time" private key generation utility.
* Can take (n+8) or more bytes of uniform input e.g. from CSPRNG or KDF * Can take (n+8) or more bytes of uniform input e.g. from CSPRNG or KDF
* and convert them into private scalar, with the modulo bias being neglible. * and convert them into private scalar, with the modulo bias being negligible.
* Needs at least 40 bytes of input for 32-byte private key. * Needs at least 40 bytes of input for 32-byte private key.
* https://research.kudelskisecurity.com/2020/07/28/the-definitive-guide-to-modulo-bias-and-how-to-avoid-it/ * https://research.kudelskisecurity.com/2020/07/28/the-definitive-guide-to-modulo-bias-and-how-to-avoid-it/
* @param hash hash output from SHA3 or a similar function * @param hash hash output from SHA3 or a similar function
* @param groupOrder size of subgroup - (e.g. curveFn.CURVE.n)
* @param isLE interpret hash bytes as LE num
* @returns valid private scalar * @returns valid private scalar
*/ */
export function hashToPrivateScalar( export function hashToPrivateScalar(

View File

@@ -1,13 +1,14 @@
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */ /*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
// 100 lines of code in the file are duplicated from noble-hashes (utils).
// This is OK: `abstract` directory does not use noble-hashes.
// User may opt-in into using different hashing library. This way, noble-hashes
// won't be included into their bundle.
const _0n = BigInt(0); const _0n = BigInt(0);
const _1n = BigInt(1); const _1n = BigInt(1);
const _2n = BigInt(2); const _2n = BigInt(2);
const u8a = (a: any): a is Uint8Array => a instanceof Uint8Array; const u8a = (a: any): a is Uint8Array => a instanceof Uint8Array;
export type Hex = Uint8Array | string; // hex strings are accepted for simplicity
// We accept hex strings besides Uint8Array for simplicity export type PrivKey = Hex | bigint; // bigints are accepted to ease learning curve
export type Hex = Uint8Array | string;
// Very few implementations accept numbers, we do it to ease learning curve
export type PrivKey = Hex | bigint;
export type CHash = { export type CHash = {
(message: Uint8Array | string): Uint8Array; (message: Uint8Array | string): Uint8Array;
blockLen: number; blockLen: number;
@@ -17,6 +18,9 @@ export type CHash = {
export type FHash = (message: Uint8Array | string) => Uint8Array; 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'));
/**
* @example bytesToHex(Uint8Array.from([0xca, 0xfe, 0x01, 0x23])) // 'cafe0123'
*/
export function bytesToHex(bytes: Uint8Array): string { export function bytesToHex(bytes: Uint8Array): string {
if (!u8a(bytes)) throw new Error('Uint8Array expected'); if (!u8a(bytes)) throw new Error('Uint8Array expected');
// pre-caching improves the speed 6x // pre-caching improves the speed 6x
@@ -38,22 +42,25 @@ export function hexToNumber(hex: string): bigint {
return BigInt(hex === '' ? '0' : `0x${hex}`); return BigInt(hex === '' ? '0' : `0x${hex}`);
} }
// Caching slows it down 2-3x /**
* @example hexToBytes('cafe0123') // Uint8Array.from([0xca, 0xfe, 0x01, 0x23])
*/
export function hexToBytes(hex: string): Uint8Array { export function hexToBytes(hex: string): Uint8Array {
if (typeof hex !== 'string') throw new Error('hex string expected, got ' + typeof hex); if (typeof hex !== 'string') throw new Error('hex string expected, got ' + typeof hex);
if (hex.length % 2) throw new Error('hex string is invalid: unpadded ' + hex.length); const len = hex.length;
const array = new Uint8Array(hex.length / 2); if (len % 2) throw new Error('padded hex string expected, got unpadded hex of length ' + len);
const array = new Uint8Array(len / 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;
} }
// Big Endian // BE: Big Endian, LE: Little Endian
export function bytesToNumberBE(bytes: Uint8Array): bigint { export function bytesToNumberBE(bytes: Uint8Array): bigint {
return hexToNumber(bytesToHex(bytes)); return hexToNumber(bytesToHex(bytes));
} }
@@ -62,12 +69,26 @@ export function bytesToNumberLE(bytes: Uint8Array): bigint {
return hexToNumber(bytesToHex(Uint8Array.from(bytes).reverse())); return hexToNumber(bytesToHex(Uint8Array.from(bytes).reverse()));
} }
export const numberToBytesBE = (n: bigint, len: number) => export function numberToBytesBE(n: number | bigint, len: number): Uint8Array {
hexToBytes(n.toString(16).padStart(len * 2, '0')); return hexToBytes(n.toString(16).padStart(len * 2, '0'));
export const numberToBytesLE = (n: bigint, len: number) => numberToBytesBE(n, len).reverse(); }
// Returns variable number bytes (minimal bigint encoding?) export function numberToBytesLE(n: number | bigint, len: number): Uint8Array {
export const numberToVarBytesBE = (n: bigint) => hexToBytes(numberToHexUnpadded(n)); return numberToBytesBE(n, len).reverse();
}
// Unpadded, rarely used
export function numberToVarBytesBE(n: number | bigint): Uint8Array {
return hexToBytes(numberToHexUnpadded(n));
}
/**
* Takes hex string or Uint8Array, converts to Uint8Array.
* Validates output length.
* Will throw error for other types.
* @param title descriptive title for an error e.g. 'private key'
* @param hex hex string or Uint8Array
* @param expectedLength optional, will compare to result array's length
* @returns
*/
export function ensureBytes(title: string, hex: Hex, expectedLength?: number): Uint8Array { export function ensureBytes(title: string, hex: Hex, expectedLength?: number): Uint8Array {
let res: Uint8Array; let res: Uint8Array;
if (typeof hex === 'string') { if (typeof hex === 'string') {
@@ -89,11 +110,13 @@ export function ensureBytes(title: string, hex: Hex, expectedLength?: number): U
return res; return res;
} }
// Copies several Uint8Arrays into one. /**
export function concatBytes(...arrs: Uint8Array[]): Uint8Array { * Copies several Uint8Arrays into one.
const r = new Uint8Array(arrs.reduce((sum, a) => sum + a.length, 0)); */
export function concatBytes(...arrays: Uint8Array[]): Uint8Array {
const r = new Uint8Array(arrays.reduce((sum, a) => sum + a.length, 0));
let pad = 0; // walk through each item, ensure they have proper type let pad = 0; // walk through each item, ensure they have proper type
arrs.forEach((a) => { arrays.forEach((a) => {
if (!u8a(a)) throw new Error('Uint8Array expected'); if (!u8a(a)) throw new Error('Uint8Array expected');
r.set(a, pad); r.set(a, pad);
pad += a.length; pad += a.length;
@@ -111,29 +134,47 @@ export function equalBytes(b1: Uint8Array, b2: Uint8Array) {
// 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;
/**
* @example utf8ToBytes('abc') // new Uint8Array([97, 98, 99])
*/
export function utf8ToBytes(str: string): Uint8Array { export function utf8ToBytes(str: string): Uint8Array {
if (typeof str !== 'string') { if (typeof str !== 'string') throw new Error(`utf8ToBytes expected string, got ${typeof str}`);
throw new Error(`utf8ToBytes expected string, got ${typeof str}`); return new Uint8Array(new TextEncoder().encode(str)); // https://bugzil.la/1681809
}
return new TextEncoder().encode(str);
} }
// Bit operations // Bit operations
// Amount of bits inside bigint (Same as n.toString(2).length) /**
* Calculates amount of bits in a bigint.
* Same as `n.toString(2).length`
*/
export function bitLen(n: bigint) { export function bitLen(n: bigint) {
let len; let len;
for (len = 0; n > _0n; n >>= _1n, len += 1); for (len = 0; n > _0n; n >>= _1n, len += 1);
return len; return len;
} }
// Gets single bit at position. NOTE: first bit position is 0 (same as arrays)
// Same as !!+Array.from(n.toString(2)).reverse()[pos] /**
export const bitGet = (n: bigint, pos: number) => (n >> BigInt(pos)) & _1n; * Gets single bit at position.
// Sets single bit at position * NOTE: first bit position is 0 (same as arrays)
export const bitSet = (n: bigint, pos: number, value: boolean) => * Same as `!!+Array.from(n.toString(2)).reverse()[pos]`
n | ((value ? _1n : _0n) << BigInt(pos)); */
// Return mask for N bits (Same as BigInt(`0b${Array(i).fill('1').join('')}`)) export function bitGet(n: bigint, pos: number) {
// Not using ** operator with bigints for old engines. return (n >> BigInt(pos)) & _1n;
}
/**
* Sets single bit at position.
*/
export const bitSet = (n: bigint, pos: number, value: boolean) => {
return n | ((value ? _1n : _0n) << BigInt(pos));
};
/**
* Calculate mask for N bits. Not using ** operator with bigints because of old engines.
* Same as BigInt(`0b${Array(i).fill('1').join('')}`)
*/
export const bitMask = (n: number) => (_2n << BigInt(n - 1)) - _1n; export const bitMask = (n: number) => (_2n << BigInt(n - 1)) - _1n;
// DRBG // DRBG

View File

@@ -618,7 +618,7 @@ export interface SignatureType {
readonly s: bigint; readonly s: bigint;
readonly recovery?: number; readonly recovery?: number;
assertValidity(): void; assertValidity(): void;
addRecoveryBit(recovery: number): SignatureType; addRecoveryBit(recovery: number): RecoveredSignatureType;
hasHighS(): boolean; hasHighS(): boolean;
normalizeS(): SignatureType; normalizeS(): SignatureType;
recoverPublicKey(msgHash: Hex): ProjPointType<bigint>; recoverPublicKey(msgHash: Hex): ProjPointType<bigint>;
@@ -628,6 +628,9 @@ export interface SignatureType {
toDERRawBytes(isCompressed?: boolean): Uint8Array; toDERRawBytes(isCompressed?: boolean): Uint8Array;
toDERHex(isCompressed?: boolean): string; toDERHex(isCompressed?: boolean): string;
} }
export type RecoveredSignatureType = SignatureType & {
readonly recovery: number;
};
// Static methods // Static methods
export type SignatureConstructor = { export type SignatureConstructor = {
new (r: bigint, s: bigint): SignatureType; new (r: bigint, s: bigint): SignatureType;
@@ -669,7 +672,7 @@ export type CurveFn = {
CURVE: ReturnType<typeof validateOpts>; CURVE: ReturnType<typeof validateOpts>;
getPublicKey: (privateKey: PrivKey, isCompressed?: boolean) => Uint8Array; getPublicKey: (privateKey: PrivKey, isCompressed?: boolean) => Uint8Array;
getSharedSecret: (privateA: PrivKey, publicB: Hex, isCompressed?: boolean) => Uint8Array; getSharedSecret: (privateA: PrivKey, publicB: Hex, isCompressed?: boolean) => Uint8Array;
sign: (msgHash: Hex, privKey: PrivKey, opts?: SignOpts) => SignatureType; sign: (msgHash: Hex, privKey: PrivKey, opts?: SignOpts) => RecoveredSignatureType;
verify: (signature: Hex | SignatureLike, msgHash: Hex, publicKey: Hex, opts?: VerOpts) => boolean; verify: (signature: Hex | SignatureLike, msgHash: Hex, publicKey: Hex, opts?: VerOpts) => boolean;
ProjectivePoint: ProjConstructor<bigint>; ProjectivePoint: ProjConstructor<bigint>;
Signature: SignatureConstructor; Signature: SignatureConstructor;
@@ -782,8 +785,8 @@ export function weierstrass(curveDef: CurveType): CurveFn {
if (!isWithinCurveOrder(this.s)) throw new Error('s must be 0 < s < CURVE.n'); if (!isWithinCurveOrder(this.s)) throw new Error('s must be 0 < s < CURVE.n');
} }
addRecoveryBit(recovery: number) { addRecoveryBit(recovery: number): RecoveredSignature {
return new Signature(this.r, this.s, recovery); return new Signature(this.r, this.s, recovery) as RecoveredSignature;
} }
recoverPublicKey(msgHash: Hex): typeof Point.BASE { recoverPublicKey(msgHash: Hex): typeof Point.BASE {
@@ -828,6 +831,7 @@ export function weierstrass(curveDef: CurveType): CurveFn {
return numToNByteStr(this.r) + numToNByteStr(this.s); return numToNByteStr(this.r) + numToNByteStr(this.s);
} }
} }
type RecoveredSignature = Signature & { recovery: number };
const utils = { const utils = {
isValidPrivateKey(privateKey: PrivKey) { isValidPrivateKey(privateKey: PrivKey) {
@@ -965,7 +969,7 @@ export function weierstrass(curveDef: CurveType): CurveFn {
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!
// Converts signature params into point w r/s, checks result for validity. // Converts signature params into point w r/s, checks result for validity.
function k2sig(kBytes: Uint8Array): Signature | undefined { function k2sig(kBytes: Uint8Array): RecoveredSignature | undefined {
// RFC 6979 Section 3.2, step 3: k = bits2int(T) // RFC 6979 Section 3.2, step 3: k = bits2int(T)
const k = bits2int(kBytes); // Cannot use fields methods, since it is group element const k = bits2int(kBytes); // Cannot use fields methods, since it is group element
if (!isWithinCurveOrder(k)) return; // Important: all mod() calls here must be done over N if (!isWithinCurveOrder(k)) return; // Important: all mod() calls here must be done over N
@@ -984,7 +988,7 @@ export function weierstrass(curveDef: CurveType): CurveFn {
normS = normalizeS(s); // if lowS was passed, ensure s is always normS = normalizeS(s); // if lowS was passed, ensure s is always
recovery ^= 1; // // in the bottom half of N recovery ^= 1; // // in the bottom half of N
} }
return new Signature(r, normS, recovery); // use normS, not s return new Signature(r, normS, recovery) as RecoveredSignature; // use normS, not s
} }
return { seed, k2sig }; return { seed, k2sig };
} }
@@ -992,18 +996,22 @@ export function weierstrass(curveDef: CurveType): CurveFn {
const defaultVerOpts: VerOpts = { lowS: CURVE.lowS, prehash: false }; const defaultVerOpts: VerOpts = { lowS: CURVE.lowS, prehash: false };
/** /**
* Signs message hash (not message: you need to hash it by yourself). * Signs message hash with a private key.
* ``` * ```
* sign(m, d, k) where * sign(m, d, k) where
* (x, y) = G × k * (x, y) = G × k
* r = x mod n * r = x mod n
* s = (m + dr)/k mod n * s = (m + dr)/k mod n
* ``` * ```
* @param opts `lowS, extraEntropy, prehash` * @param msgHash NOT message. msg needs to be hashed to `msgHash`, or use `prehash`.
* @param privKey private key
* @param opts lowS for non-malleable sigs. extraEntropy for mixing randomness into k. prehash will hash first arg.
* @returns signature with recovery param
*/ */
function sign(msgHash: Hex, privKey: PrivKey, opts = defaultSigOpts): Signature { function sign(msgHash: Hex, privKey: PrivKey, opts = defaultSigOpts): RecoveredSignature {
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 drbg = ut.createHmacDrbg<Signature>(CURVE.hash.outputLen, CURVE.nByteLength, CURVE.hmac); const C = CURVE;
const drbg = ut.createHmacDrbg<RecoveredSignature>(C.hash.outputLen, C.nByteLength, C.hmac);
return drbg(seed, k2sig); // Steps B, C, D, E, F, G return drbg(seed, k2sig); // Steps B, C, D, E, F, G
} }
@@ -1084,20 +1092,29 @@ export function weierstrass(curveDef: CurveType): CurveFn {
}; };
} }
// Implementation of the Shallue and van de Woestijne method for any Weierstrass curve /**
// TODO: check if there is a way to merge this with uvRatio in Edwards && move to modular? * Implementation of the Shallue and van de Woestijne method for any weierstrass curve.
// b = True and y = sqrt(u / v) if (u / v) is square in F, and * TODO: check if there is a way to merge this with uvRatio in Edwards; move to modular.
// b = False and y = sqrt(Z * (u / v)) otherwise. * b = True and y = sqrt(u / v) if (u / v) is square in F, and
* b = False and y = sqrt(Z * (u / v)) otherwise.
* @param Fp
* @param Z
* @returns
*/
export function SWUFpSqrtRatio<T>(Fp: mod.IField<T>, Z: T) { export function SWUFpSqrtRatio<T>(Fp: mod.IField<T>, Z: T) {
// Generic implementation // Generic implementation
const q = Fp.ORDER; const q = Fp.ORDER;
let l = _0n; let l = _0n;
for (let o = q - _1n; o % _2n === _0n; o /= _2n) l += _1n; for (let o = q - _1n; o % _2n === _0n; o /= _2n) l += _1n;
const c1 = l; // 1. c1, the largest integer such that 2^c1 divides q - 1. const c1 = l; // 1. c1, the largest integer such that 2^c1 divides q - 1.
const c2 = (q - _1n) / _2n ** c1; // 2. c2 = (q - 1) / (2^c1) # Integer arithmetic // We need 2n ** c1 and 2n ** (c1-1). We can't use **; but we can use <<.
// 2n ** c1 == 2n << (c1-1)
const _2n_pow_c1_1 = _2n << (c1 - _1n - _1n);
const _2n_pow_c1 = _2n_pow_c1_1 * _2n;
const c2 = (q - _1n) / _2n_pow_c1; // 2. c2 = (q - 1) / (2^c1) # Integer arithmetic
const c3 = (c2 - _1n) / _2n; // 3. c3 = (c2 - 1) / 2 # Integer arithmetic const c3 = (c2 - _1n) / _2n; // 3. c3 = (c2 - 1) / 2 # Integer arithmetic
const c4 = _2n ** c1 - _1n; // 4. c4 = 2^c1 - 1 # Integer arithmetic const c4 = _2n_pow_c1 - _1n; // 4. c4 = 2^c1 - 1 # Integer arithmetic
const c5 = _2n ** (c1 - _1n); // 5. c5 = 2^(c1 - 1) # Integer arithmetic const c5 = _2n_pow_c1_1; // 5. c5 = 2^(c1 - 1) # Integer arithmetic
const c6 = Fp.pow(Z, c2); // 6. c6 = Z^c2 const c6 = Fp.pow(Z, c2); // 6. c6 = Z^c2
const c7 = Fp.pow(Z, (c2 + _1n) / _2n); // 7. c7 = Z^((c2 + 1) / 2) const c7 = Fp.pow(Z, (c2 + _1n) / _2n); // 7. c7 = Z^((c2 + 1) / 2)
let sqrtRatio = (u: T, v: T): { isValid: boolean; value: T } => { let sqrtRatio = (u: T, v: T): { isValid: boolean; value: T } => {
@@ -1119,7 +1136,8 @@ export function SWUFpSqrtRatio<T>(Fp: mod.IField<T>, Z: T) {
tv4 = Fp.cmov(tv5, tv4, isQR); // 16. tv4 = CMOV(tv5, tv4, isQR) tv4 = Fp.cmov(tv5, tv4, isQR); // 16. tv4 = CMOV(tv5, tv4, isQR)
// 17. for i in (c1, c1 - 1, ..., 2): // 17. for i in (c1, c1 - 1, ..., 2):
for (let i = c1; i > _1n; i--) { for (let i = c1; i > _1n; i--) {
let tv5 = _2n ** (i - _2n); // 18. tv5 = i - 2; 19. tv5 = 2^tv5 let tv5 = i - _2n; // 18. tv5 = i - 2
tv5 = _2n << (tv5 - _1n); // 19. tv5 = 2^tv5
let tvv5 = Fp.pow(tv4, tv5); // 20. tv5 = tv4^tv5 let tvv5 = Fp.pow(tv4, tv5); // 20. tv5 = tv4^tv5
const e1 = Fp.eql(tvv5, Fp.ONE); // 21. e1 = tv5 == 1 const e1 = Fp.eql(tvv5, Fp.ONE); // 21. e1 = tv5 == 1
tv2 = Fp.mul(tv3, tv1); // 22. tv2 = tv3 * tv1 tv2 = Fp.mul(tv3, tv1); // 22. tv2 = tv3 * tv1
@@ -1151,7 +1169,9 @@ export function SWUFpSqrtRatio<T>(Fp: mod.IField<T>, Z: T) {
// if (Fp.ORDER % _8n === _5n) // sqrt_ratio_5mod8 // if (Fp.ORDER % _8n === _5n) // sqrt_ratio_5mod8
return sqrtRatio; return sqrtRatio;
} }
// From draft-irtf-cfrg-hash-to-curve-16 /**
* From draft-irtf-cfrg-hash-to-curve-16
*/
export function mapToCurveSimpleSWU<T>( export function mapToCurveSimpleSWU<T>(
Fp: mod.IField<T>, Fp: mod.IField<T>,
opts: { opts: {

View File

@@ -60,11 +60,10 @@ const _8n = BigInt(8), _16n = BigInt(16);
// CURVE FIELDS // CURVE FIELDS
// Finite field over p. // Finite field over p.
const Fp = mod.Field( const Fp_raw = BigInt(
BigInt(
'0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab' '0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab'
)
); );
const Fp = mod.Field(Fp_raw);
type Fp = bigint; type Fp = bigint;
// Finite field over r. // Finite field over r.
// This particular field is not used anywhere in bls12-381, but it is still useful. // This particular field is not used anywhere in bls12-381, but it is still useful.
@@ -110,10 +109,7 @@ type Fp2Utils = {
// G² - 1 // G² - 1
// h2q // h2q
// NOTE: ORDER was wrong! // NOTE: ORDER was wrong!
const FP2_ORDER = const FP2_ORDER = Fp_raw * Fp_raw;
BigInt(
'0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab'
) ** _2n;
const Fp2: mod.IField<Fp2> & Fp2Utils = { const Fp2: mod.IField<Fp2> & Fp2Utils = {
ORDER: FP2_ORDER, ORDER: FP2_ORDER,
@@ -1197,7 +1193,7 @@ export const bls12_381: CurveFn<Fp, Fp2, Fp6, Fp12> = bls({
), ),
]), ]),
a: Fp2.ZERO, a: Fp2.ZERO,
b: Fp2.fromBigTuple([4n, _4n]), b: Fp2.fromBigTuple([_4n, _4n]),
hEff: BigInt( hEff: BigInt(
'0xbc69f08f2ee75b3584c6a0ea91b352888e2a8e9145ad7689986ff031508ffe1329c2f178731db956d82bf015d1212b02ec0ec69d7477c1ae954cbc06689f6a359894c0adebbf6b4e8020005aaa95551' '0xbc69f08f2ee75b3584c6a0ea91b352888e2a8e9145ad7689986ff031508ffe1329c2f178731db956d82bf015d1212b02ec0ec69d7477c1ae954cbc06689f6a359894c0adebbf6b4e8020005aaa95551'
), ),

View File

@@ -1,18 +1,18 @@
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */ /*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
import { sha512 } from '@noble/hashes/sha512'; import { sha512 } from '@noble/hashes/sha512';
import { concatBytes, randomBytes, utf8ToBytes } from '@noble/hashes/utils'; import { concatBytes, randomBytes, utf8ToBytes } from '@noble/hashes/utils';
import { twistedEdwards, ExtPointType } from './abstract/edwards.js'; import { ExtPointType, twistedEdwards } from './abstract/edwards.js';
import { montgomery } from './abstract/montgomery.js'; import { montgomery } from './abstract/montgomery.js';
import { mod, pow2, isNegativeLE, Field, FpSqrtEven } from './abstract/modular.js'; import { Field, FpSqrtEven, isNegativeLE, mod, pow2 } from './abstract/modular.js';
import { import {
equalBytes,
bytesToHex, bytesToHex,
bytesToNumberLE, bytesToNumberLE,
numberToBytesLE,
Hex,
ensureBytes, ensureBytes,
equalBytes,
Hex,
numberToBytesLE,
} from './abstract/utils.js'; } from './abstract/utils.js';
import * as htf from './abstract/hash-to-curve.js'; import { createHasher, htfBasicOpts, expand_message_xmd } from './abstract/hash-to-curve.js';
import { AffinePoint } from './abstract/curve.js'; import { AffinePoint } from './abstract/curve.js';
/** /**
@@ -34,6 +34,7 @@ const ED25519_SQRT_M1 = BigInt(
const _0n = BigInt(0), _1n = BigInt(1), _2n = BigInt(2), _5n = BigInt(5); const _0n = BigInt(0), _1n = BigInt(1), _2n = BigInt(2), _5n = BigInt(5);
// prettier-ignore // prettier-ignore
const _10n = BigInt(10), _20n = BigInt(20), _40n = BigInt(40), _80n = BigInt(80); const _10n = BigInt(10), _20n = BigInt(20), _40n = BigInt(40), _80n = BigInt(80);
function ed25519_pow_2_252_3(x: bigint) { function ed25519_pow_2_252_3(x: bigint) {
const P = ED25519_P; const P = ED25519_P;
const x2 = (x * x) % P; const x2 = (x * x) % P;
@@ -51,6 +52,7 @@ function ed25519_pow_2_252_3(x: bigint) {
// ^ To pow to (p+3)/8, multiply it by x. // ^ To pow to (p+3)/8, multiply it by x.
return { pow_p_5_8, b2 }; return { pow_p_5_8, b2 };
} }
function adjustScalarBytes(bytes: Uint8Array): Uint8Array { function adjustScalarBytes(bytes: Uint8Array): Uint8Array {
// Section 5: For X25519, in order to decode 32 random bytes as an integer scalar, // Section 5: For X25519, in order to decode 32 random bytes as an integer scalar,
// set the three least significant bits of the first byte // set the three least significant bits of the first byte
@@ -61,6 +63,7 @@ function adjustScalarBytes(bytes: Uint8Array): Uint8Array {
bytes[31] |= 64; // 0b0100_0000 bytes[31] |= 64; // 0b0100_0000
return bytes; return bytes;
} }
// sqrt(u/v) // sqrt(u/v)
function uvRatio(u: bigint, v: bigint): { isValid: boolean; value: bigint } { function uvRatio(u: bigint, v: bigint): { isValid: boolean; value: bigint } {
const P = ED25519_P; const P = ED25519_P;
@@ -101,10 +104,10 @@ const ed25519Defaults = {
// d is equal to -121665/121666 over finite field. // d is equal to -121665/121666 over finite field.
// Negative number is P - number, and division is invert(number, P) // Negative number is P - number, and division is invert(number, P)
d: BigInt('37095705934669439343138083508754565189542113879843219016388785533085940283555'), d: BigInt('37095705934669439343138083508754565189542113879843219016388785533085940283555'),
// Finite field 𝔽p over which we'll do calculations; 2n ** 255n - 19n // Finite field 𝔽p over which we'll do calculations; 2n**255n - 19n
Fp, Fp,
// Subgroup order: how many points curve has // Subgroup order: how many points curve has
// 2n ** 252n + 27742317777372353535851937790883648493n; // 2n**252n + 27742317777372353535851937790883648493n;
n: BigInt('7237005577332262213973186563042994240857116359379907606001950938285454250989'), n: BigInt('7237005577332262213973186563042994240857116359379907606001950938285454250989'),
// Cofactor // Cofactor
h: BigInt(8), h: BigInt(8),
@@ -121,6 +124,7 @@ const ed25519Defaults = {
} as const; } as const;
export const ed25519 = twistedEdwards(ed25519Defaults); export const ed25519 = twistedEdwards(ed25519Defaults);
function ed25519_domain(data: Uint8Array, ctx: Uint8Array, phflag: boolean) { function ed25519_domain(data: Uint8Array, ctx: Uint8Array, phflag: boolean) {
if (ctx.length > 255) throw new Error('Context is too big'); if (ctx.length > 255) throw new Error('Context is too big');
return concatBytes( return concatBytes(
@@ -130,6 +134,7 @@ function ed25519_domain(data: Uint8Array, ctx: Uint8Array, phflag: boolean) {
data data
); );
} }
export const ed25519ctx = twistedEdwards({ ...ed25519Defaults, domain: ed25519_domain }); export const ed25519ctx = twistedEdwards({ ...ed25519Defaults, domain: ed25519_domain });
export const ed25519ph = twistedEdwards({ export const ed25519ph = twistedEdwards({
...ed25519Defaults, ...ed25519Defaults,
@@ -137,7 +142,8 @@ export const ed25519ph = twistedEdwards({
prehash: sha512, prehash: sha512,
}); });
export const x25519 = montgomery({ export const x25519 = /* @__PURE__ */ (() =>
montgomery({
P: ED25519_P, P: ED25519_P,
a: BigInt(486662), a: BigInt(486662),
montgomeryBits: 255, // n is 253 bits montgomeryBits: 255, // n is 253 bits
@@ -151,20 +157,34 @@ export const x25519 = montgomery({
}, },
adjustScalarBytes, adjustScalarBytes,
randomBytes, randomBytes,
}); }))();
/** /**
* Converts ed25519 public key to x25519 public key. Uses formula: * Converts ed25519 public key to x25519 public key. Uses formula:
* * `(u, v) = ((1+y)/(1-y), sqrt(-486664)*u/x)` * * `(u, v) = ((1+y)/(1-y), sqrt(-486664)*u/x)`
* * `(x, y) = (sqrt(-486664)*u/v, (u-1)/(u+1))` * * `(x, y) = (sqrt(-486664)*u/v, (u-1)/(u+1))`
* @example * @example
* const aPub = ed25519.getPublicKey(utils.randomPrivateKey()); * const someonesPub = ed25519.getPublicKey(ed25519.utils.randomPrivateKey());
* x25519.getSharedSecret(edwardsToMontgomery(aPub), edwardsToMontgomery(someonesPub)) * const aPriv = x25519.utils.randomPrivateKey();
* x25519.getSharedSecret(aPriv, edwardsToMontgomeryPub(someonesPub))
*/ */
export function edwardsToMontgomery(edwardsPub: Hex): Uint8Array { export function edwardsToMontgomeryPub(edwardsPub: Hex): Uint8Array {
const { y } = ed25519.ExtendedPoint.fromHex(edwardsPub); const { y } = ed25519.ExtendedPoint.fromHex(edwardsPub);
const _1n = BigInt(1); const _1n = BigInt(1);
return Fp.toBytes(Fp.create((y - _1n) * Fp.inv(y + _1n))); return Fp.toBytes(Fp.create((_1n + y) * Fp.inv(_1n - y)));
}
export const edwardsToMontgomery = edwardsToMontgomeryPub; // deprecated
/**
* Converts ed25519 secret key to x25519 secret key.
* @example
* const someonesPub = x25519.getPublicKey(x25519.utils.randomPrivateKey());
* const aPriv = ed25519.utils.randomPrivateKey();
* x25519.getSharedSecret(edwardsToMontgomeryPriv(aPriv), someonesPub)
*/
export function edwardsToMontgomeryPriv(edwardsPriv: Uint8Array): Uint8Array {
const hashed = ed25519Defaults.hash(edwardsPriv.subarray(0, 32));
return ed25519Defaults.adjustScalarBytes(hashed).subarray(0, 32);
} }
// Hash To Curve Elligator2 Map (NOTE: different from ristretto255 elligator) // Hash To Curve Elligator2 Map (NOTE: different from ristretto255 elligator)
@@ -223,7 +243,8 @@ function map_to_curve_elligator2_curve25519(u: bigint) {
const ELL2_C1_EDWARDS = FpSqrtEven(Fp, Fp.neg(BigInt(486664))); // sgn0(c1) MUST equal 0 const ELL2_C1_EDWARDS = FpSqrtEven(Fp, Fp.neg(BigInt(486664))); // sgn0(c1) MUST equal 0
function map_to_curve_elligator2_edwards25519(u: bigint) { function map_to_curve_elligator2_edwards25519(u: bigint) {
const { xMn, xMd, yMn, yMd } = map_to_curve_elligator2_curve25519(u); // 1. (xMn, xMd, yMn, yMd) = map_to_curve_elligator2_curve25519(u) const { xMn, xMd, yMn, yMd } = map_to_curve_elligator2_curve25519(u); // 1. (xMn, xMd, yMn, yMd) =
// map_to_curve_elligator2_curve25519(u)
let xn = Fp.mul(xMn, yMd); // 2. xn = xMn * yMd let xn = Fp.mul(xMn, yMd); // 2. xn = xMn * yMd
xn = Fp.mul(xn, ELL2_C1_EDWARDS); // 3. xn = xn * c1 xn = Fp.mul(xn, ELL2_C1_EDWARDS); // 3. xn = xn * c1
let xd = Fp.mul(xMd, yMn); // 4. xd = xMd * yMn # xn / xd = c1 * xM / yM let xd = Fp.mul(xMd, yMn); // 4. xd = xMd * yMn # xn / xd = c1 * xM / yM
@@ -239,7 +260,9 @@ 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.createHasher(
const htf = /* @__PURE__ */ (() =>
createHasher(
ed25519.ExtendedPoint, ed25519.ExtendedPoint,
(scalars: bigint[]) => map_to_curve_elligator2_edwards25519(scalars[0]), (scalars: bigint[]) => map_to_curve_elligator2_edwards25519(scalars[0]),
{ {
@@ -251,16 +274,16 @@ const { hashToCurve, encodeToCurve } = htf.createHasher(
expand: 'xmd', expand: 'xmd',
hash: sha512, hash: sha512,
} }
); ))();
export { hashToCurve, encodeToCurve }; export const hashToCurve = /* @__PURE__ */ (() => htf.hashToCurve)();
export const encodeToCurve = /* @__PURE__ */ (() => htf.encodeToCurve)();
function assertRstPoint(other: unknown) { function assertRstPoint(other: unknown) {
if (!(other instanceof RistrettoPoint)) throw new Error('RistrettoPoint expected'); if (!(other instanceof RistPoint)) throw new Error('RistrettoPoint expected');
} }
// √(-1) aka √(a) aka 2^((p-1)/4) // √(-1) aka √(a) aka 2^((p-1)/4)
const SQRT_M1 = BigInt( const SQRT_M1 = ED25519_SQRT_M1;
'19681161376707505956807079304988542015446066515923890162744021073123829784752'
);
// √(ad - 1) // √(ad - 1)
const SQRT_AD_MINUS_ONE = BigInt( const SQRT_AD_MINUS_ONE = BigInt(
'25063068953384623474111414158702152701244531502492656460079210482610430750235' '25063068953384623474111414158702152701244531502492656460079210482610430750235'
@@ -317,32 +340,31 @@ function calcElligatorRistrettoMap(r0: bigint): ExtendedPoint {
* but it should work in its own namespace: do not combine those two. * but it should work in its own namespace: do not combine those two.
* https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-ristretto255-decaf448 * https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-ristretto255-decaf448
*/ */
export class RistrettoPoint { class RistPoint {
static BASE = new RistrettoPoint(ed25519.ExtendedPoint.BASE); static BASE: RistPoint;
static ZERO = new RistrettoPoint(ed25519.ExtendedPoint.ZERO); static ZERO: RistPoint;
// Private property to discourage combining ExtendedPoint + RistrettoPoint // Private property to discourage combining ExtendedPoint + RistrettoPoint
// Always use Ristretto encoding/decoding instead. // Always use Ristretto encoding/decoding instead.
constructor(private readonly ep: ExtendedPoint) {} constructor(private readonly ep: ExtendedPoint) {}
static fromAffine(ap: AffinePoint<bigint>) { static fromAffine(ap: AffinePoint<bigint>) {
return new RistrettoPoint(ed25519.ExtendedPoint.fromAffine(ap)); return new RistPoint(ed25519.ExtendedPoint.fromAffine(ap));
} }
/** /**
* Takes uniform output of 64-bit hash function like sha512 and converts it to `RistrettoPoint`. * Takes uniform output of 64-byte hash function like sha512 and converts it to `RistrettoPoint`.
* The hash-to-group operation applies Elligator twice and adds the results. * The hash-to-group operation applies Elligator twice and adds the results.
* **Note:** this is one-way map, there is no conversion from point to hash. * **Note:** this is one-way map, there is no conversion from point to hash.
* https://ristretto.group/formulas/elligator.html * https://ristretto.group/formulas/elligator.html
* @param hex 64-bit output of a hash function * @param hex 64-byte output of a hash function
*/ */
static hashToCurve(hex: Hex): RistrettoPoint { static hashToCurve(hex: Hex): RistPoint {
hex = ensureBytes('ristrettoHash', 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));
const R2 = calcElligatorRistrettoMap(r2); const R2 = calcElligatorRistrettoMap(r2);
return new RistrettoPoint(R1.add(R2)); return new RistPoint(R1.add(R2));
} }
/** /**
@@ -350,7 +372,7 @@ export class RistrettoPoint {
* https://ristretto.group/formulas/decoding.html * https://ristretto.group/formulas/decoding.html
* @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): RistPoint {
hex = ensureBytes('ristrettoHex', 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;
@@ -374,7 +396,7 @@ export class RistrettoPoint {
const y = mod(u1 * Dy); // 11 const y = mod(u1 * Dy); // 11
const t = mod(x * y); // 12 const t = mod(x * y); // 12
if (!isValid || isNegativeLE(t, P) || y === _0n) throw new Error(emsg); if (!isValid || isNegativeLE(t, P) || y === _0n) throw new Error(emsg);
return new RistrettoPoint(new ed25519.ExtendedPoint(x, y, _1n, t)); return new RistPoint(new ed25519.ExtendedPoint(x, y, _1n, t));
} }
/** /**
@@ -418,7 +440,7 @@ export class RistrettoPoint {
} }
// Compare one point to another. // Compare one point to another.
equals(other: RistrettoPoint): boolean { equals(other: RistPoint): boolean {
assertRstPoint(other); assertRstPoint(other);
const { ex: X1, ey: Y1 } = this.ep; const { ex: X1, ey: Y1 } = this.ep;
const { ex: X2, ey: Y2 } = other.ep; const { ex: X2, ey: Y2 } = other.ep;
@@ -429,31 +451,36 @@ export class RistrettoPoint {
return one || two; return one || two;
} }
add(other: RistrettoPoint): RistrettoPoint { add(other: RistPoint): RistPoint {
assertRstPoint(other); assertRstPoint(other);
return new RistrettoPoint(this.ep.add(other.ep)); return new RistPoint(this.ep.add(other.ep));
} }
subtract(other: RistrettoPoint): RistrettoPoint { subtract(other: RistPoint): RistPoint {
assertRstPoint(other); assertRstPoint(other);
return new RistrettoPoint(this.ep.subtract(other.ep)); return new RistPoint(this.ep.subtract(other.ep));
} }
multiply(scalar: bigint): RistrettoPoint { multiply(scalar: bigint): RistPoint {
return new RistrettoPoint(this.ep.multiply(scalar)); return new RistPoint(this.ep.multiply(scalar));
} }
multiplyUnsafe(scalar: bigint): RistrettoPoint { multiplyUnsafe(scalar: bigint): RistPoint {
return new RistrettoPoint(this.ep.multiplyUnsafe(scalar)); return new RistPoint(this.ep.multiplyUnsafe(scalar));
} }
} }
export const RistrettoPoint = /* @__PURE__ */ (() => {
if (!RistPoint.BASE) RistPoint.BASE = new RistPoint(ed25519.ExtendedPoint.BASE);
if (!RistPoint.ZERO) RistPoint.ZERO = new RistPoint(ed25519.ExtendedPoint.ZERO);
return RistPoint;
})();
// https://datatracker.ietf.org/doc/draft-irtf-cfrg-hash-to-curve/14/ // https://datatracker.ietf.org/doc/draft-irtf-cfrg-hash-to-curve/14/
// Appendix B. Hashing to ristretto255 // Appendix B. Hashing to ristretto255
export const hash_to_ristretto255 = (msg: Uint8Array, options: htf.htfBasicOpts) => { export const hash_to_ristretto255 = (msg: Uint8Array, options: htfBasicOpts) => {
const d = options.DST; const d = options.DST;
const DST = typeof d === 'string' ? utf8ToBytes(d) : d; const DST = typeof d === 'string' ? utf8ToBytes(d) : d;
const uniform_bytes = htf.expand_message_xmd(msg, DST, 64, sha512); const uniform_bytes = expand_message_xmd(msg, DST, 64, sha512);
const P = RistrettoPoint.hashToCurve(uniform_bytes); const P = RistPoint.hashToCurve(uniform_bytes);
return P; return P;
}; };

View File

@@ -4,7 +4,7 @@ import { concatBytes, randomBytes, utf8ToBytes, wrapConstructor } from '@noble/h
import { twistedEdwards } from './abstract/edwards.js'; import { twistedEdwards } from './abstract/edwards.js';
import { mod, pow2, Field } from './abstract/modular.js'; import { mod, pow2, Field } from './abstract/modular.js';
import { montgomery } from './abstract/montgomery.js'; import { montgomery } from './abstract/montgomery.js';
import * as htf from './abstract/hash-to-curve.js'; import { createHasher } from './abstract/hash-to-curve.js';
/** /**
* Edwards448 (not Ed448-Goldilocks) curve with following addons: * Edwards448 (not Ed448-Goldilocks) curve with following addons:
@@ -63,7 +63,7 @@ const ED448_DEF = {
d: BigInt( d: BigInt(
'726838724295606890549323807888004534353641360687318060281490199180612328166730772686396383698676545930088884461843637361053498018326358' '726838724295606890549323807888004534353641360687318060281490199180612328166730772686396383698676545930088884461843637361053498018326358'
), ),
// Finite field 𝔽p over which we'll do calculations; 2n ** 448n - 2n ** 224n - 1n // Finite field 𝔽p over which we'll do calculations; 2n**448n - 2n**224n - 1n
Fp, Fp,
// Subgroup order: how many points curve has; // Subgroup order: how many points curve has;
// 2n**446n - 13818066809895115352007386748515426880336692474882178609894547503885n // 2n**446n - 13818066809895115352007386748515426880336692474882178609894547503885n
@@ -122,7 +122,8 @@ export const ed448 = twistedEdwards(ED448_DEF);
// NOTE: there is no ed448ctx, since ed448 supports ctx by default // NOTE: there is no ed448ctx, since ed448 supports ctx by default
export const ed448ph = twistedEdwards({ ...ED448_DEF, prehash: shake256_64 }); export const ed448ph = twistedEdwards({ ...ED448_DEF, prehash: shake256_64 });
export const x448 = montgomery({ export const x448 = /* @__PURE__ */ (() =>
montgomery({
a: BigInt(156326), a: BigInt(156326),
montgomeryBits: 448, montgomeryBits: 448,
nByteLength: 57, nByteLength: 57,
@@ -136,7 +137,7 @@ export const x448 = montgomery({
}, },
adjustScalarBytes, adjustScalarBytes,
randomBytes, randomBytes,
}); }))();
/** /**
* Converts edwards448 public key to x448 public key. Uses formula: * Converts edwards448 public key to x448 public key. Uses formula:
@@ -146,11 +147,12 @@ export const x448 = montgomery({
* const aPub = ed448.getPublicKey(utils.randomPrivateKey()); * const aPub = ed448.getPublicKey(utils.randomPrivateKey());
* x448.getSharedSecret(edwardsToMontgomery(aPub), edwardsToMontgomery(someonesPub)) * x448.getSharedSecret(edwardsToMontgomery(aPub), edwardsToMontgomery(someonesPub))
*/ */
export function edwardsToMontgomery(edwardsPub: string | Uint8Array): Uint8Array { export function edwardsToMontgomeryPub(edwardsPub: string | Uint8Array): Uint8Array {
const { y } = ed448.ExtendedPoint.fromHex(edwardsPub); const { y } = ed448.ExtendedPoint.fromHex(edwardsPub);
const _1n = BigInt(1); const _1n = BigInt(1);
return Fp.toBytes(Fp.create((y - _1n) * Fp.inv(y + _1n))); return Fp.toBytes(Fp.create((y - _1n) * Fp.inv(y + _1n)));
} }
export const edwardsToMontgomery = edwardsToMontgomeryPub; // deprecated
// Hash To Curve Elligator2 Map // Hash To Curve Elligator2 Map
const ELL2_C1 = (Fp.ORDER - BigInt(3)) / BigInt(4); // 1. c1 = (q - 3) / 4 # Integer arithmetic const ELL2_C1 = (Fp.ORDER - BigInt(3)) / BigInt(4); // 1. c1 = (q - 3) / 4 # Integer arithmetic
@@ -227,7 +229,8 @@ 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.createHasher( const htf = /* @__PURE__ */ (() =>
createHasher(
ed448.ExtendedPoint, ed448.ExtendedPoint,
(scalars: bigint[]) => map_to_curve_elligator2_edwards448(scalars[0]), (scalars: bigint[]) => map_to_curve_elligator2_edwards448(scalars[0]),
{ {
@@ -239,5 +242,6 @@ const { hashToCurve, encodeToCurve } = htf.createHasher(
expand: 'xof', expand: 'xof',
hash: shake256, hash: shake256,
} }
); ))();
export { hashToCurve, encodeToCurve }; export const hashToCurve = /* @__PURE__ */ (() => htf.hashToCurve)();
export const encodeToCurve = /* @__PURE__ */ (() => htf.encodeToCurve)();

View File

@@ -3,7 +3,7 @@ import { createCurve } from './_shortw_utils.js';
import { sha256 } from '@noble/hashes/sha256'; import { sha256 } from '@noble/hashes/sha256';
import { Field } from './abstract/modular.js'; import { 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 { createHasher } from './abstract/hash-to-curve.js';
// NIST secp256r1 aka p256 // NIST secp256r1 aka p256
// https://www.secg.org/sec2-v2.pdf, https://neuromancer.sk/std/nist/P-256 // https://www.secg.org/sec2-v2.pdf, https://neuromancer.sk/std/nist/P-256
@@ -12,12 +12,6 @@ const Fp = Field(BigInt('0xffffffff00000001000000000000000000000000fffffffffffff
const CURVE_A = Fp.create(BigInt('-3')); const CURVE_A = Fp.create(BigInt('-3'));
const CURVE_B = BigInt('0x5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b'); const CURVE_B = BigInt('0x5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b');
const mapSWU = mapToCurveSimpleSWU(Fp, {
A: CURVE_A,
B: CURVE_B,
Z: Fp.create(BigInt('-10')),
});
// prettier-ignore // prettier-ignore
export const p256 = createCurve({ export const p256 = createCurve({
a: CURVE_A, // Equation params: a, b a: CURVE_A, // Equation params: a, b
@@ -33,10 +27,15 @@ export const p256 = createCurve({
} as const, sha256); } as const, sha256);
export const secp256r1 = p256; export const secp256r1 = p256;
const { hashToCurve, encodeToCurve } = htf.createHasher( const mapSWU = /* @__PURE__ */ (() =>
secp256r1.ProjectivePoint, mapToCurveSimpleSWU(Fp, {
(scalars: bigint[]) => mapSWU(scalars[0]), A: CURVE_A,
{ B: CURVE_B,
Z: Fp.create(BigInt('-10')),
}))();
const htf = /* @__PURE__ */ (() =>
createHasher(secp256r1.ProjectivePoint, (scalars: bigint[]) => mapSWU(scalars[0]), {
DST: 'P256_XMD:SHA-256_SSWU_RO_', DST: 'P256_XMD:SHA-256_SSWU_RO_',
encodeDST: 'P256_XMD:SHA-256_SSWU_NU_', encodeDST: 'P256_XMD:SHA-256_SSWU_NU_',
p: Fp.ORDER, p: Fp.ORDER,
@@ -44,6 +43,6 @@ const { hashToCurve, encodeToCurve } = htf.createHasher(
k: 128, k: 128,
expand: 'xmd', expand: 'xmd',
hash: sha256, hash: sha256,
} }))();
); export const hashToCurve = /* @__PURE__ */ (() => htf.hashToCurve)();
export { hashToCurve, encodeToCurve }; export const encodeToCurve = /* @__PURE__ */ (() => htf.encodeToCurve)();

View File

@@ -3,7 +3,7 @@ import { createCurve } from './_shortw_utils.js';
import { sha384 } from '@noble/hashes/sha512'; import { sha384 } from '@noble/hashes/sha512';
import { Field } from './abstract/modular.js'; import { 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 { createHasher } from './abstract/hash-to-curve.js';
// NIST secp384r1 aka p384 // NIST secp384r1 aka p384
// https://www.secg.org/sec2-v2.pdf, https://neuromancer.sk/std/nist/P-384 // https://www.secg.org/sec2-v2.pdf, https://neuromancer.sk/std/nist/P-384
@@ -31,16 +31,15 @@ export const p384 = createCurve({
} as const, sha384); } as const, sha384);
export const secp384r1 = p384; export const secp384r1 = p384;
const mapSWU = mapToCurveSimpleSWU(Fp, { const mapSWU = /* @__PURE__ */ (() =>
mapToCurveSimpleSWU(Fp, {
A: CURVE_A, A: CURVE_A,
B: CURVE_B, B: CURVE_B,
Z: Fp.create(BigInt('-12')), Z: Fp.create(BigInt('-12')),
}); }))();
const { hashToCurve, encodeToCurve } = htf.createHasher( const htf = /* @__PURE__ */ (() =>
secp384r1.ProjectivePoint, createHasher(secp384r1.ProjectivePoint, (scalars: bigint[]) => mapSWU(scalars[0]), {
(scalars: bigint[]) => mapSWU(scalars[0]),
{
DST: 'P384_XMD:SHA-384_SSWU_RO_', DST: 'P384_XMD:SHA-384_SSWU_RO_',
encodeDST: 'P384_XMD:SHA-384_SSWU_NU_', encodeDST: 'P384_XMD:SHA-384_SSWU_NU_',
p: Fp.ORDER, p: Fp.ORDER,
@@ -48,6 +47,6 @@ const { hashToCurve, encodeToCurve } = htf.createHasher(
k: 192, k: 192,
expand: 'xmd', expand: 'xmd',
hash: sha384, hash: sha384,
} }))();
); export const hashToCurve = /* @__PURE__ */ (() => htf.hashToCurve)();
export { hashToCurve, encodeToCurve }; export const encodeToCurve = /* @__PURE__ */ (() => htf.encodeToCurve)();

View File

@@ -3,7 +3,7 @@ import { createCurve } from './_shortw_utils.js';
import { sha512 } from '@noble/hashes/sha512'; import { sha512 } from '@noble/hashes/sha512';
import { Field } from './abstract/modular.js'; import { 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 { createHasher } from './abstract/hash-to-curve.js';
// NIST secp521r1 aka p521 // NIST secp521r1 aka p521
// Note that it's 521, which differs from 512 of its hash function. // Note that it's 521, which differs from 512 of its hash function.
@@ -47,16 +47,15 @@ export const p521 = createCurve({
} as const, sha512); } as const, sha512);
export const secp521r1 = p521; export const secp521r1 = p521;
const mapSWU = mapToCurveSimpleSWU(Fp, { const mapSWU = /* @__PURE__ */ (() =>
mapToCurveSimpleSWU(Fp, {
A: CURVE.a, A: CURVE.a,
B: CURVE.b, B: CURVE.b,
Z: Fp.create(BigInt('-4')), Z: Fp.create(BigInt('-4')),
}); }))();
const { hashToCurve, encodeToCurve } = htf.createHasher( const htf = /* @__PURE__ */ (() =>
secp521r1.ProjectivePoint, createHasher(secp521r1.ProjectivePoint, (scalars: bigint[]) => mapSWU(scalars[0]), {
(scalars: bigint[]) => mapSWU(scalars[0]),
{
DST: 'P521_XMD:SHA-512_SSWU_RO_', DST: 'P521_XMD:SHA-512_SSWU_RO_',
encodeDST: 'P521_XMD:SHA-512_SSWU_NU_', encodeDST: 'P521_XMD:SHA-512_SSWU_NU_',
p: Fp.ORDER, p: Fp.ORDER,
@@ -64,6 +63,6 @@ const { hashToCurve, encodeToCurve } = htf.createHasher(
k: 256, k: 256,
expand: 'xmd', expand: 'xmd',
hash: sha512, hash: sha512,
} }))();
); export const hashToCurve = /* @__PURE__ */ (() => htf.hashToCurve)();
export { hashToCurve, encodeToCurve }; export const encodeToCurve = /* @__PURE__ */ (() => htf.encodeToCurve)();

View File

@@ -5,7 +5,7 @@ import { Field, mod, pow2 } from './abstract/modular.js';
import { ProjPointType as PointType, mapToCurveSimpleSWU } from './abstract/weierstrass.js'; import { ProjPointType as PointType, mapToCurveSimpleSWU } from './abstract/weierstrass.js';
import type { Hex, PrivKey } from './abstract/utils.js'; import type { Hex, PrivKey } from './abstract/utils.js';
import { bytesToNumberBE, concatBytes, ensureBytes, numberToBytesBE } from './abstract/utils.js'; import { bytesToNumberBE, concatBytes, ensureBytes, numberToBytesBE } from './abstract/utils.js';
import * as htf from './abstract/hash-to-curve.js'; import { createHasher, isogenyMap } from './abstract/hash-to-curve.js';
import { createCurve } from './_shortw_utils.js'; import { createCurve } from './_shortw_utils.js';
const secp256k1P = BigInt('0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f'); const secp256k1P = BigInt('0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f');
@@ -199,7 +199,7 @@ function schnorrVerify(signature: Hex, message: Hex, publicKey: Hex): boolean {
} }
} }
export const schnorr = { export const schnorr = /* @__PURE__ */ (() => ({
getPublicKey: schnorrGetPublicKey, getPublicKey: schnorrGetPublicKey,
sign: schnorrSign, sign: schnorrSign,
verify: schnorrVerify, verify: schnorrVerify,
@@ -212,9 +212,10 @@ export const schnorr = {
taggedHash, taggedHash,
mod, mod,
}, },
}; }))();
const isoMap = htf.isogenyMap( const isoMap = /* @__PURE__ */ (() =>
isogenyMap(
Fp, Fp,
[ [
// xNum // xNum
@@ -245,13 +246,15 @@ const isoMap = htf.isogenyMap(
'0x0000000000000000000000000000000000000000000000000000000000000001', // LAST 1 '0x0000000000000000000000000000000000000000000000000000000000000001', // LAST 1
], ],
].map((i) => i.map((j) => BigInt(j))) as [bigint[], bigint[], bigint[], bigint[]] ].map((i) => i.map((j) => BigInt(j))) as [bigint[], bigint[], bigint[], bigint[]]
); ))();
const mapSWU = mapToCurveSimpleSWU(Fp, { const mapSWU = /* @__PURE__ */ (() =>
mapToCurveSimpleSWU(Fp, {
A: BigInt('0x3f8731abdd661adca08a5558f0f5d272e953d363cb6f0e5d405447c01a444533'), A: BigInt('0x3f8731abdd661adca08a5558f0f5d272e953d363cb6f0e5d405447c01a444533'),
B: BigInt('1771'), B: BigInt('1771'),
Z: Fp.create(BigInt('-11')), Z: Fp.create(BigInt('-11')),
}); }))();
export const { hashToCurve, encodeToCurve } = htf.createHasher( const htf = /* @__PURE__ */ (() =>
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]));
@@ -266,4 +269,6 @@ export const { hashToCurve, encodeToCurve } = htf.createHasher(
expand: 'xmd', expand: 'xmd',
hash: sha256, hash: sha256,
} }
); ))();
export const hashToCurve = /* @__PURE__ */ (() => htf.hashToCurve)();
export const encodeToCurve = /* @__PURE__ */ (() => htf.encodeToCurve)();

View File

@@ -1,10 +1,18 @@
import { sha512 } from '@noble/hashes/sha512'; import { sha512 } from '@noble/hashes/sha512';
import { hexToBytes, bytesToHex as hex } from '@noble/hashes/utils'; import { bytesToHex as hex, hexToBytes } from '@noble/hashes/utils';
import { deepStrictEqual, throws } from 'assert'; import { deepStrictEqual, throws } from 'assert';
import { describe, should } from 'micro-should'; import { describe, should } from 'micro-should';
import { bytesToNumberLE, numberToBytesLE } from '../esm/abstract/utils.js'; import { bytesToNumberLE, numberToBytesLE } from '../esm/abstract/utils.js';
import { default as x25519vectors } from './wycheproof/x25519_test.json' assert { type: 'json' }; import { default as x25519vectors } from './wycheproof/x25519_test.json' assert { type: 'json' };
import { ed25519ctx, ed25519ph, RistrettoPoint, x25519 } from '../esm/ed25519.js'; import {
ed25519,
ed25519ctx,
ed25519ph,
edwardsToMontgomeryPub,
edwardsToMontgomeryPriv,
RistrettoPoint,
x25519,
} from '../esm/ed25519.js';
const VECTORS_RFC8032_CTX = [ const VECTORS_RFC8032_CTX = [
{ {
@@ -141,6 +149,56 @@ describe('RFC7748 X25519 ECDH', () => {
deepStrictEqual(hex(x25519.scalarMult(bobPrivate, alicePublic)), shared); deepStrictEqual(hex(x25519.scalarMult(bobPrivate, alicePublic)), shared);
}); });
should('X25519/getSharedSecret() should be commutative', () => {
for (let i = 0; i < 512; i++) {
const asec = x25519.utils.randomPrivateKey();
const apub = x25519.getPublicKey(asec);
const bsec = x25519.utils.randomPrivateKey();
const bpub = x25519.getPublicKey(bsec);
try {
deepStrictEqual(x25519.getSharedSecret(asec, bpub), x25519.getSharedSecret(bsec, apub));
} catch (error) {
console.error('not commutative', { asec, apub, bsec, bpub });
throw error;
}
}
});
should('edwardsToMontgomery should produce correct output', () => {
const edSecret = hexToBytes('77076d0a7318a57d3c16c17251b26645df4c2f87ebc0992ab177fba51db92c2a');
const edPublic = ed25519.getPublicKey(edSecret);
const xPrivate = edwardsToMontgomeryPriv(edSecret);
deepStrictEqual(
hex(xPrivate),
'a8cd44eb8e93319c0570bc11005c0e0189d34ff02f6c17773411ad191293c94f'
);
const xPublic = edwardsToMontgomeryPub(edPublic);
deepStrictEqual(
hex(xPublic),
'ed7749b4d989f6957f3bfde6c56767e988e21c9f8784d91d610011cd553f9b06'
);
});
should('edwardsToMontgomery should produce correct keyPair', () => {
const edSecret = ed25519.utils.randomPrivateKey();
const edPublic = ed25519.getPublicKey(edSecret);
const xSecret = edwardsToMontgomeryPriv(edSecret);
const expectedXPublic = x25519.getPublicKey(xSecret);
const xPublic = edwardsToMontgomeryPub(edPublic);
deepStrictEqual(xPublic, expectedXPublic);
});
should('ECDH through edwardsToMontgomery should be commutative', () => {
const edSecret1 = ed25519.utils.randomPrivateKey();
const edPublic1 = ed25519.getPublicKey(edSecret1);
const edSecret2 = ed25519.utils.randomPrivateKey();
const edPublic2 = ed25519.getPublicKey(edSecret2);
deepStrictEqual(
x25519.getSharedSecret(edwardsToMontgomeryPriv(edSecret1), edwardsToMontgomeryPub(edPublic2)),
x25519.getSharedSecret(edwardsToMontgomeryPriv(edSecret2), edwardsToMontgomeryPub(edPublic1))
);
});
should('base point', () => { should('base point', () => {
const { y } = ed25519ph.ExtendedPoint.BASE; const { y } = ed25519ph.ExtendedPoint.BASE;
const { Fp } = ed25519ph.CURVE; const { Fp } = ed25519ph.CURVE;
@@ -298,7 +356,7 @@ describe('ristretto255', () => {
// ESM is broken. // ESM is broken.
import url from 'url'; import url from 'url';
import { assert } from 'console';
if (import.meta.url === url.pathToFileURL(process.argv[1]).href) { if (import.meta.url === url.pathToFileURL(process.argv[1]).href) {
should.run(); should.run();
} }

View File

@@ -1,17 +1,19 @@
import { deepStrictEqual, strictEqual, throws } from 'assert'; import { deepStrictEqual, strictEqual, throws } from 'assert';
import { readFileSync } from 'fs'; import { readFileSync } from 'fs';
import { bytesToHex, concatBytes, hexToBytes, randomBytes } from '@noble/hashes/utils'; import { bytesToHex, concatBytes, hexToBytes, utf8ToBytes, randomBytes } from '@noble/hashes/utils';
import * as fc from 'fast-check'; import * as fc from 'fast-check';
import { describe, should } from 'micro-should'; import { describe, should } from 'micro-should';
import { ed25519, ED25519_TORSION_SUBGROUP, numberToBytesLE } from './ed25519.helpers.js'; import { ed25519 as ed, ED25519_TORSION_SUBGROUP, numberToBytesLE } from './ed25519.helpers.js';
// Old vectors allow to test sign() because they include private key // Old vectors allow to test sign() because they include private key
import { default as ed25519vectors_OLD } from './ed25519/ed25519_test_OLD.json' assert { type: 'json' }; import { default as ed25519vectors_OLD } from './ed25519/ed25519_test_OLD.json' assert { type: 'json' };
import { default as ed25519vectors } from './wycheproof/ed25519_test.json' assert { type: 'json' }; import { default as ed25519vectors } from './wycheproof/ed25519_test.json' assert { type: 'json' };
import { default as zip215 } from './ed25519/zip215.json' assert { type: 'json' }; import { default as zip215 } from './ed25519/zip215.json' assert { type: 'json' };
import { default as edgeCases } from './ed25519/edge-cases.json' assert { type: 'json' };
// Any changes to the file will need to be aware of the fact
// the file is shared between noble-curves and noble-ed25519.
describe('ed25519', () => { describe('ed25519', () => {
const ed = ed25519;
const hex = bytesToHex; const hex = bytesToHex;
const Point = ed.ExtendedPoint; const Point = ed.ExtendedPoint;
@@ -20,13 +22,6 @@ describe('ed25519', () => {
return hexToBytes(hex.padStart(64, '0')); return hexToBytes(hex.padStart(64, '0'));
} }
function utf8ToBytes(str) {
if (typeof str !== 'string') {
throw new Error(`utf8ToBytes expected string, got ${typeof str}`);
}
return new TextEncoder().encode(str);
}
ed.utils.precompute(8); ed.utils.precompute(8);
should('not accept >32byte private keys', () => { should('not accept >32byte private keys', () => {
@@ -415,27 +410,36 @@ describe('ed25519', () => {
} }
}); });
should('have strict SUF-CMA and SBS properties', () => {
// https://eprint.iacr.org/2020/1244
const list = [0, 1, 6, 7, 8, 9, 10, 11].map((i) => edgeCases[i]);
for (let v of list) {
const result = ed.verify(v.signature, v.message, v.pub_key, { zip215: false });
strictEqual(result, false, `zip215: false must not validate: ${v.signature}`);
}
});
should('not verify when sig.s >= CURVE.n', () => { should('not verify when sig.s >= CURVE.n', () => {
const privateKey = ed25519.utils.randomPrivateKey(); const privateKey = ed.utils.randomPrivateKey();
const message = Uint8Array.from([0xab, 0xbc, 0xcd, 0xde]); const message = Uint8Array.from([0xab, 0xbc, 0xcd, 0xde]);
const publicKey = ed25519.getPublicKey(privateKey); const publicKey = ed.getPublicKey(privateKey);
const signature = ed25519.sign(message, privateKey); const signature = ed.sign(message, privateKey);
const R = signature.slice(0, 32); const R = signature.slice(0, 32);
let s = signature.slice(32, 64); let s = signature.slice(32, 64);
s = bytesToHex(s.slice().reverse()); s = bytesToHex(s.slice().reverse());
s = BigInt('0x' + s); s = BigInt('0x' + s);
s = s + ed25519.CURVE.n; s = s + ed.CURVE.n;
s = numberToBytesLE(s, 32); s = numberToBytesLE(s, 32);
const sig_invalid = concatBytes(R, s); const sig_invalid = concatBytes(R, s);
deepStrictEqual(ed25519.verify(sig_invalid, message, publicKey), false); deepStrictEqual(ed.verify(sig_invalid, message, publicKey), false);
}); });
should('not accept point without z, t', () => { should('not accept point without z, t', () => {
const t = 81718630521762619991978402609047527194981150691135404693881672112315521837062n; const t = 81718630521762619991978402609047527194981150691135404693881672112315521837062n;
const point = ed25519.ExtendedPoint.fromAffine({ x: t, y: t }); const point = Point.fromAffine({ x: t, y: t });
throws(() => point.assertValidity()); throws(() => point.assertValidity());
// Otherwise (without assertValidity): // Otherwise (without assertValidity):
// const point2 = point.double(); // const point2 = point.double();

View File

@@ -0,0 +1 @@
[{"message":"8c93255d71dcab10e8f379c26200f3c7bd5f09d9bc3068d3ef4edeb4853022b6","pub_key":"c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac03fa","signature":"c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac037a0000000000000000000000000000000000000000000000000000000000000000"},{"message":"9bd9f44f4dcc75bd531b56b2cd280b0bb38fc1cd6d1230e14861d861de092e79","pub_key":"c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac03fa","signature":"f7badec5b8abeaf699583992219b7b223f1df3fbbea919844e3f7c554a43dd43a5bb704786be79fc476f91d3f3f89b03984d8068dcf1bb7dfc6637b45450ac04"},{"message":"aebf3f2601a0c8c5d39cc7d8911642f740b78168218da8471772b35f9d35b9ab","pub_key":"f7badec5b8abeaf699583992219b7b223f1df3fbbea919844e3f7c554a43dd43","signature":"c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac03fa8c4bd45aecaca5b24fb97bc10ac27ac8751a7dfe1baff8b953ec9f5833ca260e"},{"message":"9bd9f44f4dcc75bd531b56b2cd280b0bb38fc1cd6d1230e14861d861de092e79","pub_key":"cdb267ce40c5cd45306fa5d2f29731459387dbf9eb933b7bd5aed9a765b88d4d","signature":"9046a64750444938de19f227bb80485e92b83fdb4b6506c160484c016cc1852f87909e14428a7a1d62e9f22f3d3ad7802db02eb2e688b6c52fcd6648a98bd009"},{"message":"e47d62c63f830dc7a6851a0b1f33ae4bb2f507fb6cffec4011eaccd55b53f56c","pub_key":"cdb267ce40c5cd45306fa5d2f29731459387dbf9eb933b7bd5aed9a765b88d4d","signature":"160a1cb0dc9c0258cd0a7d23e94d8fa878bcb1925f2c64246b2dee1796bed5125ec6bc982a269b723e0668e540911a9a6a58921d6925e434ab10aa7940551a09"},{"message":"e47d62c63f830dc7a6851a0b1f33ae4bb2f507fb6cffec4011eaccd55b53f56c","pub_key":"cdb267ce40c5cd45306fa5d2f29731459387dbf9eb933b7bd5aed9a765b88d4d","signature":"21122a84e0b5fca4052f5b1235c80a537878b38f3142356b2c2384ebad4668b7e40bc836dac0f71076f9abe3a53f9c03c1ceeeddb658d0030494ace586687405"},{"message":"85e241a07d148b41e47d62c63f830dc7a6851a0b1f33ae4bb2f507fb6cffec40","pub_key":"442aad9f089ad9e14647b1ef9099a1ff4798d78589e66f28eca69c11f582a623","signature":"e96f66be976d82e60150baecff9906684aebb1ef181f67a7189ac78ea23b6c0e547f7690a0e2ddcd04d87dbc3490dc19b3b3052f7ff0538cb68afb369ba3a514"},{"message":"85e241a07d148b41e47d62c63f830dc7a6851a0b1f33ae4bb2f507fb6cffec40","pub_key":"442aad9f089ad9e14647b1ef9099a1ff4798d78589e66f28eca69c11f582a623","signature":"8ce5b96c8f26d0ab6c47958c9e68b937104cd36e13c33566acd2fe8d38aa19427e71f98a473474f2f13f06f97c20d58cc3f54b8bd0d272f42b695dd7e89a8c22"},{"message":"9bedc267423725d473888631ebf45988bad3db83851ee85c85e241a07d148b41","pub_key":"f7badec5b8abeaf699583992219b7b223f1df3fbbea919844e3f7c554a43dd43","signature":"ecffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff03be9678ac102edcd92b0210bb34d7428d12ffc5df5f37e359941266a4e35f0f"},{"message":"9bedc267423725d473888631ebf45988bad3db83851ee85c85e241a07d148b41","pub_key":"f7badec5b8abeaf699583992219b7b223f1df3fbbea919844e3f7c554a43dd43","signature":"ecffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffca8c5b64cd208982aa38d4936621a4775aa233aa0505711d8fdcfdaa943d4908"},{"message":"e96b7021eb39c1a163b6da4e3093dcd3f21387da4cc4572be588fafae23c155b","pub_key":"ecffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","signature":"a9d55260f765261eb9b84e106f665e00b867287a761990d7135963ee0a7d59dca5bb704786be79fc476f91d3f3f89b03984d8068dcf1bb7dfc6637b45450ac04"},{"message":"39a591f5321bbe07fd5a23dc2f39d025d74526615746727ceefd6e82ae65c06f","pub_key":"ecffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","signature":"a9d55260f765261eb9b84e106f665e00b867287a761990d7135963ee0a7d59dca5bb704786be79fc476f91d3f3f89b03984d8068dcf1bb7dfc6637b45450ac04"}]

View File

@@ -5,6 +5,7 @@ import './basic.test.js';
import './nist.test.js'; import './nist.test.js';
import './ed448.test.js'; import './ed448.test.js';
import './ed25519.test.js'; import './ed25519.test.js';
import './ed25519-addons.test.js';
import './secp256k1.test.js'; import './secp256k1.test.js';
import './secp256k1-schnorr.test.js'; import './secp256k1-schnorr.test.js';
import './jubjub.test.js'; import './jubjub.test.js';

View File

@@ -10,6 +10,7 @@ import { hexToBytes, bytesToHex } from '../esm/abstract/utils.js';
import { default as ecdsa } from './wycheproof/ecdsa_test.json' assert { type: 'json' }; import { default as ecdsa } from './wycheproof/ecdsa_test.json' assert { type: 'json' };
import { default as ecdh } from './wycheproof/ecdh_test.json' assert { type: 'json' }; import { default as ecdh } from './wycheproof/ecdh_test.json' assert { type: 'json' };
import { default as rfc6979 } from './vectors/rfc6979.json' assert { type: 'json' }; import { default as rfc6979 } from './vectors/rfc6979.json' assert { type: 'json' };
import { default as endoVectors } from './vectors/secp256k1/endomorphism.json' assert { type: 'json' };
import { default as ecdh_secp224r1_test } from './wycheproof/ecdh_secp224r1_test.json' assert { type: 'json' }; import { default as ecdh_secp224r1_test } from './wycheproof/ecdh_secp224r1_test.json' assert { type: 'json' };
import { default as ecdh_secp256r1_test } from './wycheproof/ecdh_secp256r1_test.json' assert { type: 'json' }; import { default as ecdh_secp256r1_test } from './wycheproof/ecdh_secp256r1_test.json' assert { type: 'json' };
@@ -438,7 +439,7 @@ describe('RFC6979', () => {
} }
}); });
should('DER Leading zero', () => { should('properly add leading zero to DER', () => {
// Valid DER // Valid DER
deepStrictEqual( deepStrictEqual(
DER.toSig( DER.toSig(
@@ -465,6 +466,16 @@ should('DER Leading zero', () => {
); );
}); });
should('have proper GLV endomorphism logic in secp256k1', () => {
const Point = secp256k1.ProjectivePoint;
for (let item of endoVectors) {
const point = Point.fromAffine({ x: BigInt(item.ax), y: BigInt(item.ay) });
const c = point.multiplyUnsafe(BigInt(item.scalar)).toAffine();
deepStrictEqual(c.x, BigInt(item.cx));
deepStrictEqual(c.y, BigInt(item.cy));
}
});
// ESM is broken. // ESM is broken.
import url from 'url'; import url from 'url';
if (import.meta.url === url.pathToFileURL(process.argv[1]).href) { if (import.meta.url === url.pathToFileURL(process.argv[1]).href) {

View File

@@ -14,6 +14,9 @@ import { default as privates } from './vectors/secp256k1/privates.json' assert {
import { default as points } from './vectors/secp256k1/points.json' assert { type: 'json' }; import { default as points } from './vectors/secp256k1/points.json' assert { type: 'json' };
import { default as wp } from './wycheproof/ecdsa_secp256k1_sha256_test.json' assert { type: 'json' }; import { default as wp } from './wycheproof/ecdsa_secp256k1_sha256_test.json' assert { type: 'json' };
// Any changes to the file will need to be aware of the fact
// the file is shared between noble-curves and noble-secp256k1.
const Point = secp.ProjectivePoint; const Point = secp.ProjectivePoint;
const privatesTxt = readFileSync('./test/vectors/secp256k1/privates-2.txt', 'utf-8'); const privatesTxt = readFileSync('./test/vectors/secp256k1/privates-2.txt', 'utf-8');

View File

@@ -0,0 +1,26 @@
[
{
"desc": "k1neg=true, k2neg=false",
"ax": "55066263022277343669578718895168534326250603453777594175500187360389116729240",
"ay": "32670510020758816978083085130507043184471273380659243275938904335757337482424",
"scalar": "2704427838213584814824020837927043695889",
"cx": "70912011419250646761259860556624974262679413898110209707622032756145750038852",
"cy": "46481114889376149700487001434152190585794282401306514438088690968308506923285"
},
{
"desc": "k1neg=false, k2neg=true",
"ax": "55066263022277343669578718895168534326250603453777594175500187360389116729240",
"ay": "32670510020758816978083085130507043184471273380659243275938904335757337482424",
"scalar": "367917413016453100223835821029139468248",
"cx": "10322688129782350538653828383726187034025074756440739323015371090593152139135",
"cy": "68793242610611269092604721689053086352541804982835045879816374698216278704126"
},
{
"desc": "k1neg=true, k2neg=true",
"ax": "55066263022277343669578718895168534326250603453777594175500187360389116729240",
"ay": "32670510020758816978083085130507043184471273380659243275938904335757337482424",
"scalar": "3808180077262944115495528301014462100633",
"cx": "14215418389480067884450074673878587420586762919133643262861030012154939932102",
"cy": "29847359538023735520768762420255189621104408153695873716448888266404867737302"
}
]