Compare commits

..

212 Commits
1.0.0 ... main

Author SHA1 Message Date
a8ea9d757f Build noble-curves for ES2015 for Tornado purposes 2024-04-18 14:08:07 -07:00
Paul Miller
efeca9f478
Add hex tests 2024-03-27 12:12:01 +01:00
Paul Miller
8ad2f9a185
Merge pull request from carleeto/main
Fix montgomery sample code in README.md
2024-03-25 08:28:17 +01:00
Carl Menezes
be576b4c17
Fix montgomery sample code in README.md
Resolves https://github.com/paulmillr/noble-curves/issues/132
2024-03-25 11:17:28 +13:00
Paul Miller
819514576c
Use jsbt for tsconfig and building 2024-03-17 14:19:18 +01:00
Paul Miller
a0e398489f
Release 1.4.0. 2024-03-14 07:29:55 +01:00
Paul Miller
85d194cb93
Bump hashes. Commit build lockfile 2024-03-14 07:27:17 +01:00
Paul Miller
1830124ca1
Merge pull request from xrchz/patch-1
Update README.md with Ethereum example
2024-03-08 00:21:59 +01:00
Ramana Kumar
72cc640bb1
Update README.md with Ethereum example
Shows how to use the DST to sign/verify messages with the configuration suitable for Ethereum beacon chain.
2024-03-07 21:15:01 +00:00
Paul Miller
32bda7926d
Fix bls short sig verification on hex. Closes gh-124 2024-03-01 22:22:49 +01:00
Paul Miller
38a4ca1e6b
readme: new noble library 2024-02-28 05:07:38 +01:00
Paul Miller
07f7e53e31
Update build deps 2024-02-28 04:26:25 +01:00
Paul Miller
537db4a968
hash-to-curve: adjust dst logic a bit 2024-02-27 23:34:30 +01:00
Paul Miller
a70501cec4
ci: add upload-release action 2024-02-27 23:34:03 +01:00
Paul Miller
89aaf264c1
readme 2024-02-27 23:25:10 +01:00
Paul Miller
e93caf9567
weierstrass: improve Entropy type. Use new utility. 2024-02-27 23:25:10 +01:00
Paul Miller
0ce103bd6b
utils, hash-to-curve: reduce code duplication 2024-02-27 23:25:10 +01:00
Paul Miller
6a85252dc3
Merge pull request from dhrubabasu/add-htfopts
bls: Add `htfOpts` parameter to `sign` functions
2024-02-17 17:50:59 +01:00
dhrubabasu
a8503d6819
bls: Add htfOpts parameter to sign functions 2024-02-16 18:03:27 -05:00
Paul Miller
8397b26b45
Update github workflows 2024-02-14 02:10:37 +01:00
Paul Miller
8c39a6ac5a
tsconfig: change module to Node16, copy noble-ciphers change 2024-02-14 02:10:32 +01:00
Paul Miller
a1a7dc9cbf
readme: Add link to gh discussions 2024-02-05 17:45:16 +01:00
Paul Miller
ba58a282bd
readme 2024-01-21 14:45:32 +04:00
Paul Miller
c4c479047e
Merge pull request from ardislu/fix-broken-links
README: fix broken links
2024-01-20 13:47:48 +04:00
Ardis Lu
2bf2e312a0
README: fix broken links 2024-01-19 23:39:52 -08:00
Paul Miller
0a663391bd
Improve gitignore 2024-01-14 10:02:50 +01:00
Paul Miller
7be1dfc55d
utils: copy concatBytes from hashes 2024-01-02 08:49:40 +01:00
Paul Miller
37eab5a28a
Refactor tsconfig: use inheritance 2023-12-23 18:30:48 +01:00
Paul Miller
2706fe9f79
README: mention secp256r1. 2023-12-14 16:20:40 +03:00
Paul Miller
b39b0d1daf
weierstrass: improve error wording for sqrt case 2023-12-13 15:58:51 +03:00
Paul Miller
4007ee975b
Release 1.3.0. 2023-12-12 02:21:29 +03:00
Paul Miller
f8af434b9c
Bump noble-hashes to 1.3.3 2023-12-12 02:18:35 +03:00
Paul Miller
be8033a2d8
readme 2023-12-11 01:43:32 +01:00
Paul Miller
b3c239981b
readme 2023-12-11 01:42:57 +01:00
Paul Miller
18b0bc6317
readme: Mention zip215 2023-12-11 01:40:43 +01:00
Paul Miller
30f68c9e54
utils: improve isBytes 2023-12-11 00:04:11 +01:00
Paul Miller
ada1ea5a19
bls: fix types. Closes gh-101 2023-12-10 23:42:42 +01:00
Paul Miller
0a3a13b3dc
Fix typescript esm config 2023-12-10 23:32:10 +01:00
Paul Miller
26a4fd4293
weierstrass, hash-to-curve: ensure to use utils.isBytes everywhere 2023-12-10 23:27:15 +01:00
Paul Miller
9db14fc6d0
utils: fix-up isBytes 2023-12-10 23:26:57 +01:00
Paul Miller
8e6c19de2b
utils: make equalBytes constant-time 2023-12-10 23:04:01 +01:00
Paul Miller
4ffb68853d
utils: make isBytes more resilient in weird envs, improve concatBytes type error resilience. 2023-12-10 23:00:49 +01:00
Paul Miller
008958364e
weierstrass: reformat after new prettier 2023-12-10 22:58:13 +01:00
Paul Miller
1c535a3287
deps: Bump prettier and typescript, reduce their sizes 2023-12-10 22:58:01 +01:00
Paul Miller
b8b12671ac
test: rename hash-to-curve vectors, remove colons. closes gh-102 2023-12-10 19:47:25 +01:00
Paul Miller
2f1460a4d7
BLS: Refactor mask-bit settings, improve encoding resiliency 2023-11-10 02:55:16 +01:00
Paul Miller
fb02e93ff6
ECDH tests: comment 2023-11-01 17:09:37 +01:00
Paul Miller
c525356916
ECDH tests: allow padded private keys 2023-11-01 17:06:40 +01:00
Paul Miller
a4abd8a202
ECDH tests: quick and dirty ASN1 parsing 2023-11-01 16:54:08 +01:00
Paul Miller
c19373a0b5
readme 2023-10-20 15:34:18 +02:00
Paul Miller
85006ed620
readme 2023-10-20 15:33:27 +02:00
Paul Miller
fae7f6612a
README 2023-10-20 15:27:08 +02:00
Paul Miller
36894729c0
readme note on csprng 2023-10-20 15:16:43 +02:00
Paul Miller
eabab627c7
Merge pull request from yhc125/patch-1 2023-10-16 17:36:43 +02:00
YoungHoon Cha
e1640eb74e
Update README.md
Added libraries missing from the code examples.
2023-10-17 00:28:21 +09:00
Paul Miller
7f851873f9
Merge pull request from secure12/main 2023-10-12 12:16:16 +02:00
Eric Ho
02099b9b4c
Add weierstrassPoints return type 2023-10-11 19:16:56 +01:00
Eric Ho
3b14683806
Update weierstrass.ts 2023-10-11 18:41:11 +01:00
Paul Miller
47169740c6
readme 2023-10-07 15:19:21 +02:00
Paul Miller
45c7cb560d
readme 2023-10-07 15:00:11 +02:00
Paul Miller
b36bf44f4b
readme 2023-10-07 14:48:25 +02:00
Paul Miller
30763066ac
readme 2023-10-07 14:43:51 +02:00
Paul Miller
911801ec0f
readme 2023-10-07 14:39:45 +02:00
Paul Miller
8ba25a1c40
readme 2023-10-07 14:36:25 +02:00
Paul Miller
43a06b669a
readme update 2023-10-07 14:35:00 +02:00
Paul Miller
e7720c1609
readme: Clarify ecdsa recovery 2023-10-05 06:46:31 +02:00
Paul Miller
2da6abb336
Fix x448 private keys: must be 56 bytes, not 57. Reported by @larabr 2023-10-03 01:15:43 +02:00
Paul Miller
4752ab1f1e
utils: optimize hexToBytes by 4% 2023-09-25 20:22:57 +02:00
Paul Miller
f58002e6d4
utils: refactor hexToBytes a bit 2023-09-25 19:21:18 +02:00
Paul Miller
d0294bb2a6
Clarify build:release script. Closes gh-86 2023-09-21 23:07:52 +02:00
Paul Miller
2b41e387de
Merge pull request from sublimator/nd-impl-group-x-for-x-in-decafpoint-ristrettopoint-2023-09-20
feat: impl Group<X> for X in DecafPoint/RistrettoPoint
2023-09-20 21:06:36 +02:00
Nicholas Dudfield
08850c2d6a feat: impl Group<X> for X in DecafPoint/RistrettoPoint 2023-09-20 16:23:41 +07:00
Paul Miller
ce7a8fda55
bls, bn: clarify their security level in comments 2023-09-14 03:02:10 +02:00
Paul Miller
728b485cd8
Merge pull request from arobsn/main
Improve `hexToBytes` performance
2023-09-14 00:17:42 +02:00
Paul Miller
eaefe9a272
benchmark add utils 2023-09-13 23:59:12 +02:00
Paul Miller
c935b398fe
abstract/utils: reformat hexToBytes. 2023-09-13 23:57:34 +02:00
Paul Miller
ddad219e7a
README 2023-09-13 23:29:05 +02:00
arobsn
1d83bab27d
add char code based hexToBytes function 2023-09-13 18:14:13 -03:00
Paul Miller
4be208e4b2
README: add new project using curves 2023-09-10 21:55:06 +02:00
Paul Miller
77bee0d54e
ed448: clarify why there are 56 or 57 byte keys 2023-09-10 03:00:51 +02:00
Paul Miller
6bcab6c24b
readme: add example for chash 2023-09-07 23:44:46 +02:00
Paul Miller
7befd5f881
readme 2023-09-07 15:34:29 +02:00
Paul Miller
8f78471703
Merge pull request from sublimator/patch-2
docs: audited by plural firms
2023-09-07 15:34:03 +02:00
Nicholas Dudfield
17294f4974
docs: audited by plural firms 2023-09-07 10:59:48 +07:00
Paul Miller
3890b79e7e
readme 2023-09-06 20:22:45 +02:00
Paul Miller
2acebc8176
Add new audit of noble-curves by kudelski security. 2023-09-06 01:48:09 +02:00
Paul Miller
1e67754943
Merge pull request from randombit/jack/check-short-sig-in-subgroup
Fix ShortSignature.fromHex to check the G1 point is valid
2023-08-31 20:26:17 +02:00
Jack Lloyd
156a1e909a Fix ShortSignature.fromHex to check the G1 point is valid 2023-08-31 13:28:53 -04:00
Paul Miller
ccea23a712
Fix README. Closes gh-80 2023-08-31 02:16:47 +02:00
Paul Miller
8661eef949
readme 2023-08-29 16:29:30 +02:00
Paul Miller
4743182bf7
README: update security section 2023-08-29 14:36:11 +02:00
Paul Miller
5c477a88fa
README: update security section 2023-08-29 14:25:58 +02:00
Paul Miller
df9d461adf
README: update security section 2023-08-29 14:00:53 +02:00
Paul Miller
5c21fa3855
Merge pull request from randombit/jack/update-readme-for-bls-short-sigs
Update the README to describe BLS short signature support
2023-08-28 17:02:19 +02:00
Jack Lloyd
6661a7db7b Update the README to describe BLS short signature support 2023-08-28 09:22:25 -04:00
Paul Miller
cf5f2268fb
ed448: add todo comment 2023-08-27 18:49:55 +02:00
Paul Miller
1d5286ffa7
single-file build: expose more methods 2023-08-27 18:49:55 +02:00
Paul Miller
e31efd91d8
Merge pull request from randombit/jack/add-short-signatures
Add verification of BLS short signatures
2023-08-26 00:50:42 +02:00
Jack Lloyd
c5e0e070d1 Complete BLS short signature support 2023-08-24 16:38:12 -04:00
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
Jack Lloyd
b082d41c29 Add verification of BLS short signatures 2023-08-18 15:36:17 -04: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 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 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 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 from sublimator/nd-validate-dst-as-stringoruint8array-closes-57-2023-06-22
fix: validate hash_to_field DST as stringOrUint8Array (closes )
2023-06-23 00:27:24 +02:00
Nicholas Dudfield
bf70ba9776 fix: validate hash_to_field DST as stringOrUint8Array (closes ) 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 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 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 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 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
76 changed files with 2859 additions and 1069 deletions
.github
.gitignore.prettierrc.jsonREADME.mdSECURITY.md
audit
benchmark
build
esm
package-lock.jsonpackage.json
src
test
tsconfig.esm.jsontsconfig.json

1
.github/funding.yml vendored

@ -1,2 +1 @@
github: paulmillr
# custom: https://paulmillr.com/funding/

@ -1,17 +1,23 @@
name: Node CI
on: [push, pull_request]
name: Run node.js tests
on:
- push
- pull_request
jobs:
test:
name: v18 @ ubuntu-latest
name: v${{ matrix.node }} @ ubuntu-latest
runs-on: ubuntu-latest
strategy:
matrix:
node:
- 18
- 20
steps:
- uses: actions/checkout@v3
- name: Use Node.js ${{ matrix.node }}
uses: actions/setup-node@v3
with:
node-version: 18
- run: npm install
- run: npm run build --if-present
- run: npm run lint --if-present
- run: npm test
- uses: actions/checkout@1e31de5234b9f8995739874a8ce0492dc87873e2 # v4
- name: Use Node.js ${{ matrix.node }}
uses: actions/setup-node@8f152de45cc393bb48ce5d89d36b731f54556e65 # v4
with:
node-version: ${{ matrix.node }}
- run: npm install
- run: npm run build --if-present
- run: npm test
- run: npm run lint --if-present

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

@ -0,0 +1,23 @@
name: Publish package to npm
on:
release:
types: [created]
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write
steps:
- uses: actions/checkout@1e31de5234b9f8995739874a8ce0492dc87873e2 # v4
- uses: actions/setup-node@8f152de45cc393bb48ce5d89d36b731f54556e65 # v4
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 }}

28
.github/workflows/upload-release.yml vendored Normal file

@ -0,0 +1,28 @@
name: Upload standalone file to GitHub Releases
on:
release:
types: [created]
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: write
id-token: write
steps:
- uses: actions/checkout@1e31de5234b9f8995739874a8ce0492dc87873e2 # v4
- uses: actions/setup-node@8f152de45cc393bb48ce5d89d36b731f54556e65 # v4
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: |
cd build
npm ci
npm run build:release
cd ..
- run: gh release upload ${{ github.event.release.tag_name }} build/`npx jsbt outfile`
env:
GH_TOKEN: ${{ github.token }}

18
.gitignore vendored

