Compare commits
52 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
62e806cfaf | ||
|
|
6a72821185 | ||
|
|
8cee1f559f | ||
|
|
6f10632ac0 | ||
|
|
b281167e8d | ||
|
|
c6b4aadafb | ||
|
|
aade023e48 | ||
|
|
2e04d96ce9 | ||
|
|
79dd7d3426 | ||
|
|
ff5b231e31 | ||
|
|
648fd2cc07 | ||
|
|
f67134ca86 | ||
|
|
6d0678b076 | ||
|
|
53ebde19ea | ||
|
|
a7755332c8 | ||
|
|
5f0007ab24 | ||
|
|
1ee5a5c07f | ||
|
|
708c0e14d5 | ||
|
|
624d7c9910 | ||
|
|
665ef2dd93 | ||
|
|
acc1f26acf | ||
|
|
3c4a25263e | ||
|
|
e887d516ab | ||
|
|
90e87f7ab1 | ||
|
|
5edafbac97 | ||
|
|
554c94509e | ||
|
|
7c11a021c0 | ||
|
|
531b6a3a48 | ||
|
|
fb5cd9df39 | ||
|
|
53a6d636d4 | ||
|
|
42de620010 | ||
|
|
6621053c7d | ||
|
|
9bee88888f | ||
|
|
103ba5f0a7 | ||
|
|
d5de5d2659 | ||
|
|
217cf8c654 | ||
|
|
8e307d8f89 | ||
|
|
8c0018d57f | ||
|
|
ca7f202839 | ||
|
|
816077ac0a | ||
|
|
bc03a07043 | ||
|
|
63653255e1 | ||
|
|
895ee3a1a4 | ||
|
|
16b31b9087 | ||
|
|
213796db4b | ||
|
|
049d3bce54 | ||
|
|
b2a04c2393 | ||
|
|
cb5e9a6e96 | ||
|
|
36af62357f | ||
|
|
88291eba33 | ||
|
|
848a1b0226 | ||
|
|
972e549dde |
9
.github/workflows/nodejs.yml
vendored
9
.github/workflows/nodejs.yml
vendored
@@ -3,15 +3,18 @@ name: Node CI
|
||||
on: [push, pull_request]
|
||||
jobs:
|
||||
test:
|
||||
name: v18 @ ubuntu-latest
|
||||
name: v${{ matrix.node }} @ ubuntu-latest
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
node: [18, 20]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Use Node.js ${{ matrix.node }}
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18
|
||||
node-version: ${{ matrix.node }}
|
||||
- run: npm install
|
||||
- run: npm run build --if-present
|
||||
- run: npm run lint --if-present
|
||||
- run: npm test
|
||||
- run: npm run lint --if-present
|
||||
|
||||
23
.github/workflows/publish-npm.yml
vendored
Normal file
23
.github/workflows/publish-npm.yml
vendored
Normal 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 }}
|
||||
275
README.md
275
README.md
@@ -5,9 +5,10 @@ Audited & minimal JS implementation of elliptic curve cryptography.
|
||||
- 🔒 [**Audited**](#security) by an independent security firm
|
||||
- 🔻 Tree-shaking-friendly: use only what's necessary, other code won't be included
|
||||
- 🏎 Ultra-fast, hand-optimized for caveats of JS engines
|
||||
- 🔍 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
|
||||
- ✍️ ECDSA, EdDSA, Schnorr, BLS signature schemes, ECDH key agreement
|
||||
- 🔖 SUF-CMA and SBS (non-repudiation) for ed25519, ed448 and others
|
||||
- #️⃣ Hash-to-curve
|
||||
for encoding or hashing an arbitrary string to an elliptic curve point
|
||||
- 🧜♂️ Poseidon ZK-friendly hash
|
||||
@@ -31,41 +32,34 @@ packages. See [Resources](#resources) for articles and real-world software that
|
||||
|
||||
## Usage
|
||||
|
||||
Browser, deno and node.js are supported:
|
||||
|
||||
> npm install @noble/curves
|
||||
|
||||
For [Deno](https://deno.land), use it with
|
||||
[npm specifier](https://deno.land/manual@v1.28.0/node/npm_specifiers).
|
||||
In browser, you could also include the single file from
|
||||
[GitHub's releases page](https://github.com/paulmillr/noble-curves/releases).
|
||||
We support all major platforms and runtimes.
|
||||
For [Deno](https://deno.land), ensure to use [npm specifier](https://deno.land/manual@v1.28.0/node/npm_specifiers).
|
||||
For React Native, you may need a [polyfill for crypto.getRandomValues](https://github.com/LinusU/react-native-get-random-values).
|
||||
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
|
||||
`import c from '@noble/curves'`. Instead, you need to import specific primitives.
|
||||
The library is tree-shaking-friendly and does not expose root entry point as
|
||||
`@noble/curves`. Instead, you need to import specific primitives.
|
||||
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:
|
||||
- NIST curves secp256r1 / p256, secp384r1 / p384, secp521r1 / p521
|
||||
- SECG curve secp256k1
|
||||
- ed25519 / curve25519 / x25519 / ristretto255,
|
||||
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
|
||||
- ed25519 / curve25519 / x25519 / ristretto255, edwards448 / curve448 / x448
|
||||
- pairing-friendly curves bls12-381, bn254
|
||||
- [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
|
||||
|
||||
Each curve can be used in the following way:
|
||||
#### Generic example for all curves, secp256k1
|
||||
|
||||
```ts
|
||||
// Each curve has similar methods
|
||||
import { secp256k1 } from '@noble/curves/secp256k1'; // ESM and Common.js
|
||||
// import { secp256k1 } from 'npm:@noble/curves@1.2.0/secp256k1'; // Deno
|
||||
const priv = secp256k1.utils.randomPrivateKey();
|
||||
@@ -79,7 +73,7 @@ const privHex = '46c930bc7bb4db7f55da20798697421b98c4175a52c630294d75a84b9c12623
|
||||
const pub2 = secp256k1.getPublicKey(privHex);
|
||||
```
|
||||
|
||||
All curves:
|
||||
#### All imports
|
||||
|
||||
```typescript
|
||||
import { secp256k1, schnorr } from '@noble/curves/secp256k1';
|
||||
@@ -94,7 +88,7 @@ import { bn254 } from '@noble/curves/bn254';
|
||||
import { jubjub } from '@noble/curves/jubjub';
|
||||
```
|
||||
|
||||
Recovering public keys from weierstrass ECDSA signatures; using ECDH:
|
||||
#### ECDSA public key recovery & ECDH
|
||||
|
||||
```ts
|
||||
// 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
|
||||
```
|
||||
|
||||
Schnorr signatures over secp256k1 following
|
||||
[BIP340](https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki):
|
||||
#### Schnorr signatures over secp256k1 (BIP340)
|
||||
|
||||
```ts
|
||||
import { schnorr } from '@noble/curves/secp256k1';
|
||||
@@ -116,12 +109,7 @@ const sig = schnorr.sign(msg, priv);
|
||||
const isValid = schnorr.verify(sig, msg, pub);
|
||||
```
|
||||
|
||||
ed25519 module has ed25519ctx / ed25519ph variants,
|
||||
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.
|
||||
#### ed25519, X25519, ristretto255
|
||||
|
||||
```ts
|
||||
import { ed25519 } from '@noble/curves/ed25519';
|
||||
@@ -131,7 +119,19 @@ const msg = new TextEncoder().encode('hello');
|
||||
const sig = ed25519.sign(msg, priv);
|
||||
ed25519.verify(sig, msg, pub); // Default mode: follows ZIP215
|
||||
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
|
||||
import { ed25519ctx, ed25519ph } from '@noble/curves/ed25519';
|
||||
|
||||
@@ -141,11 +141,15 @@ const priv = 'a546e36bf0527c9d3b16154b82465edd62144c0ac1fc5a18506a2244ba449ac4';
|
||||
const pub = 'e6db6867583030db3594c1a424b15f7c726624ec26b3353b10a903a6d0ab1c4c';
|
||||
x25519.getSharedSecret(priv, pub) === x25519.scalarMult(priv, pub); // aliases
|
||||
x25519.getPublicKey(priv) === x25519.scalarMultBase(priv);
|
||||
x25519.getPublicKey(x25519.utils.randomPrivateKey());
|
||||
|
||||
// hash-to-curve
|
||||
import { hashToCurve, encodeToCurve } from '@noble/curves/ed25519';
|
||||
// ed25519 => x25519 conversion
|
||||
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(
|
||||
'6a493210f7499cd17fecb510ae0cea23a110e8d5b901f8acadd3095c73a3b919'
|
||||
);
|
||||
@@ -153,19 +157,36 @@ RistrettoPoint.hashToCurve('Ristretto is traditionally a short shot of espresso
|
||||
// also has add(), equals(), multiply(), toRawBytes() methods
|
||||
```
|
||||
|
||||
ed448 is similar:
|
||||
#### ed448, X448
|
||||
|
||||
```ts
|
||||
import { ed448, ed448ph, ed448ctx, x448 } from '@noble/curves/ed448';
|
||||
import { hashToCurve, encodeToCurve } from '@noble/curves/ed448';
|
||||
ed448.getPublicKey(ed448.utils.randomPrivateKey());
|
||||
import { ed448 } from '@noble/curves/ed448';
|
||||
const priv = 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
|
||||
import { secp256k1 } from '@noble/curves/secp256k1'; // ESM and Common.js
|
||||
console.log(secp256k1.CURVE.p, secp256k1.CURVE.n, secp256k1.CURVE.a, secp256k1.CURVE.b);
|
||||
import { secp256k1 } from '@noble/curves/secp256k1';
|
||||
// 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
|
||||
@@ -554,7 +575,7 @@ aggregateSignatures: {
|
||||
millerLoop: (ell: [Fp2, Fp2, Fp2][], g1: [Fp, Fp]) => Fp12;
|
||||
pairing: (P: ProjPointType<Fp>, Q: ProjPointType<Fp2>, withFinalExponent?: boolean) => Fp12;
|
||||
G1: CurvePointsRes<Fp> & ReturnType<typeof htf.createHasher<Fp>>;
|
||||
G2: CurvePointsRes<Fp2> & ReturnType<typeof htf.createHasher<Fp2>>;
|
||||
G2: CurvePointsRes<Fp2> & ReturnType<typeof htf.createHasher<Fp2>>;
|
||||
Signature: SignatureCoder<Fp2>;
|
||||
params: {
|
||||
x: bigint;
|
||||
@@ -567,7 +588,7 @@ fields: {
|
||||
Fp2: IField<Fp2>;
|
||||
Fp6: IField<Fp6>;
|
||||
Fp12: IField<Fp12>;
|
||||
Fr: IField<bigint>;
|
||||
Fr: IField<bigint>;
|
||||
};
|
||||
utils: {
|
||||
randomPrivateKey: () => Uint8Array;
|
||||
@@ -734,9 +755,9 @@ utils.equalBytes(Uint8Array.from([0xde]), Uint8Array.from([0xde]));
|
||||
|
||||
## 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).
|
||||
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.
|
||||
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.
|
||||
- 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
|
||||
|
||||
Benchmark results on Apple M2 with node v19:
|
||||
Benchmark results on Apple M2 with node v20:
|
||||
|
||||
```
|
||||
secp256k1
|
||||
init x 58 ops/sec @ 17ms/op
|
||||
getPublicKey x 5,640 ops/sec @ 177μs/op
|
||||
sign x 4,471 ops/sec @ 223μs/op
|
||||
verify x 780 ops/sec @ 1ms/op
|
||||
getSharedSecret x 465 ops/sec @ 2ms/op
|
||||
recoverPublicKey x 740 ops/sec @ 1ms/op
|
||||
schnorr.sign x 597 ops/sec @ 1ms/op
|
||||
schnorr.verify x 775 ops/sec @ 1ms/op
|
||||
init x 68 ops/sec @ 14ms/op
|
||||
getPublicKey x 6,750 ops/sec @ 148μs/op
|
||||
sign x 5,206 ops/sec @ 192μs/op
|
||||
verify x 880 ops/sec @ 1ms/op
|
||||
getSharedSecret x 536 ops/sec @ 1ms/op
|
||||
recoverPublicKey x 852 ops/sec @ 1ms/op
|
||||
schnorr.sign x 685 ops/sec @ 1ms/op
|
||||
schnorr.verify x 908 ops/sec @ 1ms/op
|
||||
|
||||
P256
|
||||
init x 31 ops/sec @ 31ms/op
|
||||
getPublicKey x 5,607 ops/sec @ 178μs/op
|
||||
sign x 4,583 ops/sec @ 218μs/op
|
||||
verify x 540 ops/sec @ 1ms/op
|
||||
p256
|
||||
init x 38 ops/sec @ 26ms/op
|
||||
getPublicKey x 6,530 ops/sec @ 153μs/op
|
||||
sign x 5,074 ops/sec @ 197μs/op
|
||||
verify x 626 ops/sec @ 1ms/op
|
||||
|
||||
P384
|
||||
init x 15 ops/sec @ 63ms/op
|
||||
getPublicKey x 2,622 ops/sec @ 381μs/op
|
||||
sign x 2,106 ops/sec @ 474μs/op
|
||||
verify x 222 ops/sec @ 4ms/op
|
||||
p384
|
||||
init x 17 ops/sec @ 57ms/op
|
||||
getPublicKey x 2,883 ops/sec @ 346μs/op
|
||||
sign x 2,358 ops/sec @ 424μs/op
|
||||
verify x 245 ops/sec @ 4ms/op
|
||||
|
||||
P521
|
||||
init x 8 ops/sec @ 119ms/op
|
||||
getPublicKey x 1,371 ops/sec @ 729μs/op
|
||||
sign x 1,164 ops/sec @ 858μs/op
|
||||
verify x 118 ops/sec @ 8ms/op
|
||||
p521
|
||||
init x 9 ops/sec @ 109ms/op
|
||||
getPublicKey x 1,516 ops/sec @ 659μs/op
|
||||
sign x 1,271 ops/sec @ 786μs/op
|
||||
verify x 123 ops/sec @ 8ms/op
|
||||
|
||||
ed25519
|
||||
init x 47 ops/sec @ 20ms/op
|
||||
getPublicKey x 9,414 ops/sec @ 106μs/op
|
||||
sign x 4,516 ops/sec @ 221μs/op
|
||||
verify x 912 ops/sec @ 1ms/op
|
||||
init x 54 ops/sec @ 18ms/op
|
||||
getPublicKey x 10,269 ops/sec @ 97μs/op
|
||||
sign x 5,110 ops/sec @ 195μs/op
|
||||
verify x 1,049 ops/sec @ 952μs/op
|
||||
|
||||
ed448
|
||||
init x 17 ops/sec @ 56ms/op
|
||||
getPublicKey x 3,363 ops/sec @ 297μs/op
|
||||
sign x 1,615 ops/sec @ 619μs/op
|
||||
verify x 319 ops/sec @ 3ms/op
|
||||
init x 19 ops/sec @ 51ms/op
|
||||
getPublicKey x 3,775 ops/sec @ 264μs/op
|
||||
sign x 1,771 ops/sec @ 564μs/op
|
||||
verify x 351 ops/sec @ 2ms/op
|
||||
|
||||
ecdh
|
||||
├─x25519 x 1,337 ops/sec @ 747μs/op
|
||||
├─secp256k1 x 461 ops/sec @ 2ms/op
|
||||
├─P256 x 441 ops/sec @ 2ms/op
|
||||
├─P384 x 179 ops/sec @ 5ms/op
|
||||
├─P521 x 93 ops/sec @ 10ms/op
|
||||
└─x448 x 496 ops/sec @ 2ms/op
|
||||
├─x25519 x 1,466 ops/sec @ 682μs/op
|
||||
├─secp256k1 x 539 ops/sec @ 1ms/op
|
||||
├─p256 x 511 ops/sec @ 1ms/op
|
||||
├─p384 x 199 ops/sec @ 5ms/op
|
||||
├─p521 x 103 ops/sec @ 9ms/op
|
||||
└─x448 x 548 ops/sec @ 1ms/op
|
||||
|
||||
bls12-381
|
||||
init x 32 ops/sec @ 30ms/op
|
||||
getPublicKey 1-bit x 858 ops/sec @ 1ms/op
|
||||
getPublicKey x 858 ops/sec @ 1ms/op
|
||||
sign x 49 ops/sec @ 20ms/op
|
||||
verify x 34 ops/sec @ 28ms/op
|
||||
pairing x 94 ops/sec @ 10ms/op
|
||||
aggregatePublicKeys/8 x 116 ops/sec @ 8ms/op
|
||||
aggregatePublicKeys/32 x 31 ops/sec @ 31ms/op
|
||||
aggregatePublicKeys/128 x 7 ops/sec @ 125ms/op
|
||||
aggregateSignatures/8 x 45 ops/sec @ 22ms/op
|
||||
aggregateSignatures/32 x 11 ops/sec @ 84ms/op
|
||||
aggregateSignatures/128 x 3 ops/sec @ 332ms/opp
|
||||
init x 36 ops/sec @ 27ms/op
|
||||
getPublicKey 1-bit x 973 ops/sec @ 1ms/op
|
||||
getPublicKey x 970 ops/sec @ 1ms/op
|
||||
sign x 55 ops/sec @ 17ms/op
|
||||
verify x 39 ops/sec @ 25ms/op
|
||||
pairing x 106 ops/sec @ 9ms/op
|
||||
aggregatePublicKeys/8 x 129 ops/sec @ 7ms/op
|
||||
aggregatePublicKeys/32 x 34 ops/sec @ 28ms/op
|
||||
aggregatePublicKeys/128 x 8 ops/sec @ 112ms/op
|
||||
aggregatePublicKeys/512 x 2 ops/sec @ 446ms/op
|
||||
aggregatePublicKeys/2048 x 0 ops/sec @ 1778ms/op
|
||||
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_field x 850,340 ops/sec @ 1μs/op
|
||||
secp256k1 x 2,143 ops/sec @ 466μs/op
|
||||
P256 x 3,861 ops/sec @ 258μs/op
|
||||
P384 x 1,526 ops/sec @ 655μs/op
|
||||
P521 x 748 ops/sec @ 1ms/op
|
||||
ed25519 x 2,772 ops/sec @ 360μs/op
|
||||
ed448 x 1,146 ops/sec @ 871μs/op
|
||||
hash_to_field x 91,600 ops/sec @ 10μs/op
|
||||
secp256k1 x 2,373 ops/sec @ 421μs/op
|
||||
p256 x 4,310 ops/sec @ 231μs/op
|
||||
p384 x 1,664 ops/sec @ 600μs/op
|
||||
p521 x 807 ops/sec @ 1ms/op
|
||||
ed25519 x 3,088 ops/sec @ 323μs/op
|
||||
ed448 x 1,247 ops/sec @ 801μs/op
|
||||
```
|
||||
|
||||
## Contributing & testing
|
||||
@@ -836,9 +865,11 @@ ed448 x 1,146 ops/sec @ 871μs/op
|
||||
## Upgrading
|
||||
|
||||
Previously, the library was split into single-feature packages
|
||||
noble-secp256k1 and noble-ed25519. curves can be thought as a continuation of their
|
||||
original work. The libraries now changed their direction towards providing
|
||||
minimal 4kb implementations of cryptography and are not as feature-complete.
|
||||
noble-secp256k1, noble-ed25519 and noble-bls12-381.
|
||||
|
||||
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.
|
||||
|
||||
@@ -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
|
||||
`etc` (`sha512Sync` and others)
|
||||
- `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):
|
||||
|
||||
@@ -888,10 +920,13 @@ Upgrading from [@noble/bls12-381](https://github.com/paulmillr/noble-bls12-381):
|
||||
|
||||
## 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/)
|
||||
- [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
|
||||
- [BLS signatures for busy people](https://gist.github.com/paulmillr/18b802ad219b1aee34d773d08ec26ca2)
|
||||
- [BLS12-381 for the rest of us](https://hackmd.io/@benjaminion/bls12-381)
|
||||
- [Key concepts of pairings](https://medium.com/@alonmuroch_65570/bls-signatures-part-2-key-concepts-of-pairings-27a8a9533d0c)
|
||||
- Pairing over bls12-381:
|
||||
@@ -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/)
|
||||
- [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
|
||||
- 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
|
||||
- [micro-starknet](https://github.com/paulmillr/micro-starknet) stark-friendly elliptic curve algorithms.
|
||||
- BLS threshold sigs demo [genthresh.com](https://genthresh.com)
|
||||
- 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)
|
||||
- [secp256k1 compatibility layer](https://github.com/ethereum/js-ethereum-cryptography/blob/2.0.0/src/secp256k1-compat.ts)
|
||||
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](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)
|
||||
|
||||
## License
|
||||
|
||||
@@ -39,6 +39,21 @@ run(async () => {
|
||||
await mark('sign', 50, () => bls.sign('09', priv));
|
||||
await mark('verify', 50, () => bls.verify(sig, '09', pub));
|
||||
await mark('pairing', 100, () => bls.pairing(p1, p2));
|
||||
|
||||
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/32', 50, () => bls.aggregatePublicKeys(pub32));
|
||||
await mark('aggregatePublicKeys/128', 20, () => bls.aggregatePublicKeys(pub128));
|
||||
|
||||
@@ -8,7 +8,7 @@ import { ed448 } from '../ed448.js';
|
||||
|
||||
run(async () => {
|
||||
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;
|
||||
console.log();
|
||||
console.log(`\x1b[36m${name}\x1b[0m`);
|
||||
|
||||
7
build/README.md
Normal file
7
build/README.md
Normal 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
11
build/input.js
Normal 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
18
build/package.json
Normal 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"
|
||||
}
|
||||
}
|
||||
33
package-lock.json
generated
33
package-lock.json
generated
@@ -1,21 +1,15 @@
|
||||
{
|
||||
"name": "@noble/curves",
|
||||
"version": "1.0.0",
|
||||
"version": "1.1.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@noble/curves",
|
||||
"version": "1.0.0",
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
],
|
||||
"version": "1.1.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@noble/hashes": "1.3.0"
|
||||
"@noble/hashes": "1.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"fast-check": "3.0.0",
|
||||
@@ -23,18 +17,21 @@
|
||||
"micro-should": "0.4.0",
|
||||
"prettier": "2.8.4",
|
||||
"typescript": "5.0.2"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/@noble/hashes": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.0.tgz",
|
||||
"integrity": "sha512-ilHEACi9DwqJB0pw7kv+Apvh50jiiSyR/cQ3y4W7lOR5mhvn/50FLUfsnfJz0BDZtl/RR16kXvptiv6q1msYZg==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
]
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.1.tgz",
|
||||
"integrity": "sha512-EbqwksQwz9xDRGfDST86whPBgM65E0OH/pCgqW0GBVzO22bNE+NuIbeTb714+IfSjU3aRk47EUvXIb5bTsenKA==",
|
||||
"engines": {
|
||||
"node": ">= 16"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/fast-check": {
|
||||
"version": "3.0.0",
|
||||
|
||||
13
package.json
13
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@noble/curves",
|
||||
"version": "1.0.0",
|
||||
"version": "1.1.0",
|
||||
"description": "Audited & minimal JS implementation of elliptic curve cryptography",
|
||||
"files": [
|
||||
"abstract",
|
||||
@@ -28,7 +28,7 @@
|
||||
},
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@noble/hashes": "1.3.0"
|
||||
"@noble/hashes": "1.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"fast-check": "3.0.0",
|
||||
@@ -164,6 +164,8 @@
|
||||
"secp256k1",
|
||||
"ed25519",
|
||||
"ed448",
|
||||
"x25519",
|
||||
"ed25519",
|
||||
"bls12-381",
|
||||
"bn254",
|
||||
"pasta",
|
||||
@@ -174,10 +176,5 @@
|
||||
"eddsa",
|
||||
"schnorr"
|
||||
],
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
]
|
||||
"funding": "https://paulmillr.com/funding/"
|
||||
}
|
||||
@@ -75,8 +75,13 @@ export interface ExtPointConstructor extends GroupConstructor<ExtPointType> {
|
||||
export type CurveFn = {
|
||||
CURVE: ReturnType<typeof validateOpts>;
|
||||
getPublicKey: (privateKey: Hex) => Uint8Array;
|
||||
sign: (message: Hex, privateKey: Hex) => Uint8Array;
|
||||
verify: (sig: Hex, message: Hex, publicKey: Hex) => boolean;
|
||||
sign: (message: Hex, privateKey: Hex, options?: { context?: Hex }) => Uint8Array;
|
||||
verify: (
|
||||
sig: Hex,
|
||||
message: Hex,
|
||||
publicKey: Hex,
|
||||
options?: { context?: Hex; zip215: boolean }
|
||||
) => boolean;
|
||||
ExtendedPoint: ExtPointConstructor;
|
||||
utils: {
|
||||
randomPrivateKey: () => Uint8Array;
|
||||
@@ -102,7 +107,7 @@ export function twistedEdwards(curveDef: CurveType): CurveFn {
|
||||
nByteLength,
|
||||
h: cofactor,
|
||||
} = CURVE;
|
||||
const MASK = _2n ** BigInt(nByteLength * 8);
|
||||
const MASK = _2n << (BigInt(nByteLength * 8) - _1n);
|
||||
const modP = Fp.create; // Function overrides
|
||||
|
||||
// sqrt(u/v)
|
||||
@@ -379,7 +384,10 @@ export function twistedEdwards(curveDef: CurveType): CurveFn {
|
||||
let { isValid, value: x } = uvRatio(u, v); // √(u/v)
|
||||
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 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
|
||||
return Point.fromAffine({ x, y });
|
||||
}
|
||||
@@ -466,6 +474,7 @@ export function twistedEdwards(curveDef: CurveType): CurveFn {
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
if (!zip215 && A.isSmallOrder()) return false;
|
||||
|
||||
const k = hashDomainToScalar(context, R.toRawBytes(), A.toRawBytes(), msg);
|
||||
const RkA = R.add(A.multiplyUnsafe(k));
|
||||
|
||||
@@ -22,10 +22,10 @@ export function mod(a: bigint, b: bigint): bigint {
|
||||
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.
|
||||
* @example
|
||||
* powMod(2n, 6n, 11n) // 64n % 11n == 9n
|
||||
* pow(2n, 6n, 11n) // 64n % 11n == 9n
|
||||
*/
|
||||
// TODO: use field version && remove
|
||||
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) {
|
||||
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.
|
||||
let a = mod(number, modulo);
|
||||
let b = modulo;
|
||||
@@ -198,10 +198,6 @@ export function FpSqrt(P: bigint) {
|
||||
// Little-endian check for first LE bit (last BE bit);
|
||||
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
|
||||
export interface IField<T> {
|
||||
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.
|
||||
* 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.
|
||||
* 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 groupOrder size of subgroup - (e.g. curveFn.CURVE.n)
|
||||
* @param isLE interpret hash bytes as LE num
|
||||
* @returns valid private scalar
|
||||
*/
|
||||
export function hashToPrivateScalar(
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
/*! 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 _1n = BigInt(1);
|
||||
const _2n = BigInt(2);
|
||||
const u8a = (a: any): a is Uint8Array => a instanceof Uint8Array;
|
||||
|
||||
// We accept hex strings besides Uint8Array for simplicity
|
||||
export type Hex = Uint8Array | string;
|
||||
// Very few implementations accept numbers, we do it to ease learning curve
|
||||
export type PrivKey = Hex | bigint;
|
||||
export type Hex = Uint8Array | string; // hex strings are accepted for simplicity
|
||||
export type PrivKey = Hex | bigint; // bigints are accepted to ease learning curve
|
||||
export type CHash = {
|
||||
(message: Uint8Array | string): Uint8Array;
|
||||
blockLen: number;
|
||||
@@ -17,6 +18,9 @@ export type CHash = {
|
||||
export type FHash = (message: Uint8Array | string) => Uint8Array;
|
||||
|
||||
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 {
|
||||
if (!u8a(bytes)) throw new Error('Uint8Array expected');
|
||||
// pre-caching improves the speed 6x
|
||||
@@ -38,22 +42,25 @@ export function hexToNumber(hex: string): bigint {
|
||||
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 {
|
||||
if (typeof hex !== 'string') throw new Error('hex string expected, got ' + typeof hex);
|
||||
if (hex.length % 2) throw new Error('hex string is invalid: unpadded ' + hex.length);
|
||||
const array = new Uint8Array(hex.length / 2);
|
||||
const len = hex.length;
|
||||
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++) {
|
||||
const j = i * 2;
|
||||
const hexByte = hex.slice(j, j + 2);
|
||||
const byte = Number.parseInt(hexByte, 16);
|
||||
if (Number.isNaN(byte) || byte < 0) throw new Error('invalid byte sequence');
|
||||
if (Number.isNaN(byte) || byte < 0) throw new Error('Invalid byte sequence');
|
||||
array[i] = byte;
|
||||
}
|
||||
return array;
|
||||
}
|
||||
|
||||
// Big Endian
|
||||
// BE: Big Endian, LE: Little Endian
|
||||
export function bytesToNumberBE(bytes: Uint8Array): bigint {
|
||||
return hexToNumber(bytesToHex(bytes));
|
||||
}
|
||||
@@ -62,12 +69,26 @@ export function bytesToNumberLE(bytes: Uint8Array): bigint {
|
||||
return hexToNumber(bytesToHex(Uint8Array.from(bytes).reverse()));
|
||||
}
|
||||
|
||||
export const numberToBytesBE = (n: bigint, len: number) =>
|
||||
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 const numberToVarBytesBE = (n: bigint) => hexToBytes(numberToHexUnpadded(n));
|
||||
export function numberToBytesBE(n: number | bigint, len: number): Uint8Array {
|
||||
return hexToBytes(n.toString(16).padStart(len * 2, '0'));
|
||||
}
|
||||
export function numberToBytesLE(n: number | bigint, len: number): Uint8Array {
|
||||
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 {
|
||||
let res: Uint8Array;
|
||||
if (typeof hex === 'string') {
|
||||
@@ -89,11 +110,13 @@ export function ensureBytes(title: string, hex: Hex, expectedLength?: number): U
|
||||
return res;
|
||||
}
|
||||
|
||||
// Copies several Uint8Arrays into one.
|
||||
export function concatBytes(...arrs: Uint8Array[]): Uint8Array {
|
||||
const r = new Uint8Array(arrs.reduce((sum, a) => sum + a.length, 0));
|
||||
/**
|
||||
* Copies several Uint8Arrays into one.
|
||||
*/
|
||||
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
|
||||
arrs.forEach((a) => {
|
||||
arrays.forEach((a) => {
|
||||
if (!u8a(a)) throw new Error('Uint8Array expected');
|
||||
r.set(a, pad);
|
||||
pad += a.length;
|
||||
@@ -111,29 +134,47 @@ export function equalBytes(b1: Uint8Array, b2: Uint8Array) {
|
||||
// Global symbols in both browsers and Node.js since v11
|
||||
// See https://github.com/microsoft/TypeScript/issues/31535
|
||||
declare const TextEncoder: any;
|
||||
|
||||
/**
|
||||
* @example utf8ToBytes('abc') // new Uint8Array([97, 98, 99])
|
||||
*/
|
||||
export function utf8ToBytes(str: string): Uint8Array {
|
||||
if (typeof str !== 'string') {
|
||||
throw new Error(`utf8ToBytes expected string, got ${typeof str}`);
|
||||
}
|
||||
return new TextEncoder().encode(str);
|
||||
if (typeof str !== 'string') throw new Error(`utf8ToBytes expected string, got ${typeof str}`);
|
||||
return new Uint8Array(new TextEncoder().encode(str)); // https://bugzil.la/1681809
|
||||
}
|
||||
|
||||
// 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) {
|
||||
let len;
|
||||
for (len = 0; n > _0n; n >>= _1n, len += 1);
|
||||
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;
|
||||
// Sets single bit at position
|
||||
export const bitSet = (n: bigint, pos: number, value: boolean) =>
|
||||
n | ((value ? _1n : _0n) << BigInt(pos));
|
||||
// Return mask for N bits (Same as BigInt(`0b${Array(i).fill('1').join('')}`))
|
||||
// Not using ** operator with bigints for old engines.
|
||||
|
||||
/**
|
||||
* Gets single bit at position.
|
||||
* NOTE: first bit position is 0 (same as arrays)
|
||||
* Same as `!!+Array.from(n.toString(2)).reverse()[pos]`
|
||||
*/
|
||||
export function bitGet(n: bigint, pos: number) {
|
||||
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;
|
||||
|
||||
// DRBG
|
||||
|
||||
@@ -618,7 +618,7 @@ export interface SignatureType {
|
||||
readonly s: bigint;
|
||||
readonly recovery?: number;
|
||||
assertValidity(): void;
|
||||
addRecoveryBit(recovery: number): SignatureType;
|
||||
addRecoveryBit(recovery: number): RecoveredSignatureType;
|
||||
hasHighS(): boolean;
|
||||
normalizeS(): SignatureType;
|
||||
recoverPublicKey(msgHash: Hex): ProjPointType<bigint>;
|
||||
@@ -628,6 +628,9 @@ export interface SignatureType {
|
||||
toDERRawBytes(isCompressed?: boolean): Uint8Array;
|
||||
toDERHex(isCompressed?: boolean): string;
|
||||
}
|
||||
export type RecoveredSignatureType = SignatureType & {
|
||||
readonly recovery: number;
|
||||
};
|
||||
// Static methods
|
||||
export type SignatureConstructor = {
|
||||
new (r: bigint, s: bigint): SignatureType;
|
||||
@@ -669,7 +672,7 @@ export type CurveFn = {
|
||||
CURVE: ReturnType<typeof validateOpts>;
|
||||
getPublicKey: (privateKey: PrivKey, isCompressed?: boolean) => Uint8Array;
|
||||
getSharedSecret: (privateA: PrivKey, publicB: Hex, isCompressed?: boolean) => Uint8Array;
|
||||
sign: (msgHash: Hex, privKey: PrivKey, opts?: SignOpts) => SignatureType;
|
||||
sign: (msgHash: Hex, privKey: PrivKey, opts?: SignOpts) => RecoveredSignatureType;
|
||||
verify: (signature: Hex | SignatureLike, msgHash: Hex, publicKey: Hex, opts?: VerOpts) => boolean;
|
||||
ProjectivePoint: ProjConstructor<bigint>;
|
||||
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');
|
||||
}
|
||||
|
||||
addRecoveryBit(recovery: number) {
|
||||
return new Signature(this.r, this.s, recovery);
|
||||
addRecoveryBit(recovery: number): RecoveredSignature {
|
||||
return new Signature(this.r, this.s, recovery) as RecoveredSignature;
|
||||
}
|
||||
|
||||
recoverPublicKey(msgHash: Hex): typeof Point.BASE {
|
||||
@@ -828,6 +831,7 @@ export function weierstrass(curveDef: CurveType): CurveFn {
|
||||
return numToNByteStr(this.r) + numToNByteStr(this.s);
|
||||
}
|
||||
}
|
||||
type RecoveredSignature = Signature & { recovery: number };
|
||||
|
||||
const utils = {
|
||||
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 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.
|
||||
function k2sig(kBytes: Uint8Array): Signature | undefined {
|
||||
function k2sig(kBytes: Uint8Array): RecoveredSignature | undefined {
|
||||
// RFC 6979 Section 3.2, step 3: k = bits2int(T)
|
||||
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
|
||||
@@ -984,7 +988,7 @@ export function weierstrass(curveDef: CurveType): CurveFn {
|
||||
normS = normalizeS(s); // if lowS was passed, ensure s is always
|
||||
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 };
|
||||
}
|
||||
@@ -992,18 +996,22 @@ export function weierstrass(curveDef: CurveType): CurveFn {
|
||||
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
|
||||
* (x, y) = G × k
|
||||
* r = x 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 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
|
||||
}
|
||||
|
||||
@@ -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?
|
||||
// b = True and y = sqrt(u / v) if (u / v) is square in F, and
|
||||
// b = False and y = sqrt(Z * (u / v)) otherwise.
|
||||
/**
|
||||
* 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.
|
||||
* 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) {
|
||||
// Generic implementation
|
||||
const q = Fp.ORDER;
|
||||
let l = _0n;
|
||||
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 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 c4 = _2n ** c1 - _1n; // 4. c4 = 2^c1 - 1 # Integer arithmetic
|
||||
const c5 = _2n ** (c1 - _1n); // 5. c5 = 2^(c1 - 1) # Integer arithmetic
|
||||
const c4 = _2n_pow_c1 - _1n; // 4. c4 = 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 c7 = Fp.pow(Z, (c2 + _1n) / _2n); // 7. c7 = Z^((c2 + 1) / 2)
|
||||
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)
|
||||
// 17. for i in (c1, c1 - 1, ..., 2):
|
||||
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
|
||||
const e1 = Fp.eql(tvv5, Fp.ONE); // 21. e1 = tv5 == 1
|
||||
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
|
||||
return sqrtRatio;
|
||||
}
|
||||
// From draft-irtf-cfrg-hash-to-curve-16
|
||||
/**
|
||||
* From draft-irtf-cfrg-hash-to-curve-16
|
||||
*/
|
||||
export function mapToCurveSimpleSWU<T>(
|
||||
Fp: mod.IField<T>,
|
||||
opts: {
|
||||
|
||||
@@ -60,11 +60,10 @@ const _8n = BigInt(8), _16n = BigInt(16);
|
||||
|
||||
// CURVE FIELDS
|
||||
// Finite field over p.
|
||||
const Fp = mod.Field(
|
||||
BigInt(
|
||||
'0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab'
|
||||
)
|
||||
const Fp_raw = BigInt(
|
||||
'0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab'
|
||||
);
|
||||
const Fp = mod.Field(Fp_raw);
|
||||
type Fp = bigint;
|
||||
// Finite field over r.
|
||||
// This particular field is not used anywhere in bls12-381, but it is still useful.
|
||||
@@ -110,10 +109,7 @@ type Fp2Utils = {
|
||||
// G² - 1
|
||||
// h2q
|
||||
// NOTE: ORDER was wrong!
|
||||
const FP2_ORDER =
|
||||
BigInt(
|
||||
'0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab'
|
||||
) ** _2n;
|
||||
const FP2_ORDER = Fp_raw * Fp_raw;
|
||||
|
||||
const Fp2: mod.IField<Fp2> & Fp2Utils = {
|
||||
ORDER: FP2_ORDER,
|
||||
@@ -1197,7 +1193,7 @@ export const bls12_381: CurveFn<Fp, Fp2, Fp6, Fp12> = bls({
|
||||
),
|
||||
]),
|
||||
a: Fp2.ZERO,
|
||||
b: Fp2.fromBigTuple([4n, _4n]),
|
||||
b: Fp2.fromBigTuple([_4n, _4n]),
|
||||
hEff: BigInt(
|
||||
'0xbc69f08f2ee75b3584c6a0ea91b352888e2a8e9145ad7689986ff031508ffe1329c2f178731db956d82bf015d1212b02ec0ec69d7477c1ae954cbc06689f6a359894c0adebbf6b4e8020005aaa95551'
|
||||
),
|
||||
|
||||
165
src/ed25519.ts
165
src/ed25519.ts
@@ -1,18 +1,18 @@
|
||||
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
|
||||
import { sha512 } from '@noble/hashes/sha512';
|
||||
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 { mod, pow2, isNegativeLE, Field, FpSqrtEven } from './abstract/modular.js';
|
||||
import { Field, FpSqrtEven, isNegativeLE, mod, pow2 } from './abstract/modular.js';
|
||||
import {
|
||||
equalBytes,
|
||||
bytesToHex,
|
||||
bytesToNumberLE,
|
||||
numberToBytesLE,
|
||||
Hex,
|
||||
ensureBytes,
|
||||
equalBytes,
|
||||
Hex,
|
||||
numberToBytesLE,
|
||||
} 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';
|
||||
|
||||
/**
|
||||
@@ -34,6 +34,7 @@ const ED25519_SQRT_M1 = BigInt(
|
||||
const _0n = BigInt(0), _1n = BigInt(1), _2n = BigInt(2), _5n = BigInt(5);
|
||||
// prettier-ignore
|
||||
const _10n = BigInt(10), _20n = BigInt(20), _40n = BigInt(40), _80n = BigInt(80);
|
||||
|
||||
function ed25519_pow_2_252_3(x: bigint) {
|
||||
const P = ED25519_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.
|
||||
return { pow_p_5_8, b2 };
|
||||
}
|
||||
|
||||
function adjustScalarBytes(bytes: Uint8Array): Uint8Array {
|
||||
// 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
|
||||
@@ -61,6 +63,7 @@ function adjustScalarBytes(bytes: Uint8Array): Uint8Array {
|
||||
bytes[31] |= 64; // 0b0100_0000
|
||||
return bytes;
|
||||
}
|
||||
|
||||
// sqrt(u/v)
|
||||
function uvRatio(u: bigint, v: bigint): { isValid: boolean; value: bigint } {
|
||||
const P = ED25519_P;
|
||||
@@ -101,10 +104,10 @@ const ed25519Defaults = {
|
||||
// d is equal to -121665/121666 over finite field.
|
||||
// Negative number is P - number, and division is invert(number, P)
|
||||
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,
|
||||
// Subgroup order: how many points curve has
|
||||
// 2n ** 252n + 27742317777372353535851937790883648493n;
|
||||
// 2n**252n + 27742317777372353535851937790883648493n;
|
||||
n: BigInt('7237005577332262213973186563042994240857116359379907606001950938285454250989'),
|
||||
// Cofactor
|
||||
h: BigInt(8),
|
||||
@@ -121,6 +124,7 @@ const ed25519Defaults = {
|
||||
} as const;
|
||||
|
||||
export const ed25519 = twistedEdwards(ed25519Defaults);
|
||||
|
||||
function ed25519_domain(data: Uint8Array, ctx: Uint8Array, phflag: boolean) {
|
||||
if (ctx.length > 255) throw new Error('Context is too big');
|
||||
return concatBytes(
|
||||
@@ -130,6 +134,7 @@ function ed25519_domain(data: Uint8Array, ctx: Uint8Array, phflag: boolean) {
|
||||
data
|
||||
);
|
||||
}
|
||||
|
||||
export const ed25519ctx = twistedEdwards({ ...ed25519Defaults, domain: ed25519_domain });
|
||||
export const ed25519ph = twistedEdwards({
|
||||
...ed25519Defaults,
|
||||
@@ -137,34 +142,49 @@ export const ed25519ph = twistedEdwards({
|
||||
prehash: sha512,
|
||||
});
|
||||
|
||||
export const x25519 = montgomery({
|
||||
P: ED25519_P,
|
||||
a: BigInt(486662),
|
||||
montgomeryBits: 255, // n is 253 bits
|
||||
nByteLength: 32,
|
||||
Gu: BigInt(9),
|
||||
powPminus2: (x: bigint): bigint => {
|
||||
const P = ED25519_P;
|
||||
// x^(p-2) aka x^(2^255-21)
|
||||
const { pow_p_5_8, b2 } = ed25519_pow_2_252_3(x);
|
||||
return mod(pow2(pow_p_5_8, BigInt(3), P) * b2, P);
|
||||
},
|
||||
adjustScalarBytes,
|
||||
randomBytes,
|
||||
});
|
||||
export const x25519 = /* @__PURE__ */ (() =>
|
||||
montgomery({
|
||||
P: ED25519_P,
|
||||
a: BigInt(486662),
|
||||
montgomeryBits: 255, // n is 253 bits
|
||||
nByteLength: 32,
|
||||
Gu: BigInt(9),
|
||||
powPminus2: (x: bigint): bigint => {
|
||||
const P = ED25519_P;
|
||||
// x^(p-2) aka x^(2^255-21)
|
||||
const { pow_p_5_8, b2 } = ed25519_pow_2_252_3(x);
|
||||
return mod(pow2(pow_p_5_8, BigInt(3), P) * b2, P);
|
||||
},
|
||||
adjustScalarBytes,
|
||||
randomBytes,
|
||||
}))();
|
||||
|
||||
/**
|
||||
* Converts ed25519 public key to x25519 public key. Uses formula:
|
||||
* * `(u, v) = ((1+y)/(1-y), sqrt(-486664)*u/x)`
|
||||
* * `(x, y) = (sqrt(-486664)*u/v, (u-1)/(u+1))`
|
||||
* @example
|
||||
* const aPub = ed25519.getPublicKey(utils.randomPrivateKey());
|
||||
* x25519.getSharedSecret(edwardsToMontgomery(aPub), edwardsToMontgomery(someonesPub))
|
||||
* const someonesPub = ed25519.getPublicKey(ed25519.utils.randomPrivateKey());
|
||||
* 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 _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)
|
||||
@@ -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
|
||||
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
|
||||
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
|
||||
@@ -239,28 +260,30 @@ function map_to_curve_elligator2_edwards25519(u: bigint) {
|
||||
const inv = Fp.invertBatch([xd, yd]); // batch division
|
||||
return { x: Fp.mul(xn, inv[0]), y: Fp.mul(yn, inv[1]) }; // 13. return (xn, xd, yn, yd)
|
||||
}
|
||||
const { hashToCurve, encodeToCurve } = htf.createHasher(
|
||||
ed25519.ExtendedPoint,
|
||||
(scalars: bigint[]) => map_to_curve_elligator2_edwards25519(scalars[0]),
|
||||
{
|
||||
DST: 'edwards25519_XMD:SHA-512_ELL2_RO_',
|
||||
encodeDST: 'edwards25519_XMD:SHA-512_ELL2_NU_',
|
||||
p: Fp.ORDER,
|
||||
m: 1,
|
||||
k: 128,
|
||||
expand: 'xmd',
|
||||
hash: sha512,
|
||||
}
|
||||
);
|
||||
export { hashToCurve, encodeToCurve };
|
||||
|
||||
const htf = /* @__PURE__ */ (() =>
|
||||
createHasher(
|
||||
ed25519.ExtendedPoint,
|
||||
(scalars: bigint[]) => map_to_curve_elligator2_edwards25519(scalars[0]),
|
||||
{
|
||||
DST: 'edwards25519_XMD:SHA-512_ELL2_RO_',
|
||||
encodeDST: 'edwards25519_XMD:SHA-512_ELL2_NU_',
|
||||
p: Fp.ORDER,
|
||||
m: 1,
|
||||
k: 128,
|
||||
expand: 'xmd',
|
||||
hash: sha512,
|
||||
}
|
||||
))();
|
||||
export const hashToCurve = /* @__PURE__ */ (() => htf.hashToCurve)();
|
||||
export const encodeToCurve = /* @__PURE__ */ (() => htf.encodeToCurve)();
|
||||
|
||||
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)
|
||||
const SQRT_M1 = BigInt(
|
||||
'19681161376707505956807079304988542015446066515923890162744021073123829784752'
|
||||
);
|
||||
const SQRT_M1 = ED25519_SQRT_M1;
|
||||
// √(ad - 1)
|
||||
const SQRT_AD_MINUS_ONE = BigInt(
|
||||
'25063068953384623474111414158702152701244531502492656460079210482610430750235'
|
||||
@@ -317,32 +340,31 @@ function calcElligatorRistrettoMap(r0: bigint): ExtendedPoint {
|
||||
* but it should work in its own namespace: do not combine those two.
|
||||
* https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-ristretto255-decaf448
|
||||
*/
|
||||
export class RistrettoPoint {
|
||||
static BASE = new RistrettoPoint(ed25519.ExtendedPoint.BASE);
|
||||
static ZERO = new RistrettoPoint(ed25519.ExtendedPoint.ZERO);
|
||||
|
||||
class RistPoint {
|
||||
static BASE: RistPoint;
|
||||
static ZERO: RistPoint;
|
||||
// Private property to discourage combining ExtendedPoint + RistrettoPoint
|
||||
// Always use Ristretto encoding/decoding instead.
|
||||
constructor(private readonly ep: ExtendedPoint) {}
|
||||
|
||||
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.
|
||||
* **Note:** this is one-way map, there is no conversion from point to hash.
|
||||
* 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);
|
||||
const r1 = bytes255ToNumberLE(hex.slice(0, 32));
|
||||
const R1 = calcElligatorRistrettoMap(r1);
|
||||
const r2 = bytes255ToNumberLE(hex.slice(32, 64));
|
||||
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
|
||||
* @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);
|
||||
const { a, d } = ed25519.CURVE;
|
||||
const P = ed25519.CURVE.Fp.ORDER;
|
||||
@@ -374,7 +396,7 @@ export class RistrettoPoint {
|
||||
const y = mod(u1 * Dy); // 11
|
||||
const t = mod(x * y); // 12
|
||||
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.
|
||||
equals(other: RistrettoPoint): boolean {
|
||||
equals(other: RistPoint): boolean {
|
||||
assertRstPoint(other);
|
||||
const { ex: X1, ey: Y1 } = this.ep;
|
||||
const { ex: X2, ey: Y2 } = other.ep;
|
||||
@@ -429,31 +451,36 @@ export class RistrettoPoint {
|
||||
return one || two;
|
||||
}
|
||||
|
||||
add(other: RistrettoPoint): RistrettoPoint {
|
||||
add(other: RistPoint): RistPoint {
|
||||
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);
|
||||
return new RistrettoPoint(this.ep.subtract(other.ep));
|
||||
return new RistPoint(this.ep.subtract(other.ep));
|
||||
}
|
||||
|
||||
multiply(scalar: bigint): RistrettoPoint {
|
||||
return new RistrettoPoint(this.ep.multiply(scalar));
|
||||
multiply(scalar: bigint): RistPoint {
|
||||
return new RistPoint(this.ep.multiply(scalar));
|
||||
}
|
||||
|
||||
multiplyUnsafe(scalar: bigint): RistrettoPoint {
|
||||
return new RistrettoPoint(this.ep.multiplyUnsafe(scalar));
|
||||
multiplyUnsafe(scalar: bigint): RistPoint {
|
||||
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/
|
||||
// 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 DST = typeof d === 'string' ? utf8ToBytes(d) : d;
|
||||
const uniform_bytes = htf.expand_message_xmd(msg, DST, 64, sha512);
|
||||
const P = RistrettoPoint.hashToCurve(uniform_bytes);
|
||||
const uniform_bytes = expand_message_xmd(msg, DST, 64, sha512);
|
||||
const P = RistPoint.hashToCurve(uniform_bytes);
|
||||
return P;
|
||||
};
|
||||
|
||||
68
src/ed448.ts
68
src/ed448.ts
@@ -4,7 +4,7 @@ import { concatBytes, randomBytes, utf8ToBytes, wrapConstructor } from '@noble/h
|
||||
import { twistedEdwards } from './abstract/edwards.js';
|
||||
import { mod, pow2, Field } from './abstract/modular.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:
|
||||
@@ -63,7 +63,7 @@ const ED448_DEF = {
|
||||
d: BigInt(
|
||||
'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,
|
||||
// Subgroup order: how many points curve has;
|
||||
// 2n**446n - 13818066809895115352007386748515426880336692474882178609894547503885n
|
||||
@@ -122,21 +122,22 @@ export const ed448 = twistedEdwards(ED448_DEF);
|
||||
// NOTE: there is no ed448ctx, since ed448 supports ctx by default
|
||||
export const ed448ph = twistedEdwards({ ...ED448_DEF, prehash: shake256_64 });
|
||||
|
||||
export const x448 = montgomery({
|
||||
a: BigInt(156326),
|
||||
montgomeryBits: 448,
|
||||
nByteLength: 57,
|
||||
P: ed448P,
|
||||
Gu: BigInt(5),
|
||||
powPminus2: (x: bigint): bigint => {
|
||||
const P = ed448P;
|
||||
const Pminus3div4 = ed448_pow_Pminus3div4(x);
|
||||
const Pminus3 = pow2(Pminus3div4, BigInt(2), P);
|
||||
return mod(Pminus3 * x, P); // Pminus3 * x = Pminus2
|
||||
},
|
||||
adjustScalarBytes,
|
||||
randomBytes,
|
||||
});
|
||||
export const x448 = /* @__PURE__ */ (() =>
|
||||
montgomery({
|
||||
a: BigInt(156326),
|
||||
montgomeryBits: 448,
|
||||
nByteLength: 57,
|
||||
P: ed448P,
|
||||
Gu: BigInt(5),
|
||||
powPminus2: (x: bigint): bigint => {
|
||||
const P = ed448P;
|
||||
const Pminus3div4 = ed448_pow_Pminus3div4(x);
|
||||
const Pminus3 = pow2(Pminus3div4, BigInt(2), P);
|
||||
return mod(Pminus3 * x, P); // Pminus3 * x = Pminus2
|
||||
},
|
||||
adjustScalarBytes,
|
||||
randomBytes,
|
||||
}))();
|
||||
|
||||
/**
|
||||
* Converts edwards448 public key to x448 public key. Uses formula:
|
||||
@@ -146,11 +147,12 @@ export const x448 = montgomery({
|
||||
* const aPub = ed448.getPublicKey(utils.randomPrivateKey());
|
||||
* 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 _1n = BigInt(1);
|
||||
return Fp.toBytes(Fp.create((y - _1n) * Fp.inv(y + _1n)));
|
||||
}
|
||||
export const edwardsToMontgomery = edwardsToMontgomeryPub; // deprecated
|
||||
|
||||
// Hash To Curve Elligator2 Map
|
||||
const ELL2_C1 = (Fp.ORDER - BigInt(3)) / BigInt(4); // 1. c1 = (q - 3) / 4 # Integer arithmetic
|
||||
@@ -227,17 +229,19 @@ function map_to_curve_elligator2_edwards448(u: bigint) {
|
||||
return { x: Fp.mul(xEn, inv[0]), y: Fp.mul(yEn, inv[1]) }; // 38. return (xEn, xEd, yEn, yEd)
|
||||
}
|
||||
|
||||
const { hashToCurve, encodeToCurve } = htf.createHasher(
|
||||
ed448.ExtendedPoint,
|
||||
(scalars: bigint[]) => map_to_curve_elligator2_edwards448(scalars[0]),
|
||||
{
|
||||
DST: 'edwards448_XOF:SHAKE256_ELL2_RO_',
|
||||
encodeDST: 'edwards448_XOF:SHAKE256_ELL2_NU_',
|
||||
p: Fp.ORDER,
|
||||
m: 1,
|
||||
k: 224,
|
||||
expand: 'xof',
|
||||
hash: shake256,
|
||||
}
|
||||
);
|
||||
export { hashToCurve, encodeToCurve };
|
||||
const htf = /* @__PURE__ */ (() =>
|
||||
createHasher(
|
||||
ed448.ExtendedPoint,
|
||||
(scalars: bigint[]) => map_to_curve_elligator2_edwards448(scalars[0]),
|
||||
{
|
||||
DST: 'edwards448_XOF:SHAKE256_ELL2_RO_',
|
||||
encodeDST: 'edwards448_XOF:SHAKE256_ELL2_NU_',
|
||||
p: Fp.ORDER,
|
||||
m: 1,
|
||||
k: 224,
|
||||
expand: 'xof',
|
||||
hash: shake256,
|
||||
}
|
||||
))();
|
||||
export const hashToCurve = /* @__PURE__ */ (() => htf.hashToCurve)();
|
||||
export const encodeToCurve = /* @__PURE__ */ (() => htf.encodeToCurve)();
|
||||
|
||||
27
src/p256.ts
27
src/p256.ts
@@ -3,7 +3,7 @@ import { createCurve } from './_shortw_utils.js';
|
||||
import { sha256 } from '@noble/hashes/sha256';
|
||||
import { Field } from './abstract/modular.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
|
||||
// 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_B = BigInt('0x5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b');
|
||||
|
||||
const mapSWU = mapToCurveSimpleSWU(Fp, {
|
||||
A: CURVE_A,
|
||||
B: CURVE_B,
|
||||
Z: Fp.create(BigInt('-10')),
|
||||
});
|
||||
|
||||
// prettier-ignore
|
||||
export const p256 = createCurve({
|
||||
a: CURVE_A, // Equation params: a, b
|
||||
@@ -33,10 +27,15 @@ export const p256 = createCurve({
|
||||
} as const, sha256);
|
||||
export const secp256r1 = p256;
|
||||
|
||||
const { hashToCurve, encodeToCurve } = htf.createHasher(
|
||||
secp256r1.ProjectivePoint,
|
||||
(scalars: bigint[]) => mapSWU(scalars[0]),
|
||||
{
|
||||
const mapSWU = /* @__PURE__ */ (() =>
|
||||
mapToCurveSimpleSWU(Fp, {
|
||||
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_',
|
||||
encodeDST: 'P256_XMD:SHA-256_SSWU_NU_',
|
||||
p: Fp.ORDER,
|
||||
@@ -44,6 +43,6 @@ const { hashToCurve, encodeToCurve } = htf.createHasher(
|
||||
k: 128,
|
||||
expand: 'xmd',
|
||||
hash: sha256,
|
||||
}
|
||||
);
|
||||
export { hashToCurve, encodeToCurve };
|
||||
}))();
|
||||
export const hashToCurve = /* @__PURE__ */ (() => htf.hashToCurve)();
|
||||
export const encodeToCurve = /* @__PURE__ */ (() => htf.encodeToCurve)();
|
||||
|
||||
25
src/p384.ts
25
src/p384.ts
@@ -3,7 +3,7 @@ import { createCurve } from './_shortw_utils.js';
|
||||
import { sha384 } from '@noble/hashes/sha512';
|
||||
import { Field } from './abstract/modular.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
|
||||
// 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);
|
||||
export const secp384r1 = p384;
|
||||
|
||||
const mapSWU = mapToCurveSimpleSWU(Fp, {
|
||||
A: CURVE_A,
|
||||
B: CURVE_B,
|
||||
Z: Fp.create(BigInt('-12')),
|
||||
});
|
||||
const mapSWU = /* @__PURE__ */ (() =>
|
||||
mapToCurveSimpleSWU(Fp, {
|
||||
A: CURVE_A,
|
||||
B: CURVE_B,
|
||||
Z: Fp.create(BigInt('-12')),
|
||||
}))();
|
||||
|
||||
const { hashToCurve, encodeToCurve } = htf.createHasher(
|
||||
secp384r1.ProjectivePoint,
|
||||
(scalars: bigint[]) => mapSWU(scalars[0]),
|
||||
{
|
||||
const htf = /* @__PURE__ */ (() =>
|
||||
createHasher(secp384r1.ProjectivePoint, (scalars: bigint[]) => mapSWU(scalars[0]), {
|
||||
DST: 'P384_XMD:SHA-384_SSWU_RO_',
|
||||
encodeDST: 'P384_XMD:SHA-384_SSWU_NU_',
|
||||
p: Fp.ORDER,
|
||||
@@ -48,6 +47,6 @@ const { hashToCurve, encodeToCurve } = htf.createHasher(
|
||||
k: 192,
|
||||
expand: 'xmd',
|
||||
hash: sha384,
|
||||
}
|
||||
);
|
||||
export { hashToCurve, encodeToCurve };
|
||||
}))();
|
||||
export const hashToCurve = /* @__PURE__ */ (() => htf.hashToCurve)();
|
||||
export const encodeToCurve = /* @__PURE__ */ (() => htf.encodeToCurve)();
|
||||
|
||||
25
src/p521.ts
25
src/p521.ts
@@ -3,7 +3,7 @@ import { createCurve } from './_shortw_utils.js';
|
||||
import { sha512 } from '@noble/hashes/sha512';
|
||||
import { Field } from './abstract/modular.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
|
||||
// Note that it's 521, which differs from 512 of its hash function.
|
||||
@@ -47,16 +47,15 @@ export const p521 = createCurve({
|
||||
} as const, sha512);
|
||||
export const secp521r1 = p521;
|
||||
|
||||
const mapSWU = mapToCurveSimpleSWU(Fp, {
|
||||
A: CURVE.a,
|
||||
B: CURVE.b,
|
||||
Z: Fp.create(BigInt('-4')),
|
||||
});
|
||||
const mapSWU = /* @__PURE__ */ (() =>
|
||||
mapToCurveSimpleSWU(Fp, {
|
||||
A: CURVE.a,
|
||||
B: CURVE.b,
|
||||
Z: Fp.create(BigInt('-4')),
|
||||
}))();
|
||||
|
||||
const { hashToCurve, encodeToCurve } = htf.createHasher(
|
||||
secp521r1.ProjectivePoint,
|
||||
(scalars: bigint[]) => mapSWU(scalars[0]),
|
||||
{
|
||||
const htf = /* @__PURE__ */ (() =>
|
||||
createHasher(secp521r1.ProjectivePoint, (scalars: bigint[]) => mapSWU(scalars[0]), {
|
||||
DST: 'P521_XMD:SHA-512_SSWU_RO_',
|
||||
encodeDST: 'P521_XMD:SHA-512_SSWU_NU_',
|
||||
p: Fp.ORDER,
|
||||
@@ -64,6 +63,6 @@ const { hashToCurve, encodeToCurve } = htf.createHasher(
|
||||
k: 256,
|
||||
expand: 'xmd',
|
||||
hash: sha512,
|
||||
}
|
||||
);
|
||||
export { hashToCurve, encodeToCurve };
|
||||
}))();
|
||||
export const hashToCurve = /* @__PURE__ */ (() => htf.hashToCurve)();
|
||||
export const encodeToCurve = /* @__PURE__ */ (() => htf.encodeToCurve)();
|
||||
|
||||
115
src/secp256k1.ts
115
src/secp256k1.ts
@@ -5,7 +5,7 @@ import { Field, mod, pow2 } from './abstract/modular.js';
|
||||
import { ProjPointType as PointType, mapToCurveSimpleSWU } from './abstract/weierstrass.js';
|
||||
import type { Hex, PrivKey } from './abstract/utils.js';
|
||||
import { bytesToNumberBE, concatBytes, ensureBytes, numberToBytesBE } from './abstract/utils.js';
|
||||
import * as htf from './abstract/hash-to-curve.js';
|
||||
import { createHasher, isogenyMap } from './abstract/hash-to-curve.js';
|
||||
import { createCurve } from './_shortw_utils.js';
|
||||
|
||||
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,
|
||||
sign: schnorrSign,
|
||||
verify: schnorrVerify,
|
||||
@@ -212,58 +212,63 @@ export const schnorr = {
|
||||
taggedHash,
|
||||
mod,
|
||||
},
|
||||
};
|
||||
}))();
|
||||
|
||||
const isoMap = htf.isogenyMap(
|
||||
Fp,
|
||||
[
|
||||
// xNum
|
||||
const isoMap = /* @__PURE__ */ (() =>
|
||||
isogenyMap(
|
||||
Fp,
|
||||
[
|
||||
'0x8e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38daaaaa8c7',
|
||||
'0x7d3d4c80bc321d5b9f315cea7fd44c5d595d2fc0bf63b92dfff1044f17c6581',
|
||||
'0x534c328d23f234e6e2a413deca25caece4506144037c40314ecbd0b53d9dd262',
|
||||
'0x8e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38daaaaa88c',
|
||||
],
|
||||
// xDen
|
||||
[
|
||||
'0xd35771193d94918a9ca34ccbb7b640dd86cd409542f8487d9fe6b745781eb49b',
|
||||
'0xedadc6f64383dc1df7c4b2d51b54225406d36b641f5e41bbc52a56612a8c6d14',
|
||||
'0x0000000000000000000000000000000000000000000000000000000000000001', // LAST 1
|
||||
],
|
||||
// yNum
|
||||
[
|
||||
'0x4bda12f684bda12f684bda12f684bda12f684bda12f684bda12f684b8e38e23c',
|
||||
'0xc75e0c32d5cb7c0fa9d0a54b12a0a6d5647ab046d686da6fdffc90fc201d71a3',
|
||||
'0x29a6194691f91a73715209ef6512e576722830a201be2018a765e85a9ecee931',
|
||||
'0x2f684bda12f684bda12f684bda12f684bda12f684bda12f684bda12f38e38d84',
|
||||
],
|
||||
// yDen
|
||||
[
|
||||
'0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffff93b',
|
||||
'0x7a06534bb8bdb49fd5e9e6632722c2989467c1bfc8e8d978dfb425d2685c2573',
|
||||
'0x6484aa716545ca2cf3a70c3fa8fe337e0a3d21162f0d6299a7bf8192bfd2a76f',
|
||||
'0x0000000000000000000000000000000000000000000000000000000000000001', // LAST 1
|
||||
],
|
||||
].map((i) => i.map((j) => BigInt(j))) as [bigint[], bigint[], bigint[], bigint[]]
|
||||
);
|
||||
const mapSWU = mapToCurveSimpleSWU(Fp, {
|
||||
A: BigInt('0x3f8731abdd661adca08a5558f0f5d272e953d363cb6f0e5d405447c01a444533'),
|
||||
B: BigInt('1771'),
|
||||
Z: Fp.create(BigInt('-11')),
|
||||
});
|
||||
export const { hashToCurve, encodeToCurve } = htf.createHasher(
|
||||
secp256k1.ProjectivePoint,
|
||||
(scalars: bigint[]) => {
|
||||
const { x, y } = mapSWU(Fp.create(scalars[0]));
|
||||
return isoMap(x, y);
|
||||
},
|
||||
{
|
||||
DST: 'secp256k1_XMD:SHA-256_SSWU_RO_',
|
||||
encodeDST: 'secp256k1_XMD:SHA-256_SSWU_NU_',
|
||||
p: Fp.ORDER,
|
||||
m: 1,
|
||||
k: 128,
|
||||
expand: 'xmd',
|
||||
hash: sha256,
|
||||
}
|
||||
);
|
||||
// xNum
|
||||
[
|
||||
'0x8e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38daaaaa8c7',
|
||||
'0x7d3d4c80bc321d5b9f315cea7fd44c5d595d2fc0bf63b92dfff1044f17c6581',
|
||||
'0x534c328d23f234e6e2a413deca25caece4506144037c40314ecbd0b53d9dd262',
|
||||
'0x8e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38daaaaa88c',
|
||||
],
|
||||
// xDen
|
||||
[
|
||||
'0xd35771193d94918a9ca34ccbb7b640dd86cd409542f8487d9fe6b745781eb49b',
|
||||
'0xedadc6f64383dc1df7c4b2d51b54225406d36b641f5e41bbc52a56612a8c6d14',
|
||||
'0x0000000000000000000000000000000000000000000000000000000000000001', // LAST 1
|
||||
],
|
||||
// yNum
|
||||
[
|
||||
'0x4bda12f684bda12f684bda12f684bda12f684bda12f684bda12f684b8e38e23c',
|
||||
'0xc75e0c32d5cb7c0fa9d0a54b12a0a6d5647ab046d686da6fdffc90fc201d71a3',
|
||||
'0x29a6194691f91a73715209ef6512e576722830a201be2018a765e85a9ecee931',
|
||||
'0x2f684bda12f684bda12f684bda12f684bda12f684bda12f684bda12f38e38d84',
|
||||
],
|
||||
// yDen
|
||||
[
|
||||
'0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffff93b',
|
||||
'0x7a06534bb8bdb49fd5e9e6632722c2989467c1bfc8e8d978dfb425d2685c2573',
|
||||
'0x6484aa716545ca2cf3a70c3fa8fe337e0a3d21162f0d6299a7bf8192bfd2a76f',
|
||||
'0x0000000000000000000000000000000000000000000000000000000000000001', // LAST 1
|
||||
],
|
||||
].map((i) => i.map((j) => BigInt(j))) as [bigint[], bigint[], bigint[], bigint[]]
|
||||
))();
|
||||
const mapSWU = /* @__PURE__ */ (() =>
|
||||
mapToCurveSimpleSWU(Fp, {
|
||||
A: BigInt('0x3f8731abdd661adca08a5558f0f5d272e953d363cb6f0e5d405447c01a444533'),
|
||||
B: BigInt('1771'),
|
||||
Z: Fp.create(BigInt('-11')),
|
||||
}))();
|
||||
const htf = /* @__PURE__ */ (() =>
|
||||
createHasher(
|
||||
secp256k1.ProjectivePoint,
|
||||
(scalars: bigint[]) => {
|
||||
const { x, y } = mapSWU(Fp.create(scalars[0]));
|
||||
return isoMap(x, y);
|
||||
},
|
||||
{
|
||||
DST: 'secp256k1_XMD:SHA-256_SSWU_RO_',
|
||||
encodeDST: 'secp256k1_XMD:SHA-256_SSWU_NU_',
|
||||
p: Fp.ORDER,
|
||||
m: 1,
|
||||
k: 128,
|
||||
expand: 'xmd',
|
||||
hash: sha256,
|
||||
}
|
||||
))();
|
||||
export const hashToCurve = /* @__PURE__ */ (() => htf.hashToCurve)();
|
||||
export const encodeToCurve = /* @__PURE__ */ (() => htf.encodeToCurve)();
|
||||
|
||||
@@ -1,10 +1,18 @@
|
||||
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 { describe, should } from 'micro-should';
|
||||
import { bytesToNumberLE, numberToBytesLE } from '../esm/abstract/utils.js';
|
||||
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 = [
|
||||
{
|
||||
@@ -141,6 +149,56 @@ describe('RFC7748 X25519 ECDH', () => {
|
||||
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', () => {
|
||||
const { y } = ed25519ph.ExtendedPoint.BASE;
|
||||
const { Fp } = ed25519ph.CURVE;
|
||||
@@ -298,7 +356,7 @@ describe('ristretto255', () => {
|
||||
|
||||
// ESM is broken.
|
||||
import url from 'url';
|
||||
import { assert } from 'console';
|
||||
|
||||
if (import.meta.url === url.pathToFileURL(process.argv[1]).href) {
|
||||
should.run();
|
||||
}
|
||||
|
||||
@@ -1,17 +1,19 @@
|
||||
import { deepStrictEqual, strictEqual, throws } from 'assert';
|
||||
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 { 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
|
||||
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 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', () => {
|
||||
const ed = ed25519;
|
||||
const hex = bytesToHex;
|
||||
const Point = ed.ExtendedPoint;
|
||||
|
||||
@@ -20,13 +22,6 @@ describe('ed25519', () => {
|
||||
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);
|
||||
|
||||
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', () => {
|
||||
const privateKey = ed25519.utils.randomPrivateKey();
|
||||
const privateKey = ed.utils.randomPrivateKey();
|
||||
const message = Uint8Array.from([0xab, 0xbc, 0xcd, 0xde]);
|
||||
const publicKey = ed25519.getPublicKey(privateKey);
|
||||
const signature = ed25519.sign(message, privateKey);
|
||||
const publicKey = ed.getPublicKey(privateKey);
|
||||
const signature = ed.sign(message, privateKey);
|
||||
|
||||
const R = signature.slice(0, 32);
|
||||
let s = signature.slice(32, 64);
|
||||
|
||||
s = bytesToHex(s.slice().reverse());
|
||||
s = BigInt('0x' + s);
|
||||
s = s + ed25519.CURVE.n;
|
||||
s = s + ed.CURVE.n;
|
||||
s = numberToBytesLE(s, 32);
|
||||
|
||||
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', () => {
|
||||
const t = 81718630521762619991978402609047527194981150691135404693881672112315521837062n;
|
||||
const point = ed25519.ExtendedPoint.fromAffine({ x: t, y: t });
|
||||
const point = Point.fromAffine({ x: t, y: t });
|
||||
throws(() => point.assertValidity());
|
||||
// Otherwise (without assertValidity):
|
||||
// const point2 = point.double();
|
||||
|
||||
1
test/ed25519/edge-cases.json
Normal file
1
test/ed25519/edge-cases.json
Normal 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"}]
|
||||
@@ -5,6 +5,7 @@ import './basic.test.js';
|
||||
import './nist.test.js';
|
||||
import './ed448.test.js';
|
||||
import './ed25519.test.js';
|
||||
import './ed25519-addons.test.js';
|
||||
import './secp256k1.test.js';
|
||||
import './secp256k1-schnorr.test.js';
|
||||
import './jubjub.test.js';
|
||||
|
||||
@@ -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 ecdh } from './wycheproof/ecdh_test.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_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
|
||||
deepStrictEqual(
|
||||
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.
|
||||
import url from 'url';
|
||||
if (import.meta.url === url.pathToFileURL(process.argv[1]).href) {
|
||||
|
||||
@@ -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 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 privatesTxt = readFileSync('./test/vectors/secp256k1/privates-2.txt', 'utf-8');
|
||||
|
||||
|
||||
26
test/vectors/secp256k1/endomorphism.json
Normal file
26
test/vectors/secp256k1/endomorphism.json
Normal 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"
|
||||
}
|
||||
]
|
||||
Reference in New Issue
Block a user