112 Commits
1.0.0 ... 1.2.0

Author SHA1 Message Date
Paul Miller
0d7756dceb Release 1.2.0. 2023-08-23 20:00:32 +02:00
Paul Miller
b716b4603f Update lockfile for 1.2 2023-08-23 19:58:55 +02:00
Paul Miller
d7a139822d Release 1.2.0. 2023-08-23 19:55:35 +02:00
Paul Miller
fb6c379a26 Update README 2023-08-23 19:48:52 +02:00
Paul Miller
eeac255c88 update noble-hashes to 1.3.2 2023-08-23 19:45:47 +02:00
Paul Miller
925fc3f810 modular: adjust getFieldsBytseLength 2023-08-23 19:43:55 +02:00
Paul Miller
eb8e7ec964 hash-to-curve, weierstrass, bls, ed: upgrade h2c comments to rfc 9380 2023-08-23 19:43:14 +02:00
Paul Miller
e7ac5e85d3 poseidon: refactor params 2023-08-21 18:16:40 +02:00
Paul Miller
d285fcce06 modular: Document FpPow 2023-08-21 17:52:21 +02:00
Paul Miller
ef667bb404 poseidon: refactor validateOpts, fix tests 2023-08-21 17:48:34 +02:00
Paul Miller
62749382e7 poseidon: remove default sboxPower: 5 2023-08-21 17:04:58 +02:00
Paul Miller
f90e871725 weierstrass: prohibit (0, 0, 0) in assertValidity 2023-08-21 16:05:53 +02:00
Paul Miller
f049398718 modular: bring back 1.1.0 hashToPrivateScalar for clean diff 2023-08-18 23:14:08 +02:00
Paul Miller
ca99179bd8 bls, modular: lint 2023-08-18 23:09:53 +02:00
Paul Miller
1545230ee5 modular, weierstrass, bls: use new mapHashToField 2023-08-18 23:08:46 +02:00
Paul Miller
2ce3b825f8 readme 2023-08-16 02:36:24 +02:00
Paul Miller
8315fe3580 readme 2023-08-16 02:33:10 +02:00
Paul Miller
9b7889e16f README: improve docs for ecdh 2023-08-16 02:14:41 +02:00
Paul Miller
e8b9509c16 abstract/modular: add more comments everywhere 2023-08-11 12:23:52 +02:00
Paul Miller
d92c9d14ad README: update Field documentation, reformat with prettier 2023-08-11 12:23:19 +02:00
Paul Miller
05794c0283 weierstrass, bls: improve randomPrivateKey security and decrease bias 2023-08-11 12:22:37 +02:00
Paul Miller
ca5583f713 ed25519, ed448: rename hash_to_ristretto to hashToRistretto. And decaf 2023-08-10 20:01:13 +02:00
Paul Miller
8c48abe16a Lint 2023-08-08 15:43:31 +02:00
Paul Miller
08bb00cc8f poseidon: prohibit sBoxPower other than 3, 5, 7 2023-08-08 15:43:14 +02:00
Paul Miller
1ef16033fe readme 2023-08-07 13:54:02 +02:00
Paul Miller
113b6d7c00 readme 2023-08-07 13:48:18 +02:00
Paul Miller
5c3dc0be50 README: more blog posts 2023-08-07 13:45:38 +02:00
Paul Miller
e7d01f4038 Update README.md 2023-08-07 13:11:30 +02:00
Paul Miller
9a39625eda test: lint 2023-08-05 11:25:56 +02:00
Paul Miller
af8462b09e tests/bls12: fix crashes on zero messages 2023-08-05 10:56:52 +02:00
Paul Miller
bfd9ae040d readme: add alt_bn128 2023-08-05 01:19:42 +02:00
Paul Miller
2bd437df4e readme 2023-08-05 00:47:59 +02:00
Paul Miller
b0af0a8977 readme 2023-08-05 00:31:41 +02:00
Paul Miller
aee10c8141 readme 2023-07-18 09:11:24 +02:00
Paul Miller
ff92bafb6f readme 2023-07-18 09:09:01 +02:00
Paul Miller
54679ff788 Usage 2023-07-18 09:08:28 +02:00
Paul Miller
ee4571c7a1 readme: toc 2023-07-18 09:07:11 +02:00
Paul Miller
fe7afdd392 readme 2023-07-16 06:31:52 +02:00
Paul Miller
dba2f0e732 lint 2023-07-12 23:58:30 +02:00
Paul Miller
52c5df0264 utils: add PURE flag 2023-07-12 20:28:45 +02:00
Paul Miller
ebea4a4bcd weierstrass, bls12-381: adjust var names for typescript flag 2023-07-12 20:28:38 +02:00
Paul Miller
33a53006f7 build: update esbuild 2023-07-12 20:28:11 +02:00
Paul Miller
549e286ef0 package.json: declare side-effects free 2023-07-12 20:26:12 +02:00
Paul Miller
3f0c0b59f1 readme 2023-07-11 19:00:56 +02:00
Paul Miller
62205347e1 readme for finalExponentiate 2023-07-11 18:59:40 +02:00
Paul Miller
476e75104f Merge pull request #62 from steveluscher/pure-and-twisted
Add pure annotation to all calls to `twistedEdwards`
2023-07-01 04:27:31 +02:00
steveluscher
413725cfb3 Add pure annotation to all calls to twistedEdwards
This PR makes it so that if you only use _one_ export:

```ts
import { ed25519 } from '@noble/curves`;
```

…then only the `twistedEdwards` call that constructs that export will remain after bundling and tree-shaking.

Before this change, the compiled bundle contains all the code that constructs `ed25519ph` and `ed25519ctx` remains.

```js
var ed25519 = twistedEdwards(ed25519Defaults);
function ed25519_domain(data, ctx, phflag) {
  if (ctx.length > 255)
    throw new Error("Context is too big");
  return concatBytes(utf8ToBytes("SigEd25519 no Ed25519 collisions"), new Uint8Array([phflag ? 1 : 0, ctx.length]), ctx, data);
}
twistedEdwards({ ...ed25519Defaults, domain: ed25519_domain });
twistedEdwards({
  ...ed25519Defaults,
  domain: ed25519_domain,
  prehash: sha512
});
```

```js
var ed25519 = twistedEdwards(ed25519Defaults);
```
2023-06-30 17:36:16 +00:00
Paul Miller
cf17f7fe01 readme 2023-06-28 17:33:13 +02:00
Paul Miller
49fb90ae9a Add README link to new library noble-ciphers 2023-06-28 16:04:09 +02:00
Paul Miller
309d29a084 Merge pull request #56 from CoinSpace/extraentropy
fix: check extraEntropy according to the spec
2023-06-28 14:44:56 +02:00
Evgeny Vlasenko
d3aa051770 feat: tests for extraEntropy 2023-06-28 16:12:44 +04:00
Paul Miller
5609ec7644 Adjust readme docs on sig key recovery 2023-06-27 01:38:02 +02:00
Paul Miller
af8c1eebee Merge pull request #59 from stknob/decaf448
Add decaf448
2023-06-27 00:32:53 +02:00
Stefan Knoblich
08ea57ce5c Expand ristretto255 and ed448 + decaf448 README section
Signed-off-by: Stefan Knoblich <stkn@bitplumber.de>
2023-06-26 22:48:48 +02:00
Stefan Knoblich
ee3d3815b4 Add benchmarks for hash_to_ristretto255 and hash_to_decaf448
Signed-off-by: Stefan Knoblich <stkn@bitplumber.de>
2023-06-26 22:48:48 +02:00
Stefan Knoblich
f471405798 Add benchmarks for ristretto255 and decaf448
Signed-off-by: Stefan Knoblich <stkn@bitplumber.de>
2023-06-26 22:48:48 +02:00
Stefan Knoblich
e3a4bbffe9 Add decaf448
Based on draft-irtf-cfrg-ristretto255-decaf448-07,
draft-irtf-cfrg-hash-to-curve-16 and the ristretto255 implementation.

Signed-off-by: Stefan Knoblich <stkn@bitplumber.de>
2023-06-26 22:48:48 +02:00
Paul Miller
c2edc97868 Merge pull request #58 from sublimator/nd-validate-dst-as-stringoruint8array-closes-57-2023-06-22
fix: validate hash_to_field DST as stringOrUint8Array (closes #57)
2023-06-23 00:27:24 +02:00
Nicholas Dudfield
bf70ba9776 fix: validate hash_to_field DST as stringOrUint8Array (closes #57) 2023-06-22 07:19:08 +07:00
Evgeny Vlasenko
c71920722c fix: check extraEntropy according to the spec 2023-06-16 19:43:12 +04:00
Paul Miller
62e806cfaf Release 1.1.0. 2023-06-03 14:31:43 +02:00
Paul Miller
6a72821185 readme 2023-06-03 14:27:05 +02:00
Paul Miller
8cee1f559f Bump noble-hashes to 1.3.1 2023-06-03 14:23:18 +02:00
Paul Miller
6f10632ac0 Add build directory that allows to test tree-shaking 2023-06-02 17:16:56 +02:00
Paul Miller
b281167e8d Fix utf8ToBytes in firefox extension context 2023-06-02 15:57:46 +02:00
Paul Miller
c6b4aadafb utils: harmonize with noble-hashes 2023-06-02 15:35:37 +02:00
Paul Miller
aade023e48 pkg.json: Adjust funding field 2023-05-27 16:10:58 +02:00
Paul Miller
2e04d96ce9 readme 2023-05-26 13:27:41 +02:00
Paul Miller
79dd7d3426 readme 2023-05-20 12:34:51 +02:00
Paul Miller
ff5b231e31 secp256k1 & other implementations: reduce bundle size by 20% by using PURE.
PURE annotation helps bundlers during tree-shaking and eliminates dead code.

* secp256k1: 75.4kb => 62.3kb

* ed25519: 67.5kb => 51.1kb

* ed448: 55.1kb => 44.0kb

* p256: 67.8kb => 59.8kb

* p384: 75.4kb => 67.4kb

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

View File

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

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

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

628
README.md
View File

@@ -8,70 +8,80 @@ Audited & minimal JS implementation of elliptic curve cryptography.
- 🔍 Unique tests ensure correctness: property-based, cross-library and Wycheproof vectors, fuzzing - 🔍 Unique tests ensure correctness: property-based, cross-library and Wycheproof vectors, fuzzing
- ➰ Short Weierstrass, Edwards, Montgomery curves - ➰ Short Weierstrass, Edwards, Montgomery curves
- ✍️ ECDSA, EdDSA, Schnorr, BLS signature schemes, ECDH key agreement - ✍️ ECDSA, EdDSA, Schnorr, BLS signature schemes, ECDH key agreement
- #⃣ Hash-to-curve - 🔖 SUF-CMA and SBS (non-repudiation) for ed25519, ed448 and others
for encoding or hashing an arbitrary string to an elliptic curve point - #⃣ hash-to-curve for encoding or hashing an arbitrary string to an elliptic curve point
- 🧜‍♂️ Poseidon ZK-friendly hash - 🧜‍♂️ Poseidon ZK-friendly hash
Check out [Upgrading](#upgrading) if you've previously used single-feature noble
packages. See [Resources](#resources) for articles and real-world software that uses curves.
### This library belongs to _noble_ crypto ### This library belongs to _noble_ crypto
> **noble-crypto** — high-security, easily auditable set of contained cryptographic libraries and tools. > **noble-crypto** — high-security, easily auditable set of contained cryptographic libraries and tools.
- No dependencies, protection against supply chain attacks - No dependencies, protection against supply chain attacks
- Auditable TypeScript / JS code - Auditable TypeScript / JS code
- Supported in all major browsers and stable node.js versions - Supported on all major platforms
- All releases are signed with PGP keys - Releases are signed with PGP keys and built transparently with NPM provenance
- Check out [homepage](https://paulmillr.com/noble/) & all libraries: - Check out [homepage](https://paulmillr.com/noble/) & all libraries:
[curves](https://github.com/paulmillr/noble-curves) [ciphers](https://github.com/paulmillr/noble-ciphers),
(4kb versions [secp256k1](https://github.com/paulmillr/noble-secp256k1), [curves](https://github.com/paulmillr/noble-curves),
[ed25519](https://github.com/paulmillr/noble-ed25519)), [hashes](https://github.com/paulmillr/noble-hashes),
[hashes](https://github.com/paulmillr/noble-hashes) 4kb [secp256k1](https://github.com/paulmillr/noble-secp256k1) /
[ed25519](https://github.com/paulmillr/noble-ed25519)
## Usage ## Usage
Browser, deno and node.js are supported:
> npm install @noble/curves > npm install @noble/curves
For [Deno](https://deno.land), use it with We support all major platforms and runtimes.
[npm specifier](https://deno.land/manual@v1.28.0/node/npm_specifiers). For [Deno](https://deno.land), ensure to use [npm specifier](https://deno.land/manual@v1.28.0/node/npm_specifiers).
In browser, you could also include the single file from For React Native, you may need a [polyfill for crypto.getRandomValues](https://github.com/LinusU/react-native-get-random-values).
[GitHub's releases page](https://github.com/paulmillr/noble-curves/releases). If you don't like NPM, a standalone [noble-curves.js](https://github.com/paulmillr/noble-curves/releases) is also available.
The library is tree-shaking-friendly and does NOT expose root entry point as - [Implementations](#implementations)
`import c from '@noble/curves'`. Instead, you need to import specific primitives. - [ECDSA signature scheme](#ecdsa-signature-scheme)
This is done to ensure small size of your apps. - [ECDSA public key recovery & extra entropy](#ecdsa-public-key-recovery--extra-entropy)
- [ECDH (Elliptic Curve Diffie-Hellman)](#ecdh-elliptic-curve-diffie-hellman)
Package consists of two parts: - [Schnorr signatures over secp256k1, BIP340](#schnorr-signatures-over-secp256k1-bip340)
- [ed25519, X25519, ristretto255](#ed25519-x25519-ristretto255)
1. [Implementations](#implementations), utilizing one dependency [noble-hashes](https://github.com/paulmillr/noble-hashes), - [ed448, X448, decaf448](#ed448-x448-decaf448)
providing ready-to-use: - [bls12-381](#bls12-381)
- NIST curves secp256r1 / p256, secp384r1 / p384, secp521r1 / p521 - [All available imports](#all-available-imports)
- SECG curve secp256k1 - [Accessing a curve's variables](#accessing-a-curves-variables)
- ed25519 / curve25519 / x25519 / ristretto255, - [Abstract API](#abstract-api)
edwards448 / curve448 / x448 - [abstract/weierstrass: Short Weierstrass curve](#abstractweierstrass-short-weierstrass-curve)
implementing - [abstract/edwards: Twisted Edwards curve](#abstractedwards-twisted-edwards-curve)
[RFC7748](https://www.rfc-editor.org/rfc/rfc7748) / - [abstract/montgomery: Montgomery curve](#abstractmontgomery-montgomery-curve)
[RFC8032](https://www.rfc-editor.org/rfc/rfc8032) / - [abstract/bls: Barreto-Lynn-Scott curves](#abstractbls-barreto-lynn-scott-curves)
[FIPS 186-5](https://csrc.nist.gov/publications/detail/fips/186/5/final) / - [abstract/hash-to-curve: Hashing strings to curve points](#abstracthash-to-curve-hashing-strings-to-curve-points)
[ZIP215](https://zips.z.cash/zip-0215) standards - [abstract/poseidon: Poseidon hash](#abstractposeidon-poseidon-hash)
- pairing-friendly curves bls12-381, bn254 - [abstract/modular: Modular arithmetics utilities](#abstractmodular-modular-arithmetics-utilities)
- [pasta](https://electriccoin.co/blog/the-pasta-curves-for-halo-2-and-beyond/) curves - [Creating private keys from hashes](#creating-private-keys-from-hashes)
2. [Abstract](#abstract-api), zero-dependency EC algorithms - [abstract/utils: Useful utilities](#abstractutils-useful-utilities)
- [Security](#security)
- [Speed](#speed)
- [Contributing & testing](#contributing--testing)
- [Upgrading](#upgrading)
- [Resources](#resources)
- [Demos](#demos)
- [Projects using curves](#projects-using-curves)
- [License](#license)
### Implementations ### Implementations
Each curve can be used in the following way: Implementations are utilizing [noble-hashes](https://github.com/paulmillr/noble-hashes).
[Abstract API](#abstract-api) doesn't depend on them: you can use a different hashing library.
#### ECDSA signature scheme
Generic example that works for all curves, shown for secp256k1:
```ts ```ts
// import * from '@noble/curves'; // Error: use sub-imports, to ensure small app size
import { secp256k1 } from '@noble/curves/secp256k1'; // ESM and Common.js import { secp256k1 } from '@noble/curves/secp256k1'; // ESM and Common.js
// import { secp256k1 } from 'npm:@noble/curves@1.2.0/secp256k1'; // Deno // import { secp256k1 } from 'npm:@noble/curves@1.2.0/secp256k1'; // Deno
const priv = secp256k1.utils.randomPrivateKey(); const priv = secp256k1.utils.randomPrivateKey();
const pub = secp256k1.getPublicKey(priv); const pub = secp256k1.getPublicKey(priv);
const msg = new Uint8Array(32).fill(1); const msg = new Uint8Array(32).fill(1); // message hash (not message) in ecdsa
const sig = secp256k1.sign(msg, priv); const sig = secp256k1.sign(msg, priv); // `{prehash: true}` option is available
const isValid = secp256k1.verify(sig, msg, pub) === true; const isValid = secp256k1.verify(sig, msg, pub) === true;
// hex strings are also supported besides Uint8Arrays: // hex strings are also supported besides Uint8Arrays:
@@ -79,33 +89,25 @@ const privHex = '46c930bc7bb4db7f55da20798697421b98c4175a52c630294d75a84b9c12623
const pub2 = secp256k1.getPublicKey(privHex); const pub2 = secp256k1.getPublicKey(privHex);
``` ```
All curves: #### ECDSA public key recovery & extra entropy
```typescript
import { secp256k1, schnorr } from '@noble/curves/secp256k1';
import { ed25519, ed25519ph, ed25519ctx, x25519, RistrettoPoint } from '@noble/curves/ed25519';
import { ed448, ed448ph, ed448ctx, x448 } from '@noble/curves/ed448';
import { p256 } from '@noble/curves/p256';
import { p384 } from '@noble/curves/p384';
import { p521 } from '@noble/curves/p521';
import { pallas, vesta } from '@noble/curves/pasta';
import { bls12_381 } from '@noble/curves/bls12-381';
import { bn254 } from '@noble/curves/bn254';
import { jubjub } from '@noble/curves/jubjub';
```
Recovering public keys from weierstrass ECDSA signatures; using ECDH:
```ts ```ts
sig.recoverPublicKey(msg).toRawBytes(); // === pub; // public key recovery
// extraEntropy https://moderncrypto.org/mail-archive/curves/2017/000925.html // extraEntropy https://moderncrypto.org/mail-archive/curves/2017/000925.html
const sigImprovedSecurity = secp256k1.sign(msg, priv, { extraEntropy: true }); const sigImprovedSecurity = secp256k1.sign(msg, priv, { extraEntropy: true });
sig.recoverPublicKey(msg) === pub; // public key recovery
const someonesPub = secp256k1.getPublicKey(secp256k1.utils.randomPrivateKey());
const shared = secp256k1.getSharedSecret(priv, someonesPub); // ECDH
``` ```
Schnorr signatures over secp256k1 following #### ECDH (Elliptic Curve Diffie-Hellman)
[BIP340](https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki):
```ts
// 1. The output includes parity byte. Strip it using shared.slice(1)
// 2. The output is not hashed. More secure way is sha256(shared) or hkdf(shared)
const someonesPub = secp256k1.getPublicKey(secp256k1.utils.randomPrivateKey());
const shared = secp256k1.getSharedSecret(priv, someonesPub);
```
#### Schnorr signatures over secp256k1 (BIP340)
```ts ```ts
import { schnorr } from '@noble/curves/secp256k1'; import { schnorr } from '@noble/curves/secp256k1';
@@ -116,12 +118,7 @@ const sig = schnorr.sign(msg, priv);
const isValid = schnorr.verify(sig, msg, pub); const isValid = schnorr.verify(sig, msg, pub);
``` ```
ed25519 module has ed25519ctx / ed25519ph variants, #### ed25519, X25519, ristretto255
x25519 ECDH and [ristretto255](https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-ristretto255-decaf448).
Default `verify` behavior follows [ZIP215](https://zips.z.cash/zip-0215) and
[can be used in consensus-critical applications](https://hdevalence.ca/blog/2020-10-04-its-25519am).
`zip215: false` option switches verification criteria to RFC8032 / FIPS 186-5.
```ts ```ts
import { ed25519 } from '@noble/curves/ed25519'; import { ed25519 } from '@noble/curves/ed25519';
@@ -131,7 +128,18 @@ const msg = new TextEncoder().encode('hello');
const sig = ed25519.sign(msg, priv); const sig = ed25519.sign(msg, priv);
ed25519.verify(sig, msg, pub); // Default mode: follows ZIP215 ed25519.verify(sig, msg, pub); // Default mode: follows ZIP215
ed25519.verify(sig, msg, pub, { zip215: false }); // RFC8032 / FIPS 186-5 ed25519.verify(sig, msg, pub, { zip215: false }); // RFC8032 / FIPS 186-5
```
Default `verify` behavior follows [ZIP215](https://zips.z.cash/zip-0215) and
[can be used in consensus-critical applications](https://hdevalence.ca/blog/2020-10-04-its-25519am).
It has SUF-CMA (strong unforgeability under chosen message attacks).
`zip215: false` option switches verification criteria to strict
[RFC8032](https://www.rfc-editor.org/rfc/rfc8032) / [FIPS 186-5](https://csrc.nist.gov/publications/detail/fips/186/5/final)
and additionally provides non-repudiation with SBS [(Strongly Binding Signatures)](https://eprint.iacr.org/2020/1244).
X25519 follows [RFC7748](https://www.rfc-editor.org/rfc/rfc7748).
```ts
// Variants from RFC8032: with context, prehashed // Variants from RFC8032: with context, prehashed
import { ed25519ctx, ed25519ph } from '@noble/curves/ed25519'; import { ed25519ctx, ed25519ph } from '@noble/curves/ed25519';
@@ -141,31 +149,119 @@ const priv = 'a546e36bf0527c9d3b16154b82465edd62144c0ac1fc5a18506a2244ba449ac4';
const pub = 'e6db6867583030db3594c1a424b15f7c726624ec26b3353b10a903a6d0ab1c4c'; const pub = 'e6db6867583030db3594c1a424b15f7c726624ec26b3353b10a903a6d0ab1c4c';
x25519.getSharedSecret(priv, pub) === x25519.scalarMult(priv, pub); // aliases x25519.getSharedSecret(priv, pub) === x25519.scalarMult(priv, pub); // aliases
x25519.getPublicKey(priv) === x25519.scalarMultBase(priv); x25519.getPublicKey(priv) === x25519.scalarMultBase(priv);
x25519.getPublicKey(x25519.utils.randomPrivateKey());
// hash-to-curve // ed25519 => x25519 conversion
import { hashToCurve, encodeToCurve } from '@noble/curves/ed25519'; import { edwardsToMontgomeryPub, edwardsToMontgomeryPriv } from '@noble/curves/ed25519';
edwardsToMontgomeryPub(ed25519.getPublicKey(ed25519.utils.randomPrivateKey()));
edwardsToMontgomeryPriv(ed25519.utils.randomPrivateKey());
```
ristretto255 follows [irtf draft](https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-ristretto255-decaf448).
```ts
// hash-to-curve, ristretto255
import { utf8ToBytes } from '@noble/hashes/utils';
import { sha512 } from '@noble/hashes/sha512';
import {
hashToCurve,
encodeToCurve,
RistrettoPoint,
hashToRistretto255,
} from '@noble/curves/ed25519';
const msg = utf8ToBytes('Ristretto is traditionally a short shot of espresso coffee');
hashToCurve(msg);
import { RistrettoPoint } from '@noble/curves/ed25519';
const rp = RistrettoPoint.fromHex( const rp = RistrettoPoint.fromHex(
'6a493210f7499cd17fecb510ae0cea23a110e8d5b901f8acadd3095c73a3b919' '6a493210f7499cd17fecb510ae0cea23a110e8d5b901f8acadd3095c73a3b919'
); );
RistrettoPoint.hashToCurve('Ristretto is traditionally a short shot of espresso coffee'); RistrettoPoint.BASE.multiply(2n).add(rp).subtract(RistrettoPoint.BASE).toRawBytes();
// also has add(), equals(), multiply(), toRawBytes() methods RistrettoPoint.ZERO.equals(dp) === false;
// pre-hashed hash-to-curve
RistrettoPoint.hashToCurve(sha512(msg));
// full hash-to-curve including domain separation tag
hashToRistretto255(msg, { DST: 'ristretto255_XMD:SHA-512_R255MAP_RO_' });
``` ```
ed448 is similar: #### ed448, X448, decaf448
```ts ```ts
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);
// Variants from RFC8032: prehashed
import { ed448ph } from '@noble/curves/ed448';
```
ECDH using Curve448 aka X448, follows [RFC7748](https://www.rfc-editor.org/rfc/rfc7748).
```ts
import { x448 } from '@noble/curves/ed448';
x448.getSharedSecret(priv, pub) === x448.scalarMult(priv, pub); // aliases
x448.getPublicKey(priv) === x448.scalarMultBase(priv);
// ed448 => x448 conversion
import { edwardsToMontgomeryPub } from '@noble/curves/ed448';
edwardsToMontgomeryPub(ed448.getPublicKey(ed448.utils.randomPrivateKey()));
```
decaf448 follows [irtf draft](https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-ristretto255-decaf448).
```ts
import { utf8ToBytes } from '@noble/hashes/utils';
import { shake256 } from '@noble/hashes/sha3';
import { hashToCurve, encodeToCurve, DecafPoint, hashToDecaf448 } from '@noble/curves/ed448';
const msg = utf8ToBytes('Ristretto is traditionally a short shot of espresso coffee');
hashToCurve(msg);
const dp = DecafPoint.fromHex(
'c898eb4f87f97c564c6fd61fc7e49689314a1f818ec85eeb3bd5514ac816d38778f69ef347a89fca817e66defdedce178c7cc709b2116e75'
);
DecafPoint.BASE.multiply(2n).add(dp).subtract(DecafPoint.BASE).toRawBytes();
DecafPoint.ZERO.equals(dp) === false;
// pre-hashed hash-to-curve
DecafPoint.hashToCurve(shake256(msg, { dkLen: 112 }));
// full hash-to-curve including domain separation tag
hashToDecaf448(msg, { DST: 'decaf448_XOF:SHAKE256_D448MAP_RO_' });
```
Same RFC7748 / RFC8032 / IRTF draft are followed.
#### bls12-381
See [abstract/bls](#abstractbls-barreto-lynn-scott-curves).
#### All available imports
```typescript
import { secp256k1, schnorr } from '@noble/curves/secp256k1';
import { ed25519, ed25519ph, ed25519ctx, x25519, RistrettoPoint } from '@noble/curves/ed25519';
import { ed448, ed448ph, ed448ctx, x448 } from '@noble/curves/ed448'; import { ed448, ed448ph, ed448ctx, x448 } from '@noble/curves/ed448';
import { hashToCurve, encodeToCurve } from '@noble/curves/ed448'; import { p256 } from '@noble/curves/p256';
ed448.getPublicKey(ed448.utils.randomPrivateKey()); import { p384 } from '@noble/curves/p384';
import { p521 } from '@noble/curves/p521';
import { pallas, vesta } from '@noble/curves/pasta';
import { bls12_381 } from '@noble/curves/bls12-381';
import { bn254 } from '@noble/curves/bn254'; // also known as alt_bn128
import { jubjub } from '@noble/curves/jubjub';
import { bytesToHex, hexToBytes, concatBytes, utf8ToBytes } from '@noble/curves/abstract/utils';
``` ```
Every curve has `CURVE` object that contains its parameters, field, and others: #### Accessing a curve's variables
```ts ```ts
import { secp256k1 } from '@noble/curves/secp256k1'; // ESM and Common.js import { secp256k1 } from '@noble/curves/secp256k1';
console.log(secp256k1.CURVE.p, secp256k1.CURVE.n, secp256k1.CURVE.a, secp256k1.CURVE.b); // Every curve has `CURVE` object that contains its parameters, field, and others
console.log(secp256k1.CURVE.p); // field modulus
console.log(secp256k1.CURVE.n); // curve order
console.log(secp256k1.CURVE.a, secp256k1.CURVE.b); // equation params
console.log(secp256k1.CURVE.Gx, secp256k1.CURVE.Gy); // base point coordinates
``` ```
## Abstract API ## Abstract API
@@ -178,17 +274,6 @@ Precomputes are enabled for weierstrass and edwards BASE points of a curve. You
could precompute any other point (e.g. for ECDH) using `utils.precompute()` could precompute any other point (e.g. for ECDH) using `utils.precompute()`
method: check out examples. method: check out examples.
There are following zero-dependency algorithms:
- [abstract/weierstrass: Short Weierstrass curve](#abstractweierstrass-short-weierstrass-curve)
- [abstract/edwards: Twisted Edwards curve](#abstractedwards-twisted-edwards-curve)
- [abstract/montgomery: Montgomery curve](#abstractmontgomery-montgomery-curve)
- [abstract/bls: Barreto-Lynn-Scott curves](#abstractbls-barreto-lynn-scott-curves)
- [abstract/hash-to-curve: Hashing strings to curve points](#abstracthash-to-curve-hashing-strings-to-curve-points)
- [abstract/poseidon: Poseidon hash](#abstractposeidon-poseidon-hash)
- [abstract/modular: Modular arithmetics utilities](#abstractmodular-modular-arithmetics-utilities)
- [abstract/utils: General utilities](#abstractutils-general-utilities)
### abstract/weierstrass: Short Weierstrass curve ### abstract/weierstrass: Short Weierstrass curve
```ts ```ts
@@ -212,7 +297,7 @@ const secq256k1 = weierstrass({
randomBytes, randomBytes,
}); });
// Replace weierstrass with weierstrassPoints if you don't need ECDSA, hash, hmac, randomBytes // Replace weierstrass() with weierstrassPoints() if you don't need ECDSA, hash, hmac, randomBytes
``` ```
Short Weierstrass curve's formula is `y² = x³ + ax + b`. `weierstrass` Short Weierstrass curve's formula is `y² = x³ + ax + b`. `weierstrass`
@@ -233,6 +318,11 @@ type CHash = {
}; };
``` ```
**Message hash** is expected instead of message itself:
- `sign(msgHash, privKey)` is default behavior, assuming you pre-hash msg with sha2, or other hash
- `sign(msg, privKey, {prehash: true})` option can be used if you want to pass the message itself
**Weierstrass points:** **Weierstrass points:**
1. Exported as `ProjectivePoint` 1. Exported as `ProjectivePoint`
@@ -328,6 +418,7 @@ More examples:
const priv = secq256k1.utils.randomPrivateKey(); const priv = secq256k1.utils.randomPrivateKey();
secq256k1.getPublicKey(priv); // Convert private key to public. secq256k1.getPublicKey(priv); // Convert private key to public.
const sig = secq256k1.sign(msg, priv); // Sign msg with private key. const sig = secq256k1.sign(msg, priv); // Sign msg with private key.
const sig2 = secq256k1.sign(msg, priv, { prehash: true }); // hash(msg)
secq256k1.verify(sig, msg, priv); // Verify if sig is correct. secq256k1.verify(sig, msg, priv); // Verify if sig is correct.
const Point = secq256k1.ProjectivePoint; const Point = secq256k1.ProjectivePoint;
@@ -475,6 +566,8 @@ use aggregated, batch-verifiable
[threshold signatures](https://medium.com/snigirev.stepan/bls-signatures-better-than-schnorr-5a7fe30ea716), [threshold signatures](https://medium.com/snigirev.stepan/bls-signatures-better-than-schnorr-5a7fe30ea716),
using Boneh-Lynn-Shacham signature scheme. using Boneh-Lynn-Shacham signature scheme.
The module doesn't expose `CURVE` property: use `G1.CURVE`, `G2.CURVE` instead.
Main methods and properties are: Main methods and properties are:
- `getPublicKey(privateKey)` - `getPublicKey(privateKey)`
@@ -518,7 +611,12 @@ const aggSignature3 = bls.aggregateSignatures(signatures3);
const isValid3 = bls.verifyBatch(aggSignature3, messages, publicKeys); const isValid3 = bls.verifyBatch(aggSignature3, messages, publicKeys);
console.log({ publicKeys, signatures3, aggSignature3, isValid3 }); console.log({ publicKeys, signatures3, aggSignature3, isValid3 });
// bls.pairing(PointG1, PointG2) // pairings // Pairings, with and without final exponentiation
// bls.pairing(PointG1, PointG2);
// bls.pairing(PointG1, PointG2, false);
// bls.fields.Fp12.finalExponentiate(bls.fields.Fp12.mul(eGS, ePHm));
// Others
// bls.G1.ProjectivePoint.BASE, bls.G2.ProjectivePoint.BASE // bls.G1.ProjectivePoint.BASE, bls.G2.ProjectivePoint.BASE
// bls.fields.Fp, bls.fields.Fp2, bls.fields.Fp12, bls.fields.Fr // bls.fields.Fp, bls.fields.Fp2, bls.fields.Fp12, bls.fields.Fr
@@ -577,7 +675,7 @@ utils: {
### abstract/hash-to-curve: Hashing strings to curve points ### abstract/hash-to-curve: Hashing strings to curve points
The module allows to hash arbitrary strings to elliptic curve points. Implements [hash-to-curve v16](https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-16). The module allows to hash arbitrary strings to elliptic curve points. Implements [RFC 9380](https://www.rfc-editor.org/rfc/rfc9380).
Every curve has exported `hashToCurve` and `encodeToCurve` methods. You should always prefer `hashToCurve` for security: Every curve has exported `hashToCurve` and `encodeToCurve` methods. You should always prefer `hashToCurve` for security:
@@ -593,19 +691,17 @@ bls12_381.G1.hashToCurve(randomBytes(), { DST: 'another' });
bls12_381.G2.hashToCurve(randomBytes(), { DST: 'custom' }); bls12_381.G2.hashToCurve(randomBytes(), { DST: 'custom' });
``` ```
If you need low-level methods from spec: Low-level methods from the spec:
`expand_message_xmd` [(spec)](https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-5.4.1) produces a uniformly random byte string using a cryptographic hash function H that outputs b bits.
Hash must conform to `CHash` interface (see [weierstrass section](#abstractweierstrass-short-weierstrass-curve)).
```ts ```ts
// produces a uniformly random byte string using a cryptographic hash function H that outputs b bits.
function expand_message_xmd( function expand_message_xmd(
msg: Uint8Array, msg: Uint8Array,
DST: Uint8Array, DST: Uint8Array,
lenInBytes: number, lenInBytes: number,
H: CHash H: CHash // For CHash see abstract/weierstrass docs section
): Uint8Array; ): Uint8Array;
// produces a uniformly random byte string using an extendable-output function (XOF) H.
function expand_message_xof( function expand_message_xof(
msg: Uint8Array, msg: Uint8Array,
DST: Uint8Array, DST: Uint8Array,
@@ -613,13 +709,9 @@ function expand_message_xof(
k: number, k: number,
H: CHash H: CHash
): Uint8Array; ): Uint8Array;
``` // Hashes arbitrary-length byte strings to a list of one or more elements of a finite field F
function hash_to_field(msg: Uint8Array, count: number, options: Opts): bigint[][];
`hash_to_field(msg, count, options)`
[(spec)](https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-5.3)
hashes arbitrary-length byte strings to a list of one or more elements of a finite field F.
```ts
/** /**
* * `DST` is a domain separation tag, defined in section 2.2.5 * * `DST` is a domain separation tag, defined in section 2.2.5
* * `p` characteristic of F, where F is a finite field of characteristic p and order q = p^m * * `p` characteristic of F, where F is a finite field of characteristic p and order q = p^m
@@ -630,23 +722,13 @@ hashes arbitrary-length byte strings to a list of one or more elements of a fini
*/ */
type UnicodeOrBytes = string | Uint8Array; type UnicodeOrBytes = string | Uint8Array;
type Opts = { type Opts = {
DST: UnicodeOrBytes; DST: UnicodeOrBytes;
p: bigint; p: bigint;
m: number; m: number;
k: number; k: number;
expand?: 'xmd' | 'xof'; expand?: 'xmd' | 'xof';
hash: CHash; hash: CHash;
}; };
/**
* Hashes arbitrary-length byte strings to a list of one or more elements of a finite field F
* https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-5.3
* @param msg a byte string containing the message to hash
* @param count the number of elements of F to output
* @param options `{DST: string, p: bigint, m: number, k: number, expand: 'xmd' | 'xof', hash: H}`, see above
* @returns [u_0, ..., u_(count - 1)], a list of field elements.
*/
function hash_to_field(msg: Uint8Array, count: number, options: Opts): bigint[][];
``` ```
### abstract/poseidon: Poseidon hash ### abstract/poseidon: Poseidon hash
@@ -689,30 +771,40 @@ mod.invert(17n, 10n); // invert(17) mod 10; modular multiplicative inverse
mod.invertBatch([1n, 2n, 4n], 21n); // => [1n, 11n, 16n] in one inversion mod.invertBatch([1n, 2n, 4n], 21n); // => [1n, 11n, 16n] in one inversion
``` ```
Field operations are not constant-time: they are using JS bigints, see [security](#security).
The fact is mostly irrelevant, but the important method to keep in mind is `pow`,
which may leak exponent bits, when used naïvely.
`mod.Field` is always **field over prime**. Non-prime fields aren't supported for now.
We don't test for prime-ness for speed and because algorithms are probabilistic anyway.
Initializing a non-prime field could make your app suspectible to
DoS (infilite loop) on Tonelli-Shanks square root calculation.
Unlike `mod.invert`, `mod.invertBatch` won't throw on `0`: make sure to throw an error yourself.
#### Creating private keys from hashes #### Creating private keys from hashes
Suppose you have `sha256(something)` (e.g. from HMAC) and you want to make a private key from it. You can't simply make a 32-byte private key from a 32-byte hash.
Even though p256 or secp256k1 may have 32-byte private keys, Doing so will make the key [biased](https://research.kudelskisecurity.com/2020/07/28/the-definitive-guide-to-modulo-bias-and-how-to-avoid-it/).
and sha256 output is also 32-byte, you can't just use it and reduce it modulo `CURVE.n`.
Doing so will make the result key [biased](https://research.kudelskisecurity.com/2020/07/28/the-definitive-guide-to-modulo-bias-and-how-to-avoid-it/). To make the bias negligible, we follow [FIPS 186-5 A.2](https://csrc.nist.gov/publications/detail/fips/186/5/final)
and [RFC 9380](https://www.rfc-editor.org/rfc/rfc9380#section-5.2).
This means, for 32-byte key, we would need 48-byte hash to get 2^-128 bias, which matches curve security level.
To avoid the bias, we implement FIPS 186 B.4.1, which allows to take arbitrary `hashToPrivateScalar()` that hashes to **private key** was created for this purpose.
byte array and produce valid scalars / private keys with bias being neglible. Use [abstract/hash-to-curve](#abstracthash-to-curve-hashing-strings-to-curve-points)
if you need to hash to **public key**.
Use [hash-to-curve](#abstracthash-to-curve-hashing-strings-to-curve-points) if you need
hashing to **public keys**; the function in the module instead operates on **private keys**.
```ts ```ts
import { p256 } from '@noble/curves/p256'; import { p256 } from '@noble/curves/p256';
import { sha256 } from '@noble/hashes/sha256'; import { sha256 } from '@noble/hashes/sha256';
import { hkdf } from '@noble/hashes/hkdf'; import { hkdf } from '@noble/hashes/hkdf';
const someKey = new Uint8Array(32).fill(2); // Needs to actually be random, not .fill(2) const someKey = new Uint8Array(32).fill(2); // Needs to actually be random, not .fill(2)
const derived = hkdf(sha256, someKey, undefined, 'application', 40); // 40 bytes const derived = hkdf(sha256, someKey, undefined, 'application', 48); // 48 bytes for 32-byte priv
const validPrivateKey = mod.hashToPrivateScalar(derived, p256.CURVE.n); const validPrivateKey = mod.hashToPrivateScalar(derived, p256.CURVE.n);
``` ```
### abstract/utils: General utilities ### abstract/utils: Useful utilities
```ts ```ts
import * as utils from '@noble/curves/abstract/utils'; import * as utils from '@noble/curves/abstract/utils';
@@ -734,96 +826,122 @@ utils.equalBytes(Uint8Array.from([0xde]), Uint8Array.from([0xde]));
## Security ## Security
1. The library has been audited during Jan-Feb 2023 by an independent security firm [Trail of Bits](https://www.trailofbits.com): 1. The library has been independently audited:
[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).
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.
We consider infrastructure attacks like rogue NPM modules very important; that's why it's crucial to minimize the amount of 3rd-party dependencies & native bindings. If your app uses 500 dependencies, any dep could get hacked and you'll be downloading malware with every `npm install`. Our goal is to minimize this attack vector. As for devDependencies used by the library: - in Feb 2023 by [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](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 v0.7.3 audit](https://github.com/paulmillr/noble-curves/compare/0.7.3..main).
- `@scure` base, bip32, bip39 (used in tests), micro-bmark (benchmark), micro-should (testing) are developed by us 2. The library has been fuzzed by [Guido Vranken's cryptofuzz](https://github.com/guidovranken/cryptofuzz).
and follow the same practices such as: minimal library size, auditability, signed releases You can run the fuzzer by yourself to check it.
- prettier (linter), fast-check (property-based testing), 3. [Timing attack](https://en.wikipedia.org/wiki/Timing_attack) considerations:
typescript versions are locked and rarely updated. Every update is checked with `npm-diff`. _JIT-compiler_ and _Garbage Collector_ make "constant time" extremely hard to
achieve in a scripting language. Which means _any other JS library can't have
constant-timeness_. Even statically typed Rust, a language without GC,
[makes it harder to achieve constant-time](https://www.chosenplaintext.ca/open-source/rust-timing-shield/security)
for some cases. If your goal is absolute security, don't use any JS lib — including bindings to native ones.
Use low-level libraries & languages. Nonetheless we're targetting algorithmic constant time.
We consider infrastructure attacks like rogue NPM modules very important;
that's why it's crucial to minimize the amount of 3rd-party dependencies & native bindings.
If your app uses 500 dependencies, any dep could get hacked and you'll be
downloading malware with every `npm install`. Our goal is to minimize this attack vector.
As for devDependencies used by the library:
- `@scure` base, bip32, bip39 (used in tests), micro-bmark (benchmark), micro-should (testing)
are developed by us and follow the same practices such as: minimal library size, auditability,
signed releases
- prettier (linter), fast-check (property-based testing), typescript versions
are locked and rarely updated. Every update is checked with `npm-diff`.
The packages are big, which makes it hard to audit their source code thoroughly and fully. The packages are big, which makes it hard to audit their source code thoroughly and fully.
- They are only used if you clone the git repo and want to add some feature to it. End-users won't use them. - They are only used if you clone the git repo and want to add some feature to it. End-users won't use them.
As for key generation, we're deferring to built-in
[crypto.getRandomValues](https://developer.mozilla.org/en-US/docs/Web/API/Crypto/getRandomValues)
which is considered cryptographically secure (CSPRNG).
## Speed ## Speed
Benchmark results on Apple M2 with node v19: Benchmark results on Apple M2 with node v20:
``` ```
secp256k1 secp256k1
init x 58 ops/sec @ 17ms/op init x 68 ops/sec @ 14ms/op
getPublicKey x 5,640 ops/sec @ 177μs/op getPublicKey x 6,750 ops/sec @ 148μs/op
sign x 4,471 ops/sec @ 223μs/op sign x 5,206 ops/sec @ 192μs/op
verify x 780 ops/sec @ 1ms/op verify x 880 ops/sec @ 1ms/op
getSharedSecret x 465 ops/sec @ 2ms/op getSharedSecret x 536 ops/sec @ 1ms/op
recoverPublicKey x 740 ops/sec @ 1ms/op recoverPublicKey x 852 ops/sec @ 1ms/op
schnorr.sign x 597 ops/sec @ 1ms/op schnorr.sign x 685 ops/sec @ 1ms/op
schnorr.verify x 775 ops/sec @ 1ms/op schnorr.verify x 908 ops/sec @ 1ms/op
P256 p256
init x 31 ops/sec @ 31ms/op init x 38 ops/sec @ 26ms/op
getPublicKey x 5,607 ops/sec @ 178μs/op getPublicKey x 6,530 ops/sec @ 153μs/op
sign x 4,583 ops/sec @ 218μs/op sign x 5,074 ops/sec @ 197μs/op
verify x 540 ops/sec @ 1ms/op verify x 626 ops/sec @ 1ms/op
P384 p384
init x 15 ops/sec @ 63ms/op init x 17 ops/sec @ 57ms/op
getPublicKey x 2,622 ops/sec @ 381μs/op getPublicKey x 2,883 ops/sec @ 346μs/op
sign x 2,106 ops/sec @ 474μs/op sign x 2,358 ops/sec @ 424μs/op
verify x 222 ops/sec @ 4ms/op verify x 245 ops/sec @ 4ms/op
P521 p521
init x 8 ops/sec @ 119ms/op init x 9 ops/sec @ 109ms/op
getPublicKey x 1,371 ops/sec @ 729μs/op getPublicKey x 1,516 ops/sec @ 659μs/op
sign x 1,164 ops/sec @ 858μs/op sign x 1,271 ops/sec @ 786μs/op
verify x 118 ops/sec @ 8ms/op verify x 123 ops/sec @ 8ms/op
ed25519 ed25519
init x 47 ops/sec @ 20ms/op init x 54 ops/sec @ 18ms/op
getPublicKey x 9,414 ops/sec @ 106μs/op getPublicKey x 10,269 ops/sec @ 97μs/op
sign x 4,516 ops/sec @ 221μs/op sign x 5,110 ops/sec @ 195μs/op
verify x 912 ops/sec @ 1ms/op verify x 1,049 ops/sec @ 952μs/op
ed448 ed448
init x 17 ops/sec @ 56ms/op init x 19 ops/sec @ 51ms/op
getPublicKey x 3,363 ops/sec @ 297μs/op getPublicKey x 3,775 ops/sec @ 264μs/op
sign x 1,615 ops/sec @ 619μs/op sign x 1,771 ops/sec @ 564μs/op
verify x 319 ops/sec @ 3ms/op verify x 351 ops/sec @ 2ms/op
ecdh ecdh
├─x25519 x 1,337 ops/sec @ 747μs/op ├─x25519 x 1,466 ops/sec @ 682μs/op
├─secp256k1 x 461 ops/sec @ 2ms/op ├─secp256k1 x 539 ops/sec @ 1ms/op
├─P256 x 441 ops/sec @ 2ms/op ├─p256 x 511 ops/sec @ 1ms/op
├─P384 x 179 ops/sec @ 5ms/op ├─p384 x 199 ops/sec @ 5ms/op
├─P521 x 93 ops/sec @ 10ms/op ├─p521 x 103 ops/sec @ 9ms/op
└─x448 x 496 ops/sec @ 2ms/op └─x448 x 548 ops/sec @ 1ms/op
bls12-381 bls12-381
init x 32 ops/sec @ 30ms/op init x 36 ops/sec @ 27ms/op
getPublicKey 1-bit x 858 ops/sec @ 1ms/op getPublicKey 1-bit x 973 ops/sec @ 1ms/op
getPublicKey x 858 ops/sec @ 1ms/op getPublicKey x 970 ops/sec @ 1ms/op
sign x 49 ops/sec @ 20ms/op sign x 55 ops/sec @ 17ms/op
verify x 34 ops/sec @ 28ms/op verify x 39 ops/sec @ 25ms/op
pairing x 94 ops/sec @ 10ms/op pairing x 106 ops/sec @ 9ms/op
aggregatePublicKeys/8 x 116 ops/sec @ 8ms/op aggregatePublicKeys/8 x 129 ops/sec @ 7ms/op
aggregatePublicKeys/32 x 31 ops/sec @ 31ms/op aggregatePublicKeys/32 x 34 ops/sec @ 28ms/op
aggregatePublicKeys/128 x 7 ops/sec @ 125ms/op aggregatePublicKeys/128 x 8 ops/sec @ 112ms/op
aggregateSignatures/8 x 45 ops/sec @ 22ms/op aggregatePublicKeys/512 x 2 ops/sec @ 446ms/op
aggregateSignatures/32 x 11 ops/sec @ 84ms/op aggregatePublicKeys/2048 x 0 ops/sec @ 1778ms/op
aggregateSignatures/128 x 3 ops/sec @ 332ms/opp aggregateSignatures/8 x 50 ops/sec @ 19ms/op
aggregateSignatures/32 x 13 ops/sec @ 74ms/op
aggregateSignatures/128 x 3 ops/sec @ 296ms/op
aggregateSignatures/512 x 0 ops/sec @ 1180ms/op
aggregateSignatures/2048 x 0 ops/sec @ 4715ms/op
hash-to-curve hash-to-curve
hash_to_field x 850,340 ops/sec @ 1μs/op hash_to_field x 91,600 ops/sec @ 10μs/op
secp256k1 x 2,143 ops/sec @ 466μs/op secp256k1 x 2,373 ops/sec @ 421μs/op
P256 x 3,861 ops/sec @ 258μs/op p256 x 4,310 ops/sec @ 231μs/op
P384 x 1,526 ops/sec @ 655μs/op p384 x 1,664 ops/sec @ 600μs/op
P521 x 748 ops/sec @ 1ms/op p521 x 807 ops/sec @ 1ms/op
ed25519 x 2,772 ops/sec @ 360μs/op ed25519 x 3,088 ops/sec @ 323μs/op
ed448 x 1,146 ops/sec @ 871μs/op ed448 x 1,247 ops/sec @ 801μs/op
``` ```
## Contributing & testing ## Contributing & testing
@@ -836,33 +954,37 @@ ed448 x 1,146 ops/sec @ 871μs/op
## Upgrading ## Upgrading
Previously, the library was split into single-feature packages Previously, the library was split into single-feature packages
noble-secp256k1 and noble-ed25519. curves can be thought as a continuation of their [noble-secp256k1](https://github.com/paulmillr/noble-secp256k1),
original work. The libraries now changed their direction towards providing [noble-ed25519](https://github.com/paulmillr/noble-ed25519) and
minimal 4kb implementations of cryptography and are not as feature-complete. [noble-bls12-381](https://github.com/paulmillr/noble-bls12-381).
Upgrading from @noble/secp256k1 2.0 or @noble/ed25519 2.0: no changes, libraries are compatible. 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](https://github.com/paulmillr/noble-secp256k1) 1.7: Upgrading from noble-secp256k1 2.0 or noble-ed25519 2.0: no changes, libraries are compatible.
Upgrading from noble-secp256k1 1.7:
- `getPublicKey` - `getPublicKey`
- now produce 33-byte compressed signatures by default - now produce 33-byte compressed signatures by default
- to use old behavior, which produced 65-byte uncompressed keys, set - to use old behavior, which produced 65-byte uncompressed keys, set
argument `isCompressed` to `false`: `getPublicKey(priv, false)` argument `isCompressed` to `false`: `getPublicKey(priv, false)`
- `sign` - `sign`
- is now sync; use `signAsync` for async version - is now sync; use `signAsync` for async version
- now returns `Signature` instance with `{ r, s, recovery }` properties - now returns `Signature` instance with `{ r, s, recovery }` properties
- `canonical` option was renamed to `lowS` - `canonical` option was renamed to `lowS`
- `recovered` option has been removed because recovery bit is always returned now - `recovered` option has been removed because recovery bit is always returned now
- `der` option has been removed. There are 2 options: - `der` option has been removed. There are 2 options:
1. Use compact encoding: `fromCompact`, `toCompactRawBytes`, `toCompactHex`. 1. Use compact encoding: `fromCompact`, `toCompactRawBytes`, `toCompactHex`.
Compact encoding is simply a concatenation of 32-byte r and 32-byte s. Compact encoding is simply a concatenation of 32-byte r and 32-byte s.
2. If you must use DER encoding, switch to noble-curves (see above). 2. If you must use DER encoding, switch to noble-curves (see above).
- `verify` - `verify`
- `strict` option was renamed to `lowS` - `strict` option was renamed to `lowS`
- `getSharedSecret` - `getSharedSecret`
- now produce 33-byte compressed signatures by default - now produce 33-byte compressed signatures by default
- to use old behavior, which produced 65-byte uncompressed keys, set - to use old behavior, which produced 65-byte uncompressed keys, set
argument `isCompressed` to `false`: `getSharedSecret(a, b, false)` argument `isCompressed` to `false`: `getSharedSecret(a, b, false)`
- `recoverPublicKey(msg, sig, rec)` was changed to `sig.recoverPublicKey(msg)` - `recoverPublicKey(msg, sig, rec)` was changed to `sig.recoverPublicKey(msg)`
- `number` type for private keys have been removed: use `bigint` instead - `number` type for private keys have been removed: use `bigint` instead
- `Point` (2d xy) has been changed to `ProjectivePoint` (3d xyz) - `Point` (2d xy) has been changed to `ProjectivePoint` (3d xyz)
@@ -878,40 +1000,70 @@ Upgrading from [@noble/ed25519](https://github.com/paulmillr/noble-ed25519) 1.7:
- `utils` were split into `utils` (same api as in noble-curves) and - `utils` were split into `utils` (same api as in noble-curves) and
`etc` (`sha512Sync` and others) `etc` (`sha512Sync` and others)
- `getSharedSecret` was moved to `x25519` module - `getSharedSecret` was moved to `x25519` module
- `toX25519` has been moved to `edwardsToMontgomeryPub` and `edwardsToMontgomeryPriv` methods
Upgrading from [@noble/bls12-381](https://github.com/paulmillr/noble-bls12-381): Upgrading from [@noble/bls12-381](https://github.com/paulmillr/noble-bls12-381):
- Methods and classes were renamed: - Methods and classes were renamed:
- PointG1 -> G1.Point, PointG2 -> G2.Point - PointG1 -> G1.Point, PointG2 -> G2.Point
- PointG2.fromSignature -> Signature.decode, PointG2.toSignature -> Signature.encode - PointG2.fromSignature -> Signature.decode, PointG2.toSignature -> Signature.encode
- Fp2 ORDER was corrected - Fp2 ORDER was corrected
## Resources ## Resources
Useful articles about the library or its primitives:
- [Learning fast elliptic-curve cryptography](https://paulmillr.com/posts/noble-secp256k1-fast-ecc/) - [Learning fast elliptic-curve cryptography](https://paulmillr.com/posts/noble-secp256k1-fast-ecc/)
- EdDSA
- [A Deep dive into Ed25519 Signatures](https://cendyne.dev/posts/2022-03-06-ed25519-signatures.html)
- [Ed25519 Deep Dive Addendum](https://cendyne.dev/posts/2022-09-11-ed25519-deep-dive-addendum.html)
- [Its 255:19AM. Do you know what your validation criteria are?](https://hdevalence.ca/blog/2020-10-04-its-25519am)
- [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
- [Cofactor Explained: Clearing Elliptic Curves dirty little secret](https://loup-vaillant.fr/tutorials/cofactor)
- [Surrounded by Elligators](https://loup-vaillant.fr/articles/implementing-elligator)
- Pairings and BLS - Pairings and BLS
- [BLS12-381 for the rest of us](https://hackmd.io/@benjaminion/bls12-381) - [BLS signatures for busy people](https://gist.github.com/paulmillr/18b802ad219b1aee34d773d08ec26ca2)
- [Key concepts of pairings](https://medium.com/@alonmuroch_65570/bls-signatures-part-2-key-concepts-of-pairings-27a8a9533d0c) - [BLS12-381 for the rest of us](https://hackmd.io/@benjaminion/bls12-381)
- Pairing over bls12-381: - [Key concepts of pairings](https://medium.com/@alonmuroch_65570/bls-signatures-part-2-key-concepts-of-pairings-27a8a9533d0c)
[part 1](https://research.nccgroup.com/2020/07/06/pairing-over-bls12-381-part-1-fields/), - Pairing over bls12-381:
[part 2](https://research.nccgroup.com/2020/07/13/pairing-over-bls12-381-part-2-curves/), [fields](https://research.nccgroup.com/2020/07/06/pairing-over-bls12-381-part-1-fields/),
[part 3](https://research.nccgroup.com/2020/08/13/pairing-over-bls12-381-part-3-pairing/) [curves](https://research.nccgroup.com/2020/07/13/pairing-over-bls12-381-part-2-curves/),
- [Estimating the bit security of pairing-friendly curves](https://research.nccgroup.com/2022/02/03/estimating-the-bit-security-of-pairing-friendly-curves/) [pairings](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: ### Demos
- [Elliptic Curve Calculator](https://paulmillr.com/noble) online demo: add / multiply points, sign messages - [Elliptic Curve Calculator](https://paulmillr.com/noble): add / multiply points, sign messages
- Signers for web3 projects: - [BLS threshold signatures](https://genthresh.com)
[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 ### Projects using curves
- [scure-bip32](https://github.com/paulmillr/scure-bip32) and separate [bip32](https://github.com/bitcoinjs/bip32) HDkey libraries
- HDkey libraries: [scure-bip32](https://github.com/paulmillr/scure-bip32), [bip32](https://github.com/bitcoinjs/bip32)
- Social networks: [nostr](https://github.com/nbd-wtf/nostr-tools), [bluesky](https://github.com/bluesky-social/atproto)
- Ethereum libraries:
- [ethereum-cryptography](https://github.com/ethereum/js-ethereum-cryptography)
- [micro-eth-signer](https://github.com/paulmillr/micro-eth-signer),
[ethers](https://github.com/ethers-io/ethers.js) (old noble),
[viem.sh](https://viem.sh),
[@ethereumjs](https://github.com/ethereumjs/ethereumjs-monorepo)
- [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)
- [tapscript](https://github.com/cmdruid/tapscript)
- Solana libraries: [micro-sol-signer](https://github.com/paulmillr/micro-sol-signer), [solana-web3.js](https://github.com/solana-labs/solana-web3.js)
- Other web3 stuff:
- [scure-starknet](https://github.com/paulmillr/scure-starknet)
- [aztec](https://github.com/AztecProtocol/aztec-packages)
- [polkadot.js](https://github.com/polkadot-js/common), [drand-client](https://github.com/drand/drand-client), [moneroj](https://github.com/beritani/moneroj), [tronlib](https://github.com/CoinSpace/tronlib)
- [protonmail](https://github.com/ProtonMail/WebClients) (old noble for now)
- [did-jwt](https://github.com/decentralized-identity/did-jwt), [hpke-js](https://github.com/dajiaji/hpke-js),
[js-libp2p-noise](https://github.com/ChainSafe/js-libp2p-noise)
- [ed25519-keygen](https://github.com/paulmillr/ed25519-keygen) SSH, PGP, TOR key generation - [ed25519-keygen](https://github.com/paulmillr/ed25519-keygen) SSH, PGP, TOR key generation
- [micro-starknet](https://github.com/paulmillr/micro-starknet) stark-friendly elliptic curve algorithms. - [secp256k1 compatibility layer](https://github.com/ethereum/js-ethereum-cryptography/blob/2.0.0/src/secp256k1-compat.ts)
- BLS threshold sigs demo [genthresh.com](https://genthresh.com) for users who want to switch from secp256k1-node or tiny-secp256k1. Allows to see which methods map to corresponding noble code.
- BLS BBS signatures [github.com/Wind4Greg/BBS-Draft-Checks](https://github.com/Wind4Greg/BBS-Draft-Checks) following [draft-irtf-cfrg-bbs-signatures-latest](https://identity.foundation/bbs-signature/draft-irtf-cfrg-bbs-signatures.html) - [BLS BBS signatures](https://github.com/Wind4Greg/BBS-Draft-Checks) following [draft-irtf-cfrg-bbs-signatures-latest](https://identity.foundation/bbs-signature/draft-irtf-cfrg-bbs-signatures.html)
- [KZG trusted setup ceremony](https://github.com/dsrvlabs/czg-keremony) - [KZG trusted setup ceremony](https://github.com/dsrvlabs/czg-keremony)
- See [full list of projects on GitHub](https://github.com/paulmillr/noble-curves/network/dependents).
## License ## License

View File

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

View File

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

18
benchmark/decaf448.js Normal file
View File

@@ -0,0 +1,18 @@
import { run, mark, utils } from 'micro-bmark';
import { shake256 } from '@noble/hashes/sha3';
import * as mod from '../abstract/modular.js';
import { ed448, DecafPoint } from '../ed448.js';
run(async () => {
const RAM = false;
if (RAM) utils.logMem();
console.log(`\x1b[36mdecaf448\x1b[0m`);
const priv = mod.hashToPrivateScalar(shake256(ed448.utils.randomPrivateKey(), { dkLen: 112 }), ed448.CURVE.n);
const pub = DecafPoint.BASE.multiply(priv);
const encoded = pub.toRawBytes();
await mark('add', 1000000, () => pub.add(DecafPoint.BASE));
await mark('multiply', 1000, () => DecafPoint.BASE.multiply(priv));
await mark('encode', 10000, () => DecafPoint.BASE.toRawBytes());
await mark('decode', 10000, () => DecafPoint.fromHex(encoded));
if (RAM) utils.logMem();
});

View File

@@ -8,8 +8,8 @@ import { hashToCurve as secp256k1 } from '../secp256k1.js';
import { hashToCurve as p256 } from '../p256.js'; import { hashToCurve as p256 } from '../p256.js';
import { hashToCurve as p384 } from '../p384.js'; import { hashToCurve as p384 } from '../p384.js';
import { hashToCurve as p521 } from '../p521.js'; import { hashToCurve as p521 } from '../p521.js';
import { hashToCurve as ed25519 } from '../ed25519.js'; import { hashToCurve as ed25519, hash_to_ristretto255 } from '../ed25519.js';
import { hashToCurve as ed448 } from '../ed448.js'; import { hashToCurve as ed448, hash_to_decaf448 } from '../ed448.js';
import { utf8ToBytes } from '../abstract/utils.js'; import { utf8ToBytes } from '../abstract/utils.js';
const N = 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141n; const N = 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141n;
@@ -26,4 +26,7 @@ run(async () => {
for (let [title, fn] of Object.entries({ secp256k1, p256, p384, p521, ed25519, ed448 })) { for (let [title, fn] of Object.entries({ secp256k1, p256, p384, p521, ed25519, ed448 })) {
await mark(`hashToCurve ${title}`, 1000, () => fn(msg)); await mark(`hashToCurve ${title}`, 1000, () => fn(msg));
} }
await mark('hash_to_ristretto255', 1000, () => hash_to_ristretto255(msg, { DST: 'ristretto255_XMD:SHA-512_R255MAP_RO_' }));
await mark('hash_to_decaf448', 1000, () => hash_to_decaf448(msg, { DST: 'decaf448_XOF:SHAKE256_D448MAP_RO_' }));
}); });

18
benchmark/ristretto255.js Normal file
View File

@@ -0,0 +1,18 @@
import { run, mark, utils } from 'micro-bmark';
import { sha512 } from '@noble/hashes/sha512';
import * as mod from '../abstract/modular.js';
import { ed25519, RistrettoPoint } from '../ed25519.js';
run(async () => {
const RAM = false;
if (RAM) utils.logMem();
console.log(`\x1b[36mristretto255\x1b[0m`);
const priv = mod.hashToPrivateScalar(sha512(ed25519.utils.randomPrivateKey()), ed25519.CURVE.n);
const pub = RistrettoPoint.BASE.multiply(priv);
const encoded = pub.toRawBytes();
await mark('add', 1000000, () => pub.add(RistrettoPoint.BASE));
await mark('multiply', 10000, () => RistrettoPoint.BASE.multiply(priv));
await mark('encode', 10000, () => RistrettoPoint.BASE.toRawBytes());
await mark('decode', 10000, () => RistrettoPoint.fromHex(encoded));
if (RAM) utils.logMem();
});

7
build/README.md Normal file
View File

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

11
build/input.js Normal file
View File

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

18
build/package.json Normal file
View File

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

View File

@@ -1,7 +1,4 @@
{ {
"type": "module", "type": "module",
"browser": { "sideEffects": false
"crypto": false,
"./crypto": "./esm/crypto.js"
}
} }

33
package-lock.json generated
View File

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

View File

@@ -1,6 +1,6 @@
{ {
"name": "@noble/curves", "name": "@noble/curves",
"version": "1.0.0", "version": "1.2.0",
"description": "Audited & minimal JS implementation of elliptic curve cryptography", "description": "Audited & minimal JS implementation of elliptic curve cryptography",
"files": [ "files": [
"abstract", "abstract",
@@ -12,7 +12,7 @@
"*.d.ts.map" "*.d.ts.map"
], ],
"scripts": { "scripts": {
"bench": "cd benchmark; node secp256k1.js; node curves.js; node ecdh.js; node hash-to-curve.js; node modular.js; node bls.js", "bench": "cd benchmark; node secp256k1.js; node curves.js; node ecdh.js; node hash-to-curve.js; node modular.js; node bls.js; node ristretto255.js; node decaf448.js",
"build": "tsc && tsc -p tsconfig.esm.json", "build": "tsc && tsc -p tsconfig.esm.json",
"build:release": "rollup -c rollup.config.js", "build:release": "rollup -c rollup.config.js",
"build:clean": "rm *.{js,d.ts,d.ts.map,js.map} esm/*.{js,d.ts,d.ts.map,js.map} 2> /dev/null", "build:clean": "rm *.{js,d.ts,d.ts.map,js.map} esm/*.{js,d.ts,d.ts.map,js.map} 2> /dev/null",
@@ -28,7 +28,7 @@
}, },
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@noble/hashes": "1.3.0" "@noble/hashes": "1.3.2"
}, },
"devDependencies": { "devDependencies": {
"fast-check": "3.0.0", "fast-check": "3.0.0",
@@ -37,6 +37,7 @@
"prettier": "2.8.4", "prettier": "2.8.4",
"typescript": "5.0.2" "typescript": "5.0.2"
}, },
"sideEffects": false,
"main": "index.js", "main": "index.js",
"exports": { "exports": {
".": { ".": {
@@ -164,6 +165,8 @@
"secp256k1", "secp256k1",
"ed25519", "ed25519",
"ed448", "ed448",
"x25519",
"ed25519",
"bls12-381", "bls12-381",
"bn254", "bn254",
"pasta", "pasta",
@@ -174,10 +177,5 @@
"eddsa", "eddsa",
"schnorr" "schnorr"
], ],
"funding": [ "funding": "https://paulmillr.com/funding/"
{
"type": "individual",
"url": "https://paulmillr.com/funding/"
}
]
} }

View File

@@ -12,7 +12,7 @@
* Some projects may prefer to swap this relation, it is not supported for now. * Some projects may prefer to swap this relation, it is not supported for now.
*/ */
import { AffinePoint } from './curve.js'; import { AffinePoint } from './curve.js';
import { IField, hashToPrivateScalar } from './modular.js'; import { IField, getMinHashLength, mapHashToField } from './modular.js';
import { Hex, PrivKey, CHash, bitLen, bitGet, ensureBytes } from './utils.js'; import { Hex, PrivKey, CHash, bitLen, bitGet, ensureBytes } from './utils.js';
import * as htf from './hash-to-curve.js'; import * as htf from './hash-to-curve.js';
import { import {
@@ -122,7 +122,6 @@ export function bls<Fp2, Fp6, Fp12>(
// Fields are specific for curve, so for now we'll need to pass them with opts // Fields are specific for curve, so for now we'll need to pass them with opts
const { Fp, Fr, Fp2, Fp6, Fp12 } = CURVE.fields; const { Fp, Fr, Fp2, Fp6, Fp12 } = CURVE.fields;
const BLS_X_LEN = bitLen(CURVE.params.x); const BLS_X_LEN = bitLen(CURVE.params.x);
const groupLen = 32; // TODO: calculate; hardcoded for now
// Pre-compute coefficients for sparse multiplication // Pre-compute coefficients for sparse multiplication
// Point addition and point double calculations is reused for coefficients // Point addition and point double calculations is reused for coefficients
@@ -189,7 +188,8 @@ export function bls<Fp2, Fp6, Fp12>(
const utils = { const utils = {
randomPrivateKey: (): Uint8Array => { randomPrivateKey: (): Uint8Array => {
return Fr.toBytes(hashToPrivateScalar(CURVE.randomBytes(groupLen + 8), CURVE.params.r)); const length = getMinHashLength(Fr.ORDER);
return mapHashToField(CURVE.randomBytes(length), Fr.ORDER);
}, },
calcPairingPrecomputes, calcPairingPrecomputes,
}; };

View File

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

View File

@@ -59,7 +59,7 @@ function isNum(item: unknown): void {
} }
// Produces a uniformly random byte string using a cryptographic hash function H that outputs b bits // Produces a uniformly random byte string using a cryptographic hash function H that outputs b bits
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-5.4.1 // https://www.rfc-editor.org/rfc/rfc9380#section-5.3.1
export function expand_message_xmd( export function expand_message_xmd(
msg: Uint8Array, msg: Uint8Array,
DST: Uint8Array, DST: Uint8Array,
@@ -69,7 +69,7 @@ export function expand_message_xmd(
isBytes(msg); isBytes(msg);
isBytes(DST); isBytes(DST);
isNum(lenInBytes); isNum(lenInBytes);
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-16#section-5.3.3 // https://www.rfc-editor.org/rfc/rfc9380#section-5.3.3
if (DST.length > 255) DST = H(concatBytes(utf8ToBytes('H2C-OVERSIZE-DST-'), DST)); if (DST.length > 255) DST = H(concatBytes(utf8ToBytes('H2C-OVERSIZE-DST-'), DST));
const { outputLen: b_in_bytes, blockLen: r_in_bytes } = H; const { outputLen: b_in_bytes, blockLen: r_in_bytes } = H;
const ell = Math.ceil(lenInBytes / b_in_bytes); const ell = Math.ceil(lenInBytes / b_in_bytes);
@@ -88,6 +88,11 @@ export function expand_message_xmd(
return pseudo_random_bytes.slice(0, lenInBytes); return pseudo_random_bytes.slice(0, lenInBytes);
} }
// Produces a uniformly random byte string using an extendable-output function (XOF) H.
// 1. The collision resistance of H MUST be at least k bits.
// 2. H MUST be an XOF that has been proved indifferentiable from
// a random oracle under a reasonable cryptographic assumption.
// https://www.rfc-editor.org/rfc/rfc9380#section-5.3.2
export function expand_message_xof( export function expand_message_xof(
msg: Uint8Array, msg: Uint8Array,
DST: Uint8Array, DST: Uint8Array,
@@ -98,7 +103,7 @@ export function expand_message_xof(
isBytes(msg); isBytes(msg);
isBytes(DST); isBytes(DST);
isNum(lenInBytes); isNum(lenInBytes);
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-16#section-5.3.3 // https://www.rfc-editor.org/rfc/rfc9380#section-5.3.3
// DST = H('H2C-OVERSIZE-DST-' || a_very_long_DST, Math.ceil((lenInBytes * k) / 8)); // DST = H('H2C-OVERSIZE-DST-' || a_very_long_DST, Math.ceil((lenInBytes * k) / 8));
if (DST.length > 255) { if (DST.length > 255) {
const dkLen = Math.ceil((2 * k) / 8); const dkLen = Math.ceil((2 * k) / 8);
@@ -119,7 +124,7 @@ export function expand_message_xof(
/** /**
* Hashes arbitrary-length byte strings to a list of one or more elements of a finite field F * Hashes arbitrary-length byte strings to a list of one or more elements of a finite field F
* https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-5.3 * https://www.rfc-editor.org/rfc/rfc9380#section-5.2
* @param msg a byte string containing the message to hash * @param msg a byte string containing the message to hash
* @param count the number of elements of F to output * @param count the number of elements of F to output
* @param options `{DST: string, p: bigint, m: number, k: number, expand: 'xmd' | 'xof', hash: H}`, see above * @param options `{DST: string, p: bigint, m: number, k: number, expand: 'xmd' | 'xof', hash: H}`, see above
@@ -127,7 +132,7 @@ export function expand_message_xof(
*/ */
export function hash_to_field(msg: Uint8Array, count: number, options: Opts): bigint[][] { export function hash_to_field(msg: Uint8Array, count: number, options: Opts): bigint[][] {
validateObject(options, { validateObject(options, {
DST: 'string', DST: 'stringOrUint8Array',
p: 'bigint', p: 'bigint',
m: 'isSafeInteger', m: 'isSafeInteger',
k: 'isSafeInteger', k: 'isSafeInteger',
@@ -201,8 +206,8 @@ export function createHasher<T>(
) { ) {
if (typeof mapToCurve !== 'function') throw new Error('mapToCurve() must be defined'); if (typeof mapToCurve !== 'function') throw new Error('mapToCurve() must be defined');
return { return {
// Encodes byte string to elliptic curve // Encodes byte string to elliptic curve.
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-16#section-3 // hash_to_curve from https://www.rfc-editor.org/rfc/rfc9380#section-3
hashToCurve(msg: Uint8Array, options?: htfBasicOpts) { hashToCurve(msg: Uint8Array, options?: htfBasicOpts) {
const u = hash_to_field(msg, 2, { ...def, DST: def.DST, ...options } as Opts); const u = hash_to_field(msg, 2, { ...def, DST: def.DST, ...options } as Opts);
const u0 = Point.fromAffine(mapToCurve(u[0])); const u0 = Point.fromAffine(mapToCurve(u[0]));
@@ -212,7 +217,8 @@ export function createHasher<T>(
return P; return P;
}, },
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-16#section-3 // Encodes byte string to elliptic curve.
// encode_to_curve from https://www.rfc-editor.org/rfc/rfc9380#section-3
encodeToCurve(msg: Uint8Array, options?: htfBasicOpts) { encodeToCurve(msg: Uint8Array, options?: htfBasicOpts) {
const u = hash_to_field(msg, 1, { ...def, DST: def.encodeDST, ...options } as Opts); const u = hash_to_field(msg, 1, { ...def, DST: def.encodeDST, ...options } as Opts);
const P = Point.fromAffine(mapToCurve(u[0])).clearCofactor(); const P = Point.fromAffine(mapToCurve(u[0])).clearCofactor();

View File

@@ -22,10 +22,10 @@ export function mod(a: bigint, b: bigint): bigint {
return result >= _0n ? result : b + result; return result >= _0n ? result : b + result;
} }
/** /**
* Efficiently exponentiate num to power and do modular division. * Efficiently raise num to power and do modular division.
* Unsafe in some contexts: uses ladder, so can expose bigint bits. * Unsafe in some contexts: uses ladder, so can expose bigint bits.
* @example * @example
* powMod(2n, 6n, 11n) // 64n % 11n == 9n * pow(2n, 6n, 11n) // 64n % 11n == 9n
*/ */
// TODO: use field version && remove // TODO: use field version && remove
export function pow(num: bigint, power: bigint, modulo: bigint): bigint { export function pow(num: bigint, power: bigint, modulo: bigint): bigint {
@@ -55,7 +55,7 @@ export function invert(number: bigint, modulo: bigint): bigint {
if (number === _0n || modulo <= _0n) { if (number === _0n || modulo <= _0n) {
throw new Error(`invert: expected positive integers, got n=${number} mod=${modulo}`); throw new Error(`invert: expected positive integers, got n=${number} mod=${modulo}`);
} }
// Eucledian GCD https://brilliant.org/wiki/extended-euclidean-algorithm/ // Euclidean GCD https://brilliant.org/wiki/extended-euclidean-algorithm/
// Fermat's little theorem "CT-like" version inv(n) = n^(m-2) mod m is 30x slower. // Fermat's little theorem "CT-like" version inv(n) = n^(m-2) mod m is 30x slower.
let a = mod(number, modulo); let a = mod(number, modulo);
let b = modulo; let b = modulo;
@@ -75,9 +75,14 @@ export function invert(number: bigint, modulo: bigint): bigint {
return mod(x, modulo); return mod(x, modulo);
} }
// Tonelli-Shanks algorithm /**
// Paper 1: https://eprint.iacr.org/2012/685.pdf (page 12) * Tonelli-Shanks square root search algorithm.
// Paper 2: Square Roots from 1; 24, 51, 10 to Dan Shanks * 1. https://eprint.iacr.org/2012/685.pdf (page 12)
* 2. Square Roots from 1; 24, 51, 10 to Dan Shanks
* Will start an infinite loop if field order P is not prime.
* @param P field order
* @returns function that takes field Fp (created from P) and number n
*/
export function tonelliShanks(P: bigint) { export function tonelliShanks(P: bigint) {
// Legendre constant: used to calculate Legendre symbol (a | p), // Legendre constant: used to calculate Legendre symbol (a | p),
// which denotes the value of a^((p-1)/2) (mod p). // which denotes the value of a^((p-1)/2) (mod p).
@@ -198,11 +203,7 @@ export function FpSqrt(P: bigint) {
// Little-endian check for first LE bit (last BE bit); // Little-endian check for first LE bit (last BE bit);
export const isNegativeLE = (num: bigint, modulo: bigint) => (mod(num, modulo) & _1n) === _1n; export const isNegativeLE = (num: bigint, modulo: bigint) => (mod(num, modulo) & _1n) === _1n;
// Currently completly inconsistent naming: // Field is not always over prime: for example, Fp2 has ORDER(q)=p^m
// - 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> { export interface IField<T> {
ORDER: bigint; ORDER: bigint;
BYTES: number; BYTES: number;
@@ -232,7 +233,8 @@ export interface IField<T> {
sqrN(num: T): T; sqrN(num: T): T;
// Optional // Optional
// Should be same as sgn0 function in https://datatracker.ietf.org/doc/draft-irtf-cfrg-hash-to-curve/ // Should be same as sgn0 function in
// [RFC9380](https://www.rfc-editor.org/rfc/rfc9380#section-4.1).
// NOTE: sgn0 is 'negative in LE', which is same as odd. And negative in LE is kinda strange definition anyway. // NOTE: sgn0 is 'negative in LE', which is same as odd. And negative in LE is kinda strange definition anyway.
isOdd?(num: T): boolean; // Odd instead of even since we have it for Fp2 isOdd?(num: T): boolean; // Odd instead of even since we have it for Fp2
// legendre?(num: T): T; // legendre?(num: T): T;
@@ -264,6 +266,11 @@ export function validateField<T>(field: IField<T>) {
} }
// Generic field functions // Generic field functions
/**
* Same as `pow` but for Fp: non-constant-time.
* Unsafe in some contexts: uses ladder, so can expose bigint bits.
*/
export function FpPow<T>(f: IField<T>, num: T, power: bigint): T { export function FpPow<T>(f: IField<T>, num: T, power: bigint): T {
// Should have same speed as pow for bigints // Should have same speed as pow for bigints
// TODO: benchmark! // TODO: benchmark!
@@ -280,7 +287,10 @@ export function FpPow<T>(f: IField<T>, num: T, power: bigint): T {
return p; return p;
} }
// 0 is non-invertible: non-batched version will throw on 0 /**
* Efficiently invert an array of Field elements.
* `inv(0)` will return `undefined` here: make sure to throw an error.
*/
export function FpInvertBatch<T>(f: IField<T>, nums: T[]): T[] { export function FpInvertBatch<T>(f: IField<T>, nums: T[]): T[] {
const tmp = new Array(nums.length); const tmp = new Array(nums.length);
// Walk from first to last, multiply them by each other MOD p // Walk from first to last, multiply them by each other MOD p
@@ -323,12 +333,12 @@ export function nLength(n: bigint, nBitLength?: number) {
type FpField = IField<bigint> & Required<Pick<IField<bigint>, 'isOdd'>>; type FpField = IField<bigint> & Required<Pick<IField<bigint>, 'isOdd'>>;
/** /**
* Initializes a galois field over prime. Non-primes are not supported for now. * Initializes a finite field over prime. **Non-primes are not supported.**
* Do not init in loop: slow. Very fragile: always run a benchmark on change. * Do not init in loop: slow. Very fragile: always run a benchmark on a change.
* Major performance gains: * Major performance optimizations:
* a) non-normalized operations like mulN instead of mul * * a) denormalized operations like mulN instead of mul
* b) `Object.freeze` * * b) same object shape: never add or remove keys
* c) Same object shape: never add or remove keys * * c) Object.freeze
* @param ORDER prime positive bigint * @param ORDER prime positive bigint
* @param bitLen how many bits the field consumes * @param bitLen how many bits the field consumes
* @param isLE (def: false) if encoding / decoding should be in little-endian * @param isLE (def: false) if encoding / decoding should be in little-endian
@@ -340,7 +350,7 @@ export function Field(
isLE = false, isLE = false,
redef: Partial<IField<bigint>> = {} redef: Partial<IField<bigint>> = {}
): Readonly<FpField> { ): Readonly<FpField> {
if (ORDER <= _0n) throw new Error(`Expected Fp ORDER > 0, got ${ORDER}`); if (ORDER <= _0n) throw new Error(`Expected Field ORDER > 0, got ${ORDER}`);
const { nBitLength: BITS, nByteLength: BYTES } = nLength(ORDER, bitLen); const { nBitLength: BITS, nByteLength: BYTES } = nLength(ORDER, bitLen);
if (BYTES > 2048) throw new Error('Field lengths over 2048 bytes are not supported'); if (BYTES > 2048) throw new Error('Field lengths over 2048 bytes are not supported');
const sqrtP = FpSqrt(ORDER); const sqrtP = FpSqrt(ORDER);
@@ -404,13 +414,10 @@ export function FpSqrtEven<T>(Fp: IField<T>, elm: T) {
} }
/** /**
* FIPS 186 B.4.1-compliant "constant-time" private key generation utility. * "Constant-time" private key generation utility.
* Can take (n+8) or more bytes of uniform input e.g. from CSPRNG or KDF * Same as mapKeyToField, but accepts less bytes (40 instead of 48 for 32-byte field).
* and convert them into private scalar, with the modulo bias being neglible. * Which makes it slightly more biased, less secure.
* Needs at least 40 bytes of input for 32-byte private key. * @deprecated use mapKeyToField instead
* 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
* @returns valid private scalar
*/ */
export function hashToPrivateScalar( export function hashToPrivateScalar(
hash: string | Uint8Array, hash: string | Uint8Array,
@@ -425,3 +432,53 @@ export function hashToPrivateScalar(
const num = isLE ? bytesToNumberLE(hash) : bytesToNumberBE(hash); const num = isLE ? bytesToNumberLE(hash) : bytesToNumberBE(hash);
return mod(num, groupOrder - _1n) + _1n; return mod(num, groupOrder - _1n) + _1n;
} }
/**
* Returns total number of bytes consumed by the field element.
* For example, 32 bytes for usual 256-bit weierstrass curve.
* @param fieldOrder number of field elements, usually CURVE.n
* @returns byte length of field
*/
export function getFieldBytesLength(fieldOrder: bigint): number {
if (typeof fieldOrder !== 'bigint') throw new Error('field order must be bigint');
const bitLength = fieldOrder.toString(2).length;
return Math.ceil(bitLength / 8);
}
/**
* Returns minimal amount of bytes that can be safely reduced
* by field order.
* Should be 2^-128 for 128-bit curve such as P256.
* @param fieldOrder number of field elements, usually CURVE.n
* @returns byte length of target hash
*/
export function getMinHashLength(fieldOrder: bigint): number {
const length = getFieldBytesLength(fieldOrder);
return length + Math.ceil(length / 2);
}
/**
* "Constant-time" private key generation utility.
* Can take (n + n/2) or more bytes of uniform input e.g. from CSPRNG or KDF
* and convert them into private scalar, with the modulo bias being negligible.
* Needs at least 48 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/
* FIPS 186-5, A.2 https://csrc.nist.gov/publications/detail/fips/186/5/final
* RFC 9380, https://www.rfc-editor.org/rfc/rfc9380#section-5
* @param hash hash output from SHA3 or a similar function
* @param groupOrder size of subgroup - (e.g. secp256k1.CURVE.n)
* @param isLE interpret hash bytes as LE num
* @returns valid private scalar
*/
export function mapHashToField(key: Uint8Array, fieldOrder: bigint, isLE = false): Uint8Array {
const len = key.length;
const fieldLen = getFieldBytesLength(fieldOrder);
const minLen = getMinHashLength(fieldOrder);
// No small numbers: need to understand bias story. No huge numbers: easier to detect JS timings.
if (len < 16 || len < minLen || len > 1024)
throw new Error(`expected ${minLen}-1024 bytes of input, got ${len}`);
const num = isLE ? bytesToNumberBE(key) : bytesToNumberLE(key);
// `mod(x, 11)` can sometimes produce 0. `mod(x, 10) + 1` is the same, but no 0
const reduced = mod(num, fieldOrder - _1n) + _1n;
return isLE ? numberToBytesLE(reduced, fieldLen) : numberToBytesBE(reduced, fieldLen);
}

View File

@@ -15,34 +15,36 @@ export type PoseidonOpts = {
}; };
export function validateOpts(opts: PoseidonOpts) { export function validateOpts(opts: PoseidonOpts) {
const { Fp } = opts; const { Fp, mds, reversePartialPowIdx: rev, roundConstants: rc } = opts;
const { roundsFull, roundsPartial, sboxPower, t } = opts;
validateField(Fp); validateField(Fp);
for (const i of ['t', 'roundsFull', 'roundsPartial'] as const) { for (const i of ['t', 'roundsFull', 'roundsPartial'] as const) {
if (typeof opts[i] !== 'number' || !Number.isSafeInteger(opts[i])) if (typeof opts[i] !== 'number' || !Number.isSafeInteger(opts[i]))
throw new Error(`Poseidon: invalid param ${i}=${opts[i]} (${typeof opts[i]})`); throw new Error(`Poseidon: invalid param ${i}=${opts[i]} (${typeof opts[i]})`);
} }
if (opts.reversePartialPowIdx !== undefined && typeof opts.reversePartialPowIdx !== 'boolean')
throw new Error(`Poseidon: invalid param reversePartialPowIdx=${opts.reversePartialPowIdx}`);
// Default is 5, but by some reasons stark uses 3
let sboxPower = opts.sboxPower;
if (sboxPower === undefined) sboxPower = 5;
if (typeof sboxPower !== 'number' || !Number.isSafeInteger(sboxPower))
throw new Error(`Poseidon wrong sboxPower=${sboxPower}`);
const _sboxPower = BigInt(sboxPower); // MDS is TxT matrix
let sboxFn = (n: bigint) => FpPow(Fp, n, _sboxPower); if (!Array.isArray(mds) || mds.length !== t) throw new Error('Poseidon: wrong MDS matrix');
// Unwrapped sbox power for common cases (195->142μs) const _mds = mds.map((mdsRow) => {
if (sboxPower === 3) sboxFn = (n: bigint) => Fp.mul(Fp.sqrN(n), n); if (!Array.isArray(mdsRow) || mdsRow.length !== t)
else if (sboxPower === 5) sboxFn = (n: bigint) => Fp.mul(Fp.sqrN(Fp.sqrN(n)), n); throw new Error(`Poseidon MDS matrix row: ${mdsRow}`);
return mdsRow.map((i) => {
if (typeof i !== 'bigint') throw new Error(`Poseidon MDS matrix value=${i}`);
return Fp.create(i);
});
});
if (opts.roundsFull % 2 !== 0) if (rev !== undefined && typeof rev !== 'boolean')
throw new Error(`Poseidon roundsFull is not even: ${opts.roundsFull}`); throw new Error(`Poseidon: invalid param reversePartialPowIdx=${rev}`);
const rounds = opts.roundsFull + opts.roundsPartial;
if (!Array.isArray(opts.roundConstants) || opts.roundConstants.length !== rounds) if (roundsFull % 2 !== 0) throw new Error(`Poseidon roundsFull is not even: ${roundsFull}`);
const rounds = roundsFull + roundsPartial;
if (!Array.isArray(rc) || rc.length !== rounds)
throw new Error('Poseidon: wrong round constants'); throw new Error('Poseidon: wrong round constants');
const roundConstants = opts.roundConstants.map((rc) => { const roundConstants = rc.map((rc) => {
if (!Array.isArray(rc) || rc.length !== opts.t) if (!Array.isArray(rc) || rc.length !== t)
throw new Error(`Poseidon wrong round constants: ${rc}`); throw new Error(`Poseidon wrong round constants: ${rc}`);
return rc.map((i) => { return rc.map((i) => {
if (typeof i !== 'bigint' || !Fp.isValid(i)) if (typeof i !== 'bigint' || !Fp.isValid(i))
@@ -50,18 +52,16 @@ export function validateOpts(opts: PoseidonOpts) {
return Fp.create(i); return Fp.create(i);
}); });
}); });
// MDS is TxT matrix
if (!Array.isArray(opts.mds) || opts.mds.length !== opts.t) if (!sboxPower || ![3, 5, 7].includes(sboxPower))
throw new Error('Poseidon: wrong MDS matrix'); throw new Error(`Poseidon wrong sboxPower=${sboxPower}`);
const mds = opts.mds.map((mdsRow) => { const _sboxPower = BigInt(sboxPower);
if (!Array.isArray(mdsRow) || mdsRow.length !== opts.t) let sboxFn = (n: bigint) => FpPow(Fp, n, _sboxPower);
throw new Error(`Poseidon MDS matrix row: ${mdsRow}`); // Unwrapped sbox power for common cases (195->142μs)
return mdsRow.map((i) => { if (sboxPower === 3) sboxFn = (n: bigint) => Fp.mul(Fp.sqrN(n), n);
if (typeof i !== 'bigint') throw new Error(`Poseidon MDS matrix value=${i}`); else if (sboxPower === 5) sboxFn = (n: bigint) => Fp.mul(Fp.sqrN(Fp.sqrN(n)), n);
return Fp.create(i);
}); return Object.freeze({ ...opts, rounds, sboxFn, roundConstants, mds: _mds });
});
return Object.freeze({ ...opts, rounds, sboxFn, roundConstants, mds });
} }
export function splitConstants(rc: bigint[], t: number) { export function splitConstants(rc: bigint[], t: number) {
@@ -80,18 +80,17 @@ export function splitConstants(rc: bigint[], t: number) {
} }
export function poseidon(opts: PoseidonOpts) { export function poseidon(opts: PoseidonOpts) {
const { t, Fp, rounds, sboxFn, reversePartialPowIdx } = validateOpts(opts); const _opts = validateOpts(opts);
const halfRoundsFull = Math.floor(opts.roundsFull / 2); const { Fp, mds, roundConstants, rounds, roundsPartial, sboxFn, t } = _opts;
const partialIdx = reversePartialPowIdx ? t - 1 : 0; const halfRoundsFull = _opts.roundsFull / 2;
const partialIdx = _opts.reversePartialPowIdx ? t - 1 : 0;
const poseidonRound = (values: bigint[], isFull: boolean, idx: number) => { const poseidonRound = (values: bigint[], isFull: boolean, idx: number) => {
values = values.map((i, j) => Fp.add(i, opts.roundConstants[idx][j])); values = values.map((i, j) => Fp.add(i, roundConstants[idx][j]));
if (isFull) values = values.map((i) => sboxFn(i)); if (isFull) values = values.map((i) => sboxFn(i));
else values[partialIdx] = sboxFn(values[partialIdx]); else values[partialIdx] = sboxFn(values[partialIdx]);
// Matrix multiplication // Matrix multiplication
values = opts.mds.map((i) => values = mds.map((i) => i.reduce((acc, i, j) => Fp.add(acc, Fp.mulN(i, values[j])), Fp.ZERO));
i.reduce((acc, i, j) => Fp.add(acc, Fp.mulN(i, values[j])), Fp.ZERO)
);
return values; return values;
}; };
const poseidonHash = function poseidonHash(values: bigint[]) { const poseidonHash = function poseidonHash(values: bigint[]) {
@@ -105,7 +104,7 @@ export function poseidon(opts: PoseidonOpts) {
// Apply r_f/2 full rounds. // Apply r_f/2 full rounds.
for (let i = 0; i < halfRoundsFull; i++) values = poseidonRound(values, true, round++); for (let i = 0; i < halfRoundsFull; i++) values = poseidonRound(values, true, round++);
// Apply r_p partial rounds. // Apply r_p partial rounds.
for (let i = 0; i < opts.roundsPartial; i++) values = poseidonRound(values, false, round++); for (let i = 0; i < roundsPartial; i++) values = poseidonRound(values, false, round++);
// Apply r_f/2 full rounds. // Apply r_f/2 full rounds.
for (let i = 0; i < halfRoundsFull; i++) values = poseidonRound(values, true, round++); for (let i = 0; i < halfRoundsFull; i++) values = poseidonRound(values, true, round++);
@@ -114,6 +113,6 @@ export function poseidon(opts: PoseidonOpts) {
return values; return values;
}; };
// For verification in tests // For verification in tests
poseidonHash.roundConstants = opts.roundConstants; poseidonHash.roundConstants = roundConstants;
return poseidonHash; return poseidonHash;
} }

View File

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

View File

@@ -193,7 +193,7 @@ export function weierstrassPoints<T>(opts: CurvePointsType<T>) {
const toBytes = const toBytes =
CURVE.toBytes || CURVE.toBytes ||
((c: ProjConstructor<T>, point: ProjPointType<T>, isCompressed: boolean) => { ((_c: ProjConstructor<T>, point: ProjPointType<T>, _isCompressed: boolean) => {
const a = point.toAffine(); const a = point.toAffine();
return ut.concatBytes(Uint8Array.from([0x04]), Fp.toBytes(a.x), Fp.toBytes(a.y)); return ut.concatBytes(Uint8Array.from([0x04]), Fp.toBytes(a.x), Fp.toBytes(a.y));
}); });
@@ -333,9 +333,11 @@ export function weierstrassPoints<T>(opts: CurvePointsType<T>) {
// A point on curve is valid if it conforms to equation. // A point on curve is valid if it conforms to equation.
assertValidity(): void { assertValidity(): void {
// Zero is valid point too!
if (this.is0()) { if (this.is0()) {
if (CURVE.allowInfinityPoint) return; // (0, 1, 0) aka ZERO is invalid in most contexts.
// In BLS, ZERO can be serialized, so we allow it.
// (0, 0, 0) is wrong representation of ZERO and is always invalid.
if (CURVE.allowInfinityPoint && !Fp.is0(this.py)) return;
throw new Error('bad point: ZERO'); throw new Error('bad point: ZERO');
} }
// Some 3rd-party test vectors require different wording between here & `fromCompressedHex` // Some 3rd-party test vectors require different wording between here & `fromCompressedHex`
@@ -618,7 +620,7 @@ export interface SignatureType {
readonly s: bigint; readonly s: bigint;
readonly recovery?: number; readonly recovery?: number;
assertValidity(): void; assertValidity(): void;
addRecoveryBit(recovery: number): SignatureType; addRecoveryBit(recovery: number): RecoveredSignatureType;
hasHighS(): boolean; hasHighS(): boolean;
normalizeS(): SignatureType; normalizeS(): SignatureType;
recoverPublicKey(msgHash: Hex): ProjPointType<bigint>; recoverPublicKey(msgHash: Hex): ProjPointType<bigint>;
@@ -628,6 +630,9 @@ export interface SignatureType {
toDERRawBytes(isCompressed?: boolean): Uint8Array; toDERRawBytes(isCompressed?: boolean): Uint8Array;
toDERHex(isCompressed?: boolean): string; toDERHex(isCompressed?: boolean): string;
} }
export type RecoveredSignatureType = SignatureType & {
readonly recovery: number;
};
// Static methods // Static methods
export type SignatureConstructor = { export type SignatureConstructor = {
new (r: bigint, s: bigint): SignatureType; new (r: bigint, s: bigint): SignatureType;
@@ -669,7 +674,7 @@ export type CurveFn = {
CURVE: ReturnType<typeof validateOpts>; CURVE: ReturnType<typeof validateOpts>;
getPublicKey: (privateKey: PrivKey, isCompressed?: boolean) => Uint8Array; getPublicKey: (privateKey: PrivKey, isCompressed?: boolean) => Uint8Array;
getSharedSecret: (privateA: PrivKey, publicB: Hex, isCompressed?: boolean) => Uint8Array; getSharedSecret: (privateA: PrivKey, publicB: Hex, isCompressed?: boolean) => Uint8Array;
sign: (msgHash: Hex, privKey: PrivKey, opts?: SignOpts) => SignatureType; sign: (msgHash: Hex, privKey: PrivKey, opts?: SignOpts) => RecoveredSignatureType;
verify: (signature: Hex | SignatureLike, msgHash: Hex, publicKey: Hex, opts?: VerOpts) => boolean; verify: (signature: Hex | SignatureLike, msgHash: Hex, publicKey: Hex, opts?: VerOpts) => boolean;
ProjectivePoint: ProjConstructor<bigint>; ProjectivePoint: ProjConstructor<bigint>;
Signature: SignatureConstructor; Signature: SignatureConstructor;
@@ -704,7 +709,7 @@ export function weierstrass(curveDef: CurveType): CurveFn {
isWithinCurveOrder, isWithinCurveOrder,
} = weierstrassPoints({ } = weierstrassPoints({
...CURVE, ...CURVE,
toBytes(c, point, isCompressed: boolean): Uint8Array { toBytes(_c, point, isCompressed: boolean): Uint8Array {
const a = point.toAffine(); const a = point.toAffine();
const x = Fp.toBytes(a.x); const x = Fp.toBytes(a.x);
const cat = ut.concatBytes; const cat = ut.concatBytes;
@@ -782,8 +787,8 @@ export function weierstrass(curveDef: CurveType): CurveFn {
if (!isWithinCurveOrder(this.s)) throw new Error('s must be 0 < s < CURVE.n'); if (!isWithinCurveOrder(this.s)) throw new Error('s must be 0 < s < CURVE.n');
} }
addRecoveryBit(recovery: number) { addRecoveryBit(recovery: number): RecoveredSignature {
return new Signature(this.r, this.s, recovery); return new Signature(this.r, this.s, recovery) as RecoveredSignature;
} }
recoverPublicKey(msgHash: Hex): typeof Point.BASE { recoverPublicKey(msgHash: Hex): typeof Point.BASE {
@@ -828,6 +833,7 @@ export function weierstrass(curveDef: CurveType): CurveFn {
return numToNByteStr(this.r) + numToNByteStr(this.s); return numToNByteStr(this.r) + numToNByteStr(this.s);
} }
} }
type RecoveredSignature = Signature & { recovery: number };
const utils = { const utils = {
isValidPrivateKey(privateKey: PrivKey) { isValidPrivateKey(privateKey: PrivKey) {
@@ -841,13 +847,12 @@ export function weierstrass(curveDef: CurveType): CurveFn {
normPrivateKeyToScalar: normPrivateKeyToScalar, normPrivateKeyToScalar: normPrivateKeyToScalar,
/** /**
* Produces cryptographically secure private key from random of size (nBitLength+64) * Produces cryptographically secure private key from random of size
* as per FIPS 186 B.4.1 with modulo bias being neglible. * (groupLen + ceil(groupLen / 2)) with modulo bias being negligible.
*/ */
randomPrivateKey: (): Uint8Array => { randomPrivateKey: (): Uint8Array => {
const rand = CURVE.randomBytes(Fp.BYTES + 8); const length = mod.getMinHashLength(CURVE.n);
const num = mod.hashToPrivateScalar(rand, CURVE_ORDER); return mod.mapHashToField(CURVE.randomBytes(length), CURVE.n);
return ut.numberToBytesBE(num, CURVE.nByteLength);
}, },
/** /**
@@ -960,12 +965,12 @@ export function weierstrass(curveDef: CurveType): CurveFn {
if (ent != null) { if (ent != null) {
// K = HMAC_K(V || 0x00 || int2octets(x) || bits2octets(h1) || k') // K = HMAC_K(V || 0x00 || int2octets(x) || bits2octets(h1) || k')
const e = ent === true ? randomBytes(Fp.BYTES) : ent; // generate random bytes OR pass as-is const e = ent === true ? randomBytes(Fp.BYTES) : ent; // generate random bytes OR pass as-is
seedArgs.push(ensureBytes('extraEntropy', e, Fp.BYTES)); // check for being of size BYTES seedArgs.push(ensureBytes('extraEntropy', e)); // check for being bytes
} }
const seed = ut.concatBytes(...seedArgs); // Step D of RFC6979 3.2 const seed = ut.concatBytes(...seedArgs); // Step D of RFC6979 3.2
const m = h1int; // NOTE: no need to call bits2int second time here, it is inside truncateHash! const m = h1int; // NOTE: no need to call bits2int second time here, it is inside truncateHash!
// Converts signature params into point w r/s, checks result for validity. // Converts signature params into point w r/s, checks result for validity.
function k2sig(kBytes: Uint8Array): Signature | undefined { function k2sig(kBytes: Uint8Array): RecoveredSignature | undefined {
// RFC 6979 Section 3.2, step 3: k = bits2int(T) // RFC 6979 Section 3.2, step 3: k = bits2int(T)
const k = bits2int(kBytes); // Cannot use fields methods, since it is group element const k = bits2int(kBytes); // Cannot use fields methods, since it is group element
if (!isWithinCurveOrder(k)) return; // Important: all mod() calls here must be done over N if (!isWithinCurveOrder(k)) return; // Important: all mod() calls here must be done over N
@@ -984,7 +989,7 @@ export function weierstrass(curveDef: CurveType): CurveFn {
normS = normalizeS(s); // if lowS was passed, ensure s is always normS = normalizeS(s); // if lowS was passed, ensure s is always
recovery ^= 1; // // in the bottom half of N recovery ^= 1; // // in the bottom half of N
} }
return new Signature(r, normS, recovery); // use normS, not s return new Signature(r, normS, recovery) as RecoveredSignature; // use normS, not s
} }
return { seed, k2sig }; return { seed, k2sig };
} }
@@ -992,18 +997,22 @@ export function weierstrass(curveDef: CurveType): CurveFn {
const defaultVerOpts: VerOpts = { lowS: CURVE.lowS, prehash: false }; const defaultVerOpts: VerOpts = { lowS: CURVE.lowS, prehash: false };
/** /**
* Signs message hash (not message: you need to hash it by yourself). * Signs message hash with a private key.
* ``` * ```
* sign(m, d, k) where * sign(m, d, k) where
* (x, y) = G × k * (x, y) = G × k
* r = x mod n * r = x mod n
* s = (m + dr)/k mod n * s = (m + dr)/k mod n
* ``` * ```
* @param opts `lowS, extraEntropy, prehash` * @param msgHash NOT message. msg needs to be hashed to `msgHash`, or use `prehash`.
* @param privKey private key
* @param opts lowS for non-malleable sigs. extraEntropy for mixing randomness into k. prehash will hash first arg.
* @returns signature with recovery param
*/ */
function sign(msgHash: Hex, privKey: PrivKey, opts = defaultSigOpts): Signature { function sign(msgHash: Hex, privKey: PrivKey, opts = defaultSigOpts): RecoveredSignature {
const { seed, k2sig } = prepSig(msgHash, privKey, opts); // Steps A, D of RFC6979 3.2. const { seed, k2sig } = prepSig(msgHash, privKey, opts); // Steps A, D of RFC6979 3.2.
const drbg = ut.createHmacDrbg<Signature>(CURVE.hash.outputLen, CURVE.nByteLength, CURVE.hmac); const C = CURVE;
const drbg = ut.createHmacDrbg<RecoveredSignature>(C.hash.outputLen, C.nByteLength, C.hmac);
return drbg(seed, k2sig); // Steps B, C, D, E, F, G return drbg(seed, k2sig); // Steps B, C, D, E, F, G
} }
@@ -1084,20 +1093,29 @@ export function weierstrass(curveDef: CurveType): CurveFn {
}; };
} }
// Implementation of the Shallue and van de Woestijne method for any Weierstrass curve /**
// TODO: check if there is a way to merge this with uvRatio in Edwards && move to modular? * Implementation of the Shallue and van de Woestijne method for any weierstrass curve.
// b = True and y = sqrt(u / v) if (u / v) is square in F, and * TODO: check if there is a way to merge this with uvRatio in Edwards; move to modular.
// b = False and y = sqrt(Z * (u / v)) otherwise. * b = True and y = sqrt(u / v) if (u / v) is square in F, and
* b = False and y = sqrt(Z * (u / v)) otherwise.
* @param Fp
* @param Z
* @returns
*/
export function SWUFpSqrtRatio<T>(Fp: mod.IField<T>, Z: T) { export function SWUFpSqrtRatio<T>(Fp: mod.IField<T>, Z: T) {
// Generic implementation // Generic implementation
const q = Fp.ORDER; const q = Fp.ORDER;
let l = _0n; let l = _0n;
for (let o = q - _1n; o % _2n === _0n; o /= _2n) l += _1n; for (let o = q - _1n; o % _2n === _0n; o /= _2n) l += _1n;
const c1 = l; // 1. c1, the largest integer such that 2^c1 divides q - 1. const c1 = l; // 1. c1, the largest integer such that 2^c1 divides q - 1.
const c2 = (q - _1n) / _2n ** c1; // 2. c2 = (q - 1) / (2^c1) # Integer arithmetic // We need 2n ** c1 and 2n ** (c1-1). We can't use **; but we can use <<.
// 2n ** c1 == 2n << (c1-1)
const _2n_pow_c1_1 = _2n << (c1 - _1n - _1n);
const _2n_pow_c1 = _2n_pow_c1_1 * _2n;
const c2 = (q - _1n) / _2n_pow_c1; // 2. c2 = (q - 1) / (2^c1) # Integer arithmetic
const c3 = (c2 - _1n) / _2n; // 3. c3 = (c2 - 1) / 2 # Integer arithmetic const c3 = (c2 - _1n) / _2n; // 3. c3 = (c2 - 1) / 2 # Integer arithmetic
const c4 = _2n ** c1 - _1n; // 4. c4 = 2^c1 - 1 # Integer arithmetic const c4 = _2n_pow_c1 - _1n; // 4. c4 = 2^c1 - 1 # Integer arithmetic
const c5 = _2n ** (c1 - _1n); // 5. c5 = 2^(c1 - 1) # Integer arithmetic const c5 = _2n_pow_c1_1; // 5. c5 = 2^(c1 - 1) # Integer arithmetic
const c6 = Fp.pow(Z, c2); // 6. c6 = Z^c2 const c6 = Fp.pow(Z, c2); // 6. c6 = Z^c2
const c7 = Fp.pow(Z, (c2 + _1n) / _2n); // 7. c7 = Z^((c2 + 1) / 2) const c7 = Fp.pow(Z, (c2 + _1n) / _2n); // 7. c7 = Z^((c2 + 1) / 2)
let sqrtRatio = (u: T, v: T): { isValid: boolean; value: T } => { let sqrtRatio = (u: T, v: T): { isValid: boolean; value: T } => {
@@ -1119,7 +1137,8 @@ export function SWUFpSqrtRatio<T>(Fp: mod.IField<T>, Z: T) {
tv4 = Fp.cmov(tv5, tv4, isQR); // 16. tv4 = CMOV(tv5, tv4, isQR) tv4 = Fp.cmov(tv5, tv4, isQR); // 16. tv4 = CMOV(tv5, tv4, isQR)
// 17. for i in (c1, c1 - 1, ..., 2): // 17. for i in (c1, c1 - 1, ..., 2):
for (let i = c1; i > _1n; i--) { for (let i = c1; i > _1n; i--) {
let tv5 = _2n ** (i - _2n); // 18. tv5 = i - 2; 19. tv5 = 2^tv5 let tv5 = i - _2n; // 18. tv5 = i - 2
tv5 = _2n << (tv5 - _1n); // 19. tv5 = 2^tv5
let tvv5 = Fp.pow(tv4, tv5); // 20. tv5 = tv4^tv5 let tvv5 = Fp.pow(tv4, tv5); // 20. tv5 = tv4^tv5
const e1 = Fp.eql(tvv5, Fp.ONE); // 21. e1 = tv5 == 1 const e1 = Fp.eql(tvv5, Fp.ONE); // 21. e1 = tv5 == 1
tv2 = Fp.mul(tv3, tv1); // 22. tv2 = tv3 * tv1 tv2 = Fp.mul(tv3, tv1); // 22. tv2 = tv3 * tv1
@@ -1151,7 +1170,10 @@ export function SWUFpSqrtRatio<T>(Fp: mod.IField<T>, Z: T) {
// if (Fp.ORDER % _8n === _5n) // sqrt_ratio_5mod8 // if (Fp.ORDER % _8n === _5n) // sqrt_ratio_5mod8
return sqrtRatio; return sqrtRatio;
} }
// From draft-irtf-cfrg-hash-to-curve-16 /**
* Simplified Shallue-van de Woestijne-Ulas Method
* https://www.rfc-editor.org/rfc/rfc9380#section-6.6.2
*/
export function mapToCurveSimpleSWU<T>( export function mapToCurveSimpleSWU<T>(
Fp: mod.IField<T>, Fp: mod.IField<T>,
opts: { opts: {

View File

@@ -8,8 +8,8 @@
// The library uses G1 for public keys and G2 for signatures. Support for G1 signatures is planned. // The library uses G1 for public keys and G2 for signatures. Support for G1 signatures is planned.
// Compatible with Algorand, Chia, Dfinity, Ethereum, FIL, Zcash. Matches specs // Compatible with Algorand, Chia, Dfinity, Ethereum, FIL, Zcash. Matches specs
// [pairing-curves-11](https://tools.ietf.org/html/draft-irtf-cfrg-pairing-friendly-curves-11), // [pairing-curves-11](https://tools.ietf.org/html/draft-irtf-cfrg-pairing-friendly-curves-11),
// [bls-sigs-04](https://tools.ietf.org/html/draft-irtf-cfrg-bls-signature-04), // [bls-sigs-04](https:/cfrg-hash-to/tools.ietf.org/html/draft-irtf-cfrg-bls-signature-04),
// [hash-to-curve-12](https://tools.ietf.org/html/draft-irtf-cfrg-hash-to-curve-12). // [hash-to-curve-12](https://tools.ietf.org/html/draft-irtf--curve-12).
// //
// ### Summary // ### Summary
// 1. BLS Relies on Bilinear Pairing (expensive) // 1. BLS Relies on Bilinear Pairing (expensive)
@@ -60,11 +60,10 @@ const _8n = BigInt(8), _16n = BigInt(16);
// CURVE FIELDS // CURVE FIELDS
// Finite field over p. // Finite field over p.
const Fp = mod.Field( const Fp_raw = BigInt(
BigInt( '0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab'
'0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab'
)
); );
const Fp = mod.Field(Fp_raw);
type Fp = bigint; type Fp = bigint;
// Finite field over r. // Finite field over r.
// This particular field is not used anywhere in bls12-381, but it is still useful. // This particular field is not used anywhere in bls12-381, but it is still useful.
@@ -110,10 +109,7 @@ type Fp2Utils = {
// G² - 1 // G² - 1
// h2q // h2q
// NOTE: ORDER was wrong! // NOTE: ORDER was wrong!
const FP2_ORDER = const FP2_ORDER = Fp_raw * Fp_raw;
BigInt(
'0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab'
) ** _2n;
const Fp2: mod.IField<Fp2> & Fp2Utils = { const Fp2: mod.IField<Fp2> & Fp2Utils = {
ORDER: FP2_ORDER, ORDER: FP2_ORDER,
@@ -181,7 +177,7 @@ const Fp2: mod.IField<Fp2> & Fp2Utils = {
if (im1 > im2 || (im1 === im2 && re1 > re2)) return x1; if (im1 > im2 || (im1 === im2 && re1 > re2)) return x1;
return x2; return x2;
}, },
// Same as sgn0_fp2 in draft-irtf-cfrg-hash-to-curve-16 // Same as sgn0_m_eq_2 in RFC 9380
isOdd: (x: Fp2) => { isOdd: (x: Fp2) => {
const { re: x0, im: x1 } = Fp2.reim(x); const { re: x0, im: x1 } = Fp2.reim(x);
const sign_0 = x0 % _2n; const sign_0 = x0 % _2n;
@@ -784,8 +780,7 @@ const FP12_FROBENIUS_COEFFICIENTS = [
// HashToCurve // HashToCurve
// 3-isogeny map from E' to E // 3-isogeny map from E' to E https://www.rfc-editor.org/rfc/rfc9380#appendix-E.3
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#appendix-E.3
const isogenyMapG2 = isogenyMap( const isogenyMapG2 = isogenyMap(
Fp2, Fp2,
[ [
@@ -989,7 +984,7 @@ function G2psi2(c: ProjConstructor<Fp2>, P: ProjPointType<Fp2>) {
// //
// Parameter definitions are in section 5.3 of the spec unless otherwise noted. // Parameter definitions are in section 5.3 of the spec unless otherwise noted.
// Parameter values come from section 8.8.2 of the spec. // Parameter values come from section 8.8.2 of the spec.
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-8.8.2 // https://www.rfc-editor.org/rfc/rfc9380#section-8.8.2
// //
// Base field F is GF(p^m) // Base field F is GF(p^m)
// p = 0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab // p = 0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab
@@ -1044,7 +1039,7 @@ function signatureG2ToRawBytes(point: ProjPointType<Fp2>) {
} }
// To verify curve parameters, see pairing-friendly-curves spec: // To verify curve parameters, see pairing-friendly-curves spec:
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-pairing-friendly-curves-09 // https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-pairing-friendly-curves-11
// Basic math is done over finite fields over p. // Basic math is done over finite fields over p.
// More complicated math is done over polynominal extension fields. // More complicated math is done over polynominal extension fields.
// To simplify calculations in Fp12, we construct extension tower: // To simplify calculations in Fp12, we construct extension tower:
@@ -1112,7 +1107,7 @@ export const bls12_381: CurveFn<Fp, Fp2, Fp6, Fp12> = bls({
}, },
// Clear cofactor of G1 // Clear cofactor of G1
// https://eprint.iacr.org/2019/403 // https://eprint.iacr.org/2019/403
clearCofactor: (c, point) => { clearCofactor: (_c, point) => {
// return this.multiplyUnsafe(CURVE.h); // return this.multiplyUnsafe(CURVE.h);
return point.multiplyUnsafe(bls12_381.params.x).add(point); // x*P + P return point.multiplyUnsafe(bls12_381.params.x).add(point); // x*P + P
}, },
@@ -1197,7 +1192,7 @@ export const bls12_381: CurveFn<Fp, Fp2, Fp6, Fp12> = bls({
), ),
]), ]),
a: Fp2.ZERO, a: Fp2.ZERO,
b: Fp2.fromBigTuple([4n, _4n]), b: Fp2.fromBigTuple([_4n, _4n]),
hEff: BigInt( hEff: BigInt(
'0xbc69f08f2ee75b3584c6a0ea91b352888e2a8e9145ad7689986ff031508ffe1329c2f178731db956d82bf015d1212b02ec0ec69d7477c1ae954cbc06689f6a359894c0adebbf6b4e8020005aaa95551' '0xbc69f08f2ee75b3584c6a0ea91b352888e2a8e9145ad7689986ff031508ffe1329c2f178731db956d82bf015d1212b02ec0ec69d7477c1ae954cbc06689f6a359894c0adebbf6b4e8020005aaa95551'
), ),

View File

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

View File

@@ -1,14 +1,25 @@
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */ /*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
import { shake256 } from '@noble/hashes/sha3'; import { shake256 } from '@noble/hashes/sha3';
import { concatBytes, randomBytes, utf8ToBytes, wrapConstructor } from '@noble/hashes/utils'; import { concatBytes, randomBytes, utf8ToBytes, wrapConstructor } from '@noble/hashes/utils';
import { twistedEdwards } from './abstract/edwards.js'; import { ExtPointType, twistedEdwards } from './abstract/edwards.js';
import { mod, pow2, Field } from './abstract/modular.js'; import { mod, pow2, Field, isNegativeLE } from './abstract/modular.js';
import { montgomery } from './abstract/montgomery.js'; import { montgomery } from './abstract/montgomery.js';
import * as htf from './abstract/hash-to-curve.js'; import { createHasher, htfBasicOpts, expand_message_xof } from './abstract/hash-to-curve.js';
import {
bytesToHex,
bytesToNumberLE,
ensureBytes,
equalBytes,
Hex,
numberToBytesLE,
} from './abstract/utils.js';
import { AffinePoint } from './abstract/curve.js';
/** /**
* Edwards448 (not Ed448-Goldilocks) curve with following addons: * Edwards448 (not Ed448-Goldilocks) curve with following addons:
* * X448 ECDH * - X448 ECDH
* - Decaf cofactor elimination
* - Elligator hash-to-group / point indistinguishability
* Conforms to RFC 8032 https://www.rfc-editor.org/rfc/rfc8032.html#section-5.2 * Conforms to RFC 8032 https://www.rfc-editor.org/rfc/rfc8032.html#section-5.2
*/ */
@@ -18,15 +29,16 @@ const ed448P = BigInt(
'726838724295606890549323807888004534353641360687318060281490199180612328166730772686396383698676545930088884461843637361053498018365439' '726838724295606890549323807888004534353641360687318060281490199180612328166730772686396383698676545930088884461843637361053498018365439'
); );
// prettier-ignore
const _1n = BigInt(1), _2n = BigInt(2), _3n = BigInt(3), _4n = BigInt(4), _11n = BigInt(11);
// prettier-ignore
const _22n = BigInt(22), _44n = BigInt(44), _88n = BigInt(88), _223n = BigInt(223);
// powPminus3div4 calculates z = x^k mod p, where k = (p-3)/4. // powPminus3div4 calculates z = x^k mod p, where k = (p-3)/4.
// Used for efficient square root calculation. // Used for efficient square root calculation.
// ((P-3)/4).toString(2) would produce bits [223x 1, 0, 222x 1] // ((P-3)/4).toString(2) would produce bits [223x 1, 0, 222x 1]
function ed448_pow_Pminus3div4(x: bigint): bigint { function ed448_pow_Pminus3div4(x: bigint): bigint {
const P = ed448P; const P = ed448P;
// prettier-ignore
const _1n = BigInt(1), _2n = BigInt(2), _3n = BigInt(3), _11n = BigInt(11);
// prettier-ignore
const _22n = BigInt(22), _44n = BigInt(44), _88n = BigInt(88), _223n = BigInt(223);
const b2 = (x * x * x) % P; const b2 = (x * x * x) % P;
const b3 = (b2 * b2 * x) % P; const b3 = (b2 * b2 * x) % P;
const b6 = (pow2(b3, _3n, P) * b3) % P; const b6 = (pow2(b3, _3n, P) * b3) % P;
@@ -53,8 +65,29 @@ function adjustScalarBytes(bytes: Uint8Array): Uint8Array {
return bytes; return bytes;
} }
// Constant-time ratio of u to v. Allows to combine inversion and square root u/√v.
// Uses algo from RFC8032 5.1.3.
function uvRatio(u: bigint, v: bigint): { isValid: boolean; value: bigint } {
const P = ed448P;
// https://www.rfc-editor.org/rfc/rfc8032#section-5.2.3
// To compute the square root of (u/v), the first step is to compute the
// candidate root x = (u/v)^((p+1)/4). This can be done using the
// following trick, to use a single modular powering for both the
// inversion of v and the square root:
// x = (u/v)^((p+1)/4) = u³v(u⁵v³)^((p-3)/4) (mod p)
const u2v = mod(u * u * v, P); // u²v
const u3v = mod(u2v * u, P); // u³v
const u5v3 = mod(u3v * u2v * v, P); // u⁵v³
const root = ed448_pow_Pminus3div4(u5v3);
const x = mod(u3v * root, P);
// Verify that root is exists
const x2 = mod(x * x, P); // x²
// If vx² = u, the recovered x-coordinate is x. Otherwise, no
// square root exists, and the decoding fails.
return { isValid: mod(x2 * v, P) === u, value: x };
}
const Fp = Field(ed448P, 456, true); const Fp = Field(ed448P, 456, true);
const _4n = BigInt(4);
const ED448_DEF = { const ED448_DEF = {
// Param: a // Param: a
@@ -63,7 +96,7 @@ const ED448_DEF = {
d: BigInt( d: BigInt(
'726838724295606890549323807888004534353641360687318060281490199180612328166730772686396383698676545930088884461843637361053498018326358' '726838724295606890549323807888004534353641360687318060281490199180612328166730772686396383698676545930088884461843637361053498018326358'
), ),
// Finite field 𝔽p over which we'll do calculations; 2n ** 448n - 2n ** 224n - 1n // Finite field 𝔽p over which we'll do calculations; 2n**448n - 2n**224n - 1n
Fp, Fp,
// Subgroup order: how many points curve has; // Subgroup order: how many points curve has;
// 2n**446n - 13818066809895115352007386748515426880336692474882178609894547503885n // 2n**446n - 13818066809895115352007386748515426880336692474882178609894547503885n
@@ -94,49 +127,29 @@ const ED448_DEF = {
data data
); );
}, },
uvRatio,
// Constant-time ratio of u to v. Allows to combine inversion and square root u/√v.
// Uses algo from RFC8032 5.1.3.
uvRatio: (u: bigint, v: bigint): { isValid: boolean; value: bigint } => {
const P = ed448P;
// https://datatracker.ietf.org/doc/html/rfc8032#section-5.2.3
// To compute the square root of (u/v), the first step is to compute the
// candidate root x = (u/v)^((p+1)/4). This can be done using the
// following trick, to use a single modular powering for both the
// inversion of v and the square root:
// x = (u/v)^((p+1)/4) = u³v(u⁵v³)^((p-3)/4) (mod p)
const u2v = mod(u * u * v, P); // u²v
const u3v = mod(u2v * u, P); // u³v
const u5v3 = mod(u3v * u2v * v, P); // u⁵v³
const root = ed448_pow_Pminus3div4(u5v3);
const x = mod(u3v * root, P);
// Verify that root is exists
const x2 = mod(x * x, P); // x²
// If vx² = u, the recovered x-coordinate is x. Otherwise, no
// square root exists, and the decoding fails.
return { isValid: mod(x2 * v, P) === u, value: x };
},
} as const; } as const;
export const ed448 = twistedEdwards(ED448_DEF); export const ed448 = /* @__PURE__ */ twistedEdwards(ED448_DEF);
// NOTE: there is no ed448ctx, since ed448 supports ctx by default // NOTE: there is no ed448ctx, since ed448 supports ctx by default
export const ed448ph = twistedEdwards({ ...ED448_DEF, prehash: shake256_64 }); export const ed448ph = /* @__PURE__ */ twistedEdwards({ ...ED448_DEF, prehash: shake256_64 });
export const x448 = montgomery({ export const x448 = /* @__PURE__ */ (() =>
a: BigInt(156326), montgomery({
montgomeryBits: 448, a: BigInt(156326),
nByteLength: 57, montgomeryBits: 448,
P: ed448P, nByteLength: 57,
Gu: BigInt(5), P: ed448P,
powPminus2: (x: bigint): bigint => { Gu: BigInt(5),
const P = ed448P; powPminus2: (x: bigint): bigint => {
const Pminus3div4 = ed448_pow_Pminus3div4(x); const P = ed448P;
const Pminus3 = pow2(Pminus3div4, BigInt(2), P); const Pminus3div4 = ed448_pow_Pminus3div4(x);
return mod(Pminus3 * x, P); // Pminus3 * x = Pminus2 const Pminus3 = pow2(Pminus3div4, BigInt(2), P);
}, return mod(Pminus3 * x, P); // Pminus3 * x = Pminus2
adjustScalarBytes, },
randomBytes, adjustScalarBytes,
}); randomBytes,
}))();
/** /**
* Converts edwards448 public key to x448 public key. Uses formula: * Converts edwards448 public key to x448 public key. Uses formula:
@@ -146,11 +159,12 @@ export const x448 = montgomery({
* const aPub = ed448.getPublicKey(utils.randomPrivateKey()); * const aPub = ed448.getPublicKey(utils.randomPrivateKey());
* x448.getSharedSecret(edwardsToMontgomery(aPub), edwardsToMontgomery(someonesPub)) * x448.getSharedSecret(edwardsToMontgomery(aPub), edwardsToMontgomery(someonesPub))
*/ */
export function edwardsToMontgomery(edwardsPub: string | Uint8Array): Uint8Array { export function edwardsToMontgomeryPub(edwardsPub: string | Uint8Array): Uint8Array {
const { y } = ed448.ExtendedPoint.fromHex(edwardsPub); const { y } = ed448.ExtendedPoint.fromHex(edwardsPub);
const _1n = BigInt(1); const _1n = BigInt(1);
return Fp.toBytes(Fp.create((y - _1n) * Fp.inv(y + _1n))); return Fp.toBytes(Fp.create((y - _1n) * Fp.inv(y + _1n)));
} }
export const edwardsToMontgomery = edwardsToMontgomeryPub; // deprecated
// Hash To Curve Elligator2 Map // Hash To Curve Elligator2 Map
const ELL2_C1 = (Fp.ORDER - BigInt(3)) / BigInt(4); // 1. c1 = (q - 3) / 4 # Integer arithmetic const ELL2_C1 = (Fp.ORDER - BigInt(3)) / BigInt(4); // 1. c1 = (q - 3) / 4 # Integer arithmetic
@@ -227,17 +241,225 @@ function map_to_curve_elligator2_edwards448(u: bigint) {
return { x: Fp.mul(xEn, inv[0]), y: Fp.mul(yEn, inv[1]) }; // 38. return (xEn, xEd, yEn, yEd) return { x: Fp.mul(xEn, inv[0]), y: Fp.mul(yEn, inv[1]) }; // 38. return (xEn, xEd, yEn, yEd)
} }
const { hashToCurve, encodeToCurve } = htf.createHasher( const htf = /* @__PURE__ */ (() =>
ed448.ExtendedPoint, createHasher(
(scalars: bigint[]) => map_to_curve_elligator2_edwards448(scalars[0]), ed448.ExtendedPoint,
{ (scalars: bigint[]) => map_to_curve_elligator2_edwards448(scalars[0]),
DST: 'edwards448_XOF:SHAKE256_ELL2_RO_', {
encodeDST: 'edwards448_XOF:SHAKE256_ELL2_NU_', DST: 'edwards448_XOF:SHAKE256_ELL2_RO_',
p: Fp.ORDER, encodeDST: 'edwards448_XOF:SHAKE256_ELL2_NU_',
m: 1, p: Fp.ORDER,
k: 224, m: 1,
expand: 'xof', k: 224,
hash: shake256, expand: 'xof',
} hash: shake256,
}
))();
export const hashToCurve = /* @__PURE__ */ (() => htf.hashToCurve)();
export const encodeToCurve = /* @__PURE__ */ (() => htf.encodeToCurve)();
function assertDcfPoint(other: unknown) {
if (!(other instanceof DcfPoint)) throw new Error('DecafPoint expected');
}
// 1-d
const ONE_MINUS_D = BigInt('39082');
// 1-2d
const ONE_MINUS_TWO_D = BigInt('78163');
// √(-d)
const SQRT_MINUS_D = BigInt(
'98944233647732219769177004876929019128417576295529901074099889598043702116001257856802131563896515373927712232092845883226922417596214'
); );
export { hashToCurve, encodeToCurve }; // 1 / √(-d)
const INVSQRT_MINUS_D = BigInt(
'315019913931389607337177038330951043522456072897266928557328499619017160722351061360252776265186336876723201881398623946864393857820716'
);
// Calculates 1/√(number)
const invertSqrt = (number: bigint) => uvRatio(_1n, number);
const MAX_448B = BigInt(
'0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff'
);
const bytes448ToNumberLE = (bytes: Uint8Array) =>
ed448.CURVE.Fp.create(bytesToNumberLE(bytes) & MAX_448B);
type ExtendedPoint = ExtPointType;
// Computes Elligator map for Decaf
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-ristretto255-decaf448-07#name-element-derivation-2
function calcElligatorDecafMap(r0: bigint): ExtendedPoint {
const { d } = ed448.CURVE;
const P = ed448.CURVE.Fp.ORDER;
const mod = ed448.CURVE.Fp.create;
const r = mod(-(r0 * r0)); // 1
const u0 = mod(d * (r - _1n)); // 2
const u1 = mod((u0 + _1n) * (u0 - r)); // 3
const { isValid: was_square, value: v } = uvRatio(ONE_MINUS_TWO_D, mod((r + _1n) * u1)); // 4
let v_prime = v; // 5
if (!was_square) v_prime = mod(r0 * v);
let sgn = _1n; // 6
if (!was_square) sgn = mod(-_1n);
const s = mod(v_prime * (r + _1n)); // 7
let s_abs = s;
if (isNegativeLE(s, P)) s_abs = mod(-s);
const s2 = s * s;
const W0 = mod(s_abs * _2n); // 8
const W1 = mod(s2 + _1n); // 9
const W2 = mod(s2 - _1n); // 10
const W3 = mod(v_prime * s * (r - _1n) * ONE_MINUS_TWO_D + sgn); // 11
return new ed448.ExtendedPoint(mod(W0 * W3), mod(W2 * W1), mod(W1 * W3), mod(W0 * W2));
}
/**
* Each ed448/ExtendedPoint has 4 different equivalent points. This can be
* a source of bugs for protocols like ring signatures. Decaf was created to solve this.
* Decaf point operates in X:Y:Z:T extended coordinates like 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
*/
class DcfPoint {
static BASE: DcfPoint;
static ZERO: DcfPoint;
// Private property to discourage combining ExtendedPoint + DecafPoint
// Always use Decaf encoding/decoding instead.
constructor(private readonly ep: ExtendedPoint) {}
static fromAffine(ap: AffinePoint<bigint>) {
return new DcfPoint(ed448.ExtendedPoint.fromAffine(ap));
}
/**
* Takes uniform output of 112-byte hash function like shake256 and converts it to `DecafPoint`.
* 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://datatracker.ietf.org/doc/html/draft-irtf-cfrg-ristretto255-decaf448-07#name-element-derivation-2
* @param hex 112-byte output of a hash function
*/
static hashToCurve(hex: Hex): DcfPoint {
hex = ensureBytes('decafHash', hex, 112);
const r1 = bytes448ToNumberLE(hex.slice(0, 56));
const R1 = calcElligatorDecafMap(r1);
const r2 = bytes448ToNumberLE(hex.slice(56, 112));
const R2 = calcElligatorDecafMap(r2);
return new DcfPoint(R1.add(R2));
}
/**
* Converts decaf-encoded string to decaf point.
* https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-ristretto255-decaf448-07#name-decode-2
* @param hex Decaf-encoded 56 bytes. Not every 56-byte string is valid decaf encoding
*/
static fromHex(hex: Hex): DcfPoint {
hex = ensureBytes('decafHex', hex, 56);
const { d } = ed448.CURVE;
const P = ed448.CURVE.Fp.ORDER;
const mod = ed448.CURVE.Fp.create;
const emsg = 'DecafPoint.fromHex: the hex is not valid encoding of DecafPoint';
const s = bytes448ToNumberLE(hex);
// 1. Check that s_bytes is the canonical encoding of a field element, or else abort.
// 2. Check that s is non-negative, or else abort
if (!equalBytes(numberToBytesLE(s, 56), hex) || isNegativeLE(s, P)) throw new Error(emsg);
const s2 = mod(s * s); // 1
const u1 = mod(_1n + s2); // 2
const u1sq = mod(u1 * u1);
const u2 = mod(u1sq - _4n * d * s2); // 3
const { isValid, value: invsqrt } = invertSqrt(mod(u2 * u1sq)); // 4
let u3 = mod((s + s) * invsqrt * u1 * SQRT_MINUS_D); // 5
if (isNegativeLE(u3, P)) u3 = mod(-u3);
const x = mod(u3 * invsqrt * u2 * INVSQRT_MINUS_D); // 6
const y = mod((_1n - s2) * invsqrt * u1); // 7
const t = mod(x * y); // 8
if (!isValid) throw new Error(emsg);
return new DcfPoint(new ed448.ExtendedPoint(x, y, _1n, t));
}
/**
* Encodes decaf point to Uint8Array.
* https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-ristretto255-decaf448-07#name-encode-2
*/
toRawBytes(): Uint8Array {
let { ex: x, ey: _y, ez: z, et: t } = this.ep;
const P = ed448.CURVE.Fp.ORDER;
const mod = ed448.CURVE.Fp.create;
const u1 = mod(mod(x + t) * mod(x - t)); // 1
const x2 = mod(x * x);
const { value: invsqrt } = invertSqrt(mod(u1 * ONE_MINUS_D * x2)); // 2
let ratio = mod(invsqrt * u1 * SQRT_MINUS_D); // 3
if (isNegativeLE(ratio, P)) ratio = mod(-ratio);
const u2 = mod(INVSQRT_MINUS_D * ratio * z - t); // 4
let s = mod(ONE_MINUS_D * invsqrt * x * u2); // 5
if (isNegativeLE(s, P)) s = mod(-s);
return numberToBytesLE(s, 56);
}
toHex(): string {
return bytesToHex(this.toRawBytes());
}
toString(): string {
return this.toHex();
}
// Compare one point to another.
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-ristretto255-decaf448-07#name-equals-2
equals(other: DcfPoint): boolean {
assertDcfPoint(other);
const { ex: X1, ey: Y1 } = this.ep;
const { ex: X2, ey: Y2 } = other.ep;
const mod = ed448.CURVE.Fp.create;
// (x1 * y2 == y1 * x2)
return mod(X1 * Y2) === mod(Y1 * X2);
}
add(other: DcfPoint): DcfPoint {
assertDcfPoint(other);
return new DcfPoint(this.ep.add(other.ep));
}
subtract(other: DcfPoint): DcfPoint {
assertDcfPoint(other);
return new DcfPoint(this.ep.subtract(other.ep));
}
multiply(scalar: bigint): DcfPoint {
return new DcfPoint(this.ep.multiply(scalar));
}
multiplyUnsafe(scalar: bigint): DcfPoint {
return new DcfPoint(this.ep.multiplyUnsafe(scalar));
}
}
export const DecafPoint = /* @__PURE__ */ (() => {
// decaf448 base point is ed448 base x 2
// https://github.com/dalek-cryptography/curve25519-dalek/blob/59837c6ecff02b77b9d5ff84dbc239d0cf33ef90/vendor/ristretto.sage#L699
if (!DcfPoint.BASE) DcfPoint.BASE = new DcfPoint(ed448.ExtendedPoint.BASE).multiply(_2n);
if (!DcfPoint.ZERO) DcfPoint.ZERO = new DcfPoint(ed448.ExtendedPoint.ZERO);
return DcfPoint;
})();
// Hashing to decaf448. https://www.rfc-editor.org/rfc/rfc9380#appendix-C
export const hashToDecaf448 = (msg: Uint8Array, options: htfBasicOpts) => {
const d = options.DST;
const DST = typeof d === 'string' ? utf8ToBytes(d) : d;
const uniform_bytes = expand_message_xof(msg, DST, 112, 224, shake256);
const P = DcfPoint.hashToCurve(uniform_bytes);
return P;
};
export const hash_to_decaf448 = hashToDecaf448; // legacy

View File

@@ -11,7 +11,7 @@ import { Field } from './abstract/modular.js';
* jubjub does not use EdDSA, so `hash`/sha512 params are passed because interface expects them. * jubjub does not use EdDSA, so `hash`/sha512 params are passed because interface expects them.
*/ */
export const jubjub = twistedEdwards({ export const jubjub = /* @__PURE__ */ twistedEdwards({
// Params: a, d // Params: a, d
a: BigInt('0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000000'), a: BigInt('0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000000'),
d: BigInt('0x2a9318e74bfa2b48f5fd9207e6bd7fd4292d7f6d37579d2601065fd6d6343eb1'), d: BigInt('0x2a9318e74bfa2b48f5fd9207e6bd7fd4292d7f6d37579d2601065fd6d6343eb1'),

View File

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

View File

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

View File

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

View File

@@ -5,7 +5,7 @@ import { Field, mod, pow2 } from './abstract/modular.js';
import { ProjPointType as PointType, mapToCurveSimpleSWU } from './abstract/weierstrass.js'; import { ProjPointType as PointType, mapToCurveSimpleSWU } from './abstract/weierstrass.js';
import type { Hex, PrivKey } from './abstract/utils.js'; import type { Hex, PrivKey } from './abstract/utils.js';
import { bytesToNumberBE, concatBytes, ensureBytes, numberToBytesBE } from './abstract/utils.js'; import { bytesToNumberBE, concatBytes, ensureBytes, numberToBytesBE } from './abstract/utils.js';
import * as htf from './abstract/hash-to-curve.js'; import { createHasher, isogenyMap } from './abstract/hash-to-curve.js';
import { createCurve } from './_shortw_utils.js'; import { createCurve } from './_shortw_utils.js';
const secp256k1P = BigInt('0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f'); const secp256k1P = BigInt('0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f');
@@ -199,7 +199,7 @@ function schnorrVerify(signature: Hex, message: Hex, publicKey: Hex): boolean {
} }
} }
export const schnorr = { export const schnorr = /* @__PURE__ */ (() => ({
getPublicKey: schnorrGetPublicKey, getPublicKey: schnorrGetPublicKey,
sign: schnorrSign, sign: schnorrSign,
verify: schnorrVerify, verify: schnorrVerify,
@@ -212,58 +212,63 @@ export const schnorr = {
taggedHash, taggedHash,
mod, mod,
}, },
}; }))();
const isoMap = htf.isogenyMap( const isoMap = /* @__PURE__ */ (() =>
Fp, isogenyMap(
[ Fp,
// xNum
[ [
'0x8e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38daaaaa8c7', // xNum
'0x7d3d4c80bc321d5b9f315cea7fd44c5d595d2fc0bf63b92dfff1044f17c6581', [
'0x534c328d23f234e6e2a413deca25caece4506144037c40314ecbd0b53d9dd262', '0x8e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38daaaaa8c7',
'0x8e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38daaaaa88c', '0x7d3d4c80bc321d5b9f315cea7fd44c5d595d2fc0bf63b92dfff1044f17c6581',
], '0x534c328d23f234e6e2a413deca25caece4506144037c40314ecbd0b53d9dd262',
// xDen '0x8e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38daaaaa88c',
[ ],
'0xd35771193d94918a9ca34ccbb7b640dd86cd409542f8487d9fe6b745781eb49b', // xDen
'0xedadc6f64383dc1df7c4b2d51b54225406d36b641f5e41bbc52a56612a8c6d14', [
'0x0000000000000000000000000000000000000000000000000000000000000001', // LAST 1 '0xd35771193d94918a9ca34ccbb7b640dd86cd409542f8487d9fe6b745781eb49b',
], '0xedadc6f64383dc1df7c4b2d51b54225406d36b641f5e41bbc52a56612a8c6d14',
// yNum '0x0000000000000000000000000000000000000000000000000000000000000001', // LAST 1
[ ],
'0x4bda12f684bda12f684bda12f684bda12f684bda12f684bda12f684b8e38e23c', // yNum
'0xc75e0c32d5cb7c0fa9d0a54b12a0a6d5647ab046d686da6fdffc90fc201d71a3', [
'0x29a6194691f91a73715209ef6512e576722830a201be2018a765e85a9ecee931', '0x4bda12f684bda12f684bda12f684bda12f684bda12f684bda12f684b8e38e23c',
'0x2f684bda12f684bda12f684bda12f684bda12f684bda12f684bda12f38e38d84', '0xc75e0c32d5cb7c0fa9d0a54b12a0a6d5647ab046d686da6fdffc90fc201d71a3',
], '0x29a6194691f91a73715209ef6512e576722830a201be2018a765e85a9ecee931',
// yDen '0x2f684bda12f684bda12f684bda12f684bda12f684bda12f684bda12f38e38d84',
[ ],
'0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffff93b', // yDen
'0x7a06534bb8bdb49fd5e9e6632722c2989467c1bfc8e8d978dfb425d2685c2573', [
'0x6484aa716545ca2cf3a70c3fa8fe337e0a3d21162f0d6299a7bf8192bfd2a76f', '0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffff93b',
'0x0000000000000000000000000000000000000000000000000000000000000001', // LAST 1 '0x7a06534bb8bdb49fd5e9e6632722c2989467c1bfc8e8d978dfb425d2685c2573',
], '0x6484aa716545ca2cf3a70c3fa8fe337e0a3d21162f0d6299a7bf8192bfd2a76f',
].map((i) => i.map((j) => BigInt(j))) as [bigint[], bigint[], bigint[], bigint[]] '0x0000000000000000000000000000000000000000000000000000000000000001', // LAST 1
); ],
const mapSWU = mapToCurveSimpleSWU(Fp, { ].map((i) => i.map((j) => BigInt(j))) as [bigint[], bigint[], bigint[], bigint[]]
A: BigInt('0x3f8731abdd661adca08a5558f0f5d272e953d363cb6f0e5d405447c01a444533'), ))();
B: BigInt('1771'), const mapSWU = /* @__PURE__ */ (() =>
Z: Fp.create(BigInt('-11')), mapToCurveSimpleSWU(Fp, {
}); A: BigInt('0x3f8731abdd661adca08a5558f0f5d272e953d363cb6f0e5d405447c01a444533'),
export const { hashToCurve, encodeToCurve } = htf.createHasher( B: BigInt('1771'),
secp256k1.ProjectivePoint, Z: Fp.create(BigInt('-11')),
(scalars: bigint[]) => { }))();
const { x, y } = mapSWU(Fp.create(scalars[0])); const htf = /* @__PURE__ */ (() =>
return isoMap(x, y); createHasher(
}, secp256k1.ProjectivePoint,
{ (scalars: bigint[]) => {
DST: 'secp256k1_XMD:SHA-256_SSWU_RO_', const { x, y } = mapSWU(Fp.create(scalars[0]));
encodeDST: 'secp256k1_XMD:SHA-256_SSWU_NU_', return isoMap(x, y);
p: Fp.ORDER, },
m: 1, {
k: 128, DST: 'secp256k1_XMD:SHA-256_SSWU_RO_',
expand: 'xmd', encodeDST: 'secp256k1_XMD:SHA-256_SSWU_NU_',
hash: sha256, p: Fp.ORDER,
} m: 1,
); k: 128,
expand: 'xmd',
hash: sha256,
}
))();
export const hashToCurve = /* @__PURE__ */ (() => htf.hashToCurve)();
export const encodeToCurve = /* @__PURE__ */ (() => htf.encodeToCurve)();

View File

@@ -37,6 +37,11 @@ const B_192_40 = '40'.padEnd(192, '0');
const B_384_40 = '40'.padEnd(384, '0'); // [0x40, 0, 0...] const B_384_40 = '40'.padEnd(384, '0'); // [0x40, 0, 0...]
const getPubKey = (priv) => bls.getPublicKey(priv); const getPubKey = (priv) => bls.getPublicKey(priv);
function replaceZeroPoint(item) {
const zeros = '0000000000000000000000000000000000000000000000000000000000000000';
const ones = '1000000000000000000000000000000000000000000000000000000000000001';
return item === zeros ? ones : item;
}
function equal(a, b, comment) { function equal(a, b, comment) {
deepStrictEqual(a.equals(b), true, `eq(${comment})`); deepStrictEqual(a.equals(b), true, `eq(${comment})`);
@@ -1234,6 +1239,7 @@ describe('verify()', () => {
should('verify multi-signature as simple signature', () => { should('verify multi-signature as simple signature', () => {
fc.assert( fc.assert(
fc.property(FC_MSG, FC_BIGINT_5, (message, privateKeys) => { fc.property(FC_MSG, FC_BIGINT_5, (message, privateKeys) => {
message = replaceZeroPoint(message);
const publicKey = privateKeys.map(getPubKey); const publicKey = privateKeys.map(getPubKey);
const signatures = privateKeys.map((privateKey) => bls.sign(message, privateKey)); const signatures = privateKeys.map((privateKey) => bls.sign(message, privateKey));
const aggregatedSignature = bls.aggregateSignatures(signatures); const aggregatedSignature = bls.aggregateSignatures(signatures);
@@ -1245,6 +1251,7 @@ describe('verify()', () => {
should('not verify wrong multi-signature as simple signature', () => { should('not verify wrong multi-signature as simple signature', () => {
fc.assert( fc.assert(
fc.property(FC_MSG, FC_MSG, FC_BIGINT_5, (message, wrongMessage, privateKeys) => { fc.property(FC_MSG, FC_MSG, FC_BIGINT_5, (message, wrongMessage, privateKeys) => {
message = replaceZeroPoint(message);
const publicKey = privateKeys.map(getPubKey); const publicKey = privateKeys.map(getPubKey);
const signatures = privateKeys.map((privateKey) => bls.sign(message, privateKey)); const signatures = privateKeys.map((privateKey) => bls.sign(message, privateKey));
const aggregatedSignature = bls.aggregateSignatures(signatures); const aggregatedSignature = bls.aggregateSignatures(signatures);

View File

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

View File

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

View File

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

117
test/ed448-addons.test.js Normal file
View File

@@ -0,0 +1,117 @@
import { bytesToHex as hex, hexToBytes } from '@noble/hashes/utils';
import { deepStrictEqual, throws } from 'assert';
import { describe, should } from 'micro-should';
import { bytesToNumberLE } from '../esm/abstract/utils.js';
import { ed448, DecafPoint } from '../esm/ed448.js';
describe('decaf448', () => {
should('follow the byte encodings of small multiples', () => {
const encodingsOfSmallMultiples = [
// This is the identity point
'0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000',
// This is the basepoint
'6666666666666666666666666666666666666666666666666666666633333333333333333333333333333333333333333333333333333333',
// These are small multiples of the basepoint
'c898eb4f87f97c564c6fd61fc7e49689314a1f818ec85eeb3bd5514ac816d38778f69ef347a89fca817e66defdedce178c7cc709b2116e75',
'a0c09bf2ba7208fda0f4bfe3d0f5b29a543012306d43831b5adc6fe7f8596fa308763db15468323b11cf6e4aeb8c18fe44678f44545a69bc',
'b46f1836aa287c0a5a5653f0ec5ef9e903f436e21c1570c29ad9e5f596da97eeaf17150ae30bcb3174d04bc2d712c8c7789d7cb4fda138f4',
'1c5bbecf4741dfaae79db72dface00eaaac502c2060934b6eaaeca6a20bd3da9e0be8777f7d02033d1b15884232281a41fc7f80eed04af5e',
'86ff0182d40f7f9edb7862515821bd67bfd6165a3c44de95d7df79b8779ccf6460e3c68b70c16aaa280f2d7b3f22d745b97a89906cfc476c',
'502bcb6842eb06f0e49032bae87c554c031d6d4d2d7694efbf9c468d48220c50f8ca28843364d70cee92d6fe246e61448f9db9808b3b2408',
'0c9810f1e2ebd389caa789374d78007974ef4d17227316f40e578b336827da3f6b482a4794eb6a3975b971b5e1388f52e91ea2f1bcb0f912',
'20d41d85a18d5657a29640321563bbd04c2ffbd0a37a7ba43a4f7d263ce26faf4e1f74f9f4b590c69229ae571fe37fa639b5b8eb48bd9a55',
'e6b4b8f408c7010d0601e7eda0c309a1a42720d6d06b5759fdc4e1efe22d076d6c44d42f508d67be462914d28b8edce32e7094305164af17',
'be88bbb86c59c13d8e9d09ab98105f69c2d1dd134dbcd3b0863658f53159db64c0e139d180f3c89b8296d0ae324419c06fa87fc7daaf34c1',
'a456f9369769e8f08902124a0314c7a06537a06e32411f4f93415950a17badfa7442b6217434a3a05ef45be5f10bd7b2ef8ea00c431edec5',
'186e452c4466aa4383b4c00210d52e7922dbf9771e8b47e229a9b7b73c8d10fd7ef0b6e41530f91f24a3ed9ab71fa38b98b2fe4746d51d68',
'4ae7fdcae9453f195a8ead5cbe1a7b9699673b52c40ab27927464887be53237f7f3a21b938d40d0ec9e15b1d5130b13ffed81373a53e2b43',
'841981c3bfeec3f60cfeca75d9d8dc17f46cf0106f2422b59aec580a58f342272e3a5e575a055ddb051390c54c24c6ecb1e0aceb075f6056',
];
let B = DecafPoint.BASE;
let P = DecafPoint.ZERO;
for (const encoded of encodingsOfSmallMultiples) {
deepStrictEqual(P.toHex(), encoded);
deepStrictEqual(DecafPoint.fromHex(encoded).toHex(), encoded);
P = P.add(B);
}
});
should('not convert bad bytes encoding', () => {
const badEncodings = [
// These are all bad because they're non-canonical field encodings.
'8e24f838059ee9fef1e209126defe53dcd74ef9b6304601c6966099effffffffffffffffffffffffffffffffffffffffffffffffffffffff',
'86fcc7212bd4a0b980928666dc28c444a605ef38e09fb569e28d4443ffffffffffffffffffffffffffffffffffffffffffffffffffffffff',
'866d54bd4c4ff41a55d4eefdbeca73cbd653c7bd3135b383708ec0bdffffffffffffffffffffffffffffffffffffffffffffffffffffffff',
'4a380ccdab9c86364a89e77a464d64f9157538cfdfa686adc0d5ece4ffffffffffffffffffffffffffffffffffffffffffffffffffffffff',
'f22d9d4c945dd44d11e0b1d3d3d358d959b4844d83b08c44e659d79fffffffffffffffffffffffffffffffffffffffffffffffffffffffff',
'8cdffc681aa99e9c818c8ef4c3808b58e86acdef1ab68c8477af185bffffffffffffffffffffffffffffffffffffffffffffffffffffffff',
'0e1c12ac7b5920effbd044e897c57634e2d05b5c27f8fa3df8a086a1ffffffffffffffffffffffffffffffffffffffffffffffffffffffff',
// These are all bad because they're negative field elements.
'15141bd2121837ef71a0016bd11be757507221c26542244f23806f3fd3496b7d4c36826276f3bf5deea2c60c4fa4cec69946876da497e795',
'455d380238434ab740a56267f4f46b7d2eb2dd8ee905e51d7b0ae8a6cb2bae501e67df34ab21fa45946068c9f233939b1d9521a998b7cb93',
'810b1d8e8bf3a9c023294bbfd3d905a97531709bdc0f42390feedd7010f77e98686d400c9c86ed250ceecd9de0a18888ffecda0f4ea1c60d',
'd3af9cc41be0e5de83c0c6273bedcb9351970110044a9a41c7b9b2267cdb9d7bf4dc9c2fdb8bed32878184604f1d9944305a8df4274ce301',
'9312bcab09009e4330ff89c4bc1e9e000d863efc3c863d3b6c507a40fd2cdefde1bf0892b4b5ed9780b91ed1398fb4a7344c605aa5efda74',
'53d11bce9e62a29d63ed82ae93761bdd76e38c21e2822d6ebee5eb1c5b8a03eaf9df749e2490eda9d8ac27d1f71150de93668074d18d1c3a',
'697c1aed3cd8858515d4be8ac158b229fe184d79cb2b06e49210a6f3a7cd537bcd9bd390d96c4ab6a4406da5d93640726285370cfa95df80',
// These are all bad because they give a nonsquare x².
'58ad48715c9a102569b68b88362a4b0645781f5a19eb7e59c6a4686fd0f0750ff42e3d7af1ab38c29d69b670f31258919c9fdbf6093d06c0',
'8ca37ee2b15693f06e910cf43c4e32f1d5551dda8b1e48cb6ddd55e440dbc7b296b601919a4e4069f59239ca247ff693f7daa42f086122b1',
'982c0ec7f43d9f97c0a74b36db0abd9ca6bfb98123a90782787242c8a523cdc76df14a910d54471127e7662a1059201f902940cd39d57af5',
'baa9ab82d07ca282b968a911a6c3728d74bf2fe258901925787f03ee4be7e3cb6684fd1bcfe5071a9a974ad249a4aaa8ca81264216c68574',
'2ed9ffe2ded67a372b181ac524996402c42970629db03f5e8636cbaf6074b523d154a7a8c4472c4c353ab88cd6fec7da7780834cc5bd5242',
'f063769e4241e76d815800e4933a3a144327a30ec40758ad3723a788388399f7b3f5d45b6351eb8eddefda7d5bff4ee920d338a8b89d8b63',
'5a0104f1f55d152ceb68bc138182499891d90ee8f09b40038ccc1e07cb621fd462f781d045732a4f0bda73f0b2acf94355424ff0388d4b9c',
];
for (const badBytes of badEncodings) {
const b = hexToBytes(badBytes);
throws(() => DecafPoint.fromHex(b), badBytes);
}
});
should('create right points from uniform hash', () => {
const hashes = [
'cbb8c991fd2f0b7e1913462d6463e4fd2ce4ccdd28274dc2ca1f4165d5ee6cdccea57be3416e166fd06718a31af45a2f8e987e301be59ae6673e963001dbbda80df47014a21a26d6c7eb4ebe0312aa6fffb8d1b26bc62ca40ed51f8057a635a02c2b8c83f48fa6a2d70f58a1185902c0',
'b6d8da654b13c3101d6634a231569e6b85961c3f4b460a08ac4a5857069576b64428676584baa45b97701be6d0b0ba18ac28d443403b45699ea0fbd1164f5893d39ad8f29e48e399aec5902508ea95e33bc1e9e4620489d684eb5c26bc1ad1e09aba61fabc2cdfee0b6b6862ffc8e55a',
'36a69976c3e5d74e4904776993cbac27d10f25f5626dd45c51d15dcf7b3e6a5446a6649ec912a56895d6baa9dc395ce9e34b868d9fb2c1fc72eb6495702ea4f446c9b7a188a4e0826b1506b0747a6709f37988ff1aeb5e3788d5076ccbb01a4bc6623c92ff147a1e21b29cc3fdd0e0f4',
'd5938acbba432ecd5617c555a6a777734494f176259bff9dab844c81aadcf8f7abd1a9001d89c7008c1957272c1786a4293bb0ee7cb37cf3988e2513b14e1b75249a5343643d3c5e5545a0c1a2a4d3c685927c38bc5e5879d68745464e2589e000b31301f1dfb7471a4f1300d6fd0f99',
'4dec58199a35f531a5f0a9f71a53376d7b4bdd6bbd2904234a8ea65bbacbce2a542291378157a8f4be7b6a092672a34d85e473b26ccfbd4cdc6739783dc3f4f6ee3537b7aed81df898c7ea0ae89a15b5559596c2a5eeacf8b2b362f3db2940e3798b63203cae77c4683ebaed71533e51',
'df2aa1536abb4acab26efa538ce07fd7bca921b13e17bc5ebcba7d1b6b733deda1d04c220f6b5ab35c61b6bcb15808251cab909a01465b8ae3fc770850c66246d5a9eae9e2877e0826e2b8dc1bc08009590bc6778a84e919fbd28e02a0f9c49b48dc689eb5d5d922dc01469968ee81b5',
'e9fb440282e07145f1f7f5ecf3c273212cd3d26b836b41b02f108431488e5e84bd15f2418b3d92a3380dd66a374645c2a995976a015632d36a6c2189f202fc766e1c82f50ad9189be190a1f0e8f9b9e69c9c18cc98fdd885608f68bf0fdedd7b894081a63f70016a8abf04953affbefa',
];
const encodedHashToPoints = [
'0c709c9607dbb01c94513358745b7c23953d03b33e39c7234e268d1d6e24f34014ccbc2216b965dd231d5327e591dc3c0e8844ccfd568848',
'76ab794e28ff1224c727fa1016bf7f1d329260b7218a39aea2fdb17d8bd9119017b093d641cedf74328c327184dc6f2a64bd90eddccfcdab',
'c8d7ac384143500e50890a1c25d643343accce584caf2544f9249b2bf4a6921082be0e7f3669bb5ec24535e6c45621e1f6dec676edd8b664',
'62beffc6b8ee11ccd79dbaac8f0252c750eb052b192f41eeecb12f2979713b563caf7d22588eca5e80995241ef963e7ad7cb7962f343a973',
'f4ccb31d263731ab88bed634304956d2603174c66da38742053fa37dd902346c3862155d68db63be87439e3d68758ad7268e239d39c4fd3b',
'7e79b00e8e0a76a67c0040f62713b8b8c6d6f05e9c6d02592e8a22ea896f5deacc7c7df5ed42beae6fedb9000285b482aa504e279fd49c32',
'20b171cb16be977f15e013b9752cf86c54c631c4fc8cbf7c03c4d3ac9b8e8640e7b0e9300b987fe0ab5044669314f6ed1650ae037db853f1',
];
for (let i = 0; i < hashes.length; i++) {
const hash = hexToBytes(hashes[i]);
const point = DecafPoint.hashToCurve(hash);
deepStrictEqual(point.toHex(), encodedHashToPoints[i]);
}
});
should('have proper equality testing', () => {
const MAX_448B = BigInt(
'0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff'
);
const bytes448ToNumberLE = (bytes) => ed448.CURVE.Fp.create(bytesToNumberLE(bytes) & MAX_448B);
const priv = new Uint8Array([
23, 211, 149, 179, 209, 108, 78, 37, 229, 45, 122, 220, 85, 38, 192, 182, 96, 40, 168, 63,
175, 194, 73, 202, 14, 175, 78, 15, 117, 175, 40, 32, 218, 221, 151, 58, 158, 91, 250, 141,
18, 175, 191, 119, 152, 124, 223, 101, 54, 218, 76, 158, 43, 112, 151, 32,
]);
const pub = DecafPoint.BASE.multiply(bytes448ToNumberLE(priv));
deepStrictEqual(pub.equals(DecafPoint.ZERO), false);
});
});
// ESM is broken.
import url from 'url';
if (import.meta.url === url.pathToFileURL(process.argv[1]).href) {
should.run();
}

View File

@@ -4,7 +4,9 @@ import { should } from 'micro-should';
import './basic.test.js'; import './basic.test.js';
import './nist.test.js'; import './nist.test.js';
import './ed448.test.js'; import './ed448.test.js';
import './ed448-addons.test.js';
import './ed25519.test.js'; import './ed25519.test.js';
import './ed25519-addons.test.js';
import './secp256k1.test.js'; import './secp256k1.test.js';
import './secp256k1-schnorr.test.js'; import './secp256k1-schnorr.test.js';
import './jubjub.test.js'; import './jubjub.test.js';

View File

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

View File

@@ -163,6 +163,7 @@ should('poseidonperm_x5_255_3', () => {
t, t,
roundsFull: 8, roundsFull: 8,
roundsPartial: 57, roundsPartial: 57,
sboxPower: 5,
mds, mds,
roundConstants, roundConstants,
}); });
@@ -229,6 +230,7 @@ should('poseidonperm_x5_255_5', () => {
t, t,
roundsFull: 8, roundsFull: 8,
roundsPartial: 60, roundsPartial: 60,
sboxPower: 5,
mds, mds,
roundConstants, roundConstants,
}); });
@@ -280,6 +282,7 @@ should('poseidonperm_x5_254_3', () => {
t, t,
roundsFull: 8, roundsFull: 8,
roundsPartial: 57, roundsPartial: 57,
sboxPower: 5,
mds, mds,
roundConstants, roundConstants,
}); });
@@ -347,6 +350,7 @@ should('poseidonperm_x5_254_5', () => {
t, t,
roundsFull: 8, roundsFull: 8,
roundsPartial: 60, roundsPartial: 60,
sboxPower: 5,
mds, mds,
roundConstants, roundConstants,
}); });

View File

@@ -14,6 +14,9 @@ import { default as privates } from './vectors/secp256k1/privates.json' assert {
import { default as points } from './vectors/secp256k1/points.json' assert { type: 'json' }; import { default as points } from './vectors/secp256k1/points.json' assert { type: 'json' };
import { default as wp } from './wycheproof/ecdsa_secp256k1_sha256_test.json' assert { type: 'json' }; import { default as wp } from './wycheproof/ecdsa_secp256k1_sha256_test.json' assert { type: 'json' };
// Any changes to the file will need to be aware of the fact
// the file is shared between noble-curves and noble-secp256k1.
const Point = secp.ProjectivePoint; const Point = secp.ProjectivePoint;
const privatesTxt = readFileSync('./test/vectors/secp256k1/privates-2.txt', 'utf-8'); const privatesTxt = readFileSync('./test/vectors/secp256k1/privates-2.txt', 'utf-8');
@@ -265,6 +268,33 @@ describe('secp256k1', () => {
deepStrictEqual(sign(ent5), e.extraEntropyMax); deepStrictEqual(sign(ent5), e.extraEntropyMax);
} }
}); });
should('handle one byte {extraData}', () => {
const extraEntropy = '01';
const privKey = hexToBytes(
'0101010101010101010101010101010101010101010101010101010101010101'
);
const msg = 'd1a9dc8ed4e46a6a3e5e594615ca351d7d7ef44df1e4c94c1802f3592183794b';
const res = secp.sign(msg, privKey, { extraEntropy }).toCompactHex();
deepStrictEqual(
res,
'a250ec23a54bfdecf0e924cbf484077c5044410f915cdba86731cb2e4e925aaa5b1e4e3553d88be2c48a9a0d8d849ce2cc5720d25b2f97473e02f2550abe9545'
);
});
should('handle 48 bytes {extraData}', () => {
const extraEntropy =
'000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000001';
const privKey = hexToBytes(
'0101010101010101010101010101010101010101010101010101010101010101'
);
const msg = 'd1a9dc8ed4e46a6a3e5e594615ca351d7d7ef44df1e4c94c1802f3592183794b';
const res = secp.sign(msg, privKey, { extraEntropy }).toCompactHex();
deepStrictEqual(
res,
'2bdf40f42ac0e42ee12750d03bb12b75306dae58eb3c961c5a80d78efae93e595295b66e8eb28f1eb046bb129a976340312159ec0c20b97342667572e4a8379a'
);
});
}); });
describe('verify()', () => { describe('verify()', () => {

View File

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

View File

@@ -1,20 +1,24 @@
{ {
"compilerOptions": { "compilerOptions": {
"strict": true, "outDir": "esm",
"outDir": "esm", "target": "es2020",
"target": "es2020", "module": "es6",
"module": "es6", "moduleResolution": "node16",
"moduleResolution": "node16", "baseUrl": ".",
"noUnusedLocals": true, "paths": {
"sourceMap": true, "@noble/hashes/crypto": ["src/crypto"]
"baseUrl": ".",
"paths": {
"@noble/hashes/crypto": [ "src/crypto" ]
},
}, },
"include": ["src"], "sourceMap": true,
"exclude": [ "strict": true,
"node_modules", "allowSyntheticDefaultImports": false,
"lib", "allowUnreachableCode": false,
], "esModuleInterop": false,
"noFallthroughCasesInSwitch": true,
"noImplicitReturns": true,
"noUncheckedIndexedAccess": false,
"noUnusedLocals": true,
"noUnusedParameters": true,
},
"include": ["src"],
"exclude": ["node_modules", "lib"]
} }

View File

@@ -1,20 +1,24 @@
{ {
"compilerOptions": { "compilerOptions": {
"strict": true, "outDir": ".",
"declaration": true, "target": "es2020",
"declarationMap": true, "lib": ["es2020"], // Set explicitly to remove DOM
"sourceMap": true, "module": "commonjs",
"outDir": ".", "moduleResolution": "node",
"target": "es2020", "baseUrl": ".",
"lib": ["es2020"], // Set explicitly to remove DOM "sourceMap": true,
"module": "commonjs", "declaration": true,
"moduleResolution": "node", "declarationMap": true,
"noUnusedLocals": true, "strict": true,
"baseUrl": ".", "allowSyntheticDefaultImports": false,
}, "allowUnreachableCode": false,
"include": ["src"], "esModuleInterop": false,
"exclude": [ "noFallthroughCasesInSwitch": true,
"node_modules", "noImplicitReturns": true,
"*.d.ts" "noUncheckedIndexedAccess": false,
], "noUnusedLocals": true,
"noUnusedParameters": true
},
"include": ["src"],
"exclude": ["node_modules", "*.d.ts"]
} }