@ -1,13 +1,9 @@
build/
node_modules/
coverage/
node_modules
/*.js
/*.ts
/*.js.map
/*.d.ts.map
/esm/*.js
/esm/*.ts
/esm/*.js.map
/esm/*.d.ts.map
/esm/abstract
/abstract/
*.d.ts
*.d.ts.map
*.js.map
/build
/abstract
/esm/abstract

@ -1,4 +1,5 @@
{
"printWidth": 100,
"singleQuote": true
"singleQuote": true,
"trailingComma": "es5"
}

745
README.md

File diff suppressed because it is too large Load Diff

@ -1,5 +1,7 @@
# Security Policy
See [README's Security section](./README.md#security) for detailed description of internal security practices.
## Supported Versions
| Version | Supported |

Binary file not shown.

@ -1,11 +1,7 @@
# Audit
The library has been audited during Jan-Feb 2023 by an independent security firm [Trail of Bits](https://www.trailofbits.com):
[PDF](https://github.com/trailofbits/publications/blob/master/reviews/2023-01-ryanshea-noblecurveslibrary-securityreview.pdf).
The audit has been funded by Ryan Shea. Audit scope was abstract modules `curve`, `hash-to-curve`, `modular`, `poseidon`, `utils`, `weierstrass`, and top-level modules `_shortw_utils` and `secp256k1`. See [changes since audit](https://github.com/paulmillr/noble-curves/compare/0.7.3..main).
All audits of the library are described in [README's Security section](../README.md#security)
File in the directory was saved from
`2023-01-trailofbits-audit-curves.pdf` file in the directory was saved from
[github.com/trailofbits/publications](https://github.com/trailofbits/publications).
Check out their repo and verify checksums to ensure the PDF in this directory has not been altered.
See information about fuzzing in root [README](../README.md).

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

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

18
benchmark/decaf448.js Normal 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();
});

@ -8,8 +8,8 @@ import { hashToCurve as secp256k1 } from '../secp256k1.js';
import { hashToCurve as p256 } from '../p256.js';
import { hashToCurve as p384 } from '../p384.js';
import { hashToCurve as p521 } from '../p521.js';
import { hashToCurve as ed25519 } from '../ed25519.js';
import { hashToCurve as ed448 } from '../ed448.js';
import { hashToCurve as ed25519, hash_to_ristretto255 } from '../ed25519.js';
import { hashToCurve as ed448, hash_to_decaf448 } from '../ed448.js';
import { utf8ToBytes } from '../abstract/utils.js';
const N = 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141n;
@ -26,4 +26,7 @@ run(async () => {
for (let [title, fn] of Object.entries({ secp256k1, p256, p384, p521, ed25519, ed448 })) {
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

@ -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();
});

9
benchmark/utils.js Normal file

@ -0,0 +1,9 @@
import { hexToBytes } from '../abstract/utils.js';
import { run, mark } from 'micro-bmark';
run(async () => {
const hex32 = '0123456789abcdef'.repeat(4);
const hex256 = hex32.repeat(8);
await mark('hexToBytes 32b', 5000000, () => hexToBytes(hex32));
await mark('hexToBytes 256b', 500000, () => hexToBytes(hex256));
});

7
build/README.md Normal file

@ -0,0 +1,7 @@
# build
The directory is used to build a single file which contains everything.
The single 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.

20
build/input.js Normal file

@ -0,0 +1,20 @@
import { bytesToHex, concatBytes, hexToBytes, utf8ToBytes } from '@noble/curves/abstract/utils';
export { secp256k1, schnorr as secp256k1_schnorr } from '@noble/curves/secp256k1';
export {
ed25519,
x25519,
edwardsToMontgomeryPub as ed25519_edwardsToMontgomeryPub,
edwardsToMontgomeryPriv as ed25519_edwardsToMontgomeryPriv,
} from '@noble/curves/ed25519';
export {
ed448,
x448,
edwardsToMontgomeryPub as ed448_edwardsToMontgomeryPub,
} 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, utf8ToBytes };

445
build/package-lock.json generated Normal file

@ -0,0 +1,445 @@
{
"name": "build",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "build",
"version": "1.0.0",
"devDependencies": {
"@noble/curves": "file:..",
"esbuild": "0.20.1"
}
},
"..": {
"version": "1.4.0",
"dev": true,
"license": "MIT",
"dependencies": {
"@noble/hashes": "1.4.0"
},
"devDependencies": {
"@paulmillr/jsbt": "0.1.0",
"fast-check": "3.0.0",
"micro-bmark": "0.3.1",
"micro-should": "0.4.0",
"prettier": "3.1.1",
"typescript": "5.3.2"
},
"funding": {
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/@esbuild/aix-ppc64": {
"version": "0.20.1",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.1.tgz",
"integrity": "sha512-m55cpeupQ2DbuRGQMMZDzbv9J9PgVelPjlcmM5kxHnrBdBx6REaEd7LamYV7Dm8N7rCyR/XwU6rVP8ploKtIkA==",
"cpu": [
"ppc64"
],
"dev": true,
"optional": true,
"os": [
"aix"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/android-arm": {
"version": "0.20.1",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.20.1.tgz",
"integrity": "sha512-4j0+G27/2ZXGWR5okcJi7pQYhmkVgb4D7UKwxcqrjhvp5TKWx3cUjgB1CGj1mfdmJBQ9VnUGgUhign+FPF2Zgw==",
"cpu": [
"arm"
],
"dev": true,
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/android-arm64": {
"version": "0.20.1",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.20.1.tgz",
"integrity": "sha512-hCnXNF0HM6AjowP+Zou0ZJMWWa1VkD77BXe959zERgGJBBxB+sV+J9f/rcjeg2c5bsukD/n17RKWXGFCO5dD5A==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/android-x64": {
"version": "0.20.1",
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.20.1.tgz",
"integrity": "sha512-MSfZMBoAsnhpS+2yMFYIQUPs8Z19ajwfuaSZx+tSl09xrHZCjbeXXMsUF/0oq7ojxYEpsSo4c0SfjxOYXRbpaA==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/darwin-arm64": {
"version": "0.20.1",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.20.1.tgz",
"integrity": "sha512-Ylk6rzgMD8klUklGPzS414UQLa5NPXZD5tf8JmQU8GQrj6BrFA/Ic9tb2zRe1kOZyCbGl+e8VMbDRazCEBqPvA==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/darwin-x64": {
"version": "0.20.1",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.20.1.tgz",
"integrity": "sha512-pFIfj7U2w5sMp52wTY1XVOdoxw+GDwy9FsK3OFz4BpMAjvZVs0dT1VXs8aQm22nhwoIWUmIRaE+4xow8xfIDZA==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/freebsd-arm64": {
"version": "0.20.1",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.1.tgz",
"integrity": "sha512-UyW1WZvHDuM4xDz0jWun4qtQFauNdXjXOtIy7SYdf7pbxSWWVlqhnR/T2TpX6LX5NI62spt0a3ldIIEkPM6RHw==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/freebsd-x64": {
"version": "0.20.1",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.20.1.tgz",
"integrity": "sha512-itPwCw5C+Jh/c624vcDd9kRCCZVpzpQn8dtwoYIt2TJF3S9xJLiRohnnNrKwREvcZYx0n8sCSbvGH349XkcQeg==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-arm": {
"version": "0.20.1",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.20.1.tgz",
"integrity": "sha512-LojC28v3+IhIbfQ+Vu4Ut5n3wKcgTu6POKIHN9Wpt0HnfgUGlBuyDDQR4jWZUZFyYLiz4RBBBmfU6sNfn6RhLw==",
"cpu": [
"arm"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-arm64": {
"version": "0.20.1",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.20.1.tgz",
"integrity": "sha512-cX8WdlF6Cnvw/DO9/X7XLH2J6CkBnz7Twjpk56cshk9sjYVcuh4sXQBy5bmTwzBjNVZze2yaV1vtcJS04LbN8w==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-ia32": {
"version": "0.20.1",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.20.1.tgz",
"integrity": "sha512-4H/sQCy1mnnGkUt/xszaLlYJVTz3W9ep52xEefGtd6yXDQbz/5fZE5dFLUgsPdbUOQANcVUa5iO6g3nyy5BJiw==",
"cpu": [
"ia32"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-loong64": {
"version": "0.20.1",
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.20.1.tgz",
"integrity": "sha512-c0jgtB+sRHCciVXlyjDcWb2FUuzlGVRwGXgI+3WqKOIuoo8AmZAddzeOHeYLtD+dmtHw3B4Xo9wAUdjlfW5yYA==",
"cpu": [
"loong64"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-mips64el": {
"version": "0.20.1",
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.20.1.tgz",
"integrity": "sha512-TgFyCfIxSujyuqdZKDZ3yTwWiGv+KnlOeXXitCQ+trDODJ+ZtGOzLkSWngynP0HZnTsDyBbPy7GWVXWaEl6lhA==",
"cpu": [
"mips64el"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-ppc64": {
"version": "0.20.1",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.20.1.tgz",
"integrity": "sha512-b+yuD1IUeL+Y93PmFZDZFIElwbmFfIKLKlYI8M6tRyzE6u7oEP7onGk0vZRh8wfVGC2dZoy0EqX1V8qok4qHaw==",
"cpu": [
"ppc64"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-riscv64": {
"version": "0.20.1",
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.20.1.tgz",
"integrity": "sha512-wpDlpE0oRKZwX+GfomcALcouqjjV8MIX8DyTrxfyCfXxoKQSDm45CZr9fanJ4F6ckD4yDEPT98SrjvLwIqUCgg==",
"cpu": [
"riscv64"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-s390x": {
"version": "0.20.1",
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.20.1.tgz",
"integrity": "sha512-5BepC2Au80EohQ2dBpyTquqGCES7++p7G+7lXe1bAIvMdXm4YYcEfZtQrP4gaoZ96Wv1Ute61CEHFU7h4FMueQ==",
"cpu": [
"s390x"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-x64": {
"version": "0.20.1",
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.20.1.tgz",
"integrity": "sha512-5gRPk7pKuaIB+tmH+yKd2aQTRpqlf1E4f/mC+tawIm/CGJemZcHZpp2ic8oD83nKgUPMEd0fNanrnFljiruuyA==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/netbsd-x64": {
"version": "0.20.1",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.20.1.tgz",
"integrity": "sha512-4fL68JdrLV2nVW2AaWZBv3XEm3Ae3NZn/7qy2KGAt3dexAgSVT+Hc97JKSZnqezgMlv9x6KV0ZkZY7UO5cNLCg==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"netbsd"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/openbsd-x64": {
"version": "0.20.1",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.20.1.tgz",
"integrity": "sha512-GhRuXlvRE+twf2ES+8REbeCb/zeikNqwD3+6S5y5/x+DYbAQUNl0HNBs4RQJqrechS4v4MruEr8ZtAin/hK5iw==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"openbsd"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/sunos-x64": {
"version": "0.20.1",
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.20.1.tgz",
"integrity": "sha512-ZnWEyCM0G1Ex6JtsygvC3KUUrlDXqOihw8RicRuQAzw+c4f1D66YlPNNV3rkjVW90zXVsHwZYWbJh3v+oQFM9Q==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"sunos"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/win32-arm64": {
"version": "0.20.1",
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.20.1.tgz",
"integrity": "sha512-QZ6gXue0vVQY2Oon9WyLFCdSuYbXSoxaZrPuJ4c20j6ICedfsDilNPYfHLlMH7vGfU5DQR0czHLmJvH4Nzis/A==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/win32-ia32": {
"version": "0.20.1",
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.20.1.tgz",
"integrity": "sha512-HzcJa1NcSWTAU0MJIxOho8JftNp9YALui3o+Ny7hCh0v5f90nprly1U3Sj1Ldj/CvKKdvvFsCRvDkpsEMp4DNw==",
"cpu": [
"ia32"
],
"dev": true,
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/win32-x64": {
"version": "0.20.1",
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.20.1.tgz",
"integrity": "sha512-0MBh53o6XtI6ctDnRMeQ+xoCN8kD2qI1rY1KgF/xdWQwoFeKou7puvDfV8/Wv4Ctx2rRpET/gGdz3YlNtNACSA==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@noble/curves": {
"resolved": "..",
"link": true
},
"node_modules/esbuild": {
"version": "0.20.1",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.1.tgz",
"integrity": "sha512-OJwEgrpWm/PCMsLVWXKqvcjme3bHNpOgN7Tb6cQnR5n0TPbQx1/Xrn7rqM+wn17bYeT6MGB5sn1Bh5YiGi70nA==",
"dev": true,
"hasInstallScript": true,
"bin": {
"esbuild": "bin/esbuild"
},
"engines": {
"node": ">=12"
},
"optionalDependencies": {
"@esbuild/aix-ppc64": "0.20.1",
"@esbuild/android-arm": "0.20.1",
"@esbuild/android-arm64": "0.20.1",
"@esbuild/android-x64": "0.20.1",
"@esbuild/darwin-arm64": "0.20.1",
"@esbuild/darwin-x64": "0.20.1",
"@esbuild/freebsd-arm64": "0.20.1",
"@esbuild/freebsd-x64": "0.20.1",
"@esbuild/linux-arm": "0.20.1",
"@esbuild/linux-arm64": "0.20.1",
"@esbuild/linux-ia32": "0.20.1",
"@esbuild/linux-loong64": "0.20.1",
"@esbuild/linux-mips64el": "0.20.1",
"@esbuild/linux-ppc64": "0.20.1",
"@esbuild/linux-riscv64": "0.20.1",
"@esbuild/linux-s390x": "0.20.1",
"@esbuild/linux-x64": "0.20.1",
"@esbuild/netbsd-x64": "0.20.1",
"@esbuild/openbsd-x64": "0.20.1",
"@esbuild/sunos-x64": "0.20.1",
"@esbuild/win32-arm64": "0.20.1",
"@esbuild/win32-ia32": "0.20.1",
"@esbuild/win32-x64": "0.20.1"
}
}
}
}

14
build/package.json Normal file

@ -0,0 +1,14 @@
{
"name": "build",
"private": true,
"version": "1.0.0",
"main": "input.js",
"type": "module",
"devDependencies": {
"@noble/curves": "file:..",
"esbuild": "0.20.1"
},
"scripts": {
"build:release": "npx esbuild --bundle input.js --outfile=`npx jsbt outfile` --global-name=`npx jsbt global`"
}
}

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

69
package-lock.json generated

@ -1,40 +1,47 @@
{
"name": "@noble/curves",
"version": "1.0.0",
"name": "@tornado/noble-curves",
"version": "1.4.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@noble/curves",
"version": "1.0.0",
"funding": [
{
"type": "individual",
"url": "https://paulmillr.com/funding/"
}
],
"name": "@tornado/noble-curves",
"version": "1.4.0",
"license": "MIT",
"dependencies": {
"@noble/hashes": "1.3.0"
"@noble/hashes": "1.4.0"
},
"devDependencies": {
"@paulmillr/jsbt": "0.1.0",
"fast-check": "3.0.0",
"micro-bmark": "0.3.1",
"micro-should": "0.4.0",
"prettier": "2.8.4",
"typescript": "5.0.2"
"prettier": "3.1.1",
"typescript": "5.3.2"
},
"funding": {
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/@noble/hashes": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.0.tgz",
"integrity": "sha512-ilHEACi9DwqJB0pw7kv+Apvh50jiiSyR/cQ3y4W7lOR5mhvn/50FLUfsnfJz0BDZtl/RR16kXvptiv6q1msYZg==",
"funding": [
{
"type": "individual",
"url": "https://paulmillr.com/funding/"
}
]
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz",
"integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==",
"engines": {
"node": ">= 16"
},
"funding": {
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/@paulmillr/jsbt": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/@paulmillr/jsbt/-/jsbt-0.1.0.tgz",
"integrity": "sha512-TdowoHD36hkZARv6LW4jenkVTdK2vP0sy4ZM8E9MxaqAAIRdwmn3RlB+zWkEHi4hKTgLqMGkURfNkFtt0STX2Q==",
"dev": true,
"bin": {
"jsbt": "jsbt.js"
}
},
"node_modules/fast-check": {
"version": "3.0.0",
@ -65,15 +72,15 @@
"dev": true
},
"node_modules/prettier": {
"version": "2.8.4",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.4.tgz",
"integrity": "sha512-vIS4Rlc2FNh0BySk3Wkd6xmwxB0FpOndW5fisM5H8hsZSxU2VWVB5CWIkIjWvrHjIhxk2g3bfMKM87zNTrZddw==",
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.1.1.tgz",
"integrity": "sha512-22UbSzg8luF4UuZtzgiUOfcGM8s4tjBv6dJRT7j275NXsy2jb4aJa4NNveul5x4eqlF1wuhuR2RElK71RvmVaw==",
"dev": true,
"bin": {
"prettier": "bin-prettier.js"
"prettier": "bin/prettier.cjs"
},
"engines": {
"node": ">=10.13.0"
"node": ">=14"
},
"funding": {
"url": "https://github.com/prettier/prettier?sponsor=1"
@ -96,16 +103,16 @@
]
},
"node_modules/typescript": {
"version": "5.0.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.0.2.tgz",
"integrity": "sha512-wVORMBGO/FAs/++blGNeAVdbNKtIh1rbBL2EyQ1+J9lClJ93KiiKe8PmFIVdXhHcyv44SL9oglmfeSsndo0jRw==",
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.2.tgz",
"integrity": "sha512-6l+RyNy7oAHDfxC4FzSJcz9vnjTKxrLpDG5M2Vu4SHRVNg6xzqZp6LYSR9zjqQTu8DU/f5xwxUdADOkbrIX2gQ==",
"dev": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
},
"engines": {
"node": ">=12.20"
"node": ">=14.17"
}
}
}

@ -1,6 +1,6 @@
{
"name": "@noble/curves",
"version": "1.0.0",
"name": "@tornado/noble-curves",
"version": "1.4.0",
"description": "Audited & minimal JS implementation of elliptic curve cryptography",
"files": [
"abstract",
@ -12,9 +12,9 @@
"*.d.ts.map"
],
"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:release": "rollup -c rollup.config.js",
"build:release": "cd build && npm i && npm run build",
"build:clean": "rm *.{js,d.ts,d.ts.map,js.map} esm/*.{js,d.ts,d.ts.map,js.map} 2> /dev/null",
"lint": "prettier --check 'src/**/*.{js,ts}' 'test/*.js'",
"format": "prettier --write 'src/**/*.{js,ts}' 'test/*.js'",
@ -24,19 +24,21 @@
"homepage": "https://paulmillr.com/noble/",
"repository": {
"type": "git",
"url": "https://github.com/paulmillr/noble-curves.git"
"url": "https://git.tornado.ws/tornado-packages/noble-curvest"
},
"license": "MIT",
"dependencies": {
"@noble/hashes": "1.3.0"
"@noble/hashes": "1.4.0"
},
"devDependencies": {
"@paulmillr/jsbt": "0.1.0",
"fast-check": "3.0.0",
"micro-bmark": "0.3.1",
"micro-should": "0.4.0",
"prettier": "2.8.4",
"typescript": "5.0.2"
"prettier": "3.1.1",
"typescript": "5.3.2"
},
"sideEffects": false,
"main": "index.js",
"exports": {
".": {
@ -164,6 +166,8 @@
"secp256k1",
"ed25519",
"ed448",
"x25519",
"ed25519",
"bls12-381",
"bn254",
"pasta",
@ -174,10 +178,5 @@
"eddsa",
"schnorr"
],
"funding": [
{
"type": "individual",
"url": "https://paulmillr.com/funding/"
}
]
}
"funding": "https://paulmillr.com/funding/"
}

@ -12,9 +12,13 @@
* Some projects may prefer to swap this relation, it is not supported for now.
*/
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 * as htf from './hash-to-curve.js';
// prettier-ignore
import {
MapToCurve, Opts as HTFOpts, H2CPointConstructor, htfBasicOpts,
createHasher
} from './hash-to-curve.js';
import {
CurvePointsType,
ProjPointType as ProjPointType,
@ -27,6 +31,12 @@ type Fp = bigint; // Can be different field?
// prettier-ignore
const _2n = BigInt(2), _3n = BigInt(3);
export type ShortSignatureCoder<Fp> = {
fromHex(hex: Hex): ProjPointType<Fp>;
toRawBytes(point: ProjPointType<Fp>): Uint8Array;
toHex(point: ProjPointType<Fp>): string;
};
export type SignatureCoder<Fp2> = {
fromHex(hex: Hex): ProjPointType<Fp2>;
toRawBytes(point: ProjPointType<Fp2>): Uint8Array;
@ -35,13 +45,14 @@ export type SignatureCoder<Fp2> = {
export type CurveType<Fp, Fp2, Fp6, Fp12> = {
G1: Omit<CurvePointsType<Fp>, 'n'> & {
mapToCurve: htf.MapToCurve<Fp>;
htfDefaults: htf.Opts;
ShortSignature: SignatureCoder<Fp>;
mapToCurve: MapToCurve<Fp>;
htfDefaults: HTFOpts;
};
G2: Omit<CurvePointsType<Fp2>, 'n'> & {
Signature: SignatureCoder<Fp2>;
mapToCurve: htf.MapToCurve<Fp2>;
htfDefaults: htf.Opts;
mapToCurve: MapToCurve<Fp2>;
htfDefaults: HTFOpts;
};
fields: {
Fp: IField<Fp>;
@ -63,26 +74,39 @@ export type CurveType<Fp, Fp2, Fp6, Fp12> = {
x: bigint;
r: bigint;
};
htfDefaults: htf.Opts;
htfDefaults: HTFOpts;
hash: CHash; // Because we need outputLen for DRBG
randomBytes: (bytesLength?: number) => Uint8Array;
};
export type CurveFn<Fp, Fp2, Fp6, Fp12> = {
getPublicKey: (privateKey: PrivKey) => Uint8Array;
getPublicKeyForShortSignatures: (privateKey: PrivKey) => Uint8Array;
sign: {
(message: Hex, privateKey: PrivKey): Uint8Array;
(message: ProjPointType<Fp2>, privateKey: PrivKey): ProjPointType<Fp2>;
(message: Hex, privateKey: PrivKey, htfOpts?: htfBasicOpts): Uint8Array;
(message: ProjPointType<Fp2>, privateKey: PrivKey, htfOpts?: htfBasicOpts): ProjPointType<Fp2>;
};
signShortSignature: {
(message: Hex, privateKey: PrivKey, htfOpts?: htfBasicOpts): Uint8Array;
(message: ProjPointType<Fp>, privateKey: PrivKey, htfOpts?: htfBasicOpts): ProjPointType<Fp>;
};
verify: (
signature: Hex | ProjPointType<Fp2>,
message: Hex | ProjPointType<Fp2>,
publicKey: Hex | ProjPointType<Fp>
publicKey: Hex | ProjPointType<Fp>,
htfOpts?: htfBasicOpts
) => boolean;
verifyShortSignature: (
signature: Hex | ProjPointType<Fp>,
message: Hex | ProjPointType<Fp>,
publicKey: Hex | ProjPointType<Fp2>,
htfOpts?: htfBasicOpts
) => boolean;
verifyBatch: (
signature: Hex | ProjPointType<Fp2>,
messages: (Hex | ProjPointType<Fp2>)[],
publicKeys: (Hex | ProjPointType<Fp>)[]
publicKeys: (Hex | ProjPointType<Fp>)[],
htfOpts?: htfBasicOpts
) => boolean;
aggregatePublicKeys: {
(publicKeys: Hex[]): Uint8Array;
@ -92,11 +116,16 @@ export type CurveFn<Fp, Fp2, Fp6, Fp12> = {
(signatures: Hex[]): Uint8Array;
(signatures: ProjPointType<Fp2>[]): ProjPointType<Fp2>;
};
aggregateShortSignatures: {
(signatures: Hex[]): Uint8Array;
(signatures: ProjPointType<Fp>[]): ProjPointType<Fp>;
};
millerLoop: (ell: [Fp2, Fp2, Fp2][], g1: [Fp, Fp]) => Fp12;
pairing: (P: ProjPointType<Fp>, Q: ProjPointType<Fp2>, withFinalExponent?: boolean) => Fp12;
G1: CurvePointsRes<Fp> & ReturnType<typeof htf.createHasher<Fp>>;
G2: CurvePointsRes<Fp2> & ReturnType<typeof htf.createHasher<Fp2>>;
G1: CurvePointsRes<Fp> & ReturnType<typeof createHasher<Fp>>;
G2: CurvePointsRes<Fp2> & ReturnType<typeof createHasher<Fp2>>;
Signature: SignatureCoder<Fp2>;
ShortSignature: ShortSignatureCoder<Fp>;
params: {
x: bigint;
r: bigint;
@ -122,7 +151,6 @@ export function bls<Fp2, Fp6, Fp12>(
// 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 BLS_X_LEN = bitLen(CURVE.params.x);
const groupLen = 32; // TODO: calculate; hardcoded for now
// Pre-compute coefficients for sparse multiplication
// Point addition and point double calculations is reused for coefficients
@ -189,7 +217,8 @@ export function bls<Fp2, Fp6, Fp12>(
const utils = {
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,
};
@ -198,7 +227,7 @@ export function bls<Fp2, Fp6, Fp12>(
const G1_ = weierstrassPoints({ n: Fr.ORDER, ...CURVE.G1 });
const G1 = Object.assign(
G1_,
htf.createHasher(G1_.ProjectivePoint, CURVE.G1.mapToCurve, {
createHasher(G1_.ProjectivePoint, CURVE.G1.mapToCurve, {
...CURVE.htfDefaults,
...CURVE.G1.htfDefaults,
})
@ -224,12 +253,13 @@ export function bls<Fp2, Fp6, Fp12>(
const G2_ = weierstrassPoints({ n: Fr.ORDER, ...CURVE.G2 });
const G2 = Object.assign(
G2_,
htf.createHasher(G2_.ProjectivePoint as htf.H2CPointConstructor<Fp2>, CURVE.G2.mapToCurve, {
createHasher(G2_.ProjectivePoint as H2CPointConstructor<Fp2>, CURVE.G2.mapToCurve, {
...CURVE.htfDefaults,
...CURVE.G2.htfDefaults,
})
);
const { ShortSignature } = CURVE.G1;
const { Signature } = CURVE.G2;
// Calculates bilinear pairing
@ -251,26 +281,37 @@ export function bls<Fp2, Fp6, Fp12>(
function normP1(point: G1Hex): G1 {
return point instanceof G1.ProjectivePoint ? (point as G1) : G1.ProjectivePoint.fromHex(point);
}
function normP1Hash(point: G1Hex, htfOpts?: htfBasicOpts): G1 {
return point instanceof G1.ProjectivePoint
? point
: (G1.hashToCurve(ensureBytes('point', point), htfOpts) as G1);
}
function normP2(point: G2Hex): G2 {
return point instanceof G2.ProjectivePoint ? point : Signature.fromHex(point);
}
function normP2Hash(point: G2Hex, htfOpts?: htf.htfBasicOpts): G2 {
function normP2Hash(point: G2Hex, htfOpts?: htfBasicOpts): G2 {
return point instanceof G2.ProjectivePoint
? point
: (G2.hashToCurve(ensureBytes('point', point), htfOpts) as G2);
}
// Multiplies generator by private key.
// Multiplies generator (G1) by private key.
// P = pk x G
function getPublicKey(privateKey: PrivKey): Uint8Array {
return G1.ProjectivePoint.fromPrivateKey(privateKey).toRawBytes(true);
}
// Multiplies generator (G2) by private key.
// P = pk x G
function getPublicKeyForShortSignatures(privateKey: PrivKey): Uint8Array {
return G2.ProjectivePoint.fromPrivateKey(privateKey).toRawBytes(true);
}
// Executes `hashToCurve` on the message and then multiplies the result by private key.
// S = pk x H(m)
function sign(message: Hex, privateKey: PrivKey, htfOpts?: htf.htfBasicOpts): Uint8Array;
function sign(message: G2, privateKey: PrivKey, htfOpts?: htf.htfBasicOpts): G2;
function sign(message: G2Hex, privateKey: PrivKey, htfOpts?: htf.htfBasicOpts): Uint8Array | G2 {
function sign(message: Hex, privateKey: PrivKey, htfOpts?: htfBasicOpts): Uint8Array;
function sign(message: G2, privateKey: PrivKey, htfOpts?: htfBasicOpts): G2;
function sign(message: G2Hex, privateKey: PrivKey, htfOpts?: htfBasicOpts): Uint8Array | G2 {
const msgPoint = normP2Hash(message, htfOpts);
msgPoint.assertValidity();
const sigPoint = msgPoint.multiply(G1.normPrivateKeyToScalar(privateKey));
@ -278,13 +319,31 @@ export function bls<Fp2, Fp6, Fp12>(
return Signature.toRawBytes(sigPoint);
}
function signShortSignature(
message: Hex,
privateKey: PrivKey,
htfOpts?: htfBasicOpts
): Uint8Array;
function signShortSignature(message: G1, privateKey: PrivKey, htfOpts?: htfBasicOpts): G1;
function signShortSignature(
message: G1Hex,
privateKey: PrivKey,
htfOpts?: htfBasicOpts
): Uint8Array | G1 {
const msgPoint = normP1Hash(message, htfOpts);
msgPoint.assertValidity();
const sigPoint = msgPoint.multiply(G1.normPrivateKeyToScalar(privateKey));
if (message instanceof G1.ProjectivePoint) return sigPoint;
return ShortSignature.toRawBytes(sigPoint);
}
// Checks if pairing of public key & hash is equal to pairing of generator & signature.
// e(P, H(m)) == e(G, S)
function verify(
signature: G2Hex,
message: G2Hex,
publicKey: G1Hex,
htfOpts?: htf.htfBasicOpts
htfOpts?: htfBasicOpts
): boolean {
const P = normP1(publicKey);
const Hm = normP2Hash(message, htfOpts);
@ -298,6 +357,26 @@ export function bls<Fp2, Fp6, Fp12>(
return Fp12.eql(exp, Fp12.ONE);
}
// Checks if pairing of public key & hash is equal to pairing of generator & signature.
// e(S, G) == e(H(m), P)
function verifyShortSignature(
signature: G1Hex,
message: G1Hex,
publicKey: G2Hex,
htfOpts?: htfBasicOpts
): boolean {
const P = normP2(publicKey);
const Hm = normP1Hash(message, htfOpts);
const G = G2.ProjectivePoint.BASE;
const S = normP1(signature);
// Instead of doing 2 exponentiations, we use property of billinear maps
// and do one exp after multiplying 2 points.
const eHmP = pairing(Hm, P, false);
const eSG = pairing(S, G.negate(), false);
const exp = Fp12.finalExponentiate(Fp12.mul(eSG, eHmP));
return Fp12.eql(exp, Fp12.ONE);
}
// Adds a bunch of public key points together.
// pk1 + pk2 + pk3 = pkA
function aggregatePublicKeys(publicKeys: Hex[]): Uint8Array;
@ -328,13 +407,27 @@ export function bls<Fp2, Fp6, Fp12>(
return Signature.toRawBytes(aggAffine);
}
// Adds a bunch of signature points together.
function aggregateShortSignatures(signatures: Hex[]): Uint8Array;
function aggregateShortSignatures(signatures: G1[]): G1;
function aggregateShortSignatures(signatures: G1Hex[]): Uint8Array | G1 {
if (!signatures.length) throw new Error('Expected non-empty array');
const agg = signatures.map(normP1).reduce((sum, s) => sum.add(s), G1.ProjectivePoint.ZERO);
const aggAffine = agg; //.toAffine();
if (signatures[0] instanceof G1.ProjectivePoint) {
aggAffine.assertValidity();
return aggAffine;
}
return ShortSignature.toRawBytes(aggAffine);
}
// https://ethresear.ch/t/fast-verification-of-multiple-bls-signatures/5407
// e(G, S) = e(G, SUM(n)(Si)) = MUL(n)(e(G, Si))
function verifyBatch(
signature: G2Hex,
messages: G2Hex[],
publicKeys: G1Hex[],
htfOpts?: htf.htfBasicOpts
htfOpts?: htfBasicOpts
): boolean {
// @ts-ignore
// console.log('verifyBatch', bytesToHex(signature as any), messages, publicKeys.map(bytesToHex));
@ -370,16 +463,21 @@ export function bls<Fp2, Fp6, Fp12>(
return {
getPublicKey,
getPublicKeyForShortSignatures,
sign,
signShortSignature,
verify,
verifyBatch,
verifyShortSignature,
aggregatePublicKeys,
aggregateSignatures,
aggregateShortSignatures,
millerLoop,
pairing,
G1,
G2,
Signature,
ShortSignature,
fields: {
Fr,
Fp,

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

@ -1,7 +1,8 @@
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
import type { Group, GroupConstructor, AffinePoint } from './curve.js';
import { mod, IField } from './modular.js';
import { bytesToNumberBE, CHash, concatBytes, utf8ToBytes, validateObject } from './utils.js';
import type { CHash } from './utils.js';
import { bytesToNumberBE, abytes, concatBytes, utf8ToBytes, validateObject } from './utils.js';
/**
* * `DST` is a domain separation tag, defined in section 2.2.5
@ -21,12 +22,6 @@ export type Opts = {
hash: CHash;
};
function validateDST(dst: UnicodeOrBytes): Uint8Array {
if (dst instanceof Uint8Array) return dst;
if (typeof dst === 'string') return utf8ToBytes(dst);
throw new Error('DST must be Uint8Array or string');
}
// Octet Stream to Integer. "spec" implementation of os2ip is 2.5x slower vs bytesToNumberBE.
const os2ip = bytesToNumberBE;
@ -51,25 +46,22 @@ function strxor(a: Uint8Array, b: Uint8Array): Uint8Array {
return arr;
}
function isBytes(item: unknown): void {
if (!(item instanceof Uint8Array)) throw new Error('Uint8Array expected');
}
function isNum(item: unknown): void {
function anum(item: unknown): void {
if (!Number.isSafeInteger(item)) throw new Error('number expected');
}
// Produces a uniformly random byte string using a cryptographic hash function H that outputs b bits
// 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(
msg: Uint8Array,
DST: Uint8Array,
lenInBytes: number,
H: CHash
): Uint8Array {
isBytes(msg);
isBytes(DST);
isNum(lenInBytes);
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-16#section-5.3.3
abytes(msg);
abytes(DST);
anum(lenInBytes);
// https://www.rfc-editor.org/rfc/rfc9380#section-5.3.3
if (DST.length > 255) DST = H(concatBytes(utf8ToBytes('H2C-OVERSIZE-DST-'), DST));
const { outputLen: b_in_bytes, blockLen: r_in_bytes } = H;
const ell = Math.ceil(lenInBytes / b_in_bytes);
@ -88,6 +80,11 @@ export function expand_message_xmd(
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(
msg: Uint8Array,
DST: Uint8Array,
@ -95,10 +92,10 @@ export function expand_message_xof(
k: number,
H: CHash
): Uint8Array {
isBytes(msg);
isBytes(DST);
isNum(lenInBytes);
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-16#section-5.3.3
abytes(msg);
abytes(DST);
anum(lenInBytes);
// 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));
if (DST.length > 255) {
const dkLen = Math.ceil((2 * k) / 8);
@ -119,7 +116,7 @@ export function expand_message_xof(
/**
* 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 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
@ -127,16 +124,16 @@ export function expand_message_xof(
*/
export function hash_to_field(msg: Uint8Array, count: number, options: Opts): bigint[][] {
validateObject(options, {
DST: 'string',
DST: 'stringOrUint8Array',
p: 'bigint',
m: 'isSafeInteger',
k: 'isSafeInteger',
hash: 'hash',
});
const { p, k, m, hash, expand, DST: _DST } = options;
isBytes(msg);
isNum(count);
const DST = validateDST(_DST);
abytes(msg);
anum(count);
const DST = typeof _DST === 'string' ? utf8ToBytes(_DST) : _DST;
const log2p = p.toString(2).length;
const L = Math.ceil((log2p + k) / 8); // section 5.1 of ietf draft link above
const len_in_bytes = count * m * L;
@ -201,8 +198,8 @@ export function createHasher<T>(
) {
if (typeof mapToCurve !== 'function') throw new Error('mapToCurve() must be defined');
return {
// Encodes byte string to elliptic curve
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-16#section-3
// Encodes byte string to elliptic curve.
// hash_to_curve from https://www.rfc-editor.org/rfc/rfc9380#section-3
hashToCurve(msg: Uint8Array, options?: htfBasicOpts) {
const u = hash_to_field(msg, 2, { ...def, DST: def.DST, ...options } as Opts);
const u0 = Point.fromAffine(mapToCurve(u[0]));
@ -212,7 +209,8 @@ export function createHasher<T>(
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) {
const u = hash_to_field(msg, 1, { ...def, DST: def.encodeDST, ...options } as Opts);
const P = Point.fromAffine(mapToCurve(u[0])).clearCofactor();

@ -22,10 +22,10 @@ export function mod(a: bigint, b: bigint): bigint {
return result >= _0n ? result : b + result;
}
/**
* Efficiently exponentiate num to power and do modular division.
* Efficiently raise num to power and do modular division.
* Unsafe in some contexts: uses ladder, so can expose bigint bits.
* @example
* powMod(2n, 6n, 11n) // 64n % 11n == 9n
* pow(2n, 6n, 11n) // 64n % 11n == 9n
*/
// TODO: use field version && remove
export function pow(num: bigint, power: bigint, modulo: bigint): bigint {
@ -55,7 +55,7 @@ export function invert(number: bigint, modulo: bigint): bigint {
if (number === _0n || modulo <= _0n) {
throw new Error(`invert: expected positive integers, got n=${number} mod=${modulo}`);
}
// Eucledian GCD https://brilliant.org/wiki/extended-euclidean-algorithm/
// Euclidean GCD https://brilliant.org/wiki/extended-euclidean-algorithm/
// Fermat's little theorem "CT-like" version inv(n) = n^(m-2) mod m is 30x slower.
let a = mod(number, modulo);
let b = modulo;
@ -75,9 +75,14 @@ export function invert(number: bigint, modulo: bigint): bigint {
return mod(x, modulo);
}
// Tonelli-Shanks algorithm
// Paper 1: https://eprint.iacr.org/2012/685.pdf (page 12)
// Paper 2: Square Roots from 1; 24, 51, 10 to Dan Shanks
/**
* Tonelli-Shanks square root search algorithm.
* 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) {
// Legendre constant: used to calculate Legendre symbol (a | 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);
export const isNegativeLE = (num: bigint, modulo: bigint) => (mod(num, modulo) & _1n) === _1n;
// Currently completly inconsistent naming:
// - readable: add, mul, sqr, sqrt, inv, div, pow, eq, sub
// - unreadable mess: addition, multiply, square, squareRoot, inversion, divide, power, equals, subtract
// Field is not always over prime, Fp2 for example has ORDER(q)=p^m
// Field is not always over prime: for example, Fp2 has ORDER(q)=p^m
export interface IField<T> {
ORDER: bigint;
BYTES: number;
@ -232,7 +233,8 @@ export interface IField<T> {
sqrN(num: T): T;
// 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.
isOdd?(num: T): boolean; // Odd instead of even since we have it for Fp2
// legendre?(num: T): T;
@ -264,6 +266,11 @@ export function validateField<T>(field: IField<T>) {
}
// 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 {
// Should have same speed as pow for bigints
// TODO: benchmark!
@ -280,7 +287,10 @@ export function FpPow<T>(f: IField<T>, num: T, power: bigint): T {
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[] {
const tmp = new Array(nums.length);
// 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'>>;
/**
* Initializes a galois field over prime. Non-primes are not supported for now.
* Do not init in loop: slow. Very fragile: always run a benchmark on change.
* Major performance gains:
* a) non-normalized operations like mulN instead of mul
* b) `Object.freeze`
* c) Same object shape: never add or remove keys
* Initializes a finite field over prime. **Non-primes are not supported.**
* Do not init in loop: slow. Very fragile: always run a benchmark on a change.
* Major performance optimizations:
* * a) denormalized operations like mulN instead of mul
* * b) same object shape: never add or remove keys
* * c) Object.freeze
* @param ORDER prime positive bigint
* @param bitLen how many bits the field consumes
* @param isLE (def: false) if encoding / decoding should be in little-endian
@ -340,7 +350,7 @@ export function Field(
isLE = false,
redef: Partial<IField<bigint>> = {}
): 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);
if (BYTES > 2048) throw new Error('Field lengths over 2048 bytes are not supported');
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.
* Can take (n+8) or more bytes of uniform input e.g. from CSPRNG or KDF
* and convert them into private scalar, with the modulo bias being neglible.
* Needs at least 40 bytes of input for 32-byte private key.
* https://research.kudelskisecurity.com/2020/07/28/the-definitive-guide-to-modulo-bias-and-how-to-avoid-it/
* @param hash hash output from SHA3 or a similar function
* @returns valid private scalar
* "Constant-time" private key generation utility.
* Same as mapKeyToField, but accepts less bytes (40 instead of 48 for 32-byte field).
* Which makes it slightly more biased, less secure.
* @deprecated use mapKeyToField instead
*/
export function hashToPrivateScalar(
hash: string | Uint8Array,
@ -425,3 +432,53 @@ export function hashToPrivateScalar(
const num = isLE ? bytesToNumberLE(hash) : bytesToNumberBE(hash);
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);
}

@ -150,17 +150,15 @@ export function montgomery(curveDef: CurveType): CurveFn {
function decodeUCoordinate(uEnc: Hex): bigint {
// Section 5: When receiving such an array, implementations of X25519
// MUST mask the most significant bit in the final byte.
// This is very ugly way, but it works because fieldLen-1 is outside of bounds for X448, so this becomes NOOP
// fieldLen - scalaryBytes = 1 for X448 and = 0 for X25519
const u = ensureBytes('u coordinate', uEnc, montgomeryBytes);
// u[fieldLen-1] crashes QuickJS (TypeError: out-of-bound numeric index)
if (fieldLen === montgomeryBytes) u[fieldLen - 1] &= 127; // 0b0111_1111
if (fieldLen === 32) u[31] &= 127; // 0b0111_1111
return bytesToNumberLE(u);
}
function decodeScalar(n: Hex): bigint {
const bytes = ensureBytes('scalar', n);
if (bytes.length !== montgomeryBytes && bytes.length !== fieldLen)
throw new Error(`Expected ${montgomeryBytes} or ${fieldLen} bytes, got ${bytes.length}`);
const len = bytes.length;
if (len !== montgomeryBytes && len !== fieldLen)
throw new Error(`Expected ${montgomeryBytes} or ${fieldLen} bytes, got ${len}`);
return bytesToNumberLE(adjustScalarBytes(bytes));
}
function scalarMult(scalar: Hex, u: Hex): Uint8Array {

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

@ -1,13 +1,13 @@
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
// 100 lines of code in the file are duplicated from noble-hashes (utils).
// This is OK: `abstract` directory does not use noble-hashes.
// User may opt-in into using different hashing library. This way, noble-hashes
// won't be included into their bundle.
const _0n = BigInt(0);
const _1n = BigInt(1);
const _2n = BigInt(2);
const u8a = (a: any): a is Uint8Array => a instanceof Uint8Array;
// We accept hex strings besides Uint8Array for simplicity
export type Hex = Uint8Array | string;
// Very few implementations accept numbers, we do it to ease learning curve
export type PrivKey = Hex | bigint;
export type Hex = Uint8Array | string; // hex strings are accepted for simplicity
export type PrivKey = Hex | bigint; // bigints are accepted to ease learning curve
export type CHash = {
(message: Uint8Array | string): Uint8Array;
blockLen: number;
@ -16,9 +16,26 @@ export type CHash = {
};
export type FHash = (message: Uint8Array | string) => Uint8Array;
const hexes = Array.from({ length: 256 }, (v, i) => i.toString(16).padStart(2, '0'));
export function isBytes(a: unknown): a is Uint8Array {
return (
a instanceof Uint8Array ||
(a != null && typeof a === 'object' && a.constructor.name === 'Uint8Array')
);
}
export function abytes(item: unknown): void {
if (!isBytes(item)) throw new Error('Uint8Array expected');
}
// Array where index 0xf0 (240) is mapped to string 'f0'
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 {
if (!u8a(bytes)) throw new Error('Uint8Array expected');
abytes(bytes);
// pre-caching improves the speed 6x
let hex = '';
for (let i = 0; i < bytes.length; i++) {
@ -38,36 +55,65 @@ export function hexToNumber(hex: string): bigint {
return BigInt(hex === '' ? '0' : `0x${hex}`);
}
// Caching slows it down 2-3x
// We use optimized technique to convert hex string to byte array
const asciis = { _0: 48, _9: 57, _A: 65, _F: 70, _a: 97, _f: 102 } as const;
function asciiToBase16(char: number): number | undefined {
if (char >= asciis._0 && char <= asciis._9) return char - asciis._0;
if (char >= asciis._A && char <= asciis._F) return char - (asciis._A - 10);
if (char >= asciis._a && char <= asciis._f) return char - (asciis._a - 10);
return;
}
/**
* @example hexToBytes('cafe0123') // Uint8Array.from([0xca, 0xfe, 0x01, 0x23])
*/
export function hexToBytes(hex: string): Uint8Array {
if (typeof hex !== 'string') throw new Error('hex string expected, got ' + typeof hex);
if (hex.length % 2) throw new Error('hex string is invalid: unpadded ' + hex.length);
const array = new Uint8Array(hex.length / 2);
for (let i = 0; i < array.length; i++) {
const j = i * 2;
const hexByte = hex.slice(j, j + 2);
const byte = Number.parseInt(hexByte, 16);
if (Number.isNaN(byte) || byte < 0) throw new Error('invalid byte sequence');
array[i] = byte;
const hl = hex.length;
const al = hl / 2;
if (hl % 2) throw new Error('padded hex string expected, got unpadded hex of length ' + hl);
const array = new Uint8Array(al);
for (let ai = 0, hi = 0; ai < al; ai++, hi += 2) {
const n1 = asciiToBase16(hex.charCodeAt(hi));
const n2 = asciiToBase16(hex.charCodeAt(hi + 1));
if (n1 === undefined || n2 === undefined) {
const char = hex[hi] + hex[hi + 1];
throw new Error('hex string expected, got non-hex character "' + char + '" at index ' + hi);
}
array[ai] = n1 * 16 + n2;
}
return array;
}
// Big Endian
// BE: Big Endian, LE: Little Endian
export function bytesToNumberBE(bytes: Uint8Array): bigint {
return hexToNumber(bytesToHex(bytes));
}
export function bytesToNumberLE(bytes: Uint8Array): bigint {
if (!u8a(bytes)) throw new Error('Uint8Array expected');
abytes(bytes);
return hexToNumber(bytesToHex(Uint8Array.from(bytes).reverse()));
}
export const numberToBytesBE = (n: bigint, len: number) =>
hexToBytes(n.toString(16).padStart(len * 2, '0'));
export const numberToBytesLE = (n: bigint, len: number) => numberToBytesBE(n, len).reverse();
// Returns variable number bytes (minimal bigint encoding?)
export const numberToVarBytesBE = (n: bigint) => hexToBytes(numberToHexUnpadded(n));
export function numberToBytesBE(n: number | bigint, len: number): Uint8Array {
return hexToBytes(n.toString(16).padStart(len * 2, '0'));
}
export function numberToBytesLE(n: number | bigint, len: number): Uint8Array {
return numberToBytesBE(n, len).reverse();
}
// Unpadded, rarely used
export function numberToVarBytesBE(n: number | bigint): Uint8Array {
return hexToBytes(numberToHexUnpadded(n));
}
/**
* Takes hex string or Uint8Array, converts to Uint8Array.
* Validates output length.
* Will throw error for other types.
* @param title descriptive title for an error e.g. 'private key'
* @param hex hex string or Uint8Array
* @param expectedLength optional, will compare to result array's length
* @returns
*/
export function ensureBytes(title: string, hex: Hex, expectedLength?: number): Uint8Array {
let res: Uint8Array;
if (typeof hex === 'string') {
@ -76,7 +122,7 @@ export function ensureBytes(title: string, hex: Hex, expectedLength?: number): U
} catch (e) {
throw new Error(`${title} must be valid hex string, got "${hex}". Cause: ${e}`);
}
} else if (u8a(hex)) {
} else if (isBytes(hex)) {
// Uint8Array.from() instead of hash.slice() because node.js Buffer
// is instance of Uint8Array, and its slice() creates **mutable** copy
res = Uint8Array.from(hex);
@ -89,51 +135,77 @@ export function ensureBytes(title: string, hex: Hex, expectedLength?: number): U
return res;
}
// Copies several Uint8Arrays into one.
export function concatBytes(...arrs: Uint8Array[]): Uint8Array {
const r = new Uint8Array(arrs.reduce((sum, a) => sum + a.length, 0));
let pad = 0; // walk through each item, ensure they have proper type
arrs.forEach((a) => {
if (!u8a(a)) throw new Error('Uint8Array expected');
r.set(a, pad);
/**
* Copies several Uint8Arrays into one.
*/
export function concatBytes(...arrays: Uint8Array[]): Uint8Array {
let sum = 0;
for (let i = 0; i < arrays.length; i++) {
const a = arrays[i];
abytes(a);
sum += a.length;
}
const res = new Uint8Array(sum);
for (let i = 0, pad = 0; i < arrays.length; i++) {
const a = arrays[i];
res.set(a, pad);
pad += a.length;
});
return r;
}
return res;
}
export function equalBytes(b1: Uint8Array, b2: Uint8Array) {
// We don't care about timing attacks here
if (b1.length !== b2.length) return false;
for (let i = 0; i < b1.length; i++) if (b1[i] !== b2[i]) return false;
return true;
// Compares 2 u8a-s in kinda constant time
export function equalBytes(a: Uint8Array, b: Uint8Array) {
if (a.length !== b.length) return false;
let diff = 0;
for (let i = 0; i < a.length; i++) diff |= a[i] ^ b[i];
return diff === 0;
}
// Global symbols in both browsers and Node.js since v11
// See https://github.com/microsoft/TypeScript/issues/31535
declare const TextEncoder: any;
/**
* @example utf8ToBytes('abc') // new Uint8Array([97, 98, 99])
*/
export function utf8ToBytes(str: string): Uint8Array {
if (typeof str !== 'string') {
throw new Error(`utf8ToBytes expected string, got ${typeof str}`);
}
return new TextEncoder().encode(str);
if (typeof str !== 'string') throw new Error(`utf8ToBytes expected string, got ${typeof str}`);
return new Uint8Array(new TextEncoder().encode(str)); // https://bugzil.la/1681809
}
// Bit operations
// Amount of bits inside bigint (Same as n.toString(2).length)
/**
* Calculates amount of bits in a bigint.
* Same as `n.toString(2).length`
*/
export function bitLen(n: bigint) {
let len;
for (len = 0; n > _0n; n >>= _1n, len += 1);
return len;
}
// Gets single bit at position. NOTE: first bit position is 0 (same as arrays)
// Same as !!+Array.from(n.toString(2)).reverse()[pos]
export const bitGet = (n: bigint, pos: number) => (n >> BigInt(pos)) & _1n;
// Sets single bit at position
export const bitSet = (n: bigint, pos: number, value: boolean) =>
n | ((value ? _1n : _0n) << BigInt(pos));
// Return mask for N bits (Same as BigInt(`0b${Array(i).fill('1').join('')}`))
// Not using ** operator with bigints for old engines.
/**
* Gets single bit at position.
* NOTE: first bit position is 0 (same as arrays)
* Same as `!!+Array.from(n.toString(2)).reverse()[pos]`
*/
export function bitGet(n: bigint, pos: number) {
return (n >> BigInt(pos)) & _1n;
}
/**
* Sets single bit at position.
*/
export function bitSet(n: bigint, pos: number, value: boolean) {
return n | ((value ? _1n : _0n) << BigInt(pos));
}
/**
* Calculate mask for N bits. Not using ** operator with bigints because of old engines.
* Same as BigInt(`0b${Array(i).fill('1').join('')}`)
*/
export const bitMask = (n: number) => (_2n << BigInt(n - 1)) - _1n;
// DRBG
@ -205,6 +277,7 @@ const validatorFns = {
function: (val: any) => typeof val === 'function',
boolean: (val: any) => typeof val === 'boolean',
string: (val: any) => typeof val === 'string',
stringOrUint8Array: (val: any) => typeof val === 'string' || isBytes(val),
isSafeInteger: (val: any) => Number.isSafeInteger(val),
array: (val: any) => Array.isArray(val),
field: (val: any, object: any) => (object as any).Fp.isValid(val),

@ -27,7 +27,7 @@ export type BasicWCurve<T> = BasicCurve<T> & {
clearCofactor?: (c: ProjConstructor<T>, point: ProjPointType<T>) => ProjPointType<T>;
};
type Entropy = Hex | true;
type Entropy = Hex | boolean;
export type SignOpts = { lowS?: boolean; extraEntropy?: Entropy; prehash?: boolean };
export type VerOpts = { lowS?: boolean; prehash?: boolean };
@ -123,6 +123,7 @@ function validatePointOpts<T>(curve: CurvePointsType<T>) {
}
export type CurvePointsRes<T> = {
CURVE: ReturnType<typeof validatePointOpts<T>>;
ProjectivePoint: ProjConstructor<T>;
normPrivateKeyToScalar: (key: PrivKey) => bigint;
weierstrassEquation: (x: T) => T;
@ -157,7 +158,7 @@ export const DER = {
// parse DER signature
const { Err: E } = DER;
const data = typeof hex === 'string' ? h2b(hex) : hex;
if (!(data instanceof Uint8Array)) throw new Error('ui8a expected');
ut.abytes(data);
let l = data.length;
if (l < 2 || data[0] != 0x30) throw new E('Invalid signature tag');
if (data[1] !== l - 2) throw new E('Invalid signature: incorrect length');
@ -187,13 +188,13 @@ export const DER = {
// prettier-ignore
const _0n = BigInt(0), _1n = BigInt(1), _2n = BigInt(2), _3n = BigInt(3), _4n = BigInt(4);
export function weierstrassPoints<T>(opts: CurvePointsType<T>) {
export function weierstrassPoints<T>(opts: CurvePointsType<T>): CurvePointsRes<T> {
const CURVE = validatePointOpts(opts);
const { Fp } = CURVE; // All curves has same field / group length as for now, but they can differ
const toBytes =
CURVE.toBytes ||
((c: ProjConstructor<T>, point: ProjPointType<T>, isCompressed: boolean) => {
((_c: ProjConstructor<T>, point: ProjPointType<T>, _isCompressed: boolean) => {
const a = point.toAffine();
return ut.concatBytes(Uint8Array.from([0x04]), Fp.toBytes(a.x), Fp.toBytes(a.y));
});
@ -237,7 +238,7 @@ export function weierstrassPoints<T>(opts: CurvePointsType<T>) {
function normPrivateKeyToScalar(key: PrivKey): bigint {
const { allowedPrivateKeyLengths: lengths, nByteLength, wrapPrivateKey, n } = CURVE;
if (lengths && typeof key !== 'bigint') {
if (key instanceof Uint8Array) key = ut.bytesToHex(key);
if (ut.isBytes(key)) key = ut.bytesToHex(key);
// Normalize to hex string, pad. E.g. P521 would norm 130-132 char hex to 132-char bytes
if (typeof key !== 'string' || !lengths.includes(key.length)) throw new Error('Invalid key');
key = key.padStart(nByteLength * 2, '0');
@ -269,7 +270,11 @@ export function weierstrassPoints<T>(opts: CurvePointsType<T>) {
static readonly BASE = new Point(CURVE.Gx, CURVE.Gy, Fp.ONE);
static readonly ZERO = new Point(Fp.ZERO, Fp.ONE, Fp.ZERO);
constructor(readonly px: T, readonly py: T, readonly pz: T) {
constructor(
readonly px: T,
readonly py: T,
readonly pz: T
) {
if (px == null || !Fp.isValid(px)) throw new Error('x required');
if (py == null || !Fp.isValid(py)) throw new Error('y required');
if (pz == null || !Fp.isValid(pz)) throw new Error('z required');
@ -333,9 +338,11 @@ export function weierstrassPoints<T>(opts: CurvePointsType<T>) {
// A point on curve is valid if it conforms to equation.
assertValidity(): void {
// Zero is valid point too!
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');
}
// Some 3rd-party test vectors require different wording between here & `fromCompressedHex`
@ -618,7 +625,7 @@ export interface SignatureType {
readonly s: bigint;
readonly recovery?: number;
assertValidity(): void;
addRecoveryBit(recovery: number): SignatureType;
addRecoveryBit(recovery: number): RecoveredSignatureType;
hasHighS(): boolean;
normalizeS(): SignatureType;
recoverPublicKey(msgHash: Hex): ProjPointType<bigint>;
@ -628,6 +635,9 @@ export interface SignatureType {
toDERRawBytes(isCompressed?: boolean): Uint8Array;
toDERHex(isCompressed?: boolean): string;
}
export type RecoveredSignatureType = SignatureType & {
readonly recovery: number;
};
// Static methods
export type SignatureConstructor = {
new (r: bigint, s: bigint): SignatureType;
@ -669,7 +679,7 @@ export type CurveFn = {
CURVE: ReturnType<typeof validateOpts>;
getPublicKey: (privateKey: PrivKey, isCompressed?: boolean) => Uint8Array;
getSharedSecret: (privateA: PrivKey, publicB: Hex, isCompressed?: boolean) => Uint8Array;
sign: (msgHash: Hex, privKey: PrivKey, opts?: SignOpts) => SignatureType;
sign: (msgHash: Hex, privKey: PrivKey, opts?: SignOpts) => RecoveredSignatureType;
verify: (signature: Hex | SignatureLike, msgHash: Hex, publicKey: Hex, opts?: VerOpts) => boolean;
ProjectivePoint: ProjConstructor<bigint>;
Signature: SignatureConstructor;
@ -704,7 +714,7 @@ export function weierstrass(curveDef: CurveType): CurveFn {
isWithinCurveOrder,
} = weierstrassPoints({
...CURVE,
toBytes(c, point, isCompressed: boolean): Uint8Array {
toBytes(_c, point, isCompressed: boolean): Uint8Array {
const a = point.toAffine();
const x = Fp.toBytes(a.x);
const cat = ut.concatBytes;
@ -723,7 +733,13 @@ export function weierstrass(curveDef: CurveType): CurveFn {
const x = ut.bytesToNumberBE(tail);
if (!isValidFieldElement(x)) throw new Error('Point is not on curve');
const y2 = weierstrassEquation(x); // y² = x³ + ax + b
let y = Fp.sqrt(y2); // y = y² ^ (p+1)/4
let y: bigint;
try {
y = Fp.sqrt(y2); // y = y² ^ (p+1)/4
} catch (sqrtError) {
const suffix = sqrtError instanceof Error ? ': ' + sqrtError.message : '';
throw new Error('Point is not on curve' + suffix);
}
const isYOdd = (y & _1n) === _1n;
// ECDSA
const isHeadOdd = (head & 1) === 1;
@ -758,7 +774,11 @@ export function weierstrass(curveDef: CurveType): CurveFn {
* ECDSA signature with its (r, s) properties. Supports DER & compact representations.
*/
class Signature implements SignatureType {
constructor(readonly r: bigint, readonly s: bigint, readonly recovery?: number) {
constructor(
readonly r: bigint,
readonly s: bigint,
readonly recovery?: number
) {
this.assertValidity();
}
@ -782,8 +802,8 @@ export function weierstrass(curveDef: CurveType): CurveFn {
if (!isWithinCurveOrder(this.s)) throw new Error('s must be 0 < s < CURVE.n');
}
addRecoveryBit(recovery: number) {
return new Signature(this.r, this.s, recovery);
addRecoveryBit(recovery: number): RecoveredSignature {
return new Signature(this.r, this.s, recovery) as RecoveredSignature;
}
recoverPublicKey(msgHash: Hex): typeof Point.BASE {
@ -828,6 +848,7 @@ export function weierstrass(curveDef: CurveType): CurveFn {
return numToNByteStr(this.r) + numToNByteStr(this.s);
}
}
type RecoveredSignature = Signature & { recovery: number };
const utils = {
isValidPrivateKey(privateKey: PrivKey) {
@ -841,13 +862,12 @@ export function weierstrass(curveDef: CurveType): CurveFn {
normPrivateKeyToScalar: normPrivateKeyToScalar,
/**
* Produces cryptographically secure private key from random of size (nBitLength+64)
* as per FIPS 186 B.4.1 with modulo bias being neglible.
* Produces cryptographically secure private key from random of size
* (groupLen + ceil(groupLen / 2)) with modulo bias being negligible.
*/
randomPrivateKey: (): Uint8Array => {
const rand = CURVE.randomBytes(Fp.BYTES + 8);
const num = mod.hashToPrivateScalar(rand, CURVE_ORDER);
return ut.numberToBytesBE(num, CURVE.nByteLength);
const length = mod.getMinHashLength(CURVE.n);
return mod.mapHashToField(CURVE.randomBytes(length), CURVE.n);
},
/**
@ -879,7 +899,7 @@ export function weierstrass(curveDef: CurveType): CurveFn {
* Quick and dirty check for item being public key. Does not validate hex, or being on-curve.
*/
function isProbPub(item: PrivKey | PubKey): boolean {
const arr = item instanceof Uint8Array;
const arr = ut.isBytes(item);
const str = typeof item === 'string';
const len = (arr || str) && (item as Hex).length;
if (arr) return len === compressedLen || len === uncompressedLen;
@ -957,15 +977,15 @@ export function weierstrass(curveDef: CurveType): CurveFn {
const d = normPrivateKeyToScalar(privateKey); // validate private key, convert to bigint
const seedArgs = [int2octets(d), int2octets(h1int)];
// extraEntropy. RFC6979 3.6: additional k' (optional).
if (ent != null) {
if (ent != null && ent !== false) {
// 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
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 m = h1int; // NOTE: no need to call bits2int second time here, it is inside truncateHash!
// Converts signature params into point w r/s, checks result for validity.
function k2sig(kBytes: Uint8Array): Signature | undefined {
function k2sig(kBytes: Uint8Array): RecoveredSignature | undefined {
// RFC 6979 Section 3.2, step 3: k = bits2int(T)
const k = bits2int(kBytes); // Cannot use fields methods, since it is group element
if (!isWithinCurveOrder(k)) return; // Important: all mod() calls here must be done over N
@ -984,7 +1004,7 @@ export function weierstrass(curveDef: CurveType): CurveFn {
normS = normalizeS(s); // if lowS was passed, ensure s is always
recovery ^= 1; // // in the bottom half of N
}
return new Signature(r, normS, recovery); // use normS, not s
return new Signature(r, normS, recovery) as RecoveredSignature; // use normS, not s
}
return { seed, k2sig };
}
@ -992,18 +1012,22 @@ export function weierstrass(curveDef: CurveType): CurveFn {
const defaultVerOpts: VerOpts = { lowS: CURVE.lowS, prehash: false };
/**
* Signs message hash (not message: you need to hash it by yourself).
* Signs message hash with a private key.
* ```
* sign(m, d, k) where
* (x, y) = G × k
* r = x mod n
* s = (m + dr)/k mod n
* ```
* @param opts `lowS, extraEntropy, prehash`
* @param msgHash NOT message. msg needs to be hashed to `msgHash`, or use `prehash`.
* @param privKey private key
* @param opts lowS for non-malleable sigs. extraEntropy for mixing randomness into k. prehash will hash first arg.
* @returns signature with recovery param
*/
function sign(msgHash: Hex, privKey: PrivKey, opts = defaultSigOpts): Signature {
function sign(msgHash: Hex, privKey: PrivKey, opts = defaultSigOpts): RecoveredSignature {
const { seed, k2sig } = prepSig(msgHash, privKey, opts); // Steps A, D of RFC6979 3.2.
const drbg = ut.createHmacDrbg<Signature>(CURVE.hash.outputLen, CURVE.nByteLength, CURVE.hmac);
const C = CURVE;
const drbg = ut.createHmacDrbg<RecoveredSignature>(C.hash.outputLen, C.nByteLength, C.hmac);
return drbg(seed, k2sig); // Steps B, C, D, E, F, G
}
@ -1039,7 +1063,7 @@ export function weierstrass(curveDef: CurveType): CurveFn {
let _sig: Signature | undefined = undefined;
let P: ProjPointType<bigint>;
try {
if (typeof sg === 'string' || sg instanceof Uint8Array) {
if (typeof sg === 'string' || ut.isBytes(sg)) {
// Signature can be represented in 2 ways: compact (2*nByteLength) & DER (variable-length).
// Since DER can also be 2*nByteLength bytes, we check for it first.
try {
@ -1084,20 +1108,29 @@ export function weierstrass(curveDef: CurveType): CurveFn {
};
}
// Implementation of the Shallue and van de Woestijne method for any Weierstrass curve
// TODO: check if there is a way to merge this with uvRatio in Edwards && move to modular?
// b = True and y = sqrt(u / v) if (u / v) is square in F, and
// b = False and y = sqrt(Z * (u / v)) otherwise.
/**
* Implementation of the Shallue and van de Woestijne method for any weierstrass curve.
* TODO: check if there is a way to merge this with uvRatio in Edwards; move to modular.
* b = True and y = sqrt(u / v) if (u / v) is square in F, and
* b = False and y = sqrt(Z * (u / v)) otherwise.
* @param Fp
* @param Z
* @returns
*/
export function SWUFpSqrtRatio<T>(Fp: mod.IField<T>, Z: T) {
// Generic implementation
const q = Fp.ORDER;
let l = _0n;
for (let o = q - _1n; o % _2n === _0n; o /= _2n) l += _1n;
const c1 = l; // 1. c1, the largest integer such that 2^c1 divides q - 1.
const c2 = (q - _1n) / _2n ** c1; // 2. c2 = (q - 1) / (2^c1) # Integer arithmetic
// We need 2n ** c1 and 2n ** (c1-1). We can't use **; but we can use <<.
// 2n ** c1 == 2n << (c1-1)
const _2n_pow_c1_1 = _2n << (c1 - _1n - _1n);
const _2n_pow_c1 = _2n_pow_c1_1 * _2n;
const c2 = (q - _1n) / _2n_pow_c1; // 2. c2 = (q - 1) / (2^c1) # Integer arithmetic
const c3 = (c2 - _1n) / _2n; // 3. c3 = (c2 - 1) / 2 # Integer arithmetic
const c4 = _2n ** c1 - _1n; // 4. c4 = 2^c1 - 1 # Integer arithmetic
const c5 = _2n ** (c1 - _1n); // 5. c5 = 2^(c1 - 1) # Integer arithmetic
const c4 = _2n_pow_c1 - _1n; // 4. c4 = 2^c1 - 1 # Integer arithmetic
const c5 = _2n_pow_c1_1; // 5. c5 = 2^(c1 - 1) # Integer arithmetic
const c6 = Fp.pow(Z, c2); // 6. c6 = Z^c2
const c7 = Fp.pow(Z, (c2 + _1n) / _2n); // 7. c7 = Z^((c2 + 1) / 2)
let sqrtRatio = (u: T, v: T): { isValid: boolean; value: T } => {
@ -1119,7 +1152,8 @@ export function SWUFpSqrtRatio<T>(Fp: mod.IField<T>, Z: T) {
tv4 = Fp.cmov(tv5, tv4, isQR); // 16. tv4 = CMOV(tv5, tv4, isQR)
// 17. for i in (c1, c1 - 1, ..., 2):
for (let i = c1; i > _1n; i--) {
let tv5 = _2n ** (i - _2n); // 18. tv5 = i - 2; 19. tv5 = 2^tv5
let tv5 = i - _2n; // 18. tv5 = i - 2
tv5 = _2n << (tv5 - _1n); // 19. tv5 = 2^tv5
let tvv5 = Fp.pow(tv4, tv5); // 20. tv5 = tv4^tv5
const e1 = Fp.eql(tvv5, Fp.ONE); // 21. e1 = tv5 == 1
tv2 = Fp.mul(tv3, tv1); // 22. tv2 = tv3 * tv1
@ -1151,7 +1185,10 @@ export function SWUFpSqrtRatio<T>(Fp: mod.IField<T>, Z: T) {
// if (Fp.ORDER % _8n === _5n) // sqrt_ratio_5mod8
return sqrtRatio;
}
// From draft-irtf-cfrg-hash-to-curve-16
/**
* Simplified Shallue-van de Woestijne-Ulas Method
* https://www.rfc-editor.org/rfc/rfc9380#section-6.6.2
*/
export function mapToCurveSimpleSWU<T>(
Fp: mod.IField<T>,
opts: {

@ -1,15 +1,9 @@
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
// bls12-381 pairing-friendly Barreto-Lynn-Scott elliptic curve construction allows to:
// - Construct zk-SNARKs at the 128-bit security
// - Use threshold signatures, which allows a user to sign lots of messages with one signature and
// verify them swiftly in a batch, using Boneh-Lynn-Shacham signature scheme.
//
// The library uses G1 for public keys and G2 for signatures. Support for G1 signatures is planned.
// Compatible with Algorand, Chia, Dfinity, Ethereum, FIL, Zcash. Matches specs
// [pairing-curves-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),
// [hash-to-curve-12](https://tools.ietf.org/html/draft-irtf-cfrg-hash-to-curve-12).
// bls12-381 is pairing-friendly Barreto-Lynn-Scott elliptic curve construction allowing to:
// - Construct zk-SNARKs at the 120-bit security
// - Efficiently verify N aggregate signatures with 1 pairing and N ec additions:
// the Boneh-Lynn-Shacham signature scheme is orders of magnitude more efficient than Schnorr
//
// ### Summary
// 1. BLS Relies on Bilinear Pairing (expensive)
@ -25,8 +19,17 @@
// - `S = pk x H(m)` - signing
// - `e(P, H(m)) == e(G, S)` - verification using pairings
// - `e(G, S) = e(G, SUM(n)(Si)) = MUL(n)(e(G, Si))` - signature aggregation
// Filecoin uses little endian byte arrays for private keys -
// so ensure to reverse byte order if you'll use it with FIL.
//
// ### Compatibility and notes
// 1. It is compatible with Algorand, Chia, Dfinity, Ethereum, Filecoin, ZEC
// Filecoin uses little endian byte arrays for private keys - make sure to reverse byte order.
// 2. Some projects use G2 for public keys and G1 for signatures. It's called "short signature"
// 3. Curve security level is about 120 bits as per Barbulescu-Duquesne 2017
// https://hal.science/hal-01534101/file/main.pdf
// 4. Compatible with specs:
// [cfrg-pairing-friendly-curves-11](https://tools.ietf.org/html/draft-irtf-cfrg-pairing-friendly-curves-11),
// [cfrg-bls-signature-05](https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-bls-signature-05),
// [RFC 9380](https://www.rfc-editor.org/rfc/rfc9380).
import { sha256 } from '@noble/hashes/sha256';
import { randomBytes } from '@noble/hashes/utils';
import { bls, CurveFn } from './abstract/bls.js';
@ -37,7 +40,6 @@ import {
numberToBytesBE,
bytesToNumberBE,
bitLen,
bitSet,
bitGet,
Hex,
bitMask,
@ -60,11 +62,10 @@ const _8n = BigInt(8), _16n = BigInt(16);
// CURVE FIELDS
// Finite field over p.
const Fp = mod.Field(
BigInt(
'0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab'
)
const Fp_raw = BigInt(
'0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab'
);
const Fp = mod.Field(Fp_raw);
type Fp = bigint;
// Finite field over r.
// This particular field is not used anywhere in bls12-381, but it is still useful.
@ -110,10 +111,7 @@ type Fp2Utils = {
// G² - 1
// h2q
// NOTE: ORDER was wrong!
const FP2_ORDER =
BigInt(
'0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab'
) ** _2n;
const FP2_ORDER = Fp_raw * Fp_raw;
const Fp2: mod.IField<Fp2> & Fp2Utils = {
ORDER: FP2_ORDER,
@ -181,7 +179,7 @@ const Fp2: mod.IField<Fp2> & Fp2Utils = {
if (im1 > im2 || (im1 === im2 && re1 > re2)) return x1;
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) => {
const { re: x0, im: x1 } = Fp2.reim(x);
const sign_0 = x0 % _2n;
@ -784,8 +782,7 @@ const FP12_FROBENIUS_COEFFICIENTS = [
// HashToCurve
// 3-isogeny map from E' to E
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#appendix-E.3
// 3-isogeny map from E' to E https://www.rfc-editor.org/rfc/rfc9380#appendix-E.3
const isogenyMapG2 = isogenyMap(
Fp2,
[
@ -989,7 +986,7 @@ function G2psi2(c: ProjConstructor<Fp2>, P: ProjPointType<Fp2>) {
//
// Parameter definitions are in section 5.3 of the spec unless otherwise noted.
// 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)
// p = 0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab
@ -1021,11 +1018,41 @@ const htfDefaults = Object.freeze({
// Encoding utils
// Point on G1 curve: (x, y)
const C_BIT_POS = Fp.BITS; // C_bit, compression bit for serialization flag
const I_BIT_POS = Fp.BITS + 1; // I_bit, point-at-infinity bit for serialization flag
const S_BIT_POS = Fp.BITS + 2; // S_bit, sign bit for serialization flag
// Compressed point of infinity
const COMPRESSED_ZERO = Fp.toBytes(bitSet(bitSet(_0n, I_BIT_POS, true), S_BIT_POS, true)); // set compressed & point-at-infinity bits
const COMPRESSED_ZERO = setMask(Fp.toBytes(_0n), { infinity: true, compressed: true }); // set compressed & point-at-infinity bits
function parseMask(bytes: Uint8Array) {
// Copy, so we can remove mask data. It will be removed also later, when Fp.create will call modulo.
bytes = bytes.slice();
const mask = bytes[0] & 0b1110_0000;
const compressed = !!((mask >> 7) & 1); // compression bit (0b1000_0000)
const infinity = !!((mask >> 6) & 1); // point at infinity bit (0b0100_0000)
const sort = !!((mask >> 5) & 1); // sort bit (0b0010_0000)
bytes[0] &= 0b0001_1111; // clear mask (zero first 3 bits)
return { compressed, infinity, sort, value: bytes };
}
function setMask(
bytes: Uint8Array,
mask: { compressed?: boolean; infinity?: boolean; sort?: boolean }
) {
if (bytes[0] & 0b1110_0000) throw new Error('setMask: non-empty mask');
if (mask.compressed) bytes[0] |= 0b1000_0000;
if (mask.infinity) bytes[0] |= 0b0100_0000;
if (mask.sort) bytes[0] |= 0b0010_0000;
return bytes;
}
function signatureG1ToRawBytes(point: ProjPointType<Fp>) {
point.assertValidity();
const isZero = point.equals(bls12_381.G1.ProjectivePoint.ZERO);
const { x, y } = point.toAffine();
if (isZero) return COMPRESSED_ZERO.slice();
const P = Fp.ORDER;
const sort = Boolean((y * _2n) / P);
return setMask(numberToBytesBE(x, Fp.BYTES), { compressed: true, sort });
}
function signatureG2ToRawBytes(point: ProjPointType<Fp2>) {
// NOTE: by some reasons it was missed in bls12-381, looks like bug
@ -1037,14 +1064,16 @@ function signatureG2ToRawBytes(point: ProjPointType<Fp2>) {
const { re: x0, im: x1 } = Fp2.reim(x);
const { re: y0, im: y1 } = Fp2.reim(y);
const tmp = y1 > _0n ? y1 * _2n : y0 * _2n;
const aflag1 = Boolean((tmp / Fp.ORDER) & _1n);
const z1 = bitSet(bitSet(x1, 381, aflag1), S_BIT_POS, true);
const sort = Boolean((tmp / Fp.ORDER) & _1n);
const z2 = x0;
return concatB(numberToBytesBE(z1, len), numberToBytesBE(z2, len));
return concatB(
setMask(numberToBytesBE(x1, len), { sort, compressed: true }),
numberToBytesBE(z2, len)
);
}
// 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.
// More complicated math is done over polynominal extension fields.
// To simplify calculations in Fp12, we construct extension tower:
@ -1079,7 +1108,7 @@ export const bls12_381: CurveFn<Fp, Fp2, Fp6, Fp12> = bls({
),
a: Fp.ZERO,
b: _4n,
htfDefaults: { ...htfDefaults, m: 1 },
htfDefaults: { ...htfDefaults, m: 1, DST: 'BLS_SIG_BLS12381G1_XMD:SHA-256_SSWU_RO_NUL_' },
wrapPrivateKey: true,
allowInfinityPoint: true,
// Checks is the point resides in prime-order subgroup.
@ -1112,7 +1141,7 @@ export const bls12_381: CurveFn<Fp, Fp2, Fp6, Fp12> = bls({
},
// Clear cofactor of G1
// https://eprint.iacr.org/2019/403
clearCofactor: (c, point) => {
clearCofactor: (_c, point) => {
// return this.multiplyUnsafe(CURVE.h);
return point.multiplyUnsafe(bls12_381.params.x).add(point); // x*P + P
},
@ -1121,26 +1150,30 @@ export const bls12_381: CurveFn<Fp, Fp2, Fp6, Fp12> = bls({
return isogenyMapG1(x, y);
},
fromBytes: (bytes: Uint8Array): AffinePoint<Fp> => {
bytes = bytes.slice();
if (bytes.length === 48) {
const { compressed, infinity, sort, value } = parseMask(bytes);
if (value.length === 48 && compressed) {
// TODO: Fp.bytes
const P = Fp.ORDER;
const compressedValue = bytesToNumberBE(bytes);
const bflag = bitGet(compressedValue, I_BIT_POS);
const compressedValue = bytesToNumberBE(value);
// Zero
if (bflag === _1n) return { x: _0n, y: _0n };
const x = Fp.create(compressedValue & Fp.MASK);
if (infinity) {
if (x !== _0n) throw new Error('G1: non-empty compressed point at infinity');
return { x: _0n, y: _0n };
}
const right = Fp.add(Fp.pow(x, _3n), Fp.create(bls12_381.params.G1b)); // y² = x³ + b
let y = Fp.sqrt(right);
if (!y) throw new Error('Invalid compressed G1 point');
const aflag = bitGet(compressedValue, C_BIT_POS);
if ((y * _2n) / P !== aflag) y = Fp.neg(y);
if ((y * _2n) / P !== BigInt(sort)) y = Fp.neg(y);
return { x: Fp.create(x), y: Fp.create(y) };
} else if (bytes.length === 96) {
} else if (value.length === 96 && !compressed) {
// Check if the infinity flag is set
if ((bytes[0] & (1 << 6)) !== 0) return bls12_381.G1.ProjectivePoint.ZERO.toAffine();
const x = bytesToNumberBE(bytes.subarray(0, Fp.BYTES));
const y = bytesToNumberBE(bytes.subarray(Fp.BYTES));
const x = bytesToNumberBE(value.subarray(0, Fp.BYTES));
const y = bytesToNumberBE(value.subarray(Fp.BYTES));
if (infinity) {
if (x !== _0n || y !== _0n) throw new Error('G1: non-empty point at infinity');
return bls12_381.G1.ProjectivePoint.ZERO.toAffine();
}
return { x: Fp.create(x), y: Fp.create(y) };
} else {
throw new Error('Invalid point G1, expected 48/96 bytes');
@ -1152,10 +1185,8 @@ export const bls12_381: CurveFn<Fp, Fp2, Fp6, Fp12> = bls({
if (isCompressed) {
if (isZero) return COMPRESSED_ZERO.slice();
const P = Fp.ORDER;
let num;
num = bitSet(x, C_BIT_POS, Boolean((y * _2n) / P)); // set aflag
num = bitSet(num, S_BIT_POS, true);
return numberToBytesBE(num, Fp.BYTES);
const sort = Boolean((y * _2n) / P);
return setMask(numberToBytesBE(x, Fp.BYTES), { compressed: true, sort });
} else {
if (isZero) {
// 2x PUBLIC_KEY_LENGTH
@ -1166,6 +1197,30 @@ export const bls12_381: CurveFn<Fp, Fp2, Fp6, Fp12> = bls({
}
}
},
ShortSignature: {
fromHex(hex: Hex): ProjPointType<Fp> {
const { infinity, sort, value } = parseMask(ensureBytes('signatureHex', hex, 48));
const P = Fp.ORDER;
const compressedValue = bytesToNumberBE(value);
// Zero
if (infinity) return bls12_381.G1.ProjectivePoint.ZERO;
const x = Fp.create(compressedValue & Fp.MASK);
const right = Fp.add(Fp.pow(x, _3n), Fp.create(bls12_381.params.G1b)); // y² = x³ + b
let y = Fp.sqrt(right);
if (!y) throw new Error('Invalid compressed G1 point');
const aflag = BigInt(sort);
if ((y * _2n) / P !== aflag) y = Fp.neg(y);
const point = bls12_381.G1.ProjectivePoint.fromAffine({ x, y });
point.assertValidity();
return point;
},
toRawBytes(point: ProjPointType<Fp>) {
return signatureG1ToRawBytes(point);
},
toHex(point: ProjPointType<Fp>) {
return bytesToHex(signatureG1ToRawBytes(point));
},
},
},
// G2 is the order-q subgroup of E2(Fp²) : y² = x³+4(1+√1),
// where Fp2 is Fp[√1]/(x2+1). #E2(Fp2 ) = h2q, where
@ -1197,7 +1252,7 @@ export const bls12_381: CurveFn<Fp, Fp2, Fp6, Fp12> = bls({
),
]),
a: Fp2.ZERO,
b: Fp2.fromBigTuple([4n, _4n]),
b: Fp2.fromBigTuple([_4n, _4n]),
hEff: BigInt(
'0xbc69f08f2ee75b3584c6a0ea91b352888e2a8e9145ad7689986ff031508ffe1329c2f178731db956d82bf015d1212b02ec0ec69d7477c1ae954cbc06689f6a359894c0adebbf6b4e8020005aaa95551'
),
@ -1237,45 +1292,45 @@ export const bls12_381: CurveFn<Fp, Fp2, Fp6, Fp12> = bls({
return Q; // [x²-x-1]P + [x-1]Ψ(P) + Ψ²(2P)
},
fromBytes: (bytes: Uint8Array): AffinePoint<Fp2> => {
bytes = bytes.slice();
const m_byte = bytes[0] & 0xe0;
if (m_byte === 0x20 || m_byte === 0x60 || m_byte === 0xe0) {
throw new Error('Invalid encoding flag: ' + m_byte);
const { compressed, infinity, sort, value } = parseMask(bytes);
if (
(!compressed && !infinity && sort) || // 00100000
(!compressed && infinity && sort) || // 01100000
(sort && infinity && compressed) // 11100000
) {
throw new Error('Invalid encoding flag: ' + (bytes[0] & 0b1110_0000));
}
const bitC = m_byte & 0x80; // compression bit
const bitI = m_byte & 0x40; // point at infinity bit
const bitS = m_byte & 0x20; // sign bit
const L = Fp.BYTES;
const slc = (b: Uint8Array, from: number, to?: number) => bytesToNumberBE(b.slice(from, to));
if (bytes.length === 96 && bitC) {
if (value.length === 96 && compressed) {
const b = bls12_381.params.G2b;
const P = Fp.ORDER;
bytes[0] = bytes[0] & 0x1f; // clear flags
if (bitI) {
if (infinity) {
// check that all bytes are 0
if (bytes.reduce((p, c) => (p !== 0 ? c + 1 : c), 0) > 0) {
if (value.reduce((p, c) => (p !== 0 ? c + 1 : c), 0) > 0) {
throw new Error('Invalid compressed G2 point');
}
return { x: Fp2.ZERO, y: Fp2.ZERO };
}
const x_1 = slc(bytes, 0, L);
const x_0 = slc(bytes, L, 2 * L);
const x_1 = slc(value, 0, L);
const x_0 = slc(value, L, 2 * L);
const x = Fp2.create({ c0: Fp.create(x_0), c1: Fp.create(x_1) });
const right = Fp2.add(Fp2.pow(x, _3n), b); // y² = x³ + 4 * (u+1) = x³ + b
let y = Fp2.sqrt(right);
const Y_bit = y.c1 === _0n ? (y.c0 * _2n) / P : (y.c1 * _2n) / P ? _1n : _0n;
y = bitS > 0 && Y_bit > 0 ? y : Fp2.neg(y);
y = sort && Y_bit > 0 ? y : Fp2.neg(y);
return { x, y };
} else if (bytes.length === 192 && !bitC) {
// Check if the infinity flag is set
if ((bytes[0] & (1 << 6)) !== 0) {
} else if (value.length === 192 && !compressed) {
if (infinity) {
if (value.reduce((p, c) => (p !== 0 ? c + 1 : c), 0) > 0) {
throw new Error('Invalid uncompressed G2 point');
}
return { x: Fp2.ZERO, y: Fp2.ZERO };
}
const x1 = slc(bytes, 0, L);
const x0 = slc(bytes, L, 2 * L);
const y1 = slc(bytes, 2 * L, 3 * L);
const y0 = slc(bytes, 3 * L, 4 * L);
const x1 = slc(value, 0, L);
const x0 = slc(value, L, 2 * L);
const y1 = slc(value, 2 * L, 3 * L);
const y0 = slc(value, 3 * L, 4 * L);
return { x: Fp2.fromBigTuple([x0, x1]), y: Fp2.fromBigTuple([y0, y1]) };
} else {
throw new Error('Invalid point G2, expected 96/192 bytes');
@ -1288,10 +1343,10 @@ export const bls12_381: CurveFn<Fp, Fp2, Fp6, Fp12> = bls({
if (isCompressed) {
if (isZero) return concatB(COMPRESSED_ZERO, numberToBytesBE(_0n, len));
const flag = Boolean(y.c1 === _0n ? (y.c0 * _2n) / P : (y.c1 * _2n) / P);
// set compressed & sign bits (looks like different offsets than for G1/Fp?)
let x_1 = bitSet(x.c1, C_BIT_POS, flag);
x_1 = bitSet(x_1, S_BIT_POS, true);
return concatB(numberToBytesBE(x_1, len), numberToBytesBE(x.c0, len));
return concatB(
setMask(numberToBytesBE(x.c1, len), { compressed: true, sort: flag }),
numberToBytesBE(x.c0, len)
);
} else {
if (isZero) return concatB(new Uint8Array([0x40]), new Uint8Array(4 * len - 1)); // bytes[0] |= 1 << 6;
const { re: x0, im: x1 } = Fp2.reim(x);
@ -1307,17 +1362,15 @@ export const bls12_381: CurveFn<Fp, Fp2, Fp6, Fp12> = bls({
Signature: {
// TODO: Optimize, it's very slow because of sqrt.
fromHex(hex: Hex): ProjPointType<Fp2> {
hex = ensureBytes('signatureHex', hex);
const { infinity, sort, value } = parseMask(ensureBytes('signatureHex', hex));
const P = Fp.ORDER;
const half = hex.length / 2;
const half = value.length / 2;
if (half !== 48 && half !== 96)
throw new Error('Invalid compressed signature length, must be 96 or 192');
const z1 = bytesToNumberBE(hex.slice(0, half));
const z2 = bytesToNumberBE(hex.slice(half));
const z1 = bytesToNumberBE(value.slice(0, half));
const z2 = bytesToNumberBE(value.slice(half));
// Indicates the infinity point
const bflag1 = bitGet(z1, I_BIT_POS);
if (bflag1 === _1n) return bls12_381.G2.ProjectivePoint.ZERO;
if (infinity) return bls12_381.G2.ProjectivePoint.ZERO;
const x1 = Fp.create(z1 & Fp.MASK);
const x2 = Fp.create(z2);
const x = Fp2.create({ c0: x2, c1: x1 });
@ -1329,7 +1382,7 @@ export const bls12_381: CurveFn<Fp, Fp2, Fp6, Fp12> = bls({
// Choose the y whose leftmost bit of the imaginary part is equal to the a_flag1
// If y1 happens to be zero, then use the bit of y0
const { re: y0, im: y1 } = Fp2.reim(y);
const aflag1 = bitGet(z1, 381);
const aflag1 = BigInt(sort);
const isGreater = y1 > _0n && (y1 * _2n) / P !== aflag1;
const isZero = y1 === _0n && (y0 * _2n) / P !== aflag1;
if (isGreater || isZero) y = Fp2.neg(y);

@ -6,8 +6,9 @@ import { Field } from './abstract/modular.js';
/**
* bn254 pairing-friendly curve.
* Previously known as alt_bn_128, when it had 128-bit security.
* Recent research shown it's weaker, the naming has been adjusted to its prime bit count.
* https://github.com/zcash/zcash/issues/2502
* Barbulescu-Duquesne 2017 shown it's weaker: just about 100 bits,
* so the naming has been adjusted to its prime bit count
* https://hal.science/hal-01534101/file/main.pdf
*/
export const bn254 = weierstrass({
a: BigInt(0),

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

@ -1,14 +1,25 @@
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
import { shake256 } from '@noble/hashes/sha3';
import { concatBytes, randomBytes, utf8ToBytes, wrapConstructor } from '@noble/hashes/utils';
import { twistedEdwards } from './abstract/edwards.js';
import { mod, pow2, Field } from './abstract/modular.js';
import { ExtPointType, twistedEdwards } from './abstract/edwards.js';
import { mod, pow2, Field, isNegativeLE } from './abstract/modular.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, Group } from './abstract/curve.js';
/**
* 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
*/
@ -18,15 +29,16 @@ const ed448P = BigInt(
'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.
// Used for efficient square root calculation.
// ((P-3)/4).toString(2) would produce bits [223x 1, 0, 222x 1]
function ed448_pow_Pminus3div4(x: bigint): bigint {
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 b3 = (b2 * b2 * x) % P;
const b6 = (pow2(b3, _3n, P) * b3) % P;
@ -53,8 +65,29 @@ function adjustScalarBytes(bytes: Uint8Array): Uint8Array {
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 _4n = BigInt(4);
const ED448_DEF = {
// Param: a
@ -63,13 +96,14 @@ const ED448_DEF = {
d: BigInt(
'726838724295606890549323807888004534353641360687318060281490199180612328166730772686396383698676545930088884461843637361053498018326358'
),
// Finite field 𝔽p over which we'll do calculations; 2n ** 448n - 2n ** 224n - 1n
// Finite field 𝔽p over which we'll do calculations; 2n**448n - 2n**224n - 1n
Fp,
// Subgroup order: how many points curve has;
// 2n**446n - 13818066809895115352007386748515426880336692474882178609894547503885n
n: BigInt(
'181709681073901722637330951972001133588410340171829515070372549795146003961539585716195755291692375963310293709091662304773755859649779'
),
// RFC 7748 has 56-byte keys, RFC 8032 has 57-byte keys
nBitLength: 456,
// Cofactor
h: BigInt(4),
@ -94,49 +128,30 @@ const ED448_DEF = {
data
);
},
// 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 };
},
uvRatio,
} 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
export const ed448ph = twistedEdwards({ ...ED448_DEF, prehash: shake256_64 });
export const ed448ph = /* @__PURE__ */ twistedEdwards({ ...ED448_DEF, prehash: shake256_64 });
export const x448 = montgomery({
a: BigInt(156326),
montgomeryBits: 448,
nByteLength: 57,
P: ed448P,
Gu: BigInt(5),
powPminus2: (x: bigint): bigint => {
const P = ed448P;
const Pminus3div4 = ed448_pow_Pminus3div4(x);
const Pminus3 = pow2(Pminus3div4, BigInt(2), P);
return mod(Pminus3 * x, P); // Pminus3 * x = Pminus2
},
adjustScalarBytes,
randomBytes,
});
export const x448 = /* @__PURE__ */ (() =>
montgomery({
a: BigInt(156326),
// RFC 7748 has 56-byte keys, RFC 8032 has 57-byte keys
montgomeryBits: 448,
nByteLength: 56,
P: ed448P,
Gu: BigInt(5),
powPminus2: (x: bigint): bigint => {
const P = ed448P;
const Pminus3div4 = ed448_pow_Pminus3div4(x);
const Pminus3 = pow2(Pminus3div4, BigInt(2), P);
return mod(Pminus3 * x, P); // Pminus3 * x = Pminus2
},
adjustScalarBytes,
randomBytes,
}))();
/**
* Converts edwards448 public key to x448 public key. Uses formula:
@ -146,15 +161,19 @@ export const x448 = montgomery({
* const aPub = ed448.getPublicKey(utils.randomPrivateKey());
* x448.getSharedSecret(edwardsToMontgomery(aPub), edwardsToMontgomery(someonesPub))
*/
export function edwardsToMontgomery(edwardsPub: string | Uint8Array): Uint8Array {
export function edwardsToMontgomeryPub(edwardsPub: string | Uint8Array): Uint8Array {
const { y } = ed448.ExtendedPoint.fromHex(edwardsPub);
const _1n = BigInt(1);
return Fp.toBytes(Fp.create((y - _1n) * Fp.inv(y + _1n)));
}
export const edwardsToMontgomery = edwardsToMontgomeryPub; // deprecated
// TODO: add edwardsToMontgomeryPriv, similar to ed25519 version
// Hash To Curve Elligator2 Map
const ELL2_C1 = (Fp.ORDER - BigInt(3)) / BigInt(4); // 1. c1 = (q - 3) / 4 # Integer arithmetic
const ELL2_J = BigInt(156326);
function map_to_curve_elligator2_curve448(u: bigint) {
let tv1 = Fp.sqr(u); // 1. tv1 = u^2
let e1 = Fp.eql(tv1, Fp.ONE); // 2. e1 = tv1 == 1
@ -184,6 +203,7 @@ function map_to_curve_elligator2_curve448(u: bigint) {
y = Fp.cmov(y, Fp.neg(y), e2 !== e3); // 26. y = CMOV(y, -y, e2 XOR e3)
return { xn, xd, yn: y, yd: Fp.ONE }; // 27. return (xn, xd, y, 1)
}
function map_to_curve_elligator2_edwards448(u: bigint) {
let { xn, xd, yn, yd } = map_to_curve_elligator2_curve448(u); // 1. (xn, xd, yn, yd) = map_to_curve_elligator2_curve448(u)
let xn2 = Fp.sqr(xn); // 2. xn2 = xn^2
@ -227,17 +247,234 @@ function map_to_curve_elligator2_edwards448(u: bigint) {
return { x: Fp.mul(xEn, inv[0]), y: Fp.mul(yEn, inv[1]) }; // 38. return (xEn, xEd, yEn, yEd)
}
const { hashToCurve, encodeToCurve } = htf.createHasher(
ed448.ExtendedPoint,
(scalars: bigint[]) => map_to_curve_elligator2_edwards448(scalars[0]),
{
DST: 'edwards448_XOF:SHAKE256_ELL2_RO_',
encodeDST: 'edwards448_XOF:SHAKE256_ELL2_NU_',
p: Fp.ORDER,
m: 1,
k: 224,
expand: 'xof',
hash: shake256,
}
const htf = /* @__PURE__ */ (() =>
createHasher(
ed448.ExtendedPoint,
(scalars: bigint[]) => map_to_curve_elligator2_edwards448(scalars[0]),
{
DST: 'edwards448_XOF:SHAKE256_ELL2_RO_',
encodeDST: 'edwards448_XOF:SHAKE256_ELL2_NU_',
p: Fp.ORDER,
m: 1,
k: 224,
expand: 'xof',
hash: shake256,
}
))();
export const hashToCurve = /* @__PURE__ */ (() => htf.hashToCurve)();
export const encodeToCurve = /* @__PURE__ */ (() => htf.encodeToCurve)();
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 implements Group<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));
}
double(): DcfPoint {
return new DcfPoint(this.ep.double());
}
negate(): DcfPoint {
return new DcfPoint(this.ep.negate());
}
}
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

@ -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.
*/
export const jubjub = twistedEdwards({
export const jubjub = /* @__PURE__ */ twistedEdwards({
// Params: a, d
a: BigInt('0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000000'),
d: BigInt('0x2a9318e74bfa2b48f5fd9207e6bd7fd4292d7f6d37579d2601065fd6d6343eb1'),

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

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

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

3
src/package.json Normal file

@ -0,0 +1,3 @@
{
"type": "module"
}

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

@ -2,7 +2,7 @@ import { deepStrictEqual, throws } from 'assert';
import { should, describe } from 'micro-should';
import * as fc from 'fast-check';
import * as mod from '../esm/abstract/modular.js';
import { bytesToHex as toHex } from '../esm/abstract/utils.js';
import { bytesToHex, isBytes, bytesToHex as toHex } from '../esm/abstract/utils.js';
// Generic tests for all curves in package
import { secp192r1, secp224r1 } from './_more-curves.helpers.js';
import { secp256r1 } from '../esm/p256.js';
@ -595,6 +595,18 @@ for (const name in CURVES) {
{ numRuns: NUM_RUNS }
)
);
should('.verify() should verify random signatures in hex', () =>
fc.assert(
fc.property(fc.hexaString({ minLength: 64, maxLength: 64 }), (msg) => {
const priv = toHex(C.utils.randomPrivateKey());
const pub = toHex(C.getPublicKey(priv));
const sig = C.sign(msg, priv);
let sighex = isBytes(sig) ? toHex(sig) : sig.toCompactHex();
deepStrictEqual(C.verify(sighex, msg, pub), true, `priv=${priv},pub=${pub},msg=${msg}`);
}),
{ numRuns: NUM_RUNS }
)
);
should('.verify() should verify empty signatures', () => {
const msg = new Uint8Array([]);
const priv = C.utils.randomPrivateKey();

@ -5,10 +5,16 @@ import { describe, should } from 'micro-should';
import { wNAF } from '../esm/abstract/curve.js';
import { bytesToHex, utf8ToBytes } from '../esm/abstract/utils.js';
import { hash_to_field } from '../esm/abstract/hash-to-curve.js';
import { bls12_381 as bls } from '../esm/bls12-381.js';
import { bls12_381 as bls, bls12_381 } from '../esm/bls12-381.js';
import * as utils from '../esm/abstract/utils.js';
import zkVectors from './bls12-381/zkcrypto/converted.json' assert { type: 'json' };
import pairingVectors from './bls12-381/go_pairing_vectors/pairing.json' assert { type: 'json' };
const G1_VECTORS = readFileSync('./test/bls12-381/bls12-381-g1-test-vectors.txt', 'utf-8')
.trim()
.split('\n')
.map((l) => l.split(':'));
const G2_VECTORS = readFileSync('./test/bls12-381/bls12-381-g2-test-vectors.txt', 'utf-8')
.trim()
.split('\n')
@ -37,6 +43,11 @@ const B_192_40 = '40'.padEnd(192, '0');
const B_384_40 = '40'.padEnd(384, '0'); // [0x40, 0, 0...]
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) {
deepStrictEqual(a.equals(b), true, `eq(${comment})`);
@ -847,6 +858,13 @@ describe('bls12-381/basic', () => {
});
// should aggregate signatures
should(`produce correct short signatures (${G1_VECTORS.length} vectors)`, () => {
for (let vector of G1_VECTORS) {
const [priv, msg, expected] = vector;
const sig = bls.signShortSignature(msg, priv);
deepStrictEqual(bytesToHex(sig), expected);
}
});
should(`produce correct signatures (${G2_VECTORS.length} vectors)`, () => {
for (let vector of G2_VECTORS) {
const [priv, msg, expected] = vector;
@ -1155,6 +1173,8 @@ describe('verify()', () => {
const pub = bls.getPublicKey(priv);
const res = bls.verify(sig, msg, pub);
deepStrictEqual(res, true, `${priv}-${msg}`);
const resHex = bls.verify(bytesToHex(sig), msg, pub);
deepStrictEqual(resHex, true, `${priv}-${msg}-hex`);
}
});
should('not verify signature with wrong message', () => {
@ -1175,6 +1195,43 @@ describe('verify()', () => {
const invPub = bls.getPublicKey(invPriv);
const res = bls.verify(sig, msg, invPub);
deepStrictEqual(res, false);
const resHex = bls.verify(bytesToHex(sig), msg, invPub);
deepStrictEqual(resHex, false);
}
});
should('verify signed message (short signatures)', () => {
for (let i = 0; i < NUM_RUNS; i++) {
const [priv, msg] = G1_VECTORS[i];
const sig = bls.signShortSignature(msg, priv);
const pub = bls.getPublicKeyForShortSignatures(priv);
const res = bls.verifyShortSignature(sig, msg, pub);
deepStrictEqual(res, true, `${priv}-${msg}`);
const resHex = bls.verifyShortSignature(bytesToHex(sig), msg, pub);
deepStrictEqual(resHex, true, `${priv}-${msg}`);
}
});
should('not verify signature with wrong message (short signatures)', () => {
for (let i = 0; i < NUM_RUNS; i++) {
const [priv, msg] = G1_VECTORS[i];
const invMsg = G1_VECTORS[i + 1][1];
const sig = bls.signShortSignature(msg, priv);
const pub = bls.getPublicKeyForShortSignatures(priv);
const res = bls.verifyShortSignature(sig, invMsg, pub);
deepStrictEqual(res, false);
const resHex = bls.verifyShortSignature(bytesToHex(sig), invMsg, pub);
deepStrictEqual(resHex, false);
}
});
should('not verify signature with wrong key', () => {
for (let i = 0; i < NUM_RUNS; i++) {
const [priv, msg] = G1_VECTORS[i];
const sig = bls.signShortSignature(msg, priv);
const invPriv = G1_VECTORS[i + 1][1].padStart(64, '0');
const invPub = bls.getPublicKeyForShortSignatures(invPriv);
const res = bls.verifyShortSignature(sig, msg, invPub);
deepStrictEqual(res, false);
const resHex = bls.verifyShortSignature(bytesToHex(sig), msg, invPub);
deepStrictEqual(resHex, false);
}
});
describe('batch', () => {
@ -1187,6 +1244,10 @@ describe('verify()', () => {
const signatures = messages.map((message, i) => bls.sign(message, privateKeys[i]));
const aggregatedSignature = bls.aggregateSignatures(signatures);
deepStrictEqual(bls.verifyBatch(aggregatedSignature, messages, publicKey), true);
deepStrictEqual(
bls.verifyBatch(bytesToHex(aggregatedSignature), messages, publicKey),
true
);
})
);
});
@ -1205,6 +1266,10 @@ describe('verify()', () => {
bls.verifyBatch(aggregatedSignature, wrongMessages, publicKey),
messages.every((m, i) => m === wrongMessages[i])
);
deepStrictEqual(
bls.verifyBatch(bytesToHex(aggregatedSignature), wrongMessages, publicKey),
messages.every((m, i) => m === wrongMessages[i])
);
})
);
});
@ -1227,6 +1292,10 @@ describe('verify()', () => {
bls.verifyBatch(aggregatedSignature, messages, wrongPublicKeys),
wrongPrivateKeys.every((p, i) => p === privateKeys[i])
);
deepStrictEqual(
bls.verifyBatch(bytesToHex(aggregatedSignature), messages, wrongPublicKeys),
wrongPrivateKeys.every((p, i) => p === privateKeys[i])
);
}
)
);
@ -1234,17 +1303,23 @@ describe('verify()', () => {
should('verify multi-signature as simple signature', () => {
fc.assert(
fc.property(FC_MSG, FC_BIGINT_5, (message, privateKeys) => {
message = replaceZeroPoint(message);
const publicKey = privateKeys.map(getPubKey);
const signatures = privateKeys.map((privateKey) => bls.sign(message, privateKey));
const aggregatedSignature = bls.aggregateSignatures(signatures);
const aggregatedPublicKey = bls.aggregatePublicKeys(publicKey);
deepStrictEqual(bls.verify(aggregatedSignature, message, aggregatedPublicKey), true);
deepStrictEqual(
bls.verify(bytesToHex(aggregatedSignature), message, aggregatedPublicKey),
true
);
})
);
});
should('not verify wrong multi-signature as simple signature', () => {
fc.assert(
fc.property(FC_MSG, FC_MSG, FC_BIGINT_5, (message, wrongMessage, privateKeys) => {
message = replaceZeroPoint(message);
const publicKey = privateKeys.map(getPubKey);
const signatures = privateKeys.map((privateKey) => bls.sign(message, privateKey));
const aggregatedSignature = bls.aggregateSignatures(signatures);
@ -1253,6 +1328,10 @@ describe('verify()', () => {
bls.verify(aggregatedSignature, wrongMessage, aggregatedPublicKey),
message === wrongMessage
);
deepStrictEqual(
bls.verify(bytesToHex(aggregatedSignature), wrongMessage, aggregatedPublicKey),
message === wrongMessage
);
})
);
});
@ -1368,6 +1447,37 @@ describe('bls12-381 deterministic', () => {
}
}
});
should(`zkcrypt/G1 & G2 encoding edge cases`, () => {
const Fp = bls12_381.fields.Fp;
const S_BIT_POS = Fp.BITS; // C_bit, compression bit for serialization flag
const I_BIT_POS = Fp.BITS + 1; // I_bit, point-at-infinity bit for serialization flag
const C_BIT_POS = Fp.BITS + 2; // S_bit, sort bit for serialization flag
const VECTORS = [
{ pos: C_BIT_POS, shift: 7 }, // compression_flag_set = Choice::from((bytes[0] >> 7) & 1);
{ pos: I_BIT_POS, shift: 6 }, // infinity_flag_set = Choice::from((bytes[0] >> 6) & 1)
{ pos: S_BIT_POS, shift: 5 }, // sort_flag_set = Choice::from((bytes[0] >> 5) & 1)
];
for (const { pos, shift } of VECTORS) {
const d = utils.numberToBytesBE(utils.bitSet(0n, pos, Boolean(true)), Fp.BYTES);
deepStrictEqual((d[0] >> shift) & 1, 1, `${pos}`);
}
const baseC = G1Point.BASE.toRawBytes();
deepStrictEqual(baseC.length, 48);
const baseU = G1Point.BASE.toRawBytes(false);
deepStrictEqual(baseU.length, 96);
const compressedBit = baseU.slice();
compressedBit[0] |= 0b1000_0000; // add compression bit
throws(() => G1Point.fromHex(compressedBit), 'compressed bit'); // uncompressed point with compressed length
const uncompressedBit = baseC.slice();
uncompressedBit[0] &= 0b0111_1111; // remove compression bit
throws(() => G1Point.fromHex(uncompressedBit), 'uncompressed bit');
const infinityUncompressed = baseU.slice();
infinityUncompressed[0] |= 0b0100_0000;
throws(() => G1Point.fromHex(compressedBit), 'infinity uncompressed');
const infinityCompressed = baseC.slice();
infinityCompressed[0] |= 0b0100_0000;
throws(() => G1Point.fromHex(compressedBit), 'infinity compressed');
});
});
// ESM is broken.

@ -0,0 +1,128 @@
25d8cef413ba263e8d5732d3fca51fd369db74712655a5fd7b0b3a58d8095be8::800134e27aacc74dc91153a6bd65f96a5f8c8365c722da2f1e12eb048e0aed6987fa4168a51241ce41434fd05fd4bdd9
611810ebd8f5a7faad47b2249f9d13be0506131db987b6948f1ca3194fa6b643:68:94250c0cc62ae9041c6f6e5042202b3c327991ce4b2841a4145d270f6c8311bc95673c826ada72a6d69e92a833d649e6
419bb1de76e11a476f8d5cc5d85a648ec04f24bf75f6cf1f3fae43e57bf9a491:c8d0:898f660c5b26e8c9461ab3f42eb394465d5a115702c05d2a2bc761a8873ac0f33d21f9ea9cf4c435cd31391f5c8c0a91
0d1bd9077705325666408124339dca98c0c842b35a90bc3cea8e0c36f2d35583:c43623:94f60dc44a4dbb2505befe346c0c143190fc877ded5e877418f0f890b8ae357a40e8fcc189139aaa509d2b6500f623a5
50ff7bd9b21916e55debbd0757e945386b6159ef481d9d774ee67d9b07d0e4ed:7e846556:8d8d9e84012c9c0958018202fe944b4517b618cb7df0b61b1f1ce40b43c2da6330ee0c30a37ac6c7ba0f16aeaa5b99db
29a8af03f8c73c64e14807cdabae877cb0f273169bc5ebf17f3e4ef334690656:ce8e5953d7:b37528df8825b94349cfe90a8c8665915cc49e6c41d78e28f8f1a05c5956ee9af850b82e9be756f024e396fd85d9b1ca
732fbcaef0e216eae6420eff93c68e3547267b69ca48c7ae9d79d481a466fab9:ae9eb5f425ee:b0adf372fe871a5f7efd30ba8f4ea563460a14651b903789324b78fe12c06b23569766c2d7eecdfb734de4485fee2436
4a8135a8847019dad5c1f1b609b50ee72bf5e6459f9c4206ce43de04c2a7103a:01a6d7836c68ad:914a38d1fa13ffdff56cbadd1bd77a3108aae19f76ff2a99d18784cf5c7620d44543045d757f61bdd4fa66780b25eb46
0681339753344b5a346aeec93a9b3b9d1282d620a3cdfc4fb4f0e7a075a99fa0:c335129a7fa17398:a01d6f24c038ebc110d742babcc9dd0a32eb518e1e52fac73a3e0a3395012e708112e86a314649aa1edf90dc51007042
67a4cad01442be6649e8f3de5b14d126baee62c7525ac61e0b2fe3387e7681b5:bb8a6f6e15fce1f262:82aa70654ca48c6ef55ce3edc88ee77922e1064c763aa50fb0d4a2e8b206d4e14ed849b4d175b096481a6afac232c588
1e36e3af518a276dedb69eb0e9df882721116cdb336f692eb691a6d2c7f2ec15:5fbb696cc48ea826a789:8a8f9a764916f3fd5b6cc882f9869ebd1d6a24a057a6e436509c916a9a1e9308e5f891e8e49f39afa0e9afbd3d209cb5
5984b05cfa8100d150b3a9a0a0c1e2be149a09e2ff6218b0648651f82b4e773e:5d1bf1b69e2774fdb03500:91703b32c962a8bda991561258c29cb726fea6300742cfe37ed929f68087638169750a423b5c4b465f5498b64ec660ce
4d84a172794eeeda6217cf4d10fa36f1b21103742926d4948845a8a0e417d13f:8ec1a5032dfff9289fffefaa:92142c1955d234c700373b823ab4b4b308897218096a88ea504267b26c9330b939191c72e770aaed0af3281b418af173
4de6bd9f522e6edf20d0e54cc17cc22f558f115b58478ae6155291e67c28e096:3a5e5a964d6acedcae7b23189a:838d07c7d28c62b1e5aadf8c621c4f360407b3124ac7f7ae3a40a56b1b848b8104f59b4d74e278639e35f4ffa64a3767
64fe9caf26b773198b6700a23c2618d36c7382440339a60236e210fc7f61ade3:8873768b317b84b32fca283a4082:b9fb8bc5cfda68938437c17e9f5cb448ca4bb79be278d8f1eac42b9f9b03039673c3170af211c24d7006d1af522f805e
2b5bf5af15c13c167173ac0b4750a27cd36ebb90cb0ee90d6168fc81eea0c30a:0a93cd89817651705b4fd414054a44:a2216d350329553d3adc81d0d79d000c0edf634443dafe7c292e8f7193d09facf7a6361e02e5df957021429ae306d879
507bf8c00a3364d9f297b3df5f523fc786806b3fc60123d8b231831af5dabce5:691cec082f50711675043ed04233437d:aabdfee018464bba85027210a8a8322f9de64b452d296f31bc8c507c05289b70e8642fb2b6aaa33759d857ece7735231
46b4cd59574a6c1f845d2a57d41c42096db5d53ffc9ec8d4b080c1542e24f30b:9a877c2bef8fd2ef71b2852e35afde4912:88eaa4631bb68b510601e6b099376adfe05c1eed52757611897935974d82bcc2751036723bcbfaf29c7f8c09bfd93b1f
3e04f740b39a8792e414144a3cb0c8816350f5c4744cf6569f258fc9df82a7d9:e5781adf4d2c0501969d2669619934145ffe:92989055e334649063b2242af5890a445a3d9f5fedcc127318da402d3c68f0ef658d0dc0074579218e02e31cb5ece4f2
5ff00a071e807e2beee582b790e4f37ea23211273008e37ca683b34632546b90:75659a273db98e3b14cd464cdfb217823f0496:9721a974cdd68946477db08a4221db9b9c2ca07c01b1daf7307ffcaa16603ee9f12e0ea5e446af292bde7b21f5eb4d3d
59c6a3ac6fef4c048486c141825a539e5b65ecc5f0a4425c4aa1015735928f0b:d26e8b19e6065dbdf5a7a50954fcf52e046e4a79:8be8f6ec5b39a43b6d24967009b0444a4c30ad57f285ad737b632a963aa3e9511f6e6ef27a502071bea00c4b653fe01d
053840cde56e2fab07d92aad4ca4126db0c79b582ae5b6074336f10faa1ce27d:18277161538b41f2116b62f1b4f15f763db71bc95a:a0350c66b6c72e8e745ecacd973aca076c110b218e0275b34976e7e23a3c834b260256227dbd8902e8454bb1d620e92c
4dae7ee2946935ab3799f54a67f7f3a1ede349786ac2169c0d4b66bee8659c88:ff0bc27c426ba610feb7b8d7262d27314884f9e98438:b6b4f053da80dc91f5311b1ff12c3de305b0dca42a84818d600644a2e9955afd4f68f61415ba5e4f6684e33da0fc8071
2b36bee50f49a23bf9a01d51e5b67acd8dcca3efb310708a742d93e1cb7089ef:5d9bb535996b1a0158f988ad523cdaeb934a69b043b84a:ad4ffad27a2d8e375549a7cdbfea761712f5b422fbc6d619a2de7b985f4c401b524905adb067bf40e1f1cda75a99edbf
364aa3c72c66b518e1bb9f28febcfa56f29ca5825fc8f1bb60792703124a7638:cbe2e518132f31a040fdde8e4665130d05aa7ff233d12bc6:902dca0a36d551de11ad849888e93d91023493c6a87ebe242ffba54e299747f8a122d015135a70a2473dd83f401d88bc
49a257e61ae16bf1f02cd6e81333ec3dc3b509843a56267ed0d40191c44a823b:df0079e080bbe83a8c3255fbdd26bf143c174ddcf80c969e4b:a3b5027454751a60c5a1204e8b25fe88c278ae2f29449e77c91766feb55ad81c978562e60c00084ff8244b1d8c02a2cd
4f7443821af744a272e3fae9ac7350d6344d61d47bdecd23587f794cfe29758a:e8be2bbfa55b3767413a37778a1940104c4a941e018daca7e3b8:963af9ffa575e398bbbba35e68e8a99d8ba77c870f98a6dfe5b48a283a61ccb19071121398669418f2ebbfa910782f29
22144d627be8fdea6127df0dcc8b17a141a41b44041548bd367840e372c8de90:a4c9b9b49bf2267674fd979e1eecce161cf13b5042d4ad769b45ad:86eeca7b5d03763d96bece0a865e315d260db6b6d728519bad150ef1d086bd78848994ed769da6f8ecf5ef99550588d0
25682a90cf7d1672adc57afe312e0695039d130dc2fa052174d1754dd6bf6f2c:939f9bcd0063da5bba708f16520a7ded65857ac4824e79ab1d6acc8d:a9b56b5df672c163219eb68807c39911bfdd00a6d413f6f1ee75da967017b2ef9ab99528345ed9af70bccc30b49b424d
6f4dc51d61fc10750a53a448b0177ab4ddd4727bc3690031615ef5f3ee9a37fd:b64ca223bac328283a133a74cf95ae4b14d6d68784115dba9af1a14e55:b7c5f00708da137f5e8e90b13cf9b41305173f1a616a31f69b37b89d2832d0956e1e6da88838969ce67d90f49c5d4f2e
5ca692a6163c55c4945758e4640c46ff0ca34fb870cdee9e067a22b0c0bdabed:bcff7f9acc78a02edb0e163769d6bc4e3a97e9bd3677b98a68d82c6d3b90:93d2e2d57cfe2502ac87207653bad819fe1c13cb321dc34ac074aaf647f3b1637d00f99a3ff0cb527465d45f6be31809
2b617ae10e1dc16e41fae911b14e9c150196912a4e89e20981ebcf472b4dd5dc:fc4d92a56983fc61b38b9a8b10abf5f2e914100ce449d4ff8e0ad586e7314d:864b93049434dd8c32b29c8c164b9ce286772da4e61be06b009c4fdba74f9915545cfe005602cabf6b9dfe76e084f0b6
3d12a889a4c1cb6066919f7b97086faecaa640580c43a9df4b8263160177a94f:3f0660a2f3dbc7e532fb7961b7cd00e4b95f5a44702e6e19a04321bbd4fedc02:93070b7ff8f81c45cefa10209107e37d567ae22aa92a45ba1e7a922eab7c58f5c7ef7a77881c8be9de29fad6a3ba3081
012d893034dbeb4cd2f4a7e7afa16e885e7139ffb2770f4d508ce187ebd01e1a:ae6c892504739f742ca90a2d94b84e1092e63288f220a5a75829ea83c49cacd031:a910f08de82e243ae5feeac46648d19f51b10046959bed0a887b7a2e2e4d9c5c0791c6ee9769c81a85efdcbf51c408d0
417e34572c751f1f4cddb3fa89f48640d9471e857e2424a701aa8d7283ba72d1:0f942d30f2b1090003d0faa02a8b1f4fc14500e93ef0df241d0996c7e4711ccac3a8:a3c9cf0e9e9cffdf97ef6fd8c57c60fbf5175d01b6943923eee9c12861d059bcef315c40791e8952861fe3c04f65c203
2a4bc53e3c5dd8d2c46ce784b52db7a66b1509a80103329364b78c5243e3b52a:f387978fe8bb746d7500f470ebb28bcad43501780fad6a8dc116052f93831a205b4116:abcbc4a6f49ef189dab4205790c23c0053474d9a1b02bce3979017137566d21eb2b5ce05b7f9bce8be73654ef582349d
231fd161a30aae15d7069ea9e81e06bd8a43c483468f8095cfb4b255128df5ec:dd1e2f364fcb24ad18349e07d6f74353cbd48def87b6a8a7147f3d0a461882a61a9fc77d:b9328c61b63b045372ec8fec0541cc70eada8d99414934a385680d5d3c98dd1aae317cd030c7372c1150c117a405335b
0e6dc865b8ceacd9e9e1edf3e146a00de60c08aab08dde3cb200fefc24e41eb0:b19a008107d7d89d804ad8a6cae7c039e3d003fd40b93adc746fbee76af5bbf299076482c1:8ac945e4ce9bcbd1042df2d4f29574fedcdca79d25d2358de9acf2ef860c0fc0e528d13c311e6119b73024bfbdd1ff36
41c6a0777609d976880906dcfeee73104a93f8527a23c78d5d7f7917401183db:47766621f49ec5c8235c30275ae2a92a615435d29f6bae651bdc90082a6738741e76ce43a3d2:ad5e2a36345fe3e3f6781e8936dafbd6ca0b0371ea59ba1ecb7091a6c40ede7547a82fc8a28b13bdb06a948446542e4d
6e6f8e2f4652e14aaa4ef111d7fc8be7ac4d8ada4d051caa52465a4345181990:8a266363f67331b303b5c4594e222a343ae7f5512d94a6df766d3212d1ad4ec2ddd88e62d88c51:a9c21fb52afa29e1c4a8f993e0cf6327023a1fe00db739bb915d9ab3e3238205bda9bc8b6be2f9f87cc29ff69bff9233
256a2028788ae24683db9af7d8d976782cfa323ceaf5db0e62272c222c83d331:23204fef64b612a246c470551a58b7e3c4b8ae558edd55c118001ba74ce4c11d22831683f597169e:a382ec60cea2596f472b8805b0271a0978a125c680d523a1f2d4291fffb01a3aa5d22bfe62ca439573525065fd6ec885
37b73537fcbb0f6b8bff910fcc0116d905f0960a2233a564d4cbe0b4c53f88ab:43ac6d7da7b0a419d6c893e9ccdbf3b891ec5ca9460fd70d9b2fb6dc9bd482c835af88922d74e4ef38:aafcf57e5db3c378a4d37bfa461ed23113bfa95fa9aece77a4569cb836cc86d311800e9425448c5d0d4302fe180810d1
700fb2aa7050df22fede481f8fbc24a937812ddd19dc19404351e2b5c72dc21d:381d1c6c2357c8fa5a07865e5dd0f76f5c4d63d115a49c24a7302d4cd66117683e549be5796ecd16fc56:b4409c7e09d8c79dc2f7a083a23cb02e83ba3c8f4af8ccfca70ad4ce90991333e4ff742fc912afb0de93610c1ec83261
56dbc180d43e8688bece1a617d284f2d3880e570650a3f260e9a3abae32c2c3c:7f62eaa50b2ea4288c03ecbbb42a8178aa1289bd1dcd9bb1664be0b8cf971b023b5e29cf47dbeb779f0098:a3fdcffe61c67de9a482e4fccf42ddb9344c8aa4fc3733ff711750287ae87329a82e235c5f9954a8cc5015ce7877ab9a
30a003ea75cc507f2b0861d68af83522b451976fbf9f71c6be340ab4b96bc0d2:bf5f878d46a8fa3e3a476fd161a86d053cea93675f18c30fbaab758a1f8f5f6818aaa193fc37f3fee0467264:8c223b8239b826c91eae0c24327e016129c3c13e99ef187a2abd9d710c33db5efd6e05aa4547252182050e06921510f0
6c27dc9a7c6291647e61015e9d1f6aa46a38c4f32086b36acb476af525399c0b:1881b2ada37e78f7883d64ff35ad98de04b98d277104534d3d8ae6ef37fe5c584887bf8304ebcbea472bfac050:a6c834443faf4f7d068e48a9a927f15ad5c68c22cb245bbd206ea772493393e6d01b55a31a227643629098ff29b20309
5cbc4de784ef59caa11a1faf1c5919499ca1dedecc92840e19adc121cb2a7aa9:ee42c4b9217735f1d9e32ece935893008d8c4009abd98dffa7c2f8214f26e31467f5ebd125abe9f7b6a62b5789a8:8159b26a583da405b5c5dd4da330d358465268e6f65b82c87683f2bec8521d7eb2dc5e13665cad7ee7f8bdc3a9713657
3a7c8b649c0c3826efd2646d01ba9800690a39a58af824762412403838042cd9:9aac7c53d666ac80d21af3f9422bce65ae0588acb274b6efec9b2ea75e7b12848da9f038449a5f8f8ac453af28fd02:83660e10ea5050381dcc5a8d8354f5322d60ade1758734a700221abf2cf0e2a04cda83b4cc85783476304cb8431907e3
3e5c7d16ffee2ab46e45da4aa41975bc6ece396a8d78bf3d072cbcf7c3d0c687:bf7f6717ac2a33428ad090c12cbc27dcd12a94e143c9eb46aeb11a6c65e7b09d90dd0da5b855ba80620b0ddf48a3843c:83efa6580768eec9514cb4d3c0c22e6a584aea44aee4f7dbf72bff8375668c9ee1935ba5eaae2cda072e5159a0166dbe
67db8b638d15e0f17848dfafa0105f04dc100c6bbbb8ca44cfbd7308497f648e:a4e30c5dfe87cd43153142a023fd297a9d1dde2c996f0cf3253623d5f04b36c46a7a70d815774c99d836cfce29cc876464:8f792b13eed2694c24a97623679c8be7bf325b74ee14a3b7e4b764e4ad402618bc1213ed21b04ee43af0aa0d7117f1a5
0c0a951ad354113eb871b3b9dd9db522d0abdc98f09c6caabe617a22986838a6:ab9226d0a78fc564c1c1a8a961b90cfe029160cd71e5ba95e6adc258f2ed491c36456e639d9dffd53a338cfe3190a8ab7b83:b8c474f01a43a045047ad4d8b6cfb7296ba6660eca20bb1de26fc158bd8c3744abcbb9867781f7f2a04aabff7a498b19
64518ed6d49e33c45c7dbb6b53aa3ae8032be58907f952b7d6d2efcc9b2b1f75:e7c2ac2eab22fae320a9c4aec6fc173668aa9df68d7ab00a75a65da0b121db6283bad06b131282d7045c0ce50a2c9c786c5f88:86625b564b51dca8139c4178453c3376c08a42621de931c9e1abef3f7e23d8c23a23ec617ecce51c241e25dff8949325
1796013211bb13b2f2df46e8e8f430ac043fadfe36b46904ca77fe404bc54b1e:67690484edea6aa1ab2b3a0ceca61fc08eefd1362cd4839827b4e45604911f4f97607d989388f707ffddddf42cfa0c779bf4633b:8e283416d53afb6a1281986686e7f40ee59c2abdbf633e7fd8416f0d7a40e7232ea49630fab5d752b0eddc0849816d70
3c83aed296db0756243f1c33607cae018f02c30eff97384b788817ee98e08281:ee9b7b3741e1594515e755ce40998a535e9f2ed7d382714ba2137329b5d491ae8fd8a56ce7a74058131e98fed9b3282961fd11d7df:a353943c14bf9e8553ff5d1627e121e8c0819e4406f3346b3d2c7d2721b192863d666262ddaabd0bdef1b2606acfa75e
6ab5abc9b803639a1ba34750c7baeb2c0ba315c96dcf70fcfdf634fc1e5b2197:b8b107a526fc51ce96996fa008d806f29052ed82512e73178426e1d694066534b1c7337dc522b0a59dd50cc472700b2642b512d30707:a040803c3d4f0d631b6396e5413d2ee8e5f088bf9272bb6a53789c6c9b41b12d50357b140825483fa7e215499b912439
5f6d5c23ab6996e98a6ed399472d97ceedf551135ed029cf68cab520f6ab2313:37b88c5975d31b1206b769943df826568dca065ff27c17232f5bd04bfd1ed4d01a5f1b0f70a89f3e5dc5af7fc2917594c8ae0cb3908b4b:a6ac886556754f0f99489c7fe92fb1231461afd7f6076584564ba58bd80f4e387d035dd1976e1a815b790d350b2707de
1696edc94cdf62da22c85e36c9a18580408040012de878ab6d10eeff6c51f049:72df71d31b9843d13ccafee2580500fbd486a54ed42ef9db70e074eeaa2a496054ad1782f504f5e2a7e65a42249a589c358dbfb3e307b864:a4d11fa6abad203f8ad0aa6fc98ce40b8e00ee652a0cf3c2bbdb3b3934757db0cdcb9368585633e277af90417c2ce078
1b61072c204850b8ed425526568e9d57f5f9973cea8499b9d3f3e65ee411f7d1:752d9ca07b296305ce8addc54eaaa7e03472aed19626860796c00f3230593e6812dc8114c125a78e7f2c93bb66a8abd3be431b868579cbd193:afa0a0c0472878e0b97d7e4938ee560d4e5d7fb909228fa57567e01394011339adade27d99097e5f663b5aa56f4ed01a
49f8b73e487ea32e90fbfa5967d382f828cb03dea8b6e91420e3835590964bb8:11753040da26b28d466fdd0f88494801f9e2a03b42a671a740bbf7d43e90c38fb383a9fb9992912c171f65c66096de05cfc896e449e18a16e3b9:81ad82f0ca54744103651a5ed1daa464e89ad3fd8c26c4d6a2743f8a58ca3fd4693dd338e09264829f8285494d564eff
3d7a0771e3c698956350570578e3dba2093315388fb1958a32e387bbd33845f5:247362f06c8f20e956031eed27d8f3be62dbe2154dedf195bb1f9539aecb0ae77aae3c71e6fee8acbdbb6ef8d68244ccd9f6b5a32de290a4001ea5:8e806bd7479057360710060b40be772789f6e7bc52e7e781e6c82a3877d485bb017e69078c1c3f9be628a3f09e857e91
3245ff9022c5f0af88741b86344a9dc9473c4dbb28b595711cd4138ca1bffca6:b793d1c58943269274404568a01a756b7b576659334121dfc401963d51bd0de1cf011a6ab6c5d3c8f6a42ea0bc5ee5bed2f70a096c0c05e35356c03f:abb639f7c6d7e6aedf5b2aa0696f5ba1f327668cc1c352a0b5f713e043f5204dc951571d7be952f34a126133d05e8fac
26d1b6697b3189154389abdff3eb2d909fb12a0e8440694b59b8ec5a73c366fb:37ce9416c6e2b8d4944beb6cb3775d296bf0364ea3f6d6ebebde5c36a077d2ace37c8a629d8d8abc8a89cd0e7c1a182b7ce81d7173c3a376615a9515e8:a6c860a4ebf8b54adbca5507a5b8414cb1541283e817777350f2424848d290abf0c980fce31e7d3c93ebe409f4735b50
1e384f2be1cbda26e0ff77699e1cb94b9a4a58a24159f6cf8ad5413547393e07:822f7451153b2281f3f89715f1d2edaa76628deb8913c0c11fd7e6ca6783ffcf19c4f2ceebe002b0fbc63cbe335d6ebe3ee39c5548d60fae6896dbc2eecb:859a858fdf08c16b720d5a25f89f7660fdab3f1e4be76c7f36d8bb95b53add7232e06af5eac2f71ed0e4e85fd69f0edb
0f6c6c95d4d24a72caee5861097970c074842ca0183982006d0d5b9fdcb65513:67ae1492457369dc0b494a5142c0e721613848a76870d369ab53bc4e7599398cb49c89e08b703366bfb964301e09c7b99350283e31616ecf4ae999fbff00e7:ad5378c136d51ca64e4fa7f21aa6732222622b80f30c5dd4ecc572e645a9ee2001a72b43284658d32ef10eb4c1018b95
11fa08cd0740c0c37a7c0269215f272855e378dd0f8d81f45b99033f74b721f2:bd8b3e41b647eba1d285853d9254d1f121b2371d3e38c67f31a9d8a718c7d7898664dcf216355f41ecaff9f73f77c35f625ddd7a7614ac7fc4d1754778f84f58:8b02f921cc83b296ef4cfc6baf0ac306567846012223a4bcd89c532eb5c39c80e35e577b368a67b8fd8e687325350fe6
6f38baf1cee3a83a4a99b403ed5ae143233d5b228c80b9d421f5772c7439b05d:3fb1e8ef6e99240cba6d89e6642e402e18eb3c135e104f18466b95bb90258ac84b3fbf6327fa6cedbced6e942b64d4d636e40b59ed39d92acbdb2933014f6e9b44:8f82cc3207c5880dd276d59ea1b42e194504b8188467587700da208a6459ea00dbdc3dc54ef3105489dd2c71b0f30fef
5e653c12f6483d2fbd963ce05862562ac3843884d961298f7ebf65f05e958d1f:547ddc66f31911b05895c2011da903eb00feb4b1d752ccdd4b862a27ad4b0de4832161bf6b3e132dd0b238902deb0ab8e7edad34fcdfd959032ae311b7e01e40f32f:a12db7e5a27a1a581a5e601d08e907e8ff61cee91a27638e177e048a408a67e2224bf16358c24652e0ea768979c506db
08073f9d18132bece9c3f23225118fd7feeadcc0266c1861231d01990f3d3018:1c028e2d3b3376712b2203d418b705b6d317a03c9c257f104660f007737ff11b3f430182181567625f4afdb6358ee862aeaca19c67b3b253fed777595bd79f4f6b9d26:b2adb57dd304ca200aa95254c790784635235ec3cc418ba0deaf0efcc6232434705b00e29889f4e259e704ac4f353633
691ede2f41856cddd0cb79cc89f5ad5bfa16c62942b660cb01bf5cab13a22d98:a76de84ca3f22c96b2995e2ba8474ee7f8d7f36aba70f46f26375c1f647fa3bdfe05e13c9b18f16b7933b6809d1cbd0fee5f0e1b780cce726a5c414c406f54e090098345:85dcda6d83ef31d423bcfc1d87444a6226ebd57018f4da52ef8d9e7b45c100f9bf36028bc9b0537b4baf240cf11293cc
1a518478b36de27cdb26516d1a96939a515729bca7c51c1f1e240974f3aa73f8:ec6d1b89686f1692c0fb79f6ed782bc1265475764946494aef7ebe64572bbf70e34c72214c276cf9c3c1a1b3b22c01e8a1c1c709dbdd97a199dd854cf7ec23bf564a3d6f16:ae4fd56430e237111e2886d09cd7e5f37f959d32ffae5f87ec51fa2f7bd2faa2c0c81fc60e98bc146f236929def7fc5a
359a75965e374ebe361c795d1d7fdbfe40c5709227289966c65d93ba68372832:bbbbde79882192bb916805775b36b769e652a80332897ce32f4757bede663953b40be828ec62b717ae7d3872b4baacd37bbdd85f3c501c5e11f0c738b6d16fe7a66cbf18704f:8d498db4b54bd914a9be4cf650a988e063f7d016b7a3bdee3ce330d9ab4c978bcac3c2958afec8e67cb1e244fbcb1e05
67dc679b1a67908eed7a36e1b20a557c0c1eabe7eaaae1cf8e4899da020dd8d2:c01d86741a47ca1c78edad008b24246ba9684e5f12d57ff8659b8453c187efedb4a2f697f414a823f72ee805554fecfc48047d465592c6d8425bd9ab7a1135ac370a22478d52df:9520a5b80e5569a2b07b2365ca780dfee80ad21e12a904e9e974f8cf183ae1655f2b95a598f9f80d6b9bc52040f881df
13d5386a288b72437a9279c0caf667533ef11f707ced34362eb6a4570be82b2f:55ce5ac51a3b5da8a128f2e00af927584e8b59694972ee0e6f95e012c308a180e339121050c56a8a900b04fbbe9cddc09c4c3a234d30885da9833b2bef66754015e81b5413d98652:903b042c823a494e6d833dc6f7a0050d4750f80e8b68224712425ff086239b87ea5fbcad5a13f69d7ee7dacfe47e619a
2b7969fbf66336b8928f48f6afd3a161254ac01eb4cf94451fba62d9d475f6f1:f53eaf6b0f992c803b28d2984ac74d9292763b8ee599cc0cfe8f135f89bca18ddf87db85d0914760a55d52aa4412008217f836c8517f8dd7390ff82d47ee6a62a306791d27295761e2:b37da593bc046212fa4d0ef084cf456ca7d3fd165ec863d2e980472c441f597a0aa858bf368aa4f4e77dd4b8ba071e69
65013f0a26a6e628f8c598af20dcfee2b9b5419e393ca6f832cf5d97a6ba34fa:227d5ccd9bbe4b7a586aaefd083b6a674a126ab864f5d90826cf7e67c4dbaacc7994a1879c50ed2752667066dc00006cc3d47ca53bdafcda5d38995c5b66d95b68c142da786339136332:8204f9b1e6227be7c64b5e629e5d75bfa7bfac17b2cde876ed57ace0be3da8c108fed9c189171741f2840302f1756456
1b942e9b54bc8eb2ef0a3deaec60e3c37955a44a3543b9bc0980e16675a1f904:d47e4fe5c7f225d00de4dd5284bb29d2ec57fb8a854596de15669a80e3bb8b1d9b5cb0251f1142f0d5a4b58d2b1090d94799be1d38a7ad65009cd6863ec0e5020850e1b09e5c502a12e23c:b03e1643481f7fd0c98ead4b8185100cb20718c7d4b816aa8b8b3e94987d0c1cb4558860d8e73b198a1655d1138fbf53
19d8a8169d57cc62ba1e4c4d9d22a45d0b2280945b2462f031907cb8bd83bb4a:e3ba46786a411d27a815d8fbac5f44e5cfd6f4ffe799e978d606235fd0ee14d58b68c8fd06845632c0030d1d919c90efbcc42a69b22afb1cf3e503f9a7d8193c8d3c297d7ff25740e483af34:82595dfb2e1380680208ca15077487d563e4c02381d2f4663498ee5798307de8391b2694089fe62c9efd1d2e5cc0b089
1bff7b1d26603e3f6efbeccdf394ed922e7a1c707365496113d3ad6fcc871195:145b190cd7bf22f6c45aab5e7cb87cf37a4098c5ea1b0d8df9837bd776551f4dca8bc6a6a830ceffac56033ca67d6fcf1f31794abc831f9dc83505f0201e52961fb4816ab21974ed05241eda1f:852af6a672913c27a0e240b7246399e4d23089e4ee727d44ac9a0aa3a7b13b100be82abf201edd35e3ce8c4f506ce484
371c2a48d2cd9ae2a13b3dfce09d7fbc9a01b61cf328f096fa87dfeb9e3ac883:785412beb888a53f807e537200ae520df044246aeaf2f86e8d65dc3a30056b57056cc44084fc2762069c49634cdb557cda102d5a7ea8a45bec6813738481b3e5996367d80faad7138791d510ad81:9616060a912f463131d3c0e0c9b5f8e9a40f6cf00b9d6253dd105e77604a687fb2ea7b466ee9421833f3331c25fac1ca
61d7787ebbe947d746063f1599c9313f8df517be1494a38cbb7f196a31ee7cd3:4b797f6782ee555113b5ea4166e2c3a2cbe3359256034745c66e59149b97cfba790bd091aa6f809721d6341acca9673a47f34bcdc08499080e30bb1e81defca019f62c886677577ab289be4981436d:a337aa2a22010cb98c0736df043acf9a01d0de654448f144ccc6e35dc53b5f6cc78583c5a465b282ae8add2005caed30
6a1a038e5c2bfab87ad3cd29b808c8e7a8b12961f7722d62d4ff7fd8936c6eee:2fc8caa666aeb84beb71d7c6918a8456a23c406b1378a6476607e4b27d651c4c9fde2c8682ed6005ca757dce710c4451372efc5886972cfc89f1eb7e19d80648b9869ba74ca305c6f88b464388ae3f72:9876a586403cb4c0f2b56373996ee524a489d4ded44df55b7db74a749ab74795470cc0a66a6e58193730195c5c444e76
3f528ca57a9a03e0e1af999cff2a602d43a8a7fc9774a5b35b91d46ba2332590:5c52e68adbf3a47d0352d333bca88b4559579fe3dc2efe7369fce4c10acea51c4166e8ab22d243741d7e2c2ae49a0ba35f729456f8c37b7bd31e858205a968cc0a6e5afaf2b3964b09619e241b3438c6d7:a2b1e39069444e3b1f14aab6015a6e25543ed0baa4a23ab6b187ac300c54d433580ea036af283a3a25d5421a945409ce
1cb19f5b2b6d2d76b26eefeb36d2995bccb77a0048e886b47552b209253e04d9:e8804b79ae38a9ad21cfd3e6e538b9bce254dc020dd42ebd62d4f282fe5da900b97aa86d40d5cab39516c74c33b769ab3e0a644a63a97c4cf9b59e55dfb42c1df038b1bb4ebec3d344ded09a5f90f4bafca8:8c37c7eec66b0c88268aeb7326e85d30a2e8e851750a74aa95870a7259d20f6fddab8dff3e0d4955ae79ca8e80fb515b
689216f2c9e7a748c94c898640d7f95d57dd0582eb017ce04351c44f10265472:1b9e066095b608967db1d6b93691bcdb4417f6693e6065186fbd8d1ed5267951db49d215328044d35e3555f6e1ac89fee959625b6bcfe510fe63bfd05de60b7e1e9fb5df9e721141c65bcd7a7e3363e1b5b472:8041368450aa99afec459dc86fc883d9b0ddc846e63a826b93bc07cd64e3520cb09ea5efbd049522fd049f0ab55fb61e
0b32a4f24c0c259951660e96457c1e1fa18bb7928c4796dd085dec96a99b0e37:01db4eaaf51d3322aea498726538eac137f248085db057f1faa77fdc8091e331e1d497b4b3276a51a5dc420af871a826c55dbffac511afc9319e9658e68de1ea204808c282e93100a29df7b089ae5551ff2bf95d:9634bb34b8bdb100b233df021f1d99afc8c9c9e8e76a7ff7d3fc62d733ff0819df55e71cfda092f54505a98783f786ea
04633de27fae1f070ff87e490e10528e9b40857b5109175a64543eb0ec6c82b7:85fde85aa169a8e44917086910fb1a9bee9f1b30b2d29e154998c6d659206307b5b66a1a3b1af3603becf751d37605e5b1c110578b2094062ad1e62ebd3bb75121d3569bca60bcb26fef490288da106258b904509f:80db12ebb3064c79d372d4779d4900e10c4bd141f109ecaca9c25cb3e789fa4cfd727ce372b6845c956f206f6a73901b
44dc2ef437107d48be57678f252e523a08bf63dc720da85b8da7486e875740b2:ee998b1edd10ffaf7d3eb7b163842726e33116efc46d77476fe2d3e8bb3b79f44f065e9bab6d1b9a32912744c2b8538ebdb8dc634c58ed19e179a889d7ca53983eda22ca0dfac2e5b6761f5e7a129a950dcfadc27cdb:8c7cc3d5822bc3b6ff0f9be230cf9fe91f5d86caed86bf3ee8679deaaba06b545f1cf87f63fc601a56da381e74b39e3b
5fae658e1beb5ce5aca9025861b991ffe5f0210562e0383a89372c3e9bb01683:391cbe0fbe656e0ca05e1f7ba659d7b931c8c32fc1b4a7477128a3d36fcf2e04e70f930fce6c42d667595b6870da22b29c4b667e08d905f9be6b94d01c5cb6d652b44fab93ec2da57edf40234c2998581fbc6bef11f098:ad2d3e2f04aee369179540b8d78d358ab6f49f03e540b242505eb0e9212d48843e3a4de840cf6534e8e492f4af7468e9
3c47c2af3e6fde7084b94f7125003b4730274dc73da91c3a6436a36a58b2d371:34e74e595a04107b38cffc124b941d3d549cef01e3552a75487ed3f1f23ea31046fe6db758683e6b9f034c5d4c63b6b7e92beabe3b7d599efb98250b4dcca3aa6515456b6b19ba984314260fd115b0e12380a5e68ffbcaa1:b140516e7bf5edf67a20fb360ab2932e7af32f38f669053d9fd9506453e71870ad598251dac0ab34117e4562fc946766
2e8e531b369ccefd7ac3e91b3a5e4dd671db1b2a05863e5b8170ae0dc27840b6:bbb0b60a66dbdc06791effb0aa45b5e4dd40777822a00aa1e52dbd7d0ba9cd30797612fb128c7c7debf3aa24a4967ab032180a527da239f913bc3551050b23b972642156240d2e42265053cf84e5d870fdb7a1c9c6f4c185ae:90d33306d8ccbf0b4c2f439e59d3633118bcda2a2fee59df40f4dbcb15a5b07c3198385a5d635e0eb8abdf005fe68996
4f7f6c0df5d8fb728033a4c7927b121353505ed518112592381faaf17bffe927:c870ba3e0fd1477ef1140246404729dbc4b516e32dc033abaff6149b3ecf4b932243bf9257c26777e1c064b7f3c64bcb3a5fa2e3f0fc7d40bb1b20636d90bd00536de78958c64893fe07a2528f806e2811bddfae9958b241c026:a908968636312fda482872680603307f1f5549e592eb611517e27aa9fac9e9f509356105a5e4fa5013d97613a976ffab
0feea23e93e2bb1c9714af6e8a125b6fb179dcc24b2456e40548061359e83034:b927770e3c3ecaf04844ede61c8f82c5394a636a9b481245f03cdb0b6fc75b5263e65a3dddbeadb8e5699edf04fb6b5cc2aff7af1a2b4c042669a9e3f03c0b564fa378ea9332581b8851a88ddd08f9959e0b9f66333ed081733d19:b9e40340b6f649a87312eb663694e24ddf4e842da1c38e5ed90185a61429487877463a9ce0c091f6a6803daee43d0480
26af207030b1958690b8da361e81044ab71b4ffdfd9a26f853b090d1c3a0da84:84f1728578942b1f41af223ac189c0de40fbf013608711acc97568ca4d5eb3f357ec7f76902a0b59b94d28959a25c832bde18c56ebe2749e684fd7bd1d5cabcc3ff50088271bda5b12f8cc79e53334ae997493fbc0bda2c56e27acdb:8fb67df1fdf9759139547dac10312170a10d0e220cf83e74a321778198f6ba6f93fe2bbd7e64d061392a195e835f97bb
420560ba6da9075a590bff683af1b816c6ed855dbfb89e584cb4904a1c3c18fc:87b7039c154b1ea17e34125afc51b31eb1882d5b0a27f800859c8570f7084d35d9edeeb285aa034bd0de63c85b9f22fc39b6faa69f6d420dad742c0a7828c0e5c16f9dbe93db95c8baab1b20826af7f942872e5e78345b9346a1baf203:a748c3e20476dfde9dd8ae0b1da0be834938d1a3843a93acf1e24a4dbcb808780d0812e78707326f23919f573b529883
23d5808d404b06f00e2e97215d55c84b735c4d0552577d842e0138431f69aa4e:528f09550299e52818e5a3af380374b63615c820ee972f8af249167d38c76ff28ce387f6c8712c6a21e529a048ddce22e794f221f8da9efe720e793624f66b5fde02c12c3fad14324dc7923ca6f44b7c610d5ce51e456c3027a303c71b9d:afe5949e2d7d89387864394b31745b922b4a50f7c08a1f092155007b700efc0c4c0fb3aded7c764b4a89d4638b55e033
2c2d04eeaa29b0383ffcf3607828ac5f39ca1ab6bececaa6fc8d10c1896fae79:564a760045e175bb5bcf5199f0330c9ee8a9178d7b7e2b574b42c4e8f549d63f05729d7559e1dd43431ef6f0e78a05ab1e676d8e9e972aa625feba814c5ac5b6aecbadcfb926c8de16026fb25d66d347813e636c3356208a704520de0a2f0a:9983cc5cbe5a8bcbe56bc1a71b2ca5b4a32923518ac2959aff0949c28af4124658ebb111d8fb390ca2afa50f089a3e75
6ea04b1ac55b80bd9e6f19b34eb635f6f40d65603dc312bd976245aa3a7ec2e9:94ed5fd16860d3062a01b1596040a0d60bc09f9b3c214ead3403109ab805a23210fd385ccfd5a65e80488dd13c1993fb2ec65d1093c8d87095c73a74589abd071bf41a645b0f177f3561165ea3426d29cffcd2315855599bd1dca971a026c906:aa7526a6ba48e05d1be7150815fa8ac4f80a59a08f44b2b7d77c8926e17a058fd6436b5c681ce8dad46351155217b87a
272d038fdbc32dc27d9113e69838acc52d61e764a00dec66b9174d1296734b8f:d58f636770468f71344828aa13c8b5c7dcddfc3c00d13e6480102ce6d4a2e0ef04f834058475ae8674b5536c2f5bf1a253a0fd54a36247abb73d1bac90464c214e871bcf737b269045ac59fd176294cdd0a3ad01391d1fd9f1d44db5ceb36244cf:a53737c41fd01aea3fed126a1ac8dab7d7439df0338ccb90f20f7173f3b210da7b3c091d90cd672e8f38c7b7864a56b1
62a13093e8754c1423e0f7e73218eb645f38cfc64b072bcbf2a0265946574329:113d0efee5cb3e1f678e684ebe613889dedd0a7820e8120926f4979322ef70bcef21cabcbf8a974eda198deaaddeb7ec5d0f9220f1706aca8f1df10340ca8d40025fca3688ccfba6b010b59110fede77cf0c54b6764756551e99d7016a6728b935a0:a6c302484226d30ab4bb5331c138915c34dfc1bb1a634eaa741af2aaf7a389e4945083ea2ae6ff9142f7010b0ff7360e
328b4abd2dfe8702172058f7ac506b1974a5911c4a574e3130950044214c6fbb:821030677bab3b8219adea5fd2fda6987be0422a47acadb76a27792719865f21c433b53fec7cfafb044240918492c7e6da4a1743fe84a472411beaf7e3630862e04c5b53213bcdb7dfe1cde18fff29d049c191f8b72bc8d1fda7a5c57cae62f9c96824:821b0ea7d9ba44a7fe751d380cdad155a44c20959c04c7caa3489983bf25ade2822e53031ebf0ccfc5a76b83ae0344a4
3b23859211b5eab5590185e6ae39a645b35012906f894108586824df61906152:8a25a986cc1df8d66b18a058e697ca2df03abf385ec0c39eabfe89bd340046c298a5a2752a7f555a5fbe3a5cd51b7eed0d950ed2c9c5e3a093590fbdcf3e41496ab510f238019733cc43f3a19f0773bed46d101ef847dcb91260ac36dcc7bb11c405bd8d:aaf17bb51a15ecf27d7aafe6a223eac092f257d5aac5d76b989ecb555251d7989ec56e46e7cbf6f68d478dd37d74b740
40a563397ead8c7bd84fe395aaf3994ee4b0373a8066e5f6ccd8b01a548229fe:38a06d00930a6247ff7c2ab303dc4f88e07d55442597c0b063ba32ab9dcea20748220f88c7354da3a5d21708b7a01491d58280434914ed16cf64f65e83e2bdc000491d719a5aca31f86c3df94559df1ae950a9d64d948a44f468d87909cb1a1db15c8d5e14:953ef1cd533f2986ac1bc2280ebf4e2522521a8f7b8c2689f04c133f1911405b0b6ea46f879ee7153f99f71d888c4b7e
02b22feedeacb437a6c10fd8867c831ff07b370aa287b1d57dbafe34de46ee05:dc59ca5873612bf0e0b0039ac451a3fd6913521bde999bafd87beaf2923802ae09630c05cd07e9d3e8c8bb3497f5a5fa6882d2e6c221470728dce51d96959843e799f02e5a64e1b7d7fb8c9b0ead75e7cc748825be932f735e4b639cacd8d32e0de9242e78ce:96bb0c83dfdfab8b9ecc8b360b3ba2642e285531b4e3ba861ee5169f48d9fa8bd4c84aadbe433ae3e3732eeaff15b9fc
58604274117a63977ca1d67b67fd59ebc9562d603f8ee39a02e2e83c2b115f42:769e80b21c38e39215d69c1f1c485ffee1023950edb5d375cd30a2c0e00890a952336be867909c9a55daa9ccf48b9ab5d874bac77635331d13effcadfe2dc321135fa8922c212b81820e3f49045f001f746321465b91d6a0ed34632cc1529848e7ef9fbf41f7d1:8726065a9813ad3449498ddfd51ac0d35e103d05b491880d51418b2782061e4f034b3034880e49c7dbe1e168226dd150
4d462264104d551ed98c229ee16d4aa4df79bd2fe3151554b0adb12ca48d2753:86863eb1262d36d8e51130f7a9e85229828a35a458cfb810ec97020df5fbfba89568a81bfdcd6014593cc1621da57720c54523720398dd58e006b89747d75e6eeff0d1d7852f79afd83907f7749245e64a5023f14d4ad663a2b41927ae7f77c3572fa2963fe19615:afd58b032b13a3974494f15d2113a7acca322c50ee686c763948b999641e3334f970ec89c6235b65c59edf5f1a63fd84
4d7d222d9de5be19af35cb1a31f556efff92b5afd8e92ddeb0d6820f5cf4103d:8d03abe66a28da061a733753df81c97d5abbdf1d324aba4e4276b43065532f48898dbf7dc7b87cc40cb65d6dc3db0a2a7f084240360dd2485ab44406f1ab90a790d851d49a1cf78cf4dd218b26e16eabbc4f0f7da27d0573dd30032e01134ca7e3c9569eee3b29fcc5:a915963bc69bcca6ac41a68d5587d22042cb44dc9bb58f85448818420126a9438cec846a72ac50da1f1a5bfb9fddd5e2
6f2091abc6dd00690688ae8e0644b30fc8b8f931a716ba6fed186981673f929e:cea5ee44948390d5d5f3fded51538f5fcb2a6f3a79b88d5df17f0de46280acb25ae5a918f58275991598f414f6f9a00bea7c30555057a6c04393ba1c9ad6ac555d450b96fdc8abd0b0f6d280ecb6594e021f776415bddf392e6f96d1f5ea074ce6ef81fbb26d3cdf8fe9:a4254a9388bcec6d88f08da8f18973bc381a3065d4bd3cd8c16468e073970cd6508eb77217a9fe3643dfa471faf374d8
462a706b92aa64cc85b9e376b5d27cd62f970ec3b7edb817e5b7ac6e239a0e0a:73ef7f6ddc19ad065a9000768ceeae6c455a41281fc581ca31ddde892669a7d9cf1428abfe9f32d410103ad8ade80c1c7d44aacdad110ea4011750aa40ddff3f959c13b04228a5d9f4e9b6ca71ed4f4f06b9995a09050620f18751e18dbf22e7b1793990ec8016717e9be2:b6747c6b843526201d6153e545d3d4fc270da6979d8a9ff291e36b41ab43a3c9308215f3ecfb8873a491bae22b5df37b
65237177f0ab7dd39df8c2345e6f56db27159988c7751646bee0dd2cb35f43d5:f473dd71b0fb705be4b377aeb7071bef91dcd49ac24ab5e2a593ef6fbe402b70bb2db06178b3fd6ea7c5a8333e09e721cfea23d63057a363050a0e3afbbe6b7f0def485edb1ad7345cd1cfc52fef5e688a4b9bc205307699fc22fa3fb6cad8ef9fa3eec1c013285417ad256a:913c8c823160436437293495f66e6c91022530e4c9505d25b3cd174a34c4981147d9671f13f84cf85ec78b800d3b07e4
64a1fe88f385b2d200a3dc9a5e985a13a3c351d8af112cccbdee1bb62c780688:8206710e7302d8167d219794244b11c906eac5c1c4478343bdd4e88367e18a863423a0ac012ccc9358e76be56a973b4336c81e4f31b068a57eb72c7ccb58d0bd9f9782e5eab8e4137d225b875609ef530b26d85b7c552770c66df01396fad7dab75302acb4c27f752b75b0ce49:96df03f30fbe2c5da70980d45ad4024067a82d4796de091159c197ce448d548bc5b3bc954d18e348bad62dd3c2bcab39
32dbc3afc15773c332a2605f24ce2ca3df38567b17a3c7566e772b8fa49f0db8:049f4b3cec24969d680486ead26e96798af64b7a12bcf61da12004c72680bd4f17570d235babbd92cf00037b62da694d16ec1ab11ad22861b14d128c83d9e59b182a264b8fd01d3e69ca91aea576b02ed3038f330c148345b621c4c0fba4ded3003d5b1aa1aa13c1659a4803514c:add4db113f3133a78ee27610aece4df4ff99946b4241fe0cd125025623bd7690d08e2969a59878f9feb4f82e9021772b
50034db59355c0cc3205033a648315c297c6bd57f33c5335a66b6d0dd3ce54fa:8f15974518b575933f99af74fa1305c13c6ccb5b004e379c3df457e05eed5e03aacff98d3dbfb5f4b0ef54decfa025cf83765db9a4c9d39054b3e146ad0b4fde2e4a5c208b698d2ba3842544e9df5f6bc17aab787127329bbe8b45ff40245d77e88f437637cd9a1b71a9ca0dc00b77:b32b1cba0fe06dd6d780bcc1c631df9721e8d3fbcaef372da23f40bdf5f6354e366728185e41e6d2dc04ad3e01d8b4d0
6294ef0407265a0d19bb3ff7530babd8e32f10a34dff0b37135cd28e82984a8b:f822e5229abe4eabbc054bb516ac3586e182beabdcae28d33336fdbdd23cda050e06a33e03e652165c4a5c32734138126b970308bf20086e9074a3bc8a9d5bdd391fdf7205b21716ba441782fd91245be12ef7a68a07e66b22ada6235f1b3c480f78ce4f945f3b7f985d89106831adfb:a58f90af0ee34e2bc2d13ee421aa90d80bb7c2007cce5b65ac0991a0558dc3a1ea46a8c55819bff404ab297c6ae45066
527fce2b29171bbdb60fb573e3639342633b1266e3f695ebce316abc86dd371c:2e549082f9eeb2534d30b90664e5091cf3411ba33fe14e1d86c353837cef4ce5178a59c68f6fea06507291770b52c8f8f28c7791961b2809a0ba682ac78a36de351bc0bd0c54a2916723d5ff2be3c73fb754e7bef4c0388f914ddd31b0f27581369d7a69b78dffc0018f2e1cd9bb840891:a94d698e8362efb86b25b7249fa09c5e627b8f976793ecb85d0f9b06e2ff8077ebafcc8ca104ff869e29f403d9cc9dc7
5a03fe3b4164b562d93e129103200ce033da05153fb1a1dc35452c00c8133f6d:7be5bfa4f0ae60a0c2f5258de564ea6f6a42ccd2802928a3fd14b513262b040d78825696aa9e3891947625af2a8e5ab0f663892eb6afc4d463034ff5e7480538f7b32f93f1cb326a52af92d26305d8f3555807597cc66973d29421ce1721872753fa882a3665536f7a9e05ca536b11f8cc4d:84b36b5b7ede2b059a87e6f6b8d7daeb253b9c39438ec407b5885d4186d958e9388af554e236475642eb1102f774a956
6136e51d3eadb2a46fb631b099565e6e3cc9feff15f33b9c08b49598d4a664f3:c72773b9026f66fa1299f4412a3e0df893a53a1f9c890a1cd653097459691b72387acaaf5574e293f83820117dc074d7396fee2ebbac34fb69a14ad5a528c03ca409438047e02e9c0ea765c200e3b482224af6874e03484ae19595827374b6eb4ca23d399e9e66f2c8a3732a3f2a27a796703e:98b9ad1abf6655723b2d685b84cb29861d19f8244f505a7ae4d2e4cc97eb203fadb87d2eaec040b07c100b5a790f0432
5672c6aeba5dfecc8314b1625f509a4e0dcc36846035bab283e547a5f6d1d4f4:92ed1627ec780c62d968d13bcc181dfaf6470fc1704c92cf7a068a85eae494b7630159f5e044f42a9a7c02e2dde16c8ec81eaf650b4ded6e6cb4e92ac5e54ca4e0d69a4ad3c6d93a0e91a5eaafe989c96c07f5785bdc0b73295eecac04cb71f85fa3744da3cefb9689674d994b52f98ae6a4c784:ac129ded0f581e58ae8f7616633712c68194923db58498290a267552b2885715e437f9414a9b9fb99080f968b56d836a
46c3fc71ef1b3c5a5297f9e6a8e3298838c014ab9598eb9f9adbfe067edb4e16:cd6290b58efc9e7b269dd477525cf240a050b30aebf8784a9749183a139f2c8f2b620cb1555d0006cfb0e6610f5b2b3505cf3541c37ad881c898e8f49d6b1f2618792b3c122f93148c14abebc3be947443f968af0c6a0a77ed159eff04e18852d8ffa2f35ec73e21640a1408a51526011fd7a33161:b04276d98fd1b27645187512e20927e735df17d9dc5bf003cb45661119875a6353286f3dcae081df36f0fda36e71e367
502ee3c386728a0c1b0cd6787ca78b7d6eb8aed2a9fa0ce7cc50b7c91869917c:7f5a1e1ab71a43bf38cd7accfd20af1e99ae55fba55ec043c5f71e4bce2535ca4c8e50f2e85432658670b6b20a8a1d9620b204938fa1aab412b959a364a5272b7364a0204fc3de15490bca3ecb572ab1ae0dba017619cfabbd10138e65399687c9288caac4ad7f2e81e6156dcfe30ee7d1f7770e03c4:94ab75ad8fc599d4a0f077df652a0c9992486dafcacfd5a5d2d8ba3b5ba0db4fd64f322106d9647aff961766aa6ec719
0d59c2cbc5a01509788f8d78768f6b4c70e1e56a3d064ee3a5d214b051373de0:1fb41c046728bc666e16ad566131a5c38b8562942c0f8ab175f2a82886e710e55c3af18be343a496f2426144f985c1101a08ecdd9c39081d650c7192af82a203272661c8424aad5dfd70afbff908f8cdf1b6a12cb3078663e836e317b37b47c7f986b1b4e97f7166dfaaef116205e763eb9998d64964e4:ab511a041e0ada5b71f4699a8b52479c181e1ad068ecd2f5d87fb124f2ecae402f7672851f368ec78d1d0e5ee7514cab
03b0c798cc56bd541ccf9b674cb12a8580fd6a0829ac9f5d2f2ce198c1fabf59:6310c538bdba8e586904f3283421e272ad4518bdbde268184bcc3c33b6078d9460b1a295e927a2310dd291a9b9e2bb4b49cc52b972ac6ac9f7a2bd8d8d8b2737224660135cfa5ddcc874186c794dc3898be4302cc771cb6483a7463830fdfc611adcf09b9a28d71c3ef6ff45228e40c12d84459974b8c944:b8291ecf685152e7bc7c1f3753a4dd2bd213c0ffe969249ab8927a6676ecfab3b67a73f3f7ed0f6187fa976b3e8aeec0
5ae1bb72838eb38c1f4340c6e22686961e3e34b4d2d518d76ab4f881fd479c94:332c3bbd162c8cb774480ef81cede2aa52a0967a869663cab7c8d105e33038562599881f6ef77aa4e3f4225131fc864a27db9c58f5198e6190d5eec728ec4e698b96d1f982edc5d22231b3d76be10a7deeab8f32af36a2cf5d8aed42102ae20679cbb5c8befc8a65f13837710bf86f344590bc74f96e052eae:80e602213b6c748b3e68430ccd1c4fa288aa5c0a411466dd8994ff19ae8be60ad281da36e8a35e2f2b427ac2edc255cc
4113f2add0abb7f05cf56a1281457e9e4f7cfb39d093854b3b4e3bbde237ea23:a83d1cd6999e4940b0d71be96e000e46246a6e451c646756d893b3edb330f2f445858ed12d1d35412fc0eee90204aba0d4fa88583bbc37ee389f88fe0c8dd4c34ffaa8d8c6affeb7feb5f3064af0c06ad3f7a51e4b58f8edc95bd22a02ec4bbe2b2c660762e345d00548995ddcfb10299dd192344d45791ea8bb:a37b03f854b9cb12dc7fc109bea2ccc3423418b89d283715fb8c18db78a68d60b19bdd7e6acf869090b97f538a35df66
17c979dc43d5da70f57027cffc420c6df4245db222a2c3060c7499b2663f6093:c20c4351abcf4ecda9b5478cff8af859ec743eb0d67f6ce2b347349460a2854475d0a6e8165b5174434ca77c088e641c109ac8b8d5879586c034eadfa6bc3a1e66faa02a3dc0fed87af670cc8626df5c71a8360e1911159632558e7a99d4830ce21bcbf3f551465bd1061888b83db174343692b6d796460791ab8d:8ff5917a45cdda175f9d6f4a4a99308617ceec290794fda33d7585f4a3964febf0274479f77210373f3ca6350ae0639b
50adb11ff487000da2714e104241c0039dc6c2eca5e976a6bca8cc80fd2f22d5:3ca6b6f64aab22ea7dcf85eeb05e5eff6102731bb47c94bb737e77b389334742be939b79219fff38452b411052bba588b7872654d0b40f7be1516b8f243047a9aefb40fb70f93e53def38147fe564214605ef2d7508bd7ef44fdfee7882d754e735ba282e044bb953f18f131212d879def4d4d6923d0dcc0d3bb6a14:989e4389faf99c5cbf6798a147fd85cf1331e1e7faa966d6fbed2cd43c673d6a382e0e304f560e5ba816eb1a0b306cf9
55bdf02943f5a8130bcb537972f870043f5d7e3561df8717740e1a7a39bf73df:e288bfeeead0c3050ec6834694323af7bd77dcb52fee6cb54167a73181e487583ac75e63c95e71760cd9a1584b711842f602237f72afb268ef039a044d293d4091abc1807cbec9041ece11905b32ace59db1114047f60ae679c53b465afe03a8ba02ee89e85efebbb93226eaf1cd5c6ce1ef913fbf549934dfbda69dff:94c74729eaecf336b25d82a10798adc97e70cb345313413cf6550d622b0b92b9d4dcf606afb1b7e88db6b3ce106aba04
49a477ecf8786a9f5a44a9da0a83da977b844198068f1cadce28c599d9ebddbd:9944eeed83dde7b21b72ff1491cad3d7a1ceae3f9c5c880be49024d9ec055c55189de80b521df30fd17d558f7bc6ff6d5c9dcacae3ec1242929fae1fd8bd7fedea51acd344a1fa0120f60b1a4679e5177588fc27d713173f4fd47cccc16feb8b44a2d670d7b04c8c14bf37527230f3daa6d7c3a6e958c78376c5940f1063:8045a0358069c43170b238e7dc98952bf84e1caa0925905922b5822ab28b498297e901614909376fa04ced4c852c39da
6bbc2807b27b635285670a68a42d8fafb461fab581e4e2773c199b29088bc554:0c149764b95e6461e820206e5e2b7fa4acf62a3a132db955d5c4ff1cafbfef3b3816aab1bc9bbcf14af47a4e7753f0243842d9b53b3c3c26b9a2e244f2eb06461e2128949e9b437c96cddbddb52d9d6062d4692d05dc624f94fac39401ce51389576f0fd52e670841602645b4ed6a76cc845ec8454538782b3b179e44cd5ec:aaaa82c3fd810924f41b74bdf484460810f5f3274f8102520099f9ca384a5965e50202a36edfa5b0999c7b3c2548ae74

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

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

@ -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

@ -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();
}

@ -23,29 +23,29 @@ import { default as xof_shake128_36 } from './hash-to-curve/expand_message_xof_S
import { default as xof_shake128_256 } from './hash-to-curve/expand_message_xof_SHAKE128_256.json' assert { type: 'json' };
import { default as xof_shake256_36 } from './hash-to-curve/expand_message_xof_SHAKE256_36.json' assert { type: 'json' };
// P256
import { default as p256_ro } from './hash-to-curve/P256_XMD:SHA-256_SSWU_RO_.json' assert { type: 'json' };
import { default as p256_nu } from './hash-to-curve/P256_XMD:SHA-256_SSWU_NU_.json' assert { type: 'json' };
import { default as p256_ro } from './hash-to-curve/P256_XMD_SHA-256_SSWU_RO_.json' assert { type: 'json' };
import { default as p256_nu } from './hash-to-curve/P256_XMD_SHA-256_SSWU_NU_.json' assert { type: 'json' };
// P384
import { default as p384_ro } from './hash-to-curve/P384_XMD:SHA-384_SSWU_RO_.json' assert { type: 'json' };
import { default as p384_nu } from './hash-to-curve/P384_XMD:SHA-384_SSWU_NU_.json' assert { type: 'json' };
import { default as p384_ro } from './hash-to-curve/P384_XMD_SHA-384_SSWU_RO_.json' assert { type: 'json' };
import { default as p384_nu } from './hash-to-curve/P384_XMD_SHA-384_SSWU_NU_.json' assert { type: 'json' };
// P521
import { default as p521_ro } from './hash-to-curve/P521_XMD:SHA-512_SSWU_RO_.json' assert { type: 'json' };
import { default as p521_nu } from './hash-to-curve/P521_XMD:SHA-512_SSWU_NU_.json' assert { type: 'json' };
import { default as p521_ro } from './hash-to-curve/P521_XMD_SHA-512_SSWU_RO_.json' assert { type: 'json' };
import { default as p521_nu } from './hash-to-curve/P521_XMD_SHA-512_SSWU_NU_.json' assert { type: 'json' };
// secp256k1
import { default as secp256k1_ro } from './hash-to-curve/secp256k1_XMD:SHA-256_SSWU_RO_.json' assert { type: 'json' };
import { default as secp256k1_nu } from './hash-to-curve/secp256k1_XMD:SHA-256_SSWU_NU_.json' assert { type: 'json' };
import { default as secp256k1_ro } from './hash-to-curve/secp256k1_XMD_SHA-256_SSWU_RO_.json' assert { type: 'json' };
import { default as secp256k1_nu } from './hash-to-curve/secp256k1_XMD_SHA-256_SSWU_NU_.json' assert { type: 'json' };
// bls-G1
import { default as g1_ro } from './hash-to-curve/BLS12381G1_XMD:SHA-256_SSWU_RO_.json' assert { type: 'json' };
import { default as g1_nu } from './hash-to-curve/BLS12381G1_XMD:SHA-256_SSWU_NU_.json' assert { type: 'json' };
import { default as g1_ro } from './hash-to-curve/BLS12381G1_XMD_SHA-256_SSWU_RO_.json' assert { type: 'json' };
import { default as g1_nu } from './hash-to-curve/BLS12381G1_XMD_SHA-256_SSWU_NU_.json' assert { type: 'json' };
// bls-G2
import { default as g2_ro } from './hash-to-curve/BLS12381G2_XMD:SHA-256_SSWU_RO_.json' assert { type: 'json' };
import { default as g2_nu } from './hash-to-curve/BLS12381G2_XMD:SHA-256_SSWU_NU_.json' assert { type: 'json' };
import { default as g2_ro } from './hash-to-curve/BLS12381G2_XMD_SHA-256_SSWU_RO_.json' assert { type: 'json' };
import { default as g2_nu } from './hash-to-curve/BLS12381G2_XMD_SHA-256_SSWU_NU_.json' assert { type: 'json' };
// ed25519
import { default as ed25519_ro } from './hash-to-curve/edwards25519_XMD:SHA-512_ELL2_RO_.json' assert { type: 'json' };
import { default as ed25519_nu } from './hash-to-curve/edwards25519_XMD:SHA-512_ELL2_NU_.json' assert { type: 'json' };
import { default as ed25519_ro } from './hash-to-curve/edwards25519_XMD_SHA-512_ELL2_RO_.json' assert { type: 'json' };
import { default as ed25519_nu } from './hash-to-curve/edwards25519_XMD_SHA-512_ELL2_NU_.json' assert { type: 'json' };
// ed448
import { default as ed448_ro } from './hash-to-curve/edwards448_XOF:SHAKE256_ELL2_RO_.json' assert { type: 'json' };
import { default as ed448_nu } from './hash-to-curve/edwards448_XOF:SHAKE256_ELL2_NU_.json' assert { type: 'json' };
import { default as ed448_ro } from './hash-to-curve/edwards448_XOF_SHAKE256_ELL2_RO_.json' assert { type: 'json' };
import { default as ed448_nu } from './hash-to-curve/edwards448_XOF_SHAKE256_ELL2_NU_.json' assert { type: 'json' };
function testExpandXMD(hash, vectors) {
describe(`${vectors.hash}/${vectors.DST.length}`, () => {

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

@ -10,6 +10,7 @@ import { hexToBytes, bytesToHex } from '../esm/abstract/utils.js';
import { default as ecdsa } from './wycheproof/ecdsa_test.json' assert { type: 'json' };
import { default as ecdh } from './wycheproof/ecdh_test.json' assert { type: 'json' };
import { default as rfc6979 } from './vectors/rfc6979.json' assert { type: 'json' };
import { default as endoVectors } from './vectors/secp256k1/endomorphism.json' assert { type: 'json' };
import { default as ecdh_secp224r1_test } from './wycheproof/ecdh_secp224r1_test.json' assert { type: 'json' };
import { default as ecdh_secp256r1_test } from './wycheproof/ecdh_secp256r1_test.json' assert { type: 'json' };
@ -94,31 +95,51 @@ should('fields', () => {
for (const n in vectors) deepStrictEqual(NIST[n].CURVE.Fp.ORDER, vectors[n]);
});
// We don't support ASN.1 encoding of points. For tests we've implemented quick
// and dirty parser: take X last bytes of ASN.1 encoded sequence.
// If that doesn't work, we ignore such vector.
function verifyECDHVector(test, curve) {
if (test.flags.includes('InvalidAsn')) return; // Ignore invalid ASN
if (test.result === 'valid' || test.result === 'acceptable') {
const fnLen = curve.CURVE.nByteLength; // 32 for P256
const fpLen = curve.CURVE.Fp.BYTES; // 32 for P256
const encodedHexLen = fpLen * 2 * 2 + 2; // 130 (65 * 2) for P256
const pubB = test.public.slice(-encodedHexLen); // slice(-130) for P256
let privA = test.private;
// Some wycheproof vectors are padded with 00:
// 00c6cafb74e2a50c83b3d232c4585237f44d4c5433c4b3f50ce978e6aeda3a4f5d
// instead of
// c6cafb74e2a50c83b3d232c4585237f44d4c5433c4b3f50ce978e6aeda3a4f5d
if (privA.length / 2 === fnLen + 1 && privA.startsWith('00')) privA = privA.slice(2);
if (!curve.utils.isValidPrivateKey(privA)) return; // Ignore invalid private key size
try {
curve.ProjectivePoint.fromHex(pubB);
} catch (e) {
if (e.message.startsWith('Point of length')) return; // Ignore
throw e;
}
const shared = curve.getSharedSecret(privA, pubB).subarray(1);
deepStrictEqual(hex(shared), test.shared, 'valid');
} else if (test.result === 'invalid') {
let failed = false;
try {
curve.getSharedSecret(test.private, test.public);
} catch (error) {
failed = true;
}
deepStrictEqual(failed, true, 'invalid');
} else throw new Error('unknown test result');
}
describe('wycheproof ECDH', () => {
for (const group of ecdh.testGroups) {
const CURVE = NIST[group.curve];
if (!CURVE) continue;
const curve = NIST[group.curve];
if (!curve) continue;
should(group.curve, () => {
for (const test of group.tests) {
if (test.result === 'valid' || test.result === 'acceptable') {
try {
const pub = CURVE.ProjectivePoint.fromHex(test.public);
} catch (e) {
// Our strict validation filter doesn't let weird-length DER vectors
if (e.message.startsWith('Point of length')) continue;
throw e;
}
const shared = CURVE.getSharedSecret(test.private, test.public);
deepStrictEqual(shared, test.shared, 'valid');
} else if (test.result === 'invalid') {
let failed = false;
try {
CURVE.getSharedSecret(test.private, test.public);
} catch (error) {
failed = true;
}
deepStrictEqual(failed, true, 'invalid');
} else throw new Error('unknown test result');
verifyECDHVector(test, curve);
}
});
}
@ -150,30 +171,12 @@ describe('wycheproof ECDH', () => {
for (const name in WYCHEPROOF_ECDH) {
const { curve, tests } = WYCHEPROOF_ECDH[name];
for (let i = 0; i < tests.length; i++) {
const test = tests[i];
for (let j = 0; j < test.testGroups.length; j++) {
const group = test.testGroups[j];
should(`additional ${name} (${i}/${j})`, () => {
const curveTests = tests[i];
for (let j = 0; j < curveTests.testGroups.length; j++) {
const group = curveTests.testGroups[j];
should(`additional ${name} (${group.tests.length})`, () => {
for (const test of group.tests) {
if (test.result === 'valid' || test.result === 'acceptable') {
try {
const pub = curve.ProjectivePoint.fromHex(test.public);
} catch (e) {
// Our strict validation filter doesn't let weird-length DER vectors
if (e.message.includes('Point of length')) continue;
throw e;
}
const shared = curve.getSharedSecret(test.private, test.public);
deepStrictEqual(hex(shared), test.shared, 'valid');
} else if (test.result === 'invalid') {
let failed = false;
try {
curve.getSharedSecret(test.private, test.public);
} catch (error) {
failed = true;
}
deepStrictEqual(failed, true, 'invalid');
} else throw new Error('unknown test result');
verifyECDHVector(test, curve);
}
});
}
@ -438,7 +441,7 @@ describe('RFC6979', () => {
}
});
should('DER Leading zero', () => {
should('properly add leading zero to DER', () => {
// Valid DER
deepStrictEqual(
DER.toSig(
@ -465,6 +468,16 @@ should('DER Leading zero', () => {
);
});
should('have proper GLV endomorphism logic in secp256k1', () => {
const Point = secp256k1.ProjectivePoint;
for (let item of endoVectors) {
const point = Point.fromAffine({ x: BigInt(item.ax), y: BigInt(item.ay) });
const c = point.multiplyUnsafe(BigInt(item.scalar)).toAffine();
deepStrictEqual(c.x, BigInt(item.cx));
deepStrictEqual(c.y, BigInt(item.cy));
}
});
// ESM is broken.
import url from 'url';
if (import.meta.url === url.pathToFileURL(process.argv[1]).href) {

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

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

@ -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"
}
]

@ -1,20 +1,9 @@
{
"compilerOptions": {
"strict": true,
"outDir": "esm",
"target": "es2020",
"module": "es6",
"moduleResolution": "node16",
"noUnusedLocals": true,
"sourceMap": true,
"baseUrl": ".",
"paths": {
"@noble/hashes/crypto": [ "src/crypto" ]
},
},
"include": ["src"],
"exclude": [
"node_modules",
"lib",
],
}
"extends": "@paulmillr/jsbt/tsconfigs/esm.json",
"compilerOptions": {
"outDir": "esm",
"target": "ES2015"
},
"include": ["index.ts", "src"],
"exclude": ["node_modules", "lib"]
}

@ -1,20 +1,9 @@
{
"compilerOptions": {
"strict": true,
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"outDir": ".",
"target": "es2020",
"lib": ["es2020"], // Set explicitly to remove DOM
"module": "commonjs",
"moduleResolution": "node",
"noUnusedLocals": true,
"baseUrl": ".",
},
"include": ["src"],
"exclude": [
"node_modules",
"*.d.ts"
],
}
"extends": "@paulmillr/jsbt/tsconfigs/cjs.json",
"compilerOptions": {
"outDir": ".",
"target": "ES2015"
},
"include": ["index.ts", "src"],
"exclude": ["node_modules", "lib"]
}