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 github: paulmillr
# custom: https://paulmillr.com/funding/

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

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

@ -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
node_modules/
coverage/
/*.js /*.js
/*.ts
/*.js.map
/*.d.ts.map
/esm/*.js /esm/*.js
/esm/*.ts *.d.ts
/esm/*.js.map *.d.ts.map
/esm/*.d.ts.map *.js.map
/esm/abstract /build
/abstract/ /abstract
/esm/abstract

@ -1,4 +1,5 @@
{ {
"printWidth": 100, "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 # Security Policy
See [README's Security section](./README.md#security) for detailed description of internal security practices.
## Supported Versions ## Supported Versions
| Version | Supported | | Version | Supported |

Binary file not shown.

@ -1,11 +1,7 @@
# Audit # Audit
The library has been audited during Jan-Feb 2023 by an independent security firm [Trail of Bits](https://www.trailofbits.com): All audits of the library are described in [README's Security section](../README.md#security)
[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).
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). [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. 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('sign', 50, () => bls.sign('09', priv));
await mark('verify', 50, () => bls.verify(sig, '09', pub)); await mark('verify', 50, () => bls.verify(sig, '09', pub));
await mark('pairing', 100, () => bls.pairing(p1, p2)); await mark('pairing', 100, () => bls.pairing(p1, p2));
const scalars1 = Array(4096).fill(0).map(i => 2n ** 235n - BigInt(i));
const scalars2 = Array(4096).fill(0).map(i => 2n ** 241n + BigInt(i));
const points = scalars1.map(s => bls.G1.ProjectivePoint.BASE.multiply(s));
await mark('MSM 4096 scalars x points', 1, () => {
// naive approach, not using multi-scalar-multiplication
let sum = bls.G1.ProjectivePoint.ZERO;
for (let i = 0; i < 4096; i++) {
const scalar = scalars2[i];
const G1 = points[i];
const mutliplied = G1.multiplyUnsafe(scalar);
sum = sum.add(mutliplied);
}
});
await mark('aggregatePublicKeys/8', 100, () => bls.aggregatePublicKeys(pubs.slice(0, 8))); await mark('aggregatePublicKeys/8', 100, () => bls.aggregatePublicKeys(pubs.slice(0, 8)));
await mark('aggregatePublicKeys/32', 50, () => bls.aggregatePublicKeys(pub32)); await mark('aggregatePublicKeys/32', 50, () => bls.aggregatePublicKeys(pub32));
await mark('aggregatePublicKeys/128', 20, () => bls.aggregatePublicKeys(pub128)); await mark('aggregatePublicKeys/128', 20, () => bls.aggregatePublicKeys(pub128));

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

18
benchmark/decaf448.js Normal file

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

18
benchmark/ristretto255.js Normal file

@ -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", "type": "module",
"browser": { "sideEffects": false
"crypto": false,
"./crypto": "./esm/crypto.js"
}
} }

69
package-lock.json generated

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

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

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

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

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

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

@ -150,17 +150,15 @@ export function montgomery(curveDef: CurveType): CurveFn {
function decodeUCoordinate(uEnc: Hex): bigint { function decodeUCoordinate(uEnc: Hex): bigint {
// Section 5: When receiving such an array, implementations of X25519 // Section 5: When receiving such an array, implementations of X25519
// MUST mask the most significant bit in the final byte. // 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); const u = ensureBytes('u coordinate', uEnc, montgomeryBytes);
// u[fieldLen-1] crashes QuickJS (TypeError: out-of-bound numeric index) if (fieldLen === 32) u[31] &= 127; // 0b0111_1111
if (fieldLen === montgomeryBytes) u[fieldLen - 1] &= 127; // 0b0111_1111
return bytesToNumberLE(u); return bytesToNumberLE(u);
} }
function decodeScalar(n: Hex): bigint { function decodeScalar(n: Hex): bigint {
const bytes = ensureBytes('scalar', n); const bytes = ensureBytes('scalar', n);
if (bytes.length !== montgomeryBytes && bytes.length !== fieldLen) const len = bytes.length;
throw new Error(`Expected ${montgomeryBytes} or ${fieldLen} bytes, got ${bytes.length}`); if (len !== montgomeryBytes && len !== fieldLen)
throw new Error(`Expected ${montgomeryBytes} or ${fieldLen} bytes, got ${len}`);
return bytesToNumberLE(adjustScalarBytes(bytes)); return bytesToNumberLE(adjustScalarBytes(bytes));
} }
function scalarMult(scalar: Hex, u: Hex): Uint8Array { function scalarMult(scalar: Hex, u: Hex): Uint8Array {

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

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

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

@ -1,15 +1,9 @@
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */ /*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
// bls12-381 pairing-friendly Barreto-Lynn-Scott elliptic curve construction allows to: // bls12-381 is pairing-friendly Barreto-Lynn-Scott elliptic curve construction allowing to:
// - Construct zk-SNARKs at the 128-bit security // - Construct zk-SNARKs at the 120-bit security
// - Use threshold signatures, which allows a user to sign lots of messages with one signature and // - Efficiently verify N aggregate signatures with 1 pairing and N ec additions:
// verify them swiftly in a batch, using Boneh-Lynn-Shacham signature scheme. // the Boneh-Lynn-Shacham signature scheme is orders of magnitude more efficient than Schnorr
//
// 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).
// //
// ### Summary // ### Summary
// 1. BLS Relies on Bilinear Pairing (expensive) // 1. BLS Relies on Bilinear Pairing (expensive)
@ -25,8 +19,17 @@
// - `S = pk x H(m)` - signing // - `S = pk x H(m)` - signing
// - `e(P, H(m)) == e(G, S)` - verification using pairings // - `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 // - `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 { sha256 } from '@noble/hashes/sha256';
import { randomBytes } from '@noble/hashes/utils'; import { randomBytes } from '@noble/hashes/utils';
import { bls, CurveFn } from './abstract/bls.js'; import { bls, CurveFn } from './abstract/bls.js';
@ -37,7 +40,6 @@ import {
numberToBytesBE, numberToBytesBE,
bytesToNumberBE, bytesToNumberBE,
bitLen, bitLen,
bitSet,
bitGet, bitGet,
Hex, Hex,
bitMask, bitMask,
@ -60,11 +62,10 @@ const _8n = BigInt(8), _16n = BigInt(16);
// CURVE FIELDS // CURVE FIELDS
// Finite field over p. // Finite field over p.
const Fp = mod.Field( const Fp_raw = BigInt(
BigInt( '0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab'
'0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab'
)
); );
const Fp = mod.Field(Fp_raw);
type Fp = bigint; type Fp = bigint;
// Finite field over r. // Finite field over r.
// This particular field is not used anywhere in bls12-381, but it is still useful. // This particular field is not used anywhere in bls12-381, but it is still useful.
@ -110,10 +111,7 @@ type Fp2Utils = {
// G² - 1 // G² - 1
// h2q // h2q
// NOTE: ORDER was wrong! // NOTE: ORDER was wrong!
const FP2_ORDER = const FP2_ORDER = Fp_raw * Fp_raw;
BigInt(
'0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab'
) ** _2n;
const Fp2: mod.IField<Fp2> & Fp2Utils = { const Fp2: mod.IField<Fp2> & Fp2Utils = {
ORDER: FP2_ORDER, ORDER: FP2_ORDER,
@ -181,7 +179,7 @@ const Fp2: mod.IField<Fp2> & Fp2Utils = {
if (im1 > im2 || (im1 === im2 && re1 > re2)) return x1; if (im1 > im2 || (im1 === im2 && re1 > re2)) return x1;
return x2; return x2;
}, },
// Same as sgn0_fp2 in draft-irtf-cfrg-hash-to-curve-16 // Same as sgn0_m_eq_2 in RFC 9380
isOdd: (x: Fp2) => { isOdd: (x: Fp2) => {
const { re: x0, im: x1 } = Fp2.reim(x); const { re: x0, im: x1 } = Fp2.reim(x);
const sign_0 = x0 % _2n; const sign_0 = x0 % _2n;
@ -784,8 +782,7 @@ const FP12_FROBENIUS_COEFFICIENTS = [
// HashToCurve // HashToCurve
// 3-isogeny map from E' to E // 3-isogeny map from E' to E https://www.rfc-editor.org/rfc/rfc9380#appendix-E.3
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#appendix-E.3
const isogenyMapG2 = isogenyMap( const isogenyMapG2 = isogenyMap(
Fp2, Fp2,
[ [
@ -989,7 +986,7 @@ function G2psi2(c: ProjConstructor<Fp2>, P: ProjPointType<Fp2>) {
// //
// Parameter definitions are in section 5.3 of the spec unless otherwise noted. // Parameter definitions are in section 5.3 of the spec unless otherwise noted.
// Parameter values come from section 8.8.2 of the spec. // Parameter values come from section 8.8.2 of the spec.
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-8.8.2 // https://www.rfc-editor.org/rfc/rfc9380#section-8.8.2
// //
// Base field F is GF(p^m) // Base field F is GF(p^m)
// p = 0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab // p = 0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab
@ -1021,11 +1018,41 @@ const htfDefaults = Object.freeze({
// Encoding utils // Encoding utils
// Point on G1 curve: (x, y) // 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 // 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>) { function signatureG2ToRawBytes(point: ProjPointType<Fp2>) {
// NOTE: by some reasons it was missed in bls12-381, looks like bug // 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: x0, im: x1 } = Fp2.reim(x);
const { re: y0, im: y1 } = Fp2.reim(y); const { re: y0, im: y1 } = Fp2.reim(y);
const tmp = y1 > _0n ? y1 * _2n : y0 * _2n; const tmp = y1 > _0n ? y1 * _2n : y0 * _2n;
const aflag1 = Boolean((tmp / Fp.ORDER) & _1n); const sort = Boolean((tmp / Fp.ORDER) & _1n);
const z1 = bitSet(bitSet(x1, 381, aflag1), S_BIT_POS, true);
const z2 = x0; 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: // To verify curve parameters, see pairing-friendly-curves spec:
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-pairing-friendly-curves-09 // https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-pairing-friendly-curves-11
// Basic math is done over finite fields over p. // Basic math is done over finite fields over p.
// More complicated math is done over polynominal extension fields. // More complicated math is done over polynominal extension fields.
// To simplify calculations in Fp12, we construct extension tower: // To simplify calculations in Fp12, we construct extension tower:
@ -1079,7 +1108,7 @@ export const bls12_381: CurveFn<Fp, Fp2, Fp6, Fp12> = bls({
), ),
a: Fp.ZERO, a: Fp.ZERO,
b: _4n, b: _4n,
htfDefaults: { ...htfDefaults, m: 1 }, htfDefaults: { ...htfDefaults, m: 1, DST: 'BLS_SIG_BLS12381G1_XMD:SHA-256_SSWU_RO_NUL_' },
wrapPrivateKey: true, wrapPrivateKey: true,
allowInfinityPoint: true, allowInfinityPoint: true,
// Checks is the point resides in prime-order subgroup. // 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 // Clear cofactor of G1
// https://eprint.iacr.org/2019/403 // https://eprint.iacr.org/2019/403
clearCofactor: (c, point) => { clearCofactor: (_c, point) => {
// return this.multiplyUnsafe(CURVE.h); // return this.multiplyUnsafe(CURVE.h);
return point.multiplyUnsafe(bls12_381.params.x).add(point); // x*P + P return point.multiplyUnsafe(bls12_381.params.x).add(point); // x*P + P
}, },
@ -1121,26 +1150,30 @@ export const bls12_381: CurveFn<Fp, Fp2, Fp6, Fp12> = bls({
return isogenyMapG1(x, y); return isogenyMapG1(x, y);
}, },
fromBytes: (bytes: Uint8Array): AffinePoint<Fp> => { fromBytes: (bytes: Uint8Array): AffinePoint<Fp> => {
bytes = bytes.slice(); const { compressed, infinity, sort, value } = parseMask(bytes);
if (bytes.length === 48) { if (value.length === 48 && compressed) {
// TODO: Fp.bytes // TODO: Fp.bytes
const P = Fp.ORDER; const P = Fp.ORDER;
const compressedValue = bytesToNumberBE(bytes); const compressedValue = bytesToNumberBE(value);
const bflag = bitGet(compressedValue, I_BIT_POS);
// Zero // Zero
if (bflag === _1n) return { x: _0n, y: _0n };
const x = Fp.create(compressedValue & Fp.MASK); 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 const right = Fp.add(Fp.pow(x, _3n), Fp.create(bls12_381.params.G1b)); // y² = x³ + b
let y = Fp.sqrt(right); let y = Fp.sqrt(right);
if (!y) throw new Error('Invalid compressed G1 point'); if (!y) throw new Error('Invalid compressed G1 point');
const aflag = bitGet(compressedValue, C_BIT_POS); if ((y * _2n) / P !== BigInt(sort)) y = Fp.neg(y);
if ((y * _2n) / P !== aflag) y = Fp.neg(y);
return { x: Fp.create(x), y: Fp.create(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 // Check if the infinity flag is set
if ((bytes[0] & (1 << 6)) !== 0) return bls12_381.G1.ProjectivePoint.ZERO.toAffine(); const x = bytesToNumberBE(value.subarray(0, Fp.BYTES));
const x = bytesToNumberBE(bytes.subarray(0, Fp.BYTES)); const y = bytesToNumberBE(value.subarray(Fp.BYTES));
const y = bytesToNumberBE(bytes.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) }; return { x: Fp.create(x), y: Fp.create(y) };
} else { } else {
throw new Error('Invalid point G1, expected 48/96 bytes'); 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 (isCompressed) {
if (isZero) return COMPRESSED_ZERO.slice(); if (isZero) return COMPRESSED_ZERO.slice();
const P = Fp.ORDER; const P = Fp.ORDER;
let num; const sort = Boolean((y * _2n) / P);
num = bitSet(x, C_BIT_POS, Boolean((y * _2n) / P)); // set aflag return setMask(numberToBytesBE(x, Fp.BYTES), { compressed: true, sort });
num = bitSet(num, S_BIT_POS, true);
return numberToBytesBE(num, Fp.BYTES);
} else { } else {
if (isZero) { if (isZero) {
// 2x PUBLIC_KEY_LENGTH // 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), // 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 // 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, a: Fp2.ZERO,
b: Fp2.fromBigTuple([4n, _4n]), b: Fp2.fromBigTuple([_4n, _4n]),
hEff: BigInt( hEff: BigInt(
'0xbc69f08f2ee75b3584c6a0ea91b352888e2a8e9145ad7689986ff031508ffe1329c2f178731db956d82bf015d1212b02ec0ec69d7477c1ae954cbc06689f6a359894c0adebbf6b4e8020005aaa95551' '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) return Q; // [x²-x-1]P + [x-1]Ψ(P) + Ψ²(2P)
}, },
fromBytes: (bytes: Uint8Array): AffinePoint<Fp2> => { fromBytes: (bytes: Uint8Array): AffinePoint<Fp2> => {
bytes = bytes.slice(); const { compressed, infinity, sort, value } = parseMask(bytes);
const m_byte = bytes[0] & 0xe0; if (
if (m_byte === 0x20 || m_byte === 0x60 || m_byte === 0xe0) { (!compressed && !infinity && sort) || // 00100000
throw new Error('Invalid encoding flag: ' + m_byte); (!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 L = Fp.BYTES;
const slc = (b: Uint8Array, from: number, to?: number) => bytesToNumberBE(b.slice(from, to)); 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 b = bls12_381.params.G2b;
const P = Fp.ORDER; const P = Fp.ORDER;
if (infinity) {
bytes[0] = bytes[0] & 0x1f; // clear flags
if (bitI) {
// check that all bytes are 0 // 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'); throw new Error('Invalid compressed G2 point');
} }
return { x: Fp2.ZERO, y: Fp2.ZERO }; return { x: Fp2.ZERO, y: Fp2.ZERO };
} }
const x_1 = slc(bytes, 0, L); const x_1 = slc(value, 0, L);
const x_0 = slc(bytes, L, 2 * L); const x_0 = slc(value, L, 2 * L);
const x = Fp2.create({ c0: Fp.create(x_0), c1: Fp.create(x_1) }); 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 const right = Fp2.add(Fp2.pow(x, _3n), b); // y² = x³ + 4 * (u+1) = x³ + b
let y = Fp2.sqrt(right); let y = Fp2.sqrt(right);
const Y_bit = y.c1 === _0n ? (y.c0 * _2n) / P : (y.c1 * _2n) / P ? _1n : _0n; 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 }; return { x, y };
} else if (bytes.length === 192 && !bitC) { } else if (value.length === 192 && !compressed) {
// Check if the infinity flag is set if (infinity) {
if ((bytes[0] & (1 << 6)) !== 0) { 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 }; return { x: Fp2.ZERO, y: Fp2.ZERO };
} }
const x1 = slc(bytes, 0, L); const x1 = slc(value, 0, L);
const x0 = slc(bytes, L, 2 * L); const x0 = slc(value, L, 2 * L);
const y1 = slc(bytes, 2 * L, 3 * L); const y1 = slc(value, 2 * L, 3 * L);
const y0 = slc(bytes, 3 * L, 4 * L); const y0 = slc(value, 3 * L, 4 * L);
return { x: Fp2.fromBigTuple([x0, x1]), y: Fp2.fromBigTuple([y0, y1]) }; return { x: Fp2.fromBigTuple([x0, x1]), y: Fp2.fromBigTuple([y0, y1]) };
} else { } else {
throw new Error('Invalid point G2, expected 96/192 bytes'); 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 (isCompressed) {
if (isZero) return concatB(COMPRESSED_ZERO, numberToBytesBE(_0n, len)); if (isZero) return concatB(COMPRESSED_ZERO, numberToBytesBE(_0n, len));
const flag = Boolean(y.c1 === _0n ? (y.c0 * _2n) / P : (y.c1 * _2n) / P); 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?) return concatB(
let x_1 = bitSet(x.c1, C_BIT_POS, flag); setMask(numberToBytesBE(x.c1, len), { compressed: true, sort: flag }),
x_1 = bitSet(x_1, S_BIT_POS, true); numberToBytesBE(x.c0, len)
return concatB(numberToBytesBE(x_1, len), numberToBytesBE(x.c0, len)); );
} else { } else {
if (isZero) return concatB(new Uint8Array([0x40]), new Uint8Array(4 * len - 1)); // bytes[0] |= 1 << 6; if (isZero) return concatB(new Uint8Array([0x40]), new Uint8Array(4 * len - 1)); // bytes[0] |= 1 << 6;
const { re: x0, im: x1 } = Fp2.reim(x); const { re: x0, im: x1 } = Fp2.reim(x);
@ -1307,17 +1362,15 @@ export const bls12_381: CurveFn<Fp, Fp2, Fp6, Fp12> = bls({
Signature: { Signature: {
// TODO: Optimize, it's very slow because of sqrt. // TODO: Optimize, it's very slow because of sqrt.
fromHex(hex: Hex): ProjPointType<Fp2> { fromHex(hex: Hex): ProjPointType<Fp2> {
hex = ensureBytes('signatureHex', hex); const { infinity, sort, value } = parseMask(ensureBytes('signatureHex', hex));
const P = Fp.ORDER; const P = Fp.ORDER;
const half = hex.length / 2; const half = value.length / 2;
if (half !== 48 && half !== 96) if (half !== 48 && half !== 96)
throw new Error('Invalid compressed signature length, must be 96 or 192'); throw new Error('Invalid compressed signature length, must be 96 or 192');
const z1 = bytesToNumberBE(hex.slice(0, half)); const z1 = bytesToNumberBE(value.slice(0, half));
const z2 = bytesToNumberBE(hex.slice(half)); const z2 = bytesToNumberBE(value.slice(half));
// Indicates the infinity point // Indicates the infinity point
const bflag1 = bitGet(z1, I_BIT_POS); if (infinity) return bls12_381.G2.ProjectivePoint.ZERO;
if (bflag1 === _1n) return bls12_381.G2.ProjectivePoint.ZERO;
const x1 = Fp.create(z1 & Fp.MASK); const x1 = Fp.create(z1 & Fp.MASK);
const x2 = Fp.create(z2); const x2 = Fp.create(z2);
const x = Fp2.create({ c0: x2, c1: x1 }); 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 // 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 // If y1 happens to be zero, then use the bit of y0
const { re: y0, im: y1 } = Fp2.reim(y); 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 isGreater = y1 > _0n && (y1 * _2n) / P !== aflag1;
const isZero = y1 === _0n && (y0 * _2n) / P !== aflag1; const isZero = y1 === _0n && (y0 * _2n) / P !== aflag1;
if (isGreater || isZero) y = Fp2.neg(y); if (isGreater || isZero) y = Fp2.neg(y);

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

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

@ -1,14 +1,25 @@
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */ /*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
import { shake256 } from '@noble/hashes/sha3'; import { shake256 } from '@noble/hashes/sha3';
import { concatBytes, randomBytes, utf8ToBytes, wrapConstructor } from '@noble/hashes/utils'; import { concatBytes, randomBytes, utf8ToBytes, wrapConstructor } from '@noble/hashes/utils';
import { twistedEdwards } from './abstract/edwards.js'; import { ExtPointType, twistedEdwards } from './abstract/edwards.js';
import { mod, pow2, Field } from './abstract/modular.js'; import { mod, pow2, Field, isNegativeLE } from './abstract/modular.js';
import { montgomery } from './abstract/montgomery.js'; import { montgomery } from './abstract/montgomery.js';
import * as htf from './abstract/hash-to-curve.js'; import { createHasher, htfBasicOpts, expand_message_xof } from './abstract/hash-to-curve.js';
import {
bytesToHex,
bytesToNumberLE,
ensureBytes,
equalBytes,
Hex,
numberToBytesLE,
} from './abstract/utils.js';
import { AffinePoint, Group } from './abstract/curve.js';
/** /**
* Edwards448 (not Ed448-Goldilocks) curve with following addons: * Edwards448 (not Ed448-Goldilocks) curve with following addons:
* * X448 ECDH * - X448 ECDH
* - Decaf cofactor elimination
* - Elligator hash-to-group / point indistinguishability
* Conforms to RFC 8032 https://www.rfc-editor.org/rfc/rfc8032.html#section-5.2 * Conforms to RFC 8032 https://www.rfc-editor.org/rfc/rfc8032.html#section-5.2
*/ */
@ -18,15 +29,16 @@ const ed448P = BigInt(
'726838724295606890549323807888004534353641360687318060281490199180612328166730772686396383698676545930088884461843637361053498018365439' '726838724295606890549323807888004534353641360687318060281490199180612328166730772686396383698676545930088884461843637361053498018365439'
); );
// prettier-ignore
const _1n = BigInt(1), _2n = BigInt(2), _3n = BigInt(3), _4n = BigInt(4), _11n = BigInt(11);
// prettier-ignore
const _22n = BigInt(22), _44n = BigInt(44), _88n = BigInt(88), _223n = BigInt(223);
// powPminus3div4 calculates z = x^k mod p, where k = (p-3)/4. // powPminus3div4 calculates z = x^k mod p, where k = (p-3)/4.
// Used for efficient square root calculation. // Used for efficient square root calculation.
// ((P-3)/4).toString(2) would produce bits [223x 1, 0, 222x 1] // ((P-3)/4).toString(2) would produce bits [223x 1, 0, 222x 1]
function ed448_pow_Pminus3div4(x: bigint): bigint { function ed448_pow_Pminus3div4(x: bigint): bigint {
const P = ed448P; const P = ed448P;
// prettier-ignore
const _1n = BigInt(1), _2n = BigInt(2), _3n = BigInt(3), _11n = BigInt(11);
// prettier-ignore
const _22n = BigInt(22), _44n = BigInt(44), _88n = BigInt(88), _223n = BigInt(223);
const b2 = (x * x * x) % P; const b2 = (x * x * x) % P;
const b3 = (b2 * b2 * x) % P; const b3 = (b2 * b2 * x) % P;
const b6 = (pow2(b3, _3n, P) * b3) % P; const b6 = (pow2(b3, _3n, P) * b3) % P;
@ -53,8 +65,29 @@ function adjustScalarBytes(bytes: Uint8Array): Uint8Array {
return bytes; return bytes;
} }
// Constant-time ratio of u to v. Allows to combine inversion and square root u/√v.
// Uses algo from RFC8032 5.1.3.
function uvRatio(u: bigint, v: bigint): { isValid: boolean; value: bigint } {
const P = ed448P;
// https://www.rfc-editor.org/rfc/rfc8032#section-5.2.3
// To compute the square root of (u/v), the first step is to compute the
// candidate root x = (u/v)^((p+1)/4). This can be done using the
// following trick, to use a single modular powering for both the
// inversion of v and the square root:
// x = (u/v)^((p+1)/4) = u³v(u⁵v³)^((p-3)/4) (mod p)
const u2v = mod(u * u * v, P); // u²v
const u3v = mod(u2v * u, P); // u³v
const u5v3 = mod(u3v * u2v * v, P); // u⁵v³
const root = ed448_pow_Pminus3div4(u5v3);
const x = mod(u3v * root, P);
// Verify that root is exists
const x2 = mod(x * x, P); // x²
// If vx² = u, the recovered x-coordinate is x. Otherwise, no
// square root exists, and the decoding fails.
return { isValid: mod(x2 * v, P) === u, value: x };
}
const Fp = Field(ed448P, 456, true); const Fp = Field(ed448P, 456, true);
const _4n = BigInt(4);
const ED448_DEF = { const ED448_DEF = {
// Param: a // Param: a
@ -63,13 +96,14 @@ const ED448_DEF = {
d: BigInt( d: BigInt(
'726838724295606890549323807888004534353641360687318060281490199180612328166730772686396383698676545930088884461843637361053498018326358' '726838724295606890549323807888004534353641360687318060281490199180612328166730772686396383698676545930088884461843637361053498018326358'
), ),
// Finite field 𝔽p over which we'll do calculations; 2n ** 448n - 2n ** 224n - 1n // Finite field 𝔽p over which we'll do calculations; 2n**448n - 2n**224n - 1n
Fp, Fp,
// Subgroup order: how many points curve has; // Subgroup order: how many points curve has;
// 2n**446n - 13818066809895115352007386748515426880336692474882178609894547503885n // 2n**446n - 13818066809895115352007386748515426880336692474882178609894547503885n
n: BigInt( n: BigInt(
'181709681073901722637330951972001133588410340171829515070372549795146003961539585716195755291692375963310293709091662304773755859649779' '181709681073901722637330951972001133588410340171829515070372549795146003961539585716195755291692375963310293709091662304773755859649779'
), ),
// RFC 7748 has 56-byte keys, RFC 8032 has 57-byte keys
nBitLength: 456, nBitLength: 456,
// Cofactor // Cofactor
h: BigInt(4), h: BigInt(4),
@ -94,49 +128,30 @@ const ED448_DEF = {
data data
); );
}, },
uvRatio,
// Constant-time ratio of u to v. Allows to combine inversion and square root u/√v.
// Uses algo from RFC8032 5.1.3.
uvRatio: (u: bigint, v: bigint): { isValid: boolean; value: bigint } => {
const P = ed448P;
// https://datatracker.ietf.org/doc/html/rfc8032#section-5.2.3
// To compute the square root of (u/v), the first step is to compute the
// candidate root x = (u/v)^((p+1)/4). This can be done using the
// following trick, to use a single modular powering for both the
// inversion of v and the square root:
// x = (u/v)^((p+1)/4) = u³v(u⁵v³)^((p-3)/4) (mod p)
const u2v = mod(u * u * v, P); // u²v
const u3v = mod(u2v * u, P); // u³v
const u5v3 = mod(u3v * u2v * v, P); // u⁵v³
const root = ed448_pow_Pminus3div4(u5v3);
const x = mod(u3v * root, P);
// Verify that root is exists
const x2 = mod(x * x, P); // x²
// If vx² = u, the recovered x-coordinate is x. Otherwise, no
// square root exists, and the decoding fails.
return { isValid: mod(x2 * v, P) === u, value: x };
},
} as const; } as const;
export const ed448 = twistedEdwards(ED448_DEF); export const ed448 = /* @__PURE__ */ twistedEdwards(ED448_DEF);
// NOTE: there is no ed448ctx, since ed448 supports ctx by default // NOTE: there is no ed448ctx, since ed448 supports ctx by default
export const ed448ph = twistedEdwards({ ...ED448_DEF, prehash: shake256_64 }); export const ed448ph = /* @__PURE__ */ twistedEdwards({ ...ED448_DEF, prehash: shake256_64 });
export const x448 = montgomery({ export const x448 = /* @__PURE__ */ (() =>
a: BigInt(156326), montgomery({
montgomeryBits: 448, a: BigInt(156326),
nByteLength: 57, // RFC 7748 has 56-byte keys, RFC 8032 has 57-byte keys
P: ed448P, montgomeryBits: 448,
Gu: BigInt(5), nByteLength: 56,
powPminus2: (x: bigint): bigint => { P: ed448P,
const P = ed448P; Gu: BigInt(5),
const Pminus3div4 = ed448_pow_Pminus3div4(x); powPminus2: (x: bigint): bigint => {
const Pminus3 = pow2(Pminus3div4, BigInt(2), P); const P = ed448P;
return mod(Pminus3 * x, P); // Pminus3 * x = Pminus2 const Pminus3div4 = ed448_pow_Pminus3div4(x);
}, const Pminus3 = pow2(Pminus3div4, BigInt(2), P);
adjustScalarBytes, return mod(Pminus3 * x, P); // Pminus3 * x = Pminus2
randomBytes, },
}); adjustScalarBytes,
randomBytes,
}))();
/** /**
* Converts edwards448 public key to x448 public key. Uses formula: * Converts edwards448 public key to x448 public key. Uses formula:
@ -146,15 +161,19 @@ export const x448 = montgomery({
* const aPub = ed448.getPublicKey(utils.randomPrivateKey()); * const aPub = ed448.getPublicKey(utils.randomPrivateKey());
* x448.getSharedSecret(edwardsToMontgomery(aPub), edwardsToMontgomery(someonesPub)) * x448.getSharedSecret(edwardsToMontgomery(aPub), edwardsToMontgomery(someonesPub))
*/ */
export function edwardsToMontgomery(edwardsPub: string | Uint8Array): Uint8Array { export function edwardsToMontgomeryPub(edwardsPub: string | Uint8Array): Uint8Array {
const { y } = ed448.ExtendedPoint.fromHex(edwardsPub); const { y } = ed448.ExtendedPoint.fromHex(edwardsPub);
const _1n = BigInt(1); const _1n = BigInt(1);
return Fp.toBytes(Fp.create((y - _1n) * Fp.inv(y + _1n))); return Fp.toBytes(Fp.create((y - _1n) * Fp.inv(y + _1n)));
} }
export const edwardsToMontgomery = edwardsToMontgomeryPub; // deprecated
// TODO: add edwardsToMontgomeryPriv, similar to ed25519 version
// Hash To Curve Elligator2 Map // Hash To Curve Elligator2 Map
const ELL2_C1 = (Fp.ORDER - BigInt(3)) / BigInt(4); // 1. c1 = (q - 3) / 4 # Integer arithmetic const ELL2_C1 = (Fp.ORDER - BigInt(3)) / BigInt(4); // 1. c1 = (q - 3) / 4 # Integer arithmetic
const ELL2_J = BigInt(156326); const ELL2_J = BigInt(156326);
function map_to_curve_elligator2_curve448(u: bigint) { function map_to_curve_elligator2_curve448(u: bigint) {
let tv1 = Fp.sqr(u); // 1. tv1 = u^2 let tv1 = Fp.sqr(u); // 1. tv1 = u^2
let e1 = Fp.eql(tv1, Fp.ONE); // 2. e1 = tv1 == 1 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) 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) return { xn, xd, yn: y, yd: Fp.ONE }; // 27. return (xn, xd, y, 1)
} }
function map_to_curve_elligator2_edwards448(u: bigint) { 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 { 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 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) return { x: Fp.mul(xEn, inv[0]), y: Fp.mul(yEn, inv[1]) }; // 38. return (xEn, xEd, yEn, yEd)
} }
const { hashToCurve, encodeToCurve } = htf.createHasher( const htf = /* @__PURE__ */ (() =>
ed448.ExtendedPoint, createHasher(
(scalars: bigint[]) => map_to_curve_elligator2_edwards448(scalars[0]), ed448.ExtendedPoint,
{ (scalars: bigint[]) => map_to_curve_elligator2_edwards448(scalars[0]),
DST: 'edwards448_XOF:SHAKE256_ELL2_RO_', {
encodeDST: 'edwards448_XOF:SHAKE256_ELL2_NU_', DST: 'edwards448_XOF:SHAKE256_ELL2_RO_',
p: Fp.ORDER, encodeDST: 'edwards448_XOF:SHAKE256_ELL2_NU_',
m: 1, p: Fp.ORDER,
k: 224, m: 1,
expand: 'xof', k: 224,
hash: shake256, expand: 'xof',
} hash: shake256,
}
))();
export const hashToCurve = /* @__PURE__ */ (() => htf.hashToCurve)();
export const encodeToCurve = /* @__PURE__ */ (() => htf.encodeToCurve)();
function assertDcfPoint(other: unknown) {
if (!(other instanceof DcfPoint)) throw new Error('DecafPoint expected');
}
// 1-d
const ONE_MINUS_D = BigInt('39082');
// 1-2d
const ONE_MINUS_TWO_D = BigInt('78163');
// √(-d)
const SQRT_MINUS_D = BigInt(
'98944233647732219769177004876929019128417576295529901074099889598043702116001257856802131563896515373927712232092845883226922417596214'
); );
export { hashToCurve, encodeToCurve }; // 1 / √(-d)
const INVSQRT_MINUS_D = BigInt(
'315019913931389607337177038330951043522456072897266928557328499619017160722351061360252776265186336876723201881398623946864393857820716'
);
// Calculates 1/√(number)
const invertSqrt = (number: bigint) => uvRatio(_1n, number);
const MAX_448B = BigInt(
'0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff'
);
const bytes448ToNumberLE = (bytes: Uint8Array) =>
ed448.CURVE.Fp.create(bytesToNumberLE(bytes) & MAX_448B);
type ExtendedPoint = ExtPointType;
// Computes Elligator map for Decaf
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-ristretto255-decaf448-07#name-element-derivation-2
function calcElligatorDecafMap(r0: bigint): ExtendedPoint {
const { d } = ed448.CURVE;
const P = ed448.CURVE.Fp.ORDER;
const mod = ed448.CURVE.Fp.create;
const r = mod(-(r0 * r0)); // 1
const u0 = mod(d * (r - _1n)); // 2
const u1 = mod((u0 + _1n) * (u0 - r)); // 3
const { isValid: was_square, value: v } = uvRatio(ONE_MINUS_TWO_D, mod((r + _1n) * u1)); // 4
let v_prime = v; // 5
if (!was_square) v_prime = mod(r0 * v);
let sgn = _1n; // 6
if (!was_square) sgn = mod(-_1n);
const s = mod(v_prime * (r + _1n)); // 7
let s_abs = s;
if (isNegativeLE(s, P)) s_abs = mod(-s);
const s2 = s * s;
const W0 = mod(s_abs * _2n); // 8
const W1 = mod(s2 + _1n); // 9
const W2 = mod(s2 - _1n); // 10
const W3 = mod(v_prime * s * (r - _1n) * ONE_MINUS_TWO_D + sgn); // 11
return new ed448.ExtendedPoint(mod(W0 * W3), mod(W2 * W1), mod(W1 * W3), mod(W0 * W2));
}
/**
* Each ed448/ExtendedPoint has 4 different equivalent points. This can be
* a source of bugs for protocols like ring signatures. Decaf was created to solve this.
* Decaf point operates in X:Y:Z:T extended coordinates like ExtendedPoint,
* but it should work in its own namespace: do not combine those two.
* https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-ristretto255-decaf448
*/
class DcfPoint 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. * jubjub does not use EdDSA, so `hash`/sha512 params are passed because interface expects them.
*/ */
export const jubjub = twistedEdwards({ export const jubjub = /* @__PURE__ */ twistedEdwards({
// Params: a, d // Params: a, d
a: BigInt('0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000000'), a: BigInt('0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000000'),
d: BigInt('0x2a9318e74bfa2b48f5fd9207e6bd7fd4292d7f6d37579d2601065fd6d6343eb1'), d: BigInt('0x2a9318e74bfa2b48f5fd9207e6bd7fd4292d7f6d37579d2601065fd6d6343eb1'),

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

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

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

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

@ -2,7 +2,7 @@ import { deepStrictEqual, throws } from 'assert';
import { should, describe } from 'micro-should'; import { should, describe } from 'micro-should';
import * as fc from 'fast-check'; import * as fc from 'fast-check';
import * as mod from '../esm/abstract/modular.js'; 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 // Generic tests for all curves in package
import { secp192r1, secp224r1 } from './_more-curves.helpers.js'; import { secp192r1, secp224r1 } from './_more-curves.helpers.js';
import { secp256r1 } from '../esm/p256.js'; import { secp256r1 } from '../esm/p256.js';
@ -595,6 +595,18 @@ for (const name in CURVES) {
{ numRuns: NUM_RUNS } { 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', () => { should('.verify() should verify empty signatures', () => {
const msg = new Uint8Array([]); const msg = new Uint8Array([]);
const priv = C.utils.randomPrivateKey(); const priv = C.utils.randomPrivateKey();

@ -5,10 +5,16 @@ import { describe, should } from 'micro-should';
import { wNAF } from '../esm/abstract/curve.js'; import { wNAF } from '../esm/abstract/curve.js';
import { bytesToHex, utf8ToBytes } from '../esm/abstract/utils.js'; import { bytesToHex, utf8ToBytes } from '../esm/abstract/utils.js';
import { hash_to_field } from '../esm/abstract/hash-to-curve.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 zkVectors from './bls12-381/zkcrypto/converted.json' assert { type: 'json' };
import pairingVectors from './bls12-381/go_pairing_vectors/pairing.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') const G2_VECTORS = readFileSync('./test/bls12-381/bls12-381-g2-test-vectors.txt', 'utf-8')
.trim() .trim()
.split('\n') .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 B_384_40 = '40'.padEnd(384, '0'); // [0x40, 0, 0...]
const getPubKey = (priv) => bls.getPublicKey(priv); const getPubKey = (priv) => bls.getPublicKey(priv);
function replaceZeroPoint(item) {
const zeros = '0000000000000000000000000000000000000000000000000000000000000000';
const ones = '1000000000000000000000000000000000000000000000000000000000000001';
return item === zeros ? ones : item;
}
function equal(a, b, comment) { function equal(a, b, comment) {
deepStrictEqual(a.equals(b), true, `eq(${comment})`); deepStrictEqual(a.equals(b), true, `eq(${comment})`);
@ -847,6 +858,13 @@ describe('bls12-381/basic', () => {
}); });
// should aggregate signatures // 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)`, () => { should(`produce correct signatures (${G2_VECTORS.length} vectors)`, () => {
for (let vector of G2_VECTORS) { for (let vector of G2_VECTORS) {
const [priv, msg, expected] = vector; const [priv, msg, expected] = vector;
@ -1155,6 +1173,8 @@ describe('verify()', () => {
const pub = bls.getPublicKey(priv); const pub = bls.getPublicKey(priv);
const res = bls.verify(sig, msg, pub); const res = bls.verify(sig, msg, pub);
deepStrictEqual(res, true, `${priv}-${msg}`); 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', () => { should('not verify signature with wrong message', () => {
@ -1175,6 +1195,43 @@ describe('verify()', () => {
const invPub = bls.getPublicKey(invPriv); const invPub = bls.getPublicKey(invPriv);
const res = bls.verify(sig, msg, invPub); const res = bls.verify(sig, msg, invPub);
deepStrictEqual(res, false); 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', () => { describe('batch', () => {
@ -1187,6 +1244,10 @@ describe('verify()', () => {
const signatures = messages.map((message, i) => bls.sign(message, privateKeys[i])); const signatures = messages.map((message, i) => bls.sign(message, privateKeys[i]));
const aggregatedSignature = bls.aggregateSignatures(signatures); const aggregatedSignature = bls.aggregateSignatures(signatures);
deepStrictEqual(bls.verifyBatch(aggregatedSignature, messages, publicKey), true); 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), bls.verifyBatch(aggregatedSignature, wrongMessages, publicKey),
messages.every((m, i) => m === wrongMessages[i]) 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), bls.verifyBatch(aggregatedSignature, messages, wrongPublicKeys),
wrongPrivateKeys.every((p, i) => p === privateKeys[i]) 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', () => { should('verify multi-signature as simple signature', () => {
fc.assert( fc.assert(
fc.property(FC_MSG, FC_BIGINT_5, (message, privateKeys) => { fc.property(FC_MSG, FC_BIGINT_5, (message, privateKeys) => {
message = replaceZeroPoint(message);
const publicKey = privateKeys.map(getPubKey); const publicKey = privateKeys.map(getPubKey);
const signatures = privateKeys.map((privateKey) => bls.sign(message, privateKey)); const signatures = privateKeys.map((privateKey) => bls.sign(message, privateKey));
const aggregatedSignature = bls.aggregateSignatures(signatures); const aggregatedSignature = bls.aggregateSignatures(signatures);
const aggregatedPublicKey = bls.aggregatePublicKeys(publicKey); const aggregatedPublicKey = bls.aggregatePublicKeys(publicKey);
deepStrictEqual(bls.verify(aggregatedSignature, message, aggregatedPublicKey), true); deepStrictEqual(bls.verify(aggregatedSignature, message, aggregatedPublicKey), true);
deepStrictEqual(
bls.verify(bytesToHex(aggregatedSignature), message, aggregatedPublicKey),
true
);
}) })
); );
}); });
should('not verify wrong multi-signature as simple signature', () => { should('not verify wrong multi-signature as simple signature', () => {
fc.assert( fc.assert(
fc.property(FC_MSG, FC_MSG, FC_BIGINT_5, (message, wrongMessage, privateKeys) => { fc.property(FC_MSG, FC_MSG, FC_BIGINT_5, (message, wrongMessage, privateKeys) => {
message = replaceZeroPoint(message);
const publicKey = privateKeys.map(getPubKey); const publicKey = privateKeys.map(getPubKey);
const signatures = privateKeys.map((privateKey) => bls.sign(message, privateKey)); const signatures = privateKeys.map((privateKey) => bls.sign(message, privateKey));
const aggregatedSignature = bls.aggregateSignatures(signatures); const aggregatedSignature = bls.aggregateSignatures(signatures);
@ -1253,6 +1328,10 @@ describe('verify()', () => {
bls.verify(aggregatedSignature, wrongMessage, aggregatedPublicKey), bls.verify(aggregatedSignature, wrongMessage, aggregatedPublicKey),
message === wrongMessage 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. // 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 { sha512 } from '@noble/hashes/sha512';
import { hexToBytes, bytesToHex as hex } from '@noble/hashes/utils'; import { bytesToHex as hex, hexToBytes } from '@noble/hashes/utils';
import { deepStrictEqual, throws } from 'assert'; import { deepStrictEqual, throws } from 'assert';
import { describe, should } from 'micro-should'; import { describe, should } from 'micro-should';
import { bytesToNumberLE, numberToBytesLE } from '../esm/abstract/utils.js'; import { bytesToNumberLE, numberToBytesLE } from '../esm/abstract/utils.js';
import { default as x25519vectors } from './wycheproof/x25519_test.json' assert { type: 'json' }; import { default as x25519vectors } from './wycheproof/x25519_test.json' assert { type: 'json' };
import { ed25519ctx, ed25519ph, RistrettoPoint, x25519 } from '../esm/ed25519.js'; import {
ed25519,
ed25519ctx,
ed25519ph,
edwardsToMontgomeryPub,
edwardsToMontgomeryPriv,
RistrettoPoint,
x25519,
} from '../esm/ed25519.js';
const VECTORS_RFC8032_CTX = [ const VECTORS_RFC8032_CTX = [
{ {
@ -141,6 +149,56 @@ describe('RFC7748 X25519 ECDH', () => {
deepStrictEqual(hex(x25519.scalarMult(bobPrivate, alicePublic)), shared); deepStrictEqual(hex(x25519.scalarMult(bobPrivate, alicePublic)), shared);
}); });
should('X25519/getSharedSecret() should be commutative', () => {
for (let i = 0; i < 512; i++) {
const asec = x25519.utils.randomPrivateKey();
const apub = x25519.getPublicKey(asec);
const bsec = x25519.utils.randomPrivateKey();
const bpub = x25519.getPublicKey(bsec);
try {
deepStrictEqual(x25519.getSharedSecret(asec, bpub), x25519.getSharedSecret(bsec, apub));
} catch (error) {
console.error('not commutative', { asec, apub, bsec, bpub });
throw error;
}
}
});
should('edwardsToMontgomery should produce correct output', () => {
const edSecret = hexToBytes('77076d0a7318a57d3c16c17251b26645df4c2f87ebc0992ab177fba51db92c2a');
const edPublic = ed25519.getPublicKey(edSecret);
const xPrivate = edwardsToMontgomeryPriv(edSecret);
deepStrictEqual(
hex(xPrivate),
'a8cd44eb8e93319c0570bc11005c0e0189d34ff02f6c17773411ad191293c94f'
);
const xPublic = edwardsToMontgomeryPub(edPublic);
deepStrictEqual(
hex(xPublic),
'ed7749b4d989f6957f3bfde6c56767e988e21c9f8784d91d610011cd553f9b06'
);
});
should('edwardsToMontgomery should produce correct keyPair', () => {
const edSecret = ed25519.utils.randomPrivateKey();
const edPublic = ed25519.getPublicKey(edSecret);
const xSecret = edwardsToMontgomeryPriv(edSecret);
const expectedXPublic = x25519.getPublicKey(xSecret);
const xPublic = edwardsToMontgomeryPub(edPublic);
deepStrictEqual(xPublic, expectedXPublic);
});
should('ECDH through edwardsToMontgomery should be commutative', () => {
const edSecret1 = ed25519.utils.randomPrivateKey();
const edPublic1 = ed25519.getPublicKey(edSecret1);
const edSecret2 = ed25519.utils.randomPrivateKey();
const edPublic2 = ed25519.getPublicKey(edSecret2);
deepStrictEqual(
x25519.getSharedSecret(edwardsToMontgomeryPriv(edSecret1), edwardsToMontgomeryPub(edPublic2)),
x25519.getSharedSecret(edwardsToMontgomeryPriv(edSecret2), edwardsToMontgomeryPub(edPublic1))
);
});
should('base point', () => { should('base point', () => {
const { y } = ed25519ph.ExtendedPoint.BASE; const { y } = ed25519ph.ExtendedPoint.BASE;
const { Fp } = ed25519ph.CURVE; const { Fp } = ed25519ph.CURVE;
@ -298,7 +356,7 @@ describe('ristretto255', () => {
// ESM is broken. // ESM is broken.
import url from 'url'; import url from 'url';
import { assert } from 'console';
if (import.meta.url === url.pathToFileURL(process.argv[1]).href) { if (import.meta.url === url.pathToFileURL(process.argv[1]).href) {
should.run(); should.run();
} }

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

@ -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_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' }; import { default as xof_shake256_36 } from './hash-to-curve/expand_message_xof_SHAKE256_36.json' assert { type: 'json' };
// P256 // P256
import { default as p256_ro } from './hash-to-curve/P256_XMD:SHA-256_SSWU_RO_.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' }; import { default as p256_nu } from './hash-to-curve/P256_XMD_SHA-256_SSWU_NU_.json' assert { type: 'json' };
// P384 // P384
import { default as p384_ro } from './hash-to-curve/P384_XMD:SHA-384_SSWU_RO_.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' }; import { default as p384_nu } from './hash-to-curve/P384_XMD_SHA-384_SSWU_NU_.json' assert { type: 'json' };
// P521 // P521
import { default as p521_ro } from './hash-to-curve/P521_XMD:SHA-512_SSWU_RO_.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' }; import { default as p521_nu } from './hash-to-curve/P521_XMD_SHA-512_SSWU_NU_.json' assert { type: 'json' };
// secp256k1 // secp256k1
import { default as secp256k1_ro } from './hash-to-curve/secp256k1_XMD:SHA-256_SSWU_RO_.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' }; import { default as secp256k1_nu } from './hash-to-curve/secp256k1_XMD_SHA-256_SSWU_NU_.json' assert { type: 'json' };
// bls-G1 // 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_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_nu } from './hash-to-curve/BLS12381G1_XMD_SHA-256_SSWU_NU_.json' assert { type: 'json' };
// bls-G2 // 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_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_nu } from './hash-to-curve/BLS12381G2_XMD_SHA-256_SSWU_NU_.json' assert { type: 'json' };
// ed25519 // ed25519
import { default as ed25519_ro } from './hash-to-curve/edwards25519_XMD:SHA-512_ELL2_RO_.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' }; import { default as ed25519_nu } from './hash-to-curve/edwards25519_XMD_SHA-512_ELL2_NU_.json' assert { type: 'json' };
// ed448 // ed448
import { default as ed448_ro } from './hash-to-curve/edwards448_XOF:SHAKE256_ELL2_RO_.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' }; import { default as ed448_nu } from './hash-to-curve/edwards448_XOF_SHAKE256_ELL2_NU_.json' assert { type: 'json' };
function testExpandXMD(hash, vectors) { function testExpandXMD(hash, vectors) {
describe(`${vectors.hash}/${vectors.DST.length}`, () => { describe(`${vectors.hash}/${vectors.DST.length}`, () => {

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

@ -10,6 +10,7 @@ import { hexToBytes, bytesToHex } from '../esm/abstract/utils.js';
import { default as ecdsa } from './wycheproof/ecdsa_test.json' assert { type: 'json' }; import { default as ecdsa } from './wycheproof/ecdsa_test.json' assert { type: 'json' };
import { default as ecdh } from './wycheproof/ecdh_test.json' assert { type: 'json' }; import { default as ecdh } from './wycheproof/ecdh_test.json' assert { type: 'json' };
import { default as rfc6979 } from './vectors/rfc6979.json' assert { type: 'json' }; import { default as rfc6979 } from './vectors/rfc6979.json' assert { type: 'json' };
import { default as endoVectors } from './vectors/secp256k1/endomorphism.json' assert { type: 'json' };
import { default as ecdh_secp224r1_test } from './wycheproof/ecdh_secp224r1_test.json' assert { type: 'json' }; import { default as ecdh_secp224r1_test } from './wycheproof/ecdh_secp224r1_test.json' assert { type: 'json' };
import { default as ecdh_secp256r1_test } from './wycheproof/ecdh_secp256r1_test.json' assert { type: 'json' }; import { default as ecdh_secp256r1_test } from './wycheproof/ecdh_secp256r1_test.json' assert { type: 'json' };
@ -94,31 +95,51 @@ should('fields', () => {
for (const n in vectors) deepStrictEqual(NIST[n].CURVE.Fp.ORDER, vectors[n]); 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', () => { describe('wycheproof ECDH', () => {
for (const group of ecdh.testGroups) { for (const group of ecdh.testGroups) {
const CURVE = NIST[group.curve]; const curve = NIST[group.curve];
if (!CURVE) continue; if (!curve) continue;
should(group.curve, () => { should(group.curve, () => {
for (const test of group.tests) { for (const test of group.tests) {
if (test.result === 'valid' || test.result === 'acceptable') { verifyECDHVector(test, curve);
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');
} }
}); });
} }
@ -150,30 +171,12 @@ describe('wycheproof ECDH', () => {
for (const name in WYCHEPROOF_ECDH) { for (const name in WYCHEPROOF_ECDH) {
const { curve, tests } = WYCHEPROOF_ECDH[name]; const { curve, tests } = WYCHEPROOF_ECDH[name];
for (let i = 0; i < tests.length; i++) { for (let i = 0; i < tests.length; i++) {
const test = tests[i]; const curveTests = tests[i];
for (let j = 0; j < test.testGroups.length; j++) { for (let j = 0; j < curveTests.testGroups.length; j++) {
const group = test.testGroups[j]; const group = curveTests.testGroups[j];
should(`additional ${name} (${i}/${j})`, () => { should(`additional ${name} (${group.tests.length})`, () => {
for (const test of group.tests) { for (const test of group.tests) {
if (test.result === 'valid' || test.result === 'acceptable') { verifyECDHVector(test, curve);
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');
} }
}); });
} }
@ -438,7 +441,7 @@ describe('RFC6979', () => {
} }
}); });
should('DER Leading zero', () => { should('properly add leading zero to DER', () => {
// Valid DER // Valid DER
deepStrictEqual( deepStrictEqual(
DER.toSig( DER.toSig(
@ -465,6 +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. // ESM is broken.
import url from 'url'; import url from 'url';
if (import.meta.url === url.pathToFileURL(process.argv[1]).href) { if (import.meta.url === url.pathToFileURL(process.argv[1]).href) {

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

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

@ -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": { "extends": "@paulmillr/jsbt/tsconfigs/esm.json",
"strict": true, "compilerOptions": {
"outDir": "esm", "outDir": "esm",
"target": "es2020", "target": "ES2015"
"module": "es6", },
"moduleResolution": "node16", "include": ["index.ts", "src"],
"noUnusedLocals": true, "exclude": ["node_modules", "lib"]
"sourceMap": true, }
"baseUrl": ".",
"paths": {
"@noble/hashes/crypto": [ "src/crypto" ]
},
},
"include": ["src"],
"exclude": [
"node_modules",
"lib",
],
}

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