287 Commits

Author SHA1 Message Date
Paul Miller
b8ddb603c1 Format 2024-01-25 12:41:48 +01:00
Paul Miller
943edbceba Implement ElligatorSwift 2024-01-14 11:00:03 +01: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 #93 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 #92 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 #85 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 #83 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 #82 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 #81 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 #79 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 #74 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 #62 from steveluscher/pure-and-twisted
Add pure annotation to all calls to `twistedEdwards`
2023-07-01 04:27:31 +02:00
steveluscher
413725cfb3 Add pure annotation to all calls to twistedEdwards
This PR makes it so that if you only use _one_ export:

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

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

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

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

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

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

* secp256k1: 75.4kb => 62.3kb

* ed25519: 67.5kb => 51.1kb

* ed448: 55.1kb => 44.0kb

* p256: 67.8kb => 59.8kb

* p384: 75.4kb => 67.4kb

* p521: 75.8kb => 67.8kb
2023-05-20 10:49:50 +02:00
Paul Miller
648fd2cc07 benchmark: curves should bench ed25519 first 2023-05-19 09:58:30 +02:00
Paul Miller
f67134ca86 benchmark: add msm to bls 2023-05-19 09:58:13 +02:00
Paul Miller
6d0678b076 readme 2023-05-14 06:54:17 +02:00
Paul Miller
53ebde19ea readme 2023-05-14 06:48:22 +02:00
Paul Miller
a7755332c8 readme 2023-05-14 06:40:09 +02:00
Paul Miller
5f0007ab24 readme 2023-05-13 01:31:55 +02:00
Paul Miller
1ee5a5c07f CI: auto-publish to NPM on GH release 2023-05-12 20:53:24 +02:00
Paul Miller
708c0e14d5 readme 2023-05-12 19:03:17 +02:00
Paul Miller
624d7c9910 Merge pull request #46 from sublimator/nd-sort-few-typos-things-editor-nagging-about-2023-05-09
docs(modular): sort few typos/things editor nagging about
2023-05-09 18:01:35 +02:00
Nicholas Dudfield
665ef2dd93 docs(modular): sort few typos/things editor nagging about 2023-05-09 07:35:33 +07:00
Paul Miller
acc1f26acf readme 2023-05-07 23:32:41 +02:00
Paul Miller
3c4a25263e readme 2023-05-06 22:05:33 +02:00
Paul Miller
e887d516ab readme 2023-05-06 21:20:38 +02:00
Paul Miller
90e87f7ab1 weierstrass: adjust SWUFpSqrtRatio to not use exp operator 2023-05-06 14:38:53 +02:00
Paul Miller
5edafbac97 Merge pull request #42 from sublimator/patch-1
ed25519: fix ristrettoHash size typo in hashToCurve
2023-05-05 17:48:08 +02:00
Nicholas Dudfield
554c94509e ed25519: fix ristrettoHash size typo in hashToCurve 2023-05-05 18:17:40 +07:00
Paul Miller
7c11a021c0 Drop v16 from ci, it will be out in 4 months 2023-05-05 03:54:16 +02:00
Paul Miller
531b6a3a48 Adjust CI 2023-05-05 03:53:35 +02:00
Paul Miller
fb5cd9df39 README 2023-05-05 03:52:49 +02:00
Paul Miller
53a6d636d4 Merge pull request #38 from legobeat/ci-node-version-matrix
ci: test nodejs v16/v18/v20
2023-05-05 03:50:00 +02:00
Paul Miller
42de620010 edwards: make zip215 false Strongly Binding Signature (SBS) secure. gh-40 2023-05-05 03:37:13 +02:00
Paul Miller
6621053c7d edwards: ensure Point.fromHex fails when x=0 and first x bit is 1. gh-40 2023-05-05 01:39:53 +02:00
Paul Miller
9bee88888f weierstrass: improve return type of sign(). Clarify comments. 2023-05-03 18:28:35 +02:00
legobt
103ba5f0a7 ci: test nodejs v16/v18/v20 2023-05-02 11:59:54 +09:00
Paul Miller
d5de5d2659 README: add more projects using curves 2023-04-28 02:46:07 +02:00
Paul Miller
217cf8c654 readme: more resources 2023-04-27 01:58:29 +02:00
Paul Miller
8e307d8f89 readme 2023-04-27 01:01:42 +02:00
Paul Miller
8c0018d57f readme 2023-04-27 00:54:41 +02:00
Paul Miller
ca7f202839 Add secp256k1 compatibility layer URL to readme 2023-04-27 00:16:29 +02:00
Paul Miller
816077ac0a README 2023-04-24 13:00:43 +02:00
Paul Miller
bc03a07043 readme 2023-04-23 20:31:29 +02:00
Paul Miller
63653255e1 ed448: rename to edwardsToMontgomeryPub 2023-04-23 20:29:17 +02:00
Paul Miller
895ee3a1a4 bls: refactor slightly 2023-04-23 20:29:03 +02:00
Paul Miller
16b31b9087 edwards: use bitmask instead of exp 2023-04-23 20:28:47 +02:00
Paul Miller
213796db4b ed25519: rename to edwardsToMontgomeryPub 2023-04-23 20:28:28 +02:00
Paul Miller
049d3bce54 CI: node.js 20 2023-04-23 20:17:45 +02:00
Paul Miller
b2a04c2393 Merge pull request #32 from mirceanis/31-fix-edwardsToMontgomery
ed25519: fix edwardsToMontgomery formula; implement edwardsToMontgomeryPriv
2023-04-23 20:16:22 +02:00
Paul Miller
cb5e9a6e96 Update benchmarks 2023-04-22 03:20:11 +02:00
Paul Miller
36af62357f test: adjust ed and secp tests a bit 2023-04-22 02:24:41 +02:00
Mircea Nistor
88291eba33 ed25519: fix edwardsToMontgomery formula; implement edwardsToMontgomeryPriv; add tests 2023-04-20 13:37:21 +02:00
Paul Miller
848a1b0226 nist tests: add endomorphism test 2023-04-14 19:53:20 +02:00
Paul Miller
972e549dde bls: no bigint literals 2023-04-13 17:18:39 +02:00
Paul Miller
d61c7ae4e5 Release 1.0.0. 2023-04-12 04:42:02 +02:00
Paul Miller
d3de7c8863 readme 2023-04-12 04:38:33 +02:00
Paul Miller
6316643f51 Rename bn to bn254 2023-04-12 04:33:32 +02:00
Paul Miller
7199f113c6 readme 2023-04-12 04:21:29 +02:00
Paul Miller
71f6948612 Adjust readme and ed25519 docs 2023-04-12 04:16:47 +02:00
Paul Miller
d3d03ff115 README, bls 2023-04-12 04:10:59 +02:00
Paul Miller
e2c3560686 Remove types/node 2023-04-12 03:57:30 +02:00
Paul Miller
4e9c40b3e5 readme 2023-04-11 15:19:02 +02:00
Paul Miller
09085d2ee1 readme 2023-04-11 15:17:24 +02:00
Paul Miller
8c4d781479 readme 2023-04-10 21:12:13 +02:00
Paul Miller
123431de66 nist curves: rename from P256 to p256 for consistency 2023-04-10 21:04:16 +02:00
Paul Miller
7503aff45c README 2023-04-10 19:40:58 +02:00
Paul Miller
81e6046698 test: move vector 2023-04-10 05:23:35 +02:00
Paul Miller
30f7d78c82 hash-to-curve: update benchmarks, lint 2023-04-10 05:10:38 +02:00
Paul Miller
00665b21ab htf: Prohibit expand: undefined. Closes gh-18 2023-04-10 05:02:36 +02:00
Paul Miller
5d54bba846 lint 2023-04-08 21:23:42 +02:00
Paul Miller
851af4f1bc weierstrass: adjust 1 line 2023-04-08 21:23:35 +02:00
Paul Miller
6ea40d9dab Update tests. Update wycheproof from v0.8 to v0.9 2023-04-08 19:00:18 +02:00
Paul Miller
8beb922ded weierstrass: improve DER decoding. Validate curve creation. 2023-04-07 06:09:46 +02:00
Paul Miller
fe380da8c9 edwards: change strict option to zip215 2023-04-02 18:35:34 +02:00
Paul Miller
113d906233 edwards: change API. Add options.strict, context. Add edwardsToMontgomery 2023-04-02 17:35:03 +02:00
Paul Miller
65c0dc6c59 README 2023-04-02 17:34:33 +02:00
Paul Miller
ed3ba3de6e Prettier 2023-04-02 16:50:54 +02:00
Paul Miller
d424c661fb Fix tests 2023-04-02 16:50:27 +02:00
Paul Miller
31d92cce11 README update 2023-04-02 16:42:49 +02:00
Paul Miller
c15c964f77 Refactor BLS, change API 2023-04-02 16:38:03 +02:00
Paul Miller
37ebe6c40f tests of ed25519, ed448: improve 2023-04-02 15:38:36 +02:00
Paul Miller
18eabfd3be Remove unused devdeps 2023-04-02 14:17:50 +02:00
Paul Miller
19f04a4c1c Release 0.9.1. 2023-03-31 10:02:05 +02:00
Paul Miller
d0c3bee4de weierstrass, edwards: make points expose typescript x, y 2023-03-30 09:20:35 +02:00
Paul Miller
4244f97d38 bls: get rid of bigint literals. gh-22 2023-03-28 19:01:42 +02:00
Paul Miller
618508d32c weierstrass, edwards: get rid of bigint literals. Closes gh-22 2023-03-28 19:01:00 +02:00
Paul Miller
3936449e7b edwards: add toRawBytes to ts type 2023-03-26 15:54:04 +02:00
Paul Miller
0ffa38db6b Release 0.9.0. 2023-03-24 11:12:02 +01:00
Paul Miller
c4c580edc0 Bump devdeps 2023-03-24 11:06:48 +01:00
Paul Miller
abe8adac7b README 2023-03-24 10:25:03 +01:00
Paul Miller
4fd2ae82b6 readme 2023-03-21 07:27:45 +01:00
Paul Miller
e2411f7dfd modular: add comment 2023-03-21 07:25:09 +01:00
Paul Miller
cb61e4f292 readme 2023-03-21 07:25:01 +01:00
Paul Miller
bb875791bd docs 2023-03-21 07:11:17 +01:00
Paul Miller
3df2553ced Docs 2023-03-21 07:02:07 +01:00
Paul Miller
8fabc7ff06 All files: rename Fp to Field 2023-03-21 06:51:18 +01:00
Paul Miller
f3c21eb347 weierstrass: make weierstrassPoints fromBytes / toBytes optional 2023-03-21 05:51:10 +01:00
Paul Miller
a8b8192714 Add CURVE.p param 2023-03-21 03:06:06 +01:00
Paul Miller
1c6aa07ff7 Release 0.8.3. 2023-03-16 19:41:20 +01:00
Paul Miller
e110237298 readme 2023-03-16 19:17:34 +01:00
Paul Miller
45393db807 Bump docs 2023-03-16 19:05:33 +01:00
Paul Miller
acc3a9dc4d Bump devdep types/node 2023-03-16 18:52:03 +01:00
Paul Miller
9295b0dbae Upgrade to Typescript 5 2023-03-16 18:49:48 +01:00
Paul Miller
5784ef23f6 Release 0.8.2. 2023-03-14 00:44:02 +01:00
Paul Miller
ef55efe842 Fix common.js build 2023-03-14 00:42:40 +01:00
Paul Miller
1cfd6a76ca Release 0.8.1. 2023-03-14 00:40:05 +01:00
Paul Miller
89f81b2204 pkg.json: improve bench, clean scripts 2023-03-14 00:39:21 +01:00
Paul Miller
d77ac16f51 Bring back common.js for now. Need more thorough work with consumers 2023-03-14 00:32:09 +01:00
Paul Miller
fe68da61f6 Move stark curve to micro-starknet 2023-03-10 20:18:05 +01:00
Paul Miller
32c0841bed Add Trail of Bits audit 2023-03-10 01:09:49 +01:00
Paul Miller
49a659b248 Release 0.8.0. 2023-03-03 05:12:36 +04:00
Paul Miller
9d0a2e25dc readme: esm-only 2023-03-03 05:11:21 +04:00
Paul Miller
7c461af2b2 test: remove common.js support 2023-03-03 05:09:50 +04:00
Paul Miller
4a8f447c8d package.json, tsconfig: remove common.js support. Pure ESM now 2023-03-03 05:09:36 +04:00
Paul Miller
4b2d31ce7f stark: more methods 2023-02-28 23:18:06 +04:00
Paul Miller
16115f27a6 readme update 2023-02-28 14:04:15 +04:00
Paul Miller
0e0d0f530d benchmark: add tonneli-shanks sqrt 2023-02-28 02:59:28 +04:00
Paul Miller
fa5105aef2 ecdsa: remove scalar blinding. CSPRNG dep not good: cryptofuzz, other envs will fail 2023-02-28 01:48:06 +04:00
Paul Miller
11f1626ecc modular: Add comment. Add benchmark 2023-02-27 22:41:24 +04:00
Paul Miller
53ff287bf7 Schnorr: remove getExtendedPublicKey 2023-02-27 20:29:47 +04:00
Paul Miller
214c9aa553 secp256k1: Fix schnorrGetExtPubKey y coordinate 2023-02-27 20:20:13 +04:00
Paul Miller
ec2c3e1248 Add test for ristretto equality testing 2023-02-27 19:33:41 +04:00
Paul Miller
e64a9d654c Fix ristretto255 equals 2023-02-27 19:07:45 +04:00
Paul Miller
088edd0fbb h2c: move params validation. add experimental hash_to_ristretto255 2023-02-27 15:07:24 +01:00
Paul Miller
3e90930e9d Fix types 2023-02-26 19:10:50 +01:00
Paul Miller
b8b2e91f74 Release 0.7.3. 2023-02-26 19:05:53 +01:00
Paul Miller
9ee694ae23 docs updates 2023-02-26 19:05:40 +01:00
Paul Miller
6bc4b35cf4 hash-to-curve: speed-up os2ip, change code a bit 2023-02-26 18:55:30 +01:00
Paul Miller
0163b63532 Release 0.7.2. 2023-02-25 10:13:45 +01:00
Paul Miller
7e825520f1 README 2023-02-25 10:05:48 +01:00
Paul Miller
d739297b2c Move p192, p224 from main pkg to tests for now. Reason: not popular 2023-02-25 10:00:24 +01:00
Paul Miller
285aa6375d stark: refactor 2023-02-20 16:50:29 +01:00
Paul Miller
8c77331ef2 add hash-to-curve benchmark 2023-02-20 16:33:05 +01:00
Paul Miller
669641e0a3 README wording 2023-02-16 17:54:17 +01:00
Paul Miller
68dd57ed31 Cryptofuzz 2023-02-16 17:49:48 +01:00
Paul Miller
a9fdd6df9f readme: typo 2023-02-16 12:33:32 +01:00
Paul Miller
d485d8b0e6 Fix prettier 2023-02-16 12:32:32 +01:00
Paul Miller
0fdd763dc7 montgomery: add randomPrivateKey. Add ecdh benchmark. 2023-02-16 12:32:18 +01:00
Paul Miller
586e2ad5fb Release 0.7.1. 2023-02-16 00:20:37 +01:00
Paul Miller
ed81707bdc readme 2023-02-16 00:12:23 +01:00
Paul Miller
6d56b2d78e readme 2023-02-16 00:08:18 +01:00
Paul Miller
8397241a8f bls, stark: adjust methods 2023-02-16 00:03:20 +01:00
Paul Miller
001d0cc24a weierstrass: rename method, adjust comments 2023-02-16 00:03:10 +01:00
Paul Miller
ce9d165657 readme hash-to-scalar 2023-02-15 23:46:43 +01:00
Paul Miller
2902b0299a readme 2023-02-15 23:38:26 +01:00
Paul Miller
e1cb8549e8 weierstrass, montgomery, secp: add comments 2023-02-15 23:26:56 +01:00
Paul Miller
26ebb5dcce x25519, x448: change param from a24 to a. Change Gu to bigint 2023-02-15 23:07:52 +01:00
Paul Miller
8b2863aeac Fix benchmark 2023-02-15 22:50:32 +01:00
Paul Miller
b1f50d9364 hash-to-curve: bls examples 2023-02-15 00:08:38 +01:00
Paul Miller
b81d74d3cb readme 2023-02-15 00:06:39 +01:00
Paul Miller
d5fe537159 hash-to-curve readme 2023-02-15 00:03:18 +01:00
Paul Miller
cde1d5c488 Fix tests 2023-02-14 23:51:11 +01:00
169 changed files with 268577 additions and 128632 deletions

View File

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

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

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

16
.gitignore vendored
View File

@@ -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
/build
/abstract
/esm/abstract /esm/abstract
/abstract/

View File

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

6
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,6 @@
{
"files.exclude": {
"*.{js,d.ts,js.map,d.ts.map}": true,
"esm/*.{js,d.ts,js.map,d.ts.map}": true
}
}

1317
README.md

File diff suppressed because it is too large Load Diff

View File

@@ -1,11 +1,13 @@
# 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 |
| ------- | ------------------ | | ------- | ------------------ |
| >=0.5.0 | :white_check_mark: | | >=1.0.0 | :white_check_mark: |
| <0.5.0 | :x: | | <1.0.0 | :x: |
## Reporting a Vulnerability ## Reporting a Vulnerability

Binary file not shown.

Binary file not shown.

7
audit/README.md Normal file
View File

@@ -0,0 +1,7 @@
# Audit
All audits of the library are described in [README's Security section](../README.md#security)
`2023-01-trailofbits-audit-curves.pdf` file in the directory was saved from
[github.com/trailofbits/publications](https://github.com/trailofbits/publications).
Check out their repo and verify checksums to ensure the PDF in this directory has not been altered.

View File

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

View File

@@ -1,14 +1,14 @@
import { run, mark, utils } from 'micro-bmark'; import { run, mark, utils } from 'micro-bmark';
import { generateData } from './_shared.js'; import { generateData } from './_shared.js';
import { P256 } from '../lib/p256.js'; import { p256 } from '../p256.js';
import { P384 } from '../lib/p384.js'; import { p384 } from '../p384.js';
import { P521 } from '../lib/p521.js'; import { p521 } from '../p521.js';
import { ed25519 } from '../lib/ed25519.js'; import { ed25519 } from '../ed25519.js';
import { ed448 } from '../lib/ed448.js'; import { ed448 } from '../ed448.js';
run(async () => { run(async () => {
const RAM = false const RAM = false
for (let kv of Object.entries({ P256, P384, P521, ed25519, ed448 })) { for (let kv of Object.entries({ ed25519, ed448, p256, p384, p521 })) {
const [name, curve] = kv; const [name, curve] = kv;
console.log(); console.log();
console.log(`\x1b[36m${name}\x1b[0m`); console.log(`\x1b[36m${name}\x1b[0m`);

18
benchmark/decaf448.js Normal file
View File

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

18
benchmark/ecdh.js Normal file
View File

@@ -0,0 +1,18 @@
import { run, compare } from 'micro-bmark';
import { secp256k1 } from '../secp256k1.js';
import { p256 } from '../p256.js';
import { p384 } from '../p384.js';
import { p521 } from '../p521.js';
import { x25519 } from '../ed25519.js';
import { x448 } from '../ed448.js';
run(async () => {
const curves = { x25519, secp256k1, p256, p384, p521, x448 };
const fns = {};
for (let [k, c] of Object.entries(curves)) {
const pubB = c.getPublicKey(c.utils.randomPrivateKey());
const privA = c.utils.randomPrivateKey();
fns[k] = () => c.getSharedSecret(privA, pubB);
}
await compare('ecdh', 1000, fns);
});

View File

@@ -0,0 +1,32 @@
import { run, mark, utils } from 'micro-bmark';
import { hash_to_field } from '../abstract/hash-to-curve.js';
import { hashToPrivateScalar } from '../abstract/modular.js';
import { randomBytes } from '@noble/hashes/utils';
import { sha256 } from '@noble/hashes/sha256';
// import { generateData } from './_shared.js';
import { hashToCurve as secp256k1 } from '../secp256k1.js';
import { hashToCurve as p256 } from '../p256.js';
import { hashToCurve as p384 } from '../p384.js';
import { hashToCurve as p521 } from '../p521.js';
import { hashToCurve as ed25519, hash_to_ristretto255 } from '../ed25519.js';
import { hashToCurve as ed448, hash_to_decaf448 } from '../ed448.js';
import { utf8ToBytes } from '../abstract/utils.js';
const N = 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141n;
run(async () => {
const rand = randomBytes(40);
await mark('hashToPrivateScalar', 1000000, () => hashToPrivateScalar(rand, N));
// - p, the characteristic of F
// - m, the extension degree of F, m >= 1
// - L = ceil((ceil(log2(p)) + k) / 8), where k is the security of suite (e.g. 128)
await mark('hash_to_field', 1000000, () =>
hash_to_field(rand, 1, { DST: 'secp256k1', hash: sha256, expand: 'xmd', p: N, m: 1, k: 128 })
);
const msg = utf8ToBytes('message');
for (let [title, fn] of Object.entries({ secp256k1, p256, p384, p521, ed25519, ed448 })) {
await mark(`hashToCurve ${title}`, 1000, () => fn(msg));
}
await mark('hash_to_ristretto255', 1000, () => hash_to_ristretto255(msg, { DST: 'ristretto255_XMD:SHA-512_R255MAP_RO_' }));
await mark('hash_to_decaf448', 1000, () => hash_to_decaf448(msg, { DST: 'decaf448_XOF:SHAKE256_D448MAP_RO_' }));
});

13
benchmark/modular.js Normal file
View File

@@ -0,0 +1,13 @@
import { run, mark } from 'micro-bmark';
import { secp256k1 } from '../secp256k1.js';
import { Field as Fp } from '../abstract/modular.js';
run(async () => {
console.log(`\x1b[36mmodular, secp256k1 field\x1b[0m`);
const { Fp: secpFp } = secp256k1.CURVE;
await mark('invert a', 300000, () => secpFp.inv(2n ** 232n - 5910n));
await mark('invert b', 300000, () => secpFp.inv(2n ** 231n - 5910n));
await mark('sqrt p = 3 mod 4', 15000, () => secpFp.sqrt(2n ** 231n - 5910n));
const FpStark = Fp(BigInt('0x800000000000011000000000000000000000000000000000000000000000001'));
await mark('sqrt tonneli-shanks', 500, () => FpStark.sqrt(2n ** 231n - 5909n))
});

View File

@@ -16,7 +16,6 @@
}, },
"dependencies": { "dependencies": {
"@noble/hashes": "^1.1.5", "@noble/hashes": "^1.1.5",
"@starkware-industries/starkware-crypto-utils": "^0.0.2",
"elliptic": "^6.5.4" "elliptic": "^6.5.4"
} }
} }

18
benchmark/ristretto255.js Normal file
View File

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

View File

@@ -1,5 +1,5 @@
import { run, mark, utils } from 'micro-bmark'; import { run, mark, utils } from 'micro-bmark';
import { secp256k1, schnorr } from '../lib/secp256k1.js'; import { secp256k1, schnorr } from '../secp256k1.js';
import { generateData } from './_shared.js'; import { generateData } from './_shared.js';
run(async () => { run(async () => {

View File

@@ -1,56 +0,0 @@
import { run, mark, compare, utils } from 'micro-bmark';
import * as starkwareCrypto from '@starkware-industries/starkware-crypto-utils';
import * as stark from '../lib/stark.js';
run(async () => {
const RAM = false;
if (RAM) utils.logMem();
console.log(`\x1b[36mstark\x1b[0m`);
await mark('init', 1, () => stark.utils.precompute(8));
const d = (() => {
const priv = '2dccce1da22003777062ee0870e9881b460a8b7eca276870f57c601f182136c';
const msg = 'c465dd6b1bbffdb05442eb17f5ca38ad1aa78a6f56bf4415bdee219114a47';
const pub = stark.getPublicKey(priv);
const sig = stark.sign(msg, priv);
const privateKey = '2dccce1da22003777062ee0870e9881b460a8b7eca276870f57c601f182136c';
const msgHash = 'c465dd6b1bbffdb05442eb17f5ca38ad1aa78a6f56bf4415bdee219114a47';
const keyPair = starkwareCrypto.default.ec.keyFromPrivate(privateKey, 'hex');
const publicKeyStark = starkwareCrypto.default.ec.keyFromPublic(
keyPair.getPublic(true, 'hex'),
'hex'
);
return { priv, sig, msg, pub, publicKeyStark, msgHash, keyPair };
})();
await compare('pedersen', 500, {
old: () => {
return starkwareCrypto.default.pedersen([
'3d937c035c878245caf64531a5756109c53068da139362728feb561405371cb',
'208a0a10250e382e1e4bbe2880906c2791bf6275695e02fbbc6aeff9cd8b31a',
]);
},
noble: () => {
return stark.pedersen(
'3d937c035c878245caf64531a5756109c53068da139362728feb561405371cb',
'208a0a10250e382e1e4bbe2880906c2791bf6275695e02fbbc6aeff9cd8b31a'
);
},
});
await mark('poseidon', 10000, () => stark.poseidonHash(
0x3d937c035c878245caf64531a5756109c53068da139362728feb561405371cbn,
0x208a0a10250e382e1e4bbe2880906c2791bf6275695e02fbbc6aeff9cd8b31an
));
await compare('verify', 500, {
old: () => {
return starkwareCrypto.default.verify(
d.publicKeyStark,
d.msgHash,
starkwareCrypto.default.sign(d.keyPair, d.msgHash)
);
},
noble: () => {
return stark.verify(stark.sign(d.msg, d.priv), d.msg, d.pub);
},
});
if (RAM) utils.logMem();
});

9
benchmark/utils.js Normal file
View 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
View File

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

20
build/input.js Normal file
View File

@@ -0,0 +1,20 @@
import { bytesToHex, concatBytes, hexToBytes } 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 };

18
build/package.json Normal file
View File

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

View File

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

117
package-lock.json generated
View File

@@ -1,106 +1,37 @@
{ {
"name": "@noble/curves", "name": "@noble/curves",
"version": "0.7.0", "version": "1.3.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "@noble/curves", "name": "@noble/curves",
"version": "0.7.0", "version": "1.3.0",
"funding": [
{
"type": "individual",
"url": "https://paulmillr.com/funding/"
}
],
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@noble/hashes": "1.2.0" "@noble/hashes": "1.3.3"
}, },
"devDependencies": { "devDependencies": {
"@scure/bip32": "~1.1.5",
"@scure/bip39": "~1.1.1",
"@types/node": "18.11.3",
"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.3", "prettier": "3.1.1",
"typescript": "4.7.3" "typescript": "5.3.2"
},
"funding": {
"url": "https://paulmillr.com/funding/"
} }
}, },
"node_modules/@noble/hashes": { "node_modules/@noble/hashes": {
"version": "1.2.0", "version": "1.3.3",
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.2.0.tgz", "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.3.tgz",
"integrity": "sha512-FZfhjEDbT5GRswV3C6uvLPHMiVD6lQBmpoX5+eSiPaMTXte/IKqI5dykDxzZB/WBeK/CDuQRBWarPdi3FNY2zQ==", "integrity": "sha512-V7/fPHgl+jsVPXqqeOzT8egNj2iBIVt+ECeMMG8TdcnTikP3oaBtUVqpT/gYCR68aEBJSF+XbYUxStjbFMqIIA==",
"funding": [ "engines": {
{ "node": ">= 16"
"type": "individual", },
"funding": {
"url": "https://paulmillr.com/funding/" "url": "https://paulmillr.com/funding/"
} }
]
},
"node_modules/@noble/secp256k1": {
"version": "1.7.1",
"resolved": "https://registry.npmjs.org/@noble/secp256k1/-/secp256k1-1.7.1.tgz",
"integrity": "sha512-hOUk6AyBFmqVrv7k5WAw/LpszxVbj9gGN4JRkIX52fdFAj1UA61KXmZDvqVEm+pOyec3+fIeZB02LYa/pWOArw==",
"dev": true,
"funding": [
{
"type": "individual",
"url": "https://paulmillr.com/funding/"
}
]
},
"node_modules/@scure/base": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.1.tgz",
"integrity": "sha512-ZxOhsSyxYwLJj3pLZCefNitxsj093tb2vq90mp2txoYeBqbcjDjqFhyM8eUjq/uFm6zJ+mUuqxlS2FkuSY1MTA==",
"dev": true,
"funding": [
{
"type": "individual",
"url": "https://paulmillr.com/funding/"
}
]
},
"node_modules/@scure/bip32": {
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.1.5.tgz",
"integrity": "sha512-XyNh1rB0SkEqd3tXcXMi+Xe1fvg+kUIcoRIEujP1Jgv7DqW2r9lg3Ah0NkFaCs9sTkQAQA8kw7xiRXzENi9Rtw==",
"dev": true,
"funding": [
{
"type": "individual",
"url": "https://paulmillr.com/funding/"
}
],
"dependencies": {
"@noble/hashes": "~1.2.0",
"@noble/secp256k1": "~1.7.0",
"@scure/base": "~1.1.0"
}
},
"node_modules/@scure/bip39": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.1.1.tgz",
"integrity": "sha512-t+wDck2rVkh65Hmv280fYdVdY25J9YeEUIgn2LG1WM6gxFkGzcksoDiUkWVpVp3Oex9xGC68JU2dSbUfwZ2jPg==",
"dev": true,
"funding": [
{
"type": "individual",
"url": "https://paulmillr.com/funding/"
}
],
"dependencies": {
"@noble/hashes": "~1.2.0",
"@scure/base": "~1.1.0"
}
},
"node_modules/@types/node": {
"version": "18.11.3",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.3.tgz",
"integrity": "sha512-fNjDQzzOsZeKZu5NATgXUPsaFaTxeRgFXoosrHivTl8RGeV733OLawXsGfEk9a8/tySyZUyiZ6E8LcjPFZ2y1A==",
"dev": true
}, },
"node_modules/fast-check": { "node_modules/fast-check": {
"version": "3.0.0", "version": "3.0.0",
@@ -131,15 +62,15 @@
"dev": true "dev": true
}, },
"node_modules/prettier": { "node_modules/prettier": {
"version": "2.8.3", "version": "3.1.1",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.3.tgz", "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.1.1.tgz",
"integrity": "sha512-tJ/oJ4amDihPoufT5sM0Z1SKEuKay8LfVAMlbbhnnkvt6BUserZylqo2PN+p9KeljLr0OHa2rXHU1T8reeoTrw==", "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"
@@ -162,16 +93,16 @@
] ]
}, },
"node_modules/typescript": { "node_modules/typescript": {
"version": "4.7.3", "version": "5.3.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.3.tgz", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.2.tgz",
"integrity": "sha512-WOkT3XYvrpXx4vMMqlD+8R8R37fZkjyLGlxavMc4iB8lrl8L0DeTcHbYgw/v0N/z9wAFsgBhcsF0ruoySS22mA==", "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": ">=4.2.0" "node": ">=14.17"
} }
} }
} }

View File

@@ -1,7 +1,7 @@
{ {
"name": "@noble/curves", "name": "@noble/curves",
"version": "0.7.0", "version": "1.3.0",
"description": "Minimal, auditable JS implementation of elliptic curve cryptography", "description": "Audited & minimal JS implementation of elliptic curve cryptography",
"files": [ "files": [
"abstract", "abstract",
"esm", "esm",
@@ -12,9 +12,10 @@
"*.d.ts.map" "*.d.ts.map"
], ],
"scripts": { "scripts": {
"bench": "cd benchmark; node secp256k1.js; node curves.js; node stark.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 install && npm run build",
"build:clean": "rm *.{js,d.ts,d.ts.map,js.map} esm/*.{js,d.ts,d.ts.map,js.map} 2> /dev/null",
"lint": "prettier --check 'src/**/*.{js,ts}' 'test/*.js'", "lint": "prettier --check 'src/**/*.{js,ts}' 'test/*.js'",
"format": "prettier --write 'src/**/*.{js,ts}' 'test/*.js'", "format": "prettier --write 'src/**/*.{js,ts}' 'test/*.js'",
"test": "node test/index.test.js" "test": "node test/index.test.js"
@@ -23,22 +24,20 @@
"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": "git+https://github.com/paulmillr/noble-curves.git"
}, },
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@noble/hashes": "1.2.0" "@noble/hashes": "1.3.3"
}, },
"devDependencies": { "devDependencies": {
"@scure/bip32": "~1.1.5",
"@scure/bip39": "~1.1.1",
"@types/node": "18.11.3",
"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.3", "prettier": "3.1.1",
"typescript": "4.7.3" "typescript": "5.3.2"
}, },
"sideEffects": false,
"main": "index.js", "main": "index.js",
"exports": { "exports": {
".": { ".": {
@@ -101,10 +100,10 @@
"import": "./esm/bls12-381.js", "import": "./esm/bls12-381.js",
"default": "./bls12-381.js" "default": "./bls12-381.js"
}, },
"./bn": { "./bn254": {
"types": "./bn.d.ts", "types": "./bn254.d.ts",
"import": "./esm/bn.js", "import": "./esm/bn254.js",
"default": "./bn.js" "default": "./bn254.js"
}, },
"./ed25519": { "./ed25519": {
"types": "./ed25519.d.ts", "types": "./ed25519.d.ts",
@@ -126,16 +125,6 @@
"import": "./esm/jubjub.js", "import": "./esm/jubjub.js",
"default": "./jubjub.js" "default": "./jubjub.js"
}, },
"./p192": {
"types": "./p192.d.ts",
"import": "./esm/p192.js",
"default": "./p192.js"
},
"./p224": {
"types": "./p224.d.ts",
"import": "./esm/p224.js",
"default": "./p224.js"
},
"./p256": { "./p256": {
"types": "./p256.d.ts", "types": "./p256.d.ts",
"import": "./esm/p256.js", "import": "./esm/p256.js",
@@ -160,11 +149,6 @@
"types": "./secp256k1.d.ts", "types": "./secp256k1.d.ts",
"import": "./esm/secp256k1.js", "import": "./esm/secp256k1.js",
"default": "./secp256k1.js" "default": "./secp256k1.js"
},
"./stark": {
"types": "./stark.d.ts",
"import": "./esm/stark.js",
"default": "./stark.js"
} }
}, },
"keywords": [ "keywords": [
@@ -181,20 +165,17 @@
"secp256k1", "secp256k1",
"ed25519", "ed25519",
"ed448", "ed448",
"x25519",
"ed25519",
"bls12-381", "bls12-381",
"bn254", "bn254",
"pasta", "pasta",
"bls", "bls",
"nist", "noble",
"ecc", "ecc",
"ecdsa", "ecdsa",
"eddsa", "eddsa",
"schnorr" "schnorr"
], ],
"funding": [ "funding": "https://paulmillr.com/funding/"
{
"type": "individual",
"url": "https://paulmillr.com/funding/"
}
]
} }

View File

@@ -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 { Field, 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,
@@ -24,64 +28,85 @@ import {
type Fp = bigint; // Can be different field? type Fp = bigint; // Can be different field?
// prettier-ignore
const _2n = BigInt(2), _3n = BigInt(3);
export type ShortSignatureCoder<Fp> = {
fromHex(hex: Hex): ProjPointType<Fp>;
toRawBytes(point: ProjPointType<Fp>): Uint8Array;
toHex(point: ProjPointType<Fp>): string;
};
export type SignatureCoder<Fp2> = { export type SignatureCoder<Fp2> = {
decode(hex: Hex): ProjPointType<Fp2>; fromHex(hex: Hex): ProjPointType<Fp2>;
encode(point: ProjPointType<Fp2>): Uint8Array; toRawBytes(point: ProjPointType<Fp2>): Uint8Array;
toHex(point: ProjPointType<Fp2>): string;
}; };
export type CurveType<Fp, Fp2, Fp6, Fp12> = { export type CurveType<Fp, Fp2, Fp6, Fp12> = {
r: bigint;
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;
}; };
x: bigint; fields: {
Fp: Field<Fp>; Fp: IField<Fp>;
Fr: Field<bigint>; Fr: IField<bigint>;
Fp2: Field<Fp2> & { Fp2: IField<Fp2> & {
reim: (num: Fp2) => { re: bigint; im: bigint }; reim: (num: Fp2) => { re: bigint; im: bigint };
multiplyByB: (num: Fp2) => Fp2; multiplyByB: (num: Fp2) => Fp2;
frobeniusMap(num: Fp2, power: number): Fp2; frobeniusMap(num: Fp2, power: number): Fp2;
}; };
Fp6: Field<Fp6>; Fp6: IField<Fp6>;
Fp12: Field<Fp12> & { Fp12: IField<Fp12> & {
frobeniusMap(num: Fp12, power: number): Fp12; frobeniusMap(num: Fp12, power: number): Fp12;
multiplyBy014(num: Fp12, o0: Fp2, o1: Fp2, o4: Fp2): Fp12; multiplyBy014(num: Fp12, o0: Fp2, o1: Fp2, o4: Fp2): Fp12;
conjugate(num: Fp12): Fp12; conjugate(num: Fp12): Fp12;
finalExponentiate(num: Fp12): Fp12; finalExponentiate(num: Fp12): Fp12;
}; };
htfDefaults: htf.Opts; };
params: {
x: bigint;
r: bigint;
};
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> = {
CURVE: CurveType<Fp, Fp2, Fp6, Fp12>;
Fr: Field<bigint>;
Fp: Field<Fp>;
Fp2: Field<Fp2>;
Fp6: Field<Fp6>;
Fp12: Field<Fp12>;
G1: CurvePointsRes<Fp> & ReturnType<typeof htf.createHasher<Fp>>;
G2: CurvePointsRes<Fp2> & ReturnType<typeof htf.createHasher<Fp2>>;
Signature: SignatureCoder<Fp2>;
millerLoop: (ell: [Fp2, Fp2, Fp2][], g1: [Fp, Fp]) => Fp12;
calcPairingPrecomputes: (p: AffinePoint<Fp2>) => [Fp2, Fp2, Fp2][];
pairing: (P: ProjPointType<Fp>, Q: ProjPointType<Fp2>, withFinalExponent?: boolean) => Fp12;
getPublicKey: (privateKey: PrivKey) => Uint8Array; getPublicKey: (privateKey: PrivKey) => Uint8Array;
getPublicKeyForShortSignatures: (privateKey: PrivKey) => Uint8Array;
sign: { sign: {
(message: Hex, privateKey: PrivKey): Uint8Array; (message: Hex, privateKey: PrivKey): Uint8Array;
(message: ProjPointType<Fp2>, privateKey: PrivKey): ProjPointType<Fp2>; (message: ProjPointType<Fp2>, privateKey: PrivKey): ProjPointType<Fp2>;
}; };
signShortSignature: {
(message: Hex, privateKey: PrivKey): Uint8Array;
(message: ProjPointType<Fp>, privateKey: PrivKey): 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;
verifyBatch: (
signature: Hex | ProjPointType<Fp2>,
messages: (Hex | ProjPointType<Fp2>)[],
publicKeys: (Hex | ProjPointType<Fp>)[],
htfOpts?: htfBasicOpts
) => boolean; ) => boolean;
aggregatePublicKeys: { aggregatePublicKeys: {
(publicKeys: Hex[]): Uint8Array; (publicKeys: Hex[]): Uint8Array;
@@ -91,23 +116,41 @@ export type CurveFn<Fp, Fp2, Fp6, Fp12> = {
(signatures: Hex[]): Uint8Array; (signatures: Hex[]): Uint8Array;
(signatures: ProjPointType<Fp2>[]): ProjPointType<Fp2>; (signatures: ProjPointType<Fp2>[]): ProjPointType<Fp2>;
}; };
verifyBatch: ( aggregateShortSignatures: {
signature: Hex | ProjPointType<Fp2>, (signatures: Hex[]): Uint8Array;
messages: (Hex | ProjPointType<Fp2>)[], (signatures: ProjPointType<Fp>[]): ProjPointType<Fp>;
publicKeys: (Hex | ProjPointType<Fp>)[] };
) => boolean; millerLoop: (ell: [Fp2, Fp2, Fp2][], g1: [Fp, Fp]) => Fp12;
pairing: (P: ProjPointType<Fp>, Q: ProjPointType<Fp2>, withFinalExponent?: boolean) => Fp12;
G1: CurvePointsRes<Fp> & ReturnType<typeof createHasher<Fp>>;
G2: CurvePointsRes<Fp2> & ReturnType<typeof createHasher<Fp2>>;
Signature: SignatureCoder<Fp2>;
ShortSignature: ShortSignatureCoder<Fp>;
params: {
x: bigint;
r: bigint;
G1b: bigint;
G2b: Fp2;
};
fields: {
Fp: IField<Fp>;
Fp2: IField<Fp2>;
Fp6: IField<Fp6>;
Fp12: IField<Fp12>;
Fr: IField<bigint>;
};
utils: { utils: {
randomPrivateKey: () => Uint8Array; randomPrivateKey: () => Uint8Array;
calcPairingPrecomputes: (p: AffinePoint<Fp2>) => [Fp2, Fp2, Fp2][];
}; };
}; };
export function bls<Fp2, Fp6, Fp12>( export function bls<Fp2, Fp6, Fp12>(
CURVE: CurveType<Fp, Fp2, Fp6, Fp12> CURVE: CurveType<Fp, Fp2, Fp6, Fp12>
): CurveFn<Fp, Fp2, Fp6, Fp12> { ): CurveFn<Fp, Fp2, Fp6, Fp12> {
// Fields looks pretty specific for curve, so for now we 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; const { Fp, Fr, Fp2, Fp6, Fp12 } = CURVE.fields;
const BLS_X_LEN = bitLen(CURVE.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
@@ -122,18 +165,18 @@ export function bls<Fp2, Fp6, Fp12>(
// Double // Double
let t0 = Fp2.sqr(Ry); // Ry² let t0 = Fp2.sqr(Ry); // Ry²
let t1 = Fp2.sqr(Rz); // Rz² let t1 = Fp2.sqr(Rz); // Rz²
let t2 = Fp2.multiplyByB(Fp2.mul(t1, 3n)); // 3 * T1 * B let t2 = Fp2.multiplyByB(Fp2.mul(t1, _3n)); // 3 * T1 * B
let t3 = Fp2.mul(t2, 3n); // 3 * T2 let t3 = Fp2.mul(t2, _3n); // 3 * T2
let t4 = Fp2.sub(Fp2.sub(Fp2.sqr(Fp2.add(Ry, Rz)), t1), t0); // (Ry + Rz)² - T1 - T0 let t4 = Fp2.sub(Fp2.sub(Fp2.sqr(Fp2.add(Ry, Rz)), t1), t0); // (Ry + Rz)² - T1 - T0
ell_coeff.push([ ell_coeff.push([
Fp2.sub(t2, t0), // T2 - T0 Fp2.sub(t2, t0), // T2 - T0
Fp2.mul(Fp2.sqr(Rx), 3n), // 3 * Rx² Fp2.mul(Fp2.sqr(Rx), _3n), // 3 * Rx²
Fp2.neg(t4), // -T4 Fp2.neg(t4), // -T4
]); ]);
Rx = Fp2.div(Fp2.mul(Fp2.mul(Fp2.sub(t0, t3), Rx), Ry), 2n); // ((T0 - T3) * Rx * Ry) / 2 Rx = Fp2.div(Fp2.mul(Fp2.mul(Fp2.sub(t0, t3), Rx), Ry), _2n); // ((T0 - T3) * Rx * Ry) / 2
Ry = Fp2.sub(Fp2.sqr(Fp2.div(Fp2.add(t0, t3), 2n)), Fp2.mul(Fp2.sqr(t2), 3n)); // ((T0 + T3) / 2)² - 3 * T2² Ry = Fp2.sub(Fp2.sqr(Fp2.div(Fp2.add(t0, t3), _2n)), Fp2.mul(Fp2.sqr(t2), _3n)); // ((T0 + T3) / 2)² - 3 * T2²
Rz = Fp2.mul(t0, t4); // T0 * T4 Rz = Fp2.mul(t0, t4); // T0 * T4
if (bitGet(CURVE.x, i)) { if (bitGet(CURVE.params.x, i)) {
// Addition // Addition
let t0 = Fp2.sub(Ry, Fp2.mul(Qy, Rz)); // Ry - Qy * Rz let t0 = Fp2.sub(Ry, Fp2.mul(Qy, Rz)); // Ry - Qy * Rz
let t1 = Fp2.sub(Rx, Fp2.mul(Qx, Rz)); // Rx - Qx * Rz let t1 = Fp2.sub(Rx, Fp2.mul(Qx, Rz)); // Rx - Qx * Rz
@@ -145,7 +188,7 @@ export function bls<Fp2, Fp6, Fp12>(
let t2 = Fp2.sqr(t1); // T1² let t2 = Fp2.sqr(t1); // T1²
let t3 = Fp2.mul(t2, t1); // T2 * T1 let t3 = Fp2.mul(t2, t1); // T2 * T1
let t4 = Fp2.mul(t2, Rx); // T2 * Rx let t4 = Fp2.mul(t2, Rx); // T2 * Rx
let t5 = Fp2.add(Fp2.sub(t3, Fp2.mul(t4, 2n)), Fp2.mul(Fp2.sqr(t0), Rz)); // T3 - 2 * T4 + T0² * Rz let t5 = Fp2.add(Fp2.sub(t3, Fp2.mul(t4, _2n)), Fp2.mul(Fp2.sqr(t0), Rz)); // T3 - 2 * T4 + T0² * Rz
Rx = Fp2.mul(t1, t5); // T1 * T5 Rx = Fp2.mul(t1, t5); // T1 * T5
Ry = Fp2.sub(Fp2.mul(Fp2.sub(t4, t5), t0), Fp2.mul(t3, Ry)); // (T4 - T5) * T0 - T3 * Ry Ry = Fp2.sub(Fp2.mul(Fp2.sub(t4, t5), t0), Fp2.mul(t3, Ry)); // (T4 - T5) * T0 - T3 * Ry
Rz = Fp2.mul(Rz, t3); // Rz * T3 Rz = Fp2.mul(Rz, t3); // Rz * T3
@@ -155,7 +198,7 @@ export function bls<Fp2, Fp6, Fp12>(
} }
function millerLoop(ell: [Fp2, Fp2, Fp2][], g1: [Fp, Fp]): Fp12 { function millerLoop(ell: [Fp2, Fp2, Fp2][], g1: [Fp, Fp]): Fp12 {
const { x } = CURVE; const { x } = CURVE.params;
const Px = g1[0]; const Px = g1[0];
const Py = g1[1]; const Py = g1[1];
let f12 = Fp12.ONE; let f12 = Fp12.ONE;
@@ -174,15 +217,17 @@ export function bls<Fp2, Fp6, Fp12>(
const utils = { const utils = {
randomPrivateKey: (): Uint8Array => { randomPrivateKey: (): Uint8Array => {
return Fr.toBytes(hashToPrivateScalar(CURVE.randomBytes(groupLen + 8), CURVE.r)); const length = getMinHashLength(Fr.ORDER);
return mapHashToField(CURVE.randomBytes(length), Fr.ORDER);
}, },
calcPairingPrecomputes,
}; };
// Point on G1 curve: (x, y) // Point on G1 curve: (x, y)
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,
}) })
@@ -208,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
@@ -235,31 +281,60 @@ 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 normP2(point: G2Hex): G2 { function normP1Hash(point: G1Hex, htfOpts?: htfBasicOpts): G1 {
return point instanceof G2.ProjectivePoint ? point : Signature.decode(point); return point instanceof G1.ProjectivePoint
? point
: (G1.hashToCurve(ensureBytes('point', point), htfOpts) as G1);
} }
function normP2Hash(point: G2Hex, htfOpts?: htf.htfBasicOpts): G2 { function normP2(point: G2Hex): G2 {
return point instanceof G2.ProjectivePoint ? point : Signature.fromHex(point);
}
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.normalizePrivateKey(privateKey)); const sigPoint = msgPoint.multiply(G1.normPrivateKeyToScalar(privateKey));
if (message instanceof G2.ProjectivePoint) return sigPoint; if (message instanceof G2.ProjectivePoint) return sigPoint;
return Signature.encode(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.
@@ -268,7 +343,7 @@ export function bls<Fp2, Fp6, Fp12>(
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);
@@ -282,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;
@@ -309,7 +404,21 @@ export function bls<Fp2, Fp6, Fp12>(
aggAffine.assertValidity(); aggAffine.assertValidity();
return aggAffine; return aggAffine;
} }
return Signature.encode(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
@@ -318,7 +427,7 @@ export function bls<Fp2, Fp6, Fp12>(
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));
@@ -353,24 +462,35 @@ export function bls<Fp2, Fp6, Fp12>(
G1.ProjectivePoint.BASE._setWindowSize(4); G1.ProjectivePoint.BASE._setWindowSize(4);
return { return {
CURVE, getPublicKey,
getPublicKeyForShortSignatures,
sign,
signShortSignature,
verify,
verifyBatch,
verifyShortSignature,
aggregatePublicKeys,
aggregateSignatures,
aggregateShortSignatures,
millerLoop,
pairing,
G1,
G2,
Signature,
ShortSignature,
fields: {
Fr, Fr,
Fp, Fp,
Fp2, Fp2,
Fp6, Fp6,
Fp12, Fp12,
G1, },
G2, params: {
Signature, x: CURVE.params.x,
millerLoop, r: CURVE.params.r,
calcPairingPrecomputes, G1b: CURVE.G1.b,
pairing, G2b: CURVE.G2.b,
getPublicKey, },
sign,
verify,
aggregatePublicKeys,
aggregateSignatures,
verifyBatch,
utils, utils,
}; };
} }

View File

@@ -1,6 +1,6 @@
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */ /*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
// Abelian group utilities // Abelian group utilities
import { Field, validateField, nLength } from './modular.js'; import { IField, validateField, nLength } from './modular.js';
import { validateObject } from './utils.js'; import { validateObject } from './utils.js';
const _0n = BigInt(0); const _0n = BigInt(0);
const _1n = BigInt(1); const _1n = BigInt(1);
@@ -168,7 +168,7 @@ export function wNAF<T extends Group<T>>(c: GroupConstructor<T>, bits: number) {
// Generic BasicCurve interface: works even for polynomial fields (BLS): P, n, h would be ok. // Generic BasicCurve interface: works even for polynomial fields (BLS): P, n, h would be ok.
// Though generator can be different (Fp2 / Fp6 for BLS). // Though generator can be different (Fp2 / Fp6 for BLS).
export type BasicCurve<T> = { export type BasicCurve<T> = {
Fp: Field<T>; // Field over which we'll do calculations (Fp) Fp: IField<T>; // Field over which we'll do calculations (Fp)
n: bigint; // Curve order, total count of valid points in the field n: bigint; // Curve order, total count of valid points in the field
nBitLength?: number; // bit length of curve order nBitLength?: number; // bit length of curve order
nByteLength?: number; // byte length of curve order nByteLength?: number; // byte length of curve order
@@ -195,5 +195,9 @@ export function validateBasic<FP, T>(curve: BasicCurve<FP> & T) {
} }
); );
// Set defaults // Set defaults
return Object.freeze({ ...nLength(curve.n, curve.nBitLength), ...curve } as const); return Object.freeze({
...nLength(curve.n, curve.nBitLength),
...curve,
...{ p: curve.Fp.ORDER },
} as const);
} }

View File

@@ -5,11 +5,9 @@ import * as ut from './utils.js';
import { ensureBytes, FHash, Hex } from './utils.js'; import { ensureBytes, FHash, Hex } from './utils.js';
import { Group, GroupConstructor, wNAF, BasicCurve, validateBasic, AffinePoint } from './curve.js'; import { Group, GroupConstructor, wNAF, BasicCurve, validateBasic, AffinePoint } from './curve.js';
// Be friendly to bad ECMAScript parsers by not using bigint literals like 123n // Be friendly to bad ECMAScript parsers by not using bigint literals
const _0n = BigInt(0); // prettier-ignore
const _1n = BigInt(1); const _0n = BigInt(0), _1n = BigInt(1), _2n = BigInt(2), _8n = BigInt(8);
const _2n = BigInt(2);
const _8n = BigInt(8);
// Edwards curves must declare params a & d. // Edwards curves must declare params a & d.
export type CurveType = BasicCurve<bigint> & { export type CurveType = BasicCurve<bigint> & {
@@ -20,10 +18,13 @@ export type CurveType = BasicCurve<bigint> & {
adjustScalarBytes?: (bytes: Uint8Array) => Uint8Array; // clears bits to get valid field elemtn adjustScalarBytes?: (bytes: Uint8Array) => Uint8Array; // clears bits to get valid field elemtn
domain?: (data: Uint8Array, ctx: Uint8Array, phflag: boolean) => Uint8Array; // Used for hashing domain?: (data: Uint8Array, ctx: Uint8Array, phflag: boolean) => Uint8Array; // Used for hashing
uvRatio?: (u: bigint, v: bigint) => { isValid: boolean; value: bigint }; // Ratio √(u/v) uvRatio?: (u: bigint, v: bigint) => { isValid: boolean; value: bigint }; // Ratio √(u/v)
preHash?: FHash; // RFC 8032 pre-hashing of messages to sign() / verify() prehash?: FHash; // RFC 8032 pre-hashing of messages to sign() / verify()
mapToCurve?: (scalar: bigint[]) => AffinePoint<bigint>; // for hash-to-curve standard mapToCurve?: (scalar: bigint[]) => AffinePoint<bigint>; // for hash-to-curve standard
}; };
// verification rule is either zip215 or rfc8032 / nist186-5. Consult fromHex:
const VERIFY_DEFAULT = { zip215: true };
function validateOpts(curve: CurveType) { function validateOpts(curve: CurveType) {
const opts = validateBasic(curve); const opts = validateBasic(curve);
ut.validateObject( ut.validateObject(
@@ -51,6 +52,8 @@ export interface ExtPointType extends Group<ExtPointType> {
readonly ey: bigint; readonly ey: bigint;
readonly ez: bigint; readonly ez: bigint;
readonly et: bigint; readonly et: bigint;
get x(): bigint;
get y(): bigint;
assertValidity(): void; assertValidity(): void;
multiply(scalar: bigint): ExtPointType; multiply(scalar: bigint): ExtPointType;
multiplyUnsafe(scalar: bigint): ExtPointType; multiplyUnsafe(scalar: bigint): ExtPointType;
@@ -58,6 +61,8 @@ export interface ExtPointType extends Group<ExtPointType> {
isTorsionFree(): boolean; isTorsionFree(): boolean;
clearCofactor(): ExtPointType; clearCofactor(): ExtPointType;
toAffine(iz?: bigint): AffinePoint<bigint>; toAffine(iz?: bigint): AffinePoint<bigint>;
toRawBytes(isCompressed?: boolean): Uint8Array;
toHex(isCompressed?: boolean): string;
} }
// Static methods of Extended Point with coordinates in X, Y, Z, T // Static methods of Extended Point with coordinates in X, Y, Z, T
export interface ExtPointConstructor extends GroupConstructor<ExtPointType> { export interface ExtPointConstructor extends GroupConstructor<ExtPointType> {
@@ -70,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;
@@ -88,8 +98,16 @@ export type CurveFn = {
// It is not generic twisted curve for now, but ed25519/ed448 generic implementation // It is not generic twisted curve for now, but ed25519/ed448 generic implementation
export function twistedEdwards(curveDef: CurveType): CurveFn { export function twistedEdwards(curveDef: CurveType): CurveFn {
const CURVE = validateOpts(curveDef) as ReturnType<typeof validateOpts>; const CURVE = validateOpts(curveDef) as ReturnType<typeof validateOpts>;
const { Fp, n: CURVE_ORDER, preHash, hash: cHash, randomBytes, nByteLength, h: cofactor } = CURVE; const {
const MASK = _2n ** BigInt(nByteLength * 8); Fp,
n: CURVE_ORDER,
prehash: prehash,
hash: cHash,
randomBytes,
nByteLength,
h: cofactor,
} = CURVE;
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)
@@ -109,7 +127,7 @@ export function twistedEdwards(curveDef: CurveType): CurveFn {
if (ctx.length || phflag) throw new Error('Contexts/pre-hash are not supported'); if (ctx.length || phflag) throw new Error('Contexts/pre-hash are not supported');
return data; return data;
}); // NOOP }); // NOOP
const inBig = (n: bigint) => typeof n === 'bigint' && 0n < n; // n in [1..] const inBig = (n: bigint) => typeof n === 'bigint' && _0n < n; // n in [1..]
const inRange = (n: bigint, max: bigint) => inBig(n) && inBig(max) && n < max; // n in [1..max-1] const inRange = (n: bigint, max: bigint) => inBig(n) && inBig(max) && n < max; // n in [1..max-1]
const in0MaskRange = (n: bigint) => n === _0n || inRange(n, MASK); // n in [0..MASK-1] const in0MaskRange = (n: bigint) => n === _0n || inRange(n, MASK); // n in [0..MASK-1]
function assertInRange(n: bigint, max: bigint) { function assertInRange(n: bigint, max: bigint) {
@@ -297,8 +315,9 @@ export function twistedEdwards(curveDef: CurveType): CurveFn {
// Non-constant-time multiplication. Uses double-and-add algorithm. // Non-constant-time multiplication. Uses double-and-add algorithm.
// It's faster, but should only be used when you don't care about // It's faster, but should only be used when you don't care about
// an exposed private key e.g. sig verification. // an exposed private key e.g. sig verification.
// Does NOT allow scalars higher than CURVE.n.
multiplyUnsafe(scalar: bigint): Point { multiplyUnsafe(scalar: bigint): Point {
let n = assertGE0(scalar); let n = assertGE0(scalar); // 0 <= scalar < CURVE.n
if (n === _0n) return I; if (n === _0n) return I;
if (this.equals(I) || n === _1n) return this; if (this.equals(I) || n === _1n) return this;
if (this.equals(G)) return this.wNAF(n).p; if (this.equals(G)) return this.wNAF(n).p;
@@ -341,7 +360,7 @@ export function twistedEdwards(curveDef: CurveType): CurveFn {
// Converts hash string or Uint8Array to Point. // Converts hash string or Uint8Array to Point.
// Uses algo from RFC8032 5.1.3. // Uses algo from RFC8032 5.1.3.
static fromHex(hex: Hex, strict = true): Point { static fromHex(hex: Hex, zip215 = false): Point {
const { d, a } = CURVE; const { d, a } = CURVE;
const len = Fp.BYTES; const len = Fp.BYTES;
hex = ensureBytes('pointHex', hex, len); // copy hex to a new array hex = ensureBytes('pointHex', hex, len); // copy hex to a new array
@@ -353,8 +372,8 @@ export function twistedEdwards(curveDef: CurveType): CurveFn {
// y=0 is allowed // y=0 is allowed
} else { } else {
// RFC8032 prohibits >= p, but ZIP215 doesn't // RFC8032 prohibits >= p, but ZIP215 doesn't
if (strict) assertInRange(y, Fp.ORDER); // strict=true [1..P-1] (2^255-19-1 for ed25519) if (zip215) assertInRange(y, MASK); // zip215=true [1..P-1] (2^255-19-1 for ed25519)
else assertInRange(y, MASK); // strict=false [1..MASK-1] (2^256-1 for ed25519) else assertInRange(y, Fp.ORDER); // zip215=false [1..MASK-1] (2^256-1 for ed25519)
} }
// Ed25519: x² = (y²-1)/(dy²+1) mod p. Ed448: x² = (y²-1)/(dy²-1) mod p. Generic case: // Ed25519: x² = (y²-1)/(dy²+1) mod p. Ed448: x² = (y²-1)/(dy²-1) mod p. Generic case:
@@ -365,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 });
} }
@@ -416,32 +438,44 @@ export function twistedEdwards(curveDef: CurveType): CurveFn {
// int('LE', SHA512(dom2(F, C) || msgs)) mod N // int('LE', SHA512(dom2(F, C) || msgs)) mod N
function hashDomainToScalar(context: Hex = new Uint8Array(), ...msgs: Uint8Array[]) { function hashDomainToScalar(context: Hex = new Uint8Array(), ...msgs: Uint8Array[]) {
const msg = ut.concatBytes(...msgs); const msg = ut.concatBytes(...msgs);
return modN_LE(cHash(domain(msg, ensureBytes('context', context), !!preHash))); return modN_LE(cHash(domain(msg, ensureBytes('context', context), !!prehash)));
} }
/** Signs message with privateKey. RFC8032 5.1.6 */ /** Signs message with privateKey. RFC8032 5.1.6 */
function sign(msg: Hex, privKey: Hex, context?: Hex): Uint8Array { function sign(msg: Hex, privKey: Hex, options: { context?: Hex } = {}): Uint8Array {
msg = ensureBytes('message', msg); msg = ensureBytes('message', msg);
if (preHash) msg = preHash(msg); // for ed25519ph etc. if (prehash) msg = prehash(msg); // for ed25519ph etc.
const { prefix, scalar, pointBytes } = getExtendedPublicKey(privKey); const { prefix, scalar, pointBytes } = getExtendedPublicKey(privKey);
const r = hashDomainToScalar(context, prefix, msg); // r = dom2(F, C) || prefix || PH(M) const r = hashDomainToScalar(options.context, prefix, msg); // r = dom2(F, C) || prefix || PH(M)
const R = G.multiply(r).toRawBytes(); // R = rG const R = G.multiply(r).toRawBytes(); // R = rG
const k = hashDomainToScalar(context, R, pointBytes, msg); // R || A || PH(M) const k = hashDomainToScalar(options.context, R, pointBytes, msg); // R || A || PH(M)
const s = modN(r + k * scalar); // S = (r + k * s) mod L const s = modN(r + k * scalar); // S = (r + k * s) mod L
assertGE0(s); // 0 <= s < l assertGE0(s); // 0 <= s < l
const res = ut.concatBytes(R, ut.numberToBytesLE(s, Fp.BYTES)); const res = ut.concatBytes(R, ut.numberToBytesLE(s, Fp.BYTES));
return ensureBytes('result', res, nByteLength * 2); // 64-byte signature return ensureBytes('result', res, nByteLength * 2); // 64-byte signature
} }
function verify(sig: Hex, msg: Hex, publicKey: Hex, context?: Hex): boolean { const verifyOpts: { context?: Hex; zip215?: boolean } = VERIFY_DEFAULT;
function verify(sig: Hex, msg: Hex, publicKey: Hex, options = verifyOpts): boolean {
const { context, zip215 } = options;
const len = Fp.BYTES; // Verifies EdDSA signature against message and public key. RFC8032 5.1.7. const len = Fp.BYTES; // Verifies EdDSA signature against message and public key. RFC8032 5.1.7.
sig = ensureBytes('signature', sig, 2 * len); // An extended group equation is checked. sig = ensureBytes('signature', sig, 2 * len); // An extended group equation is checked.
msg = ensureBytes('message', msg); // ZIP215 compliant, which means not fully RFC8032 compliant. msg = ensureBytes('message', msg);
if (preHash) msg = preHash(msg); // for ed25519ph, etc if (prehash) msg = prehash(msg); // for ed25519ph, etc
const A = Point.fromHex(publicKey, false); // Check for s bounds, hex validity
const R = Point.fromHex(sig.slice(0, len), false); // 0 <= R < 2^256: ZIP215 R can be >= P const s = ut.bytesToNumberLE(sig.slice(len, 2 * len));
const s = ut.bytesToNumberLE(sig.slice(len, 2 * len)); // 0 <= s < l // zip215: true is good for consensus-critical apps and allows points < 2^256
const SB = G.multiplyUnsafe(s); // zip215: false follows RFC8032 / NIST186-5 and restricts points to CURVE.p
let A, R, SB;
try {
A = Point.fromHex(publicKey, zip215);
R = Point.fromHex(sig.slice(0, len), zip215);
SB = G.multiplyUnsafe(s); // 0 <= s < l is done inside
} catch (error) {
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));
// [8][S]B = [8]R + [8][k]A' // [8][S]B = [8]R + [8][k]A'

View File

@@ -1,33 +1,37 @@
/*! 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, Field } from './modular.js'; import { mod, IField } from './modular.js';
import { CHash, concatBytes, utf8ToBytes, validateObject } from './utils.js'; import type { CHash } from './utils.js';
import { bytesToNumberBE, isBytes, concatBytes, utf8ToBytes, validateObject } from './utils.js';
/**
* * `DST` is a domain separation tag, defined in section 2.2.5
* * `p` characteristic of F, where F is a finite field of characteristic p and order q = p^m
* * `m` is extension degree (1 for prime fields)
* * `k` is the target security target in bits (e.g. 128), from section 5.1
* * `expand` is `xmd` (SHA2, SHA3, BLAKE) or `xof` (SHAKE, BLAKE-XOF)
* * `hash` conforming to `utils.CHash` interface, with `outputLen` / `blockLen` props
*/
type UnicodeOrBytes = string | Uint8Array;
export type Opts = { export type Opts = {
DST: string; // DST: a domain separation tag, defined in section 2.2.5 DST: UnicodeOrBytes;
encodeDST: string; p: bigint;
p: bigint; // characteristic of F, where F is a finite field of characteristic p and order q = p^m m: number;
m: number; // extension degree of F, m >= 1 k: number;
k: number; // k: the target security level for the suite in bits, defined in section 5.1 expand: 'xmd' | 'xof';
expand?: 'xmd' | 'xof'; // use a message that has already been processed by expand_message_xmd
// Hash functions for: expand_message_xmd is appropriate for use with a
// wide range of hash functions, including SHA-2, SHA-3, BLAKE2, and others.
// BBS+ uses blake2: https://github.com/hyperledger/aries-framework-go/issues/2247
// TODO: verify that hash is shake if expand==='xof' via types
hash: CHash; hash: CHash;
}; };
// Octet Stream to Integer (bytesToNumberBE) function validateDST(dst: UnicodeOrBytes): Uint8Array {
function os2ip(bytes: Uint8Array): bigint { if (isBytes(dst)) return dst;
let result = 0n; if (typeof dst === 'string') return utf8ToBytes(dst);
for (let i = 0; i < bytes.length; i++) { throw new Error('DST must be Uint8Array or string');
result <<= 8n;
result += BigInt(bytes[i]);
}
return result;
} }
// Integer to Octet Stream // Octet Stream to Integer. "spec" implementation of os2ip is 2.5x slower vs bytesToNumberBE.
const os2ip = bytesToNumberBE;
// Integer to Octet Stream (numberToBytesBE)
function i2osp(value: number, length: number): Uint8Array { function i2osp(value: number, length: number): Uint8Array {
if (value < 0 || value >= 1 << (8 * length)) { if (value < 0 || value >= 1 << (8 * length)) {
throw new Error(`bad I2OSP call: value=${value} length=${length}`); throw new Error(`bad I2OSP call: value=${value} length=${length}`);
@@ -48,33 +52,32 @@ function strxor(a: Uint8Array, b: Uint8Array): Uint8Array {
return arr; return arr;
} }
function isBytes(item: unknown): void { function abytes(item: unknown): void {
if (!(item instanceof Uint8Array)) throw new Error('Uint8Array expected'); if (!isBytes(item)) throw new Error('Uint8Array expected');
} }
function isNum(item: unknown): void { 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); isNum(lenInBytes);
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-16#section-5.3.3 // https://www.rfc-editor.org/rfc/rfc9380#section-5.3.3
if (DST.length > 255) DST = H(concatBytes(utf8ToBytes('H2C-OVERSIZE-DST-'), DST)); if (DST.length > 255) DST = H(concatBytes(utf8ToBytes('H2C-OVERSIZE-DST-'), DST));
const b_in_bytes = H.outputLen; const { outputLen: b_in_bytes, blockLen: r_in_bytes } = H;
const r_in_bytes = H.blockLen;
const ell = Math.ceil(lenInBytes / b_in_bytes); const ell = Math.ceil(lenInBytes / b_in_bytes);
if (ell > 255) throw new Error('Invalid xmd length'); if (ell > 255) throw new Error('Invalid xmd length');
const DST_prime = concatBytes(DST, i2osp(DST.length, 1)); const DST_prime = concatBytes(DST, i2osp(DST.length, 1));
const Z_pad = i2osp(0, r_in_bytes); const Z_pad = i2osp(0, r_in_bytes);
const l_i_b_str = i2osp(lenInBytes, 2); const l_i_b_str = i2osp(lenInBytes, 2); // len_in_bytes_str
const b = new Array<Uint8Array>(ell); const b = new Array<Uint8Array>(ell);
const b_0 = H(concatBytes(Z_pad, msg, l_i_b_str, i2osp(0, 1), DST_prime)); const b_0 = H(concatBytes(Z_pad, msg, l_i_b_str, i2osp(0, 1), DST_prime));
b[0] = H(concatBytes(b_0, i2osp(1, 1), DST_prime)); b[0] = H(concatBytes(b_0, i2osp(1, 1), DST_prime));
@@ -86,6 +89,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,
@@ -93,10 +101,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); isNum(lenInBytes);
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-16#section-5.3.3 // https://www.rfc-editor.org/rfc/rfc9380#section-5.3.3
// DST = H('H2C-OVERSIZE-DST-' || a_very_long_DST, Math.ceil((lenInBytes * k) / 8)); // DST = H('H2C-OVERSIZE-DST-' || a_very_long_DST, Math.ceil((lenInBytes * k) / 8));
if (DST.length > 255) { if (DST.length > 255) {
const dkLen = Math.ceil((2 * k) / 8); const dkLen = Math.ceil((2 * k) / 8);
@@ -117,33 +125,44 @@ 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}` * @param options `{DST: string, p: bigint, m: number, k: number, expand: 'xmd' | 'xof', hash: H}`, see above
* @returns [u_0, ..., u_(count - 1)], a list of field elements. * @returns [u_0, ..., u_(count - 1)], a list of field elements.
*/ */
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, {
DST: 'stringOrUint8Array',
p: 'bigint',
m: 'isSafeInteger',
k: 'isSafeInteger',
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); isNum(count);
if (typeof _DST !== 'string') throw new Error('DST must be valid'); const DST = validateDST(_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;
const DST = utf8ToBytes(_DST); let prb; // pseudo_random_bytes
const pseudo_random_bytes = if (expand === 'xmd') {
expand === 'xmd' prb = expand_message_xmd(msg, DST, len_in_bytes, hash);
? expand_message_xmd(msg, DST, len_in_bytes, hash) } else if (expand === 'xof') {
: expand === 'xof' prb = expand_message_xof(msg, DST, len_in_bytes, k, hash);
? expand_message_xof(msg, DST, len_in_bytes, k, hash) } else if (expand === '_internal_pass') {
: msg; // for internal tests only
prb = msg;
} else {
throw new Error('expand must be "xmd" or "xof"');
}
const u = new Array(count); const u = new Array(count);
for (let i = 0; i < count; i++) { for (let i = 0; i < count; i++) {
const e = new Array(m); const e = new Array(m);
for (let j = 0; j < m; j++) { for (let j = 0; j < m; j++) {
const elm_offset = L * (j + i * m); const elm_offset = L * (j + i * m);
const tv = pseudo_random_bytes.subarray(elm_offset, elm_offset + L); const tv = prb.subarray(elm_offset, elm_offset + L);
e[j] = mod(os2ip(tv), p); e[j] = mod(os2ip(tv), p);
} }
u[i] = e; u[i] = e;
@@ -151,7 +170,7 @@ export function hash_to_field(msg: Uint8Array, count: number, options: Opts): bi
return u; return u;
} }
export function isogenyMap<T, F extends Field<T>>(field: F, map: [T[], T[], T[], T[]]) { export function isogenyMap<T, F extends IField<T>>(field: F, map: [T[], T[], T[], T[]]) {
// Make same order as in spec // Make same order as in spec
const COEFF = map.map((i) => Array.from(i).reverse()); const COEFF = map.map((i) => Array.from(i).reverse());
return (x: T, y: T) => { return (x: T, y: T) => {
@@ -179,27 +198,17 @@ export type MapToCurve<T> = (scalar: bigint[]) => AffinePoint<T>;
// Separated from initialization opts, so users won't accidentally change per-curve parameters // Separated from initialization opts, so users won't accidentally change per-curve parameters
// (changing DST is ok!) // (changing DST is ok!)
export type htfBasicOpts = { DST: string }; export type htfBasicOpts = { DST: UnicodeOrBytes };
export function createHasher<T>( export function createHasher<T>(
Point: H2CPointConstructor<T>, Point: H2CPointConstructor<T>,
mapToCurve: MapToCurve<T>, mapToCurve: MapToCurve<T>,
def: Opts def: Opts & { encodeDST?: UnicodeOrBytes }
) { ) {
validateObject(def, { if (typeof mapToCurve !== 'function') throw new Error('mapToCurve() must be defined');
DST: 'string',
p: 'bigint',
m: 'isSafeInteger',
k: 'isSafeInteger',
hash: 'hash',
});
if (def.expand !== 'xmd' && def.expand !== 'xof' && def.expand !== undefined)
throw new Error('Invalid htf/expand');
if (typeof mapToCurve !== 'function')
throw new Error('hashToCurve: mapToCurve() has not been 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-11#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]));
@@ -209,7 +218,8 @@ export function createHasher<T>(
return P; return P;
}, },
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-16#section-3 // Encodes byte string to elliptic curve.
// encode_to_curve from https://www.rfc-editor.org/rfc/rfc9380#section-3
encodeToCurve(msg: Uint8Array, options?: htfBasicOpts) { encodeToCurve(msg: Uint8Array, options?: htfBasicOpts) {
const u = hash_to_field(msg, 1, { ...def, DST: def.encodeDST, ...options } as Opts); const u = hash_to_field(msg, 1, { ...def, DST: def.encodeDST, ...options } as Opts);
const P = Point.fromAffine(mapToCurve(u[0])).clearCofactor(); const P = Point.fromAffine(mapToCurve(u[0])).clearCofactor();

View File

@@ -22,10 +22,10 @@ export function mod(a: bigint, b: bigint): bigint {
return result >= _0n ? result : b + result; return result >= _0n ? result : b + result;
} }
/** /**
* Efficiently exponentiate num to power and do modular division. * Efficiently raise num to power and do modular division.
* Unsafe in some contexts: uses ladder, so can expose bigint bits. * Unsafe in some contexts: uses ladder, so can expose bigint bits.
* @example * @example
* powMod(2n, 6n, 11n) // 64n % 11n == 9n * pow(2n, 6n, 11n) // 64n % 11n == 9n
*/ */
// TODO: use field version && remove // TODO: use field version && remove
export function pow(num: bigint, power: bigint, modulo: bigint): bigint { export function pow(num: bigint, power: bigint, modulo: bigint): bigint {
@@ -55,7 +55,8 @@ 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.
let a = mod(number, modulo); let a = mod(number, modulo);
let b = modulo; let b = modulo;
// prettier-ignore // prettier-ignore
@@ -74,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).
@@ -96,7 +102,7 @@ export function tonelliShanks(P: bigint) {
// Fast-path // Fast-path
if (S === 1) { if (S === 1) {
const p1div4 = (P + _1n) / _4n; const p1div4 = (P + _1n) / _4n;
return function tonelliFast<T>(Fp: Field<T>, n: T) { return function tonelliFast<T>(Fp: IField<T>, n: T) {
const root = Fp.pow(n, p1div4); const root = Fp.pow(n, p1div4);
if (!Fp.eql(Fp.sqr(root), n)) throw new Error('Cannot find square root'); if (!Fp.eql(Fp.sqr(root), n)) throw new Error('Cannot find square root');
return root; return root;
@@ -105,7 +111,7 @@ export function tonelliShanks(P: bigint) {
// Slow-path // Slow-path
const Q1div2 = (Q + _1n) / _2n; const Q1div2 = (Q + _1n) / _2n;
return function tonelliSlow<T>(Fp: Field<T>, n: T): T { return function tonelliSlow<T>(Fp: IField<T>, n: T): T {
// Step 0: Check that n is indeed a square: (n | p) should not be ≡ -1 // Step 0: Check that n is indeed a square: (n | p) should not be ≡ -1
if (Fp.pow(n, legendreC) === Fp.neg(Fp.ONE)) throw new Error('Cannot find square root'); if (Fp.pow(n, legendreC) === Fp.neg(Fp.ONE)) throw new Error('Cannot find square root');
let r = S; let r = S;
@@ -145,7 +151,7 @@ export function FpSqrt(P: bigint) {
// 0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaabn; // 0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaabn;
// const NUM = 72057594037927816n; // const NUM = 72057594037927816n;
const p1div4 = (P + _1n) / _4n; const p1div4 = (P + _1n) / _4n;
return function sqrt3mod4<T>(Fp: Field<T>, n: T) { return function sqrt3mod4<T>(Fp: IField<T>, n: T) {
const root = Fp.pow(n, p1div4); const root = Fp.pow(n, p1div4);
// Throw if root**2 != n // Throw if root**2 != n
if (!Fp.eql(Fp.sqr(root), n)) throw new Error('Cannot find square root'); if (!Fp.eql(Fp.sqr(root), n)) throw new Error('Cannot find square root');
@@ -156,7 +162,7 @@ export function FpSqrt(P: bigint) {
// Atkin algorithm for q ≡ 5 (mod 8), https://eprint.iacr.org/2012/685.pdf (page 10) // Atkin algorithm for q ≡ 5 (mod 8), https://eprint.iacr.org/2012/685.pdf (page 10)
if (P % _8n === _5n) { if (P % _8n === _5n) {
const c1 = (P - _5n) / _8n; const c1 = (P - _5n) / _8n;
return function sqrt5mod8<T>(Fp: Field<T>, n: T) { return function sqrt5mod8<T>(Fp: IField<T>, n: T) {
const n2 = Fp.mul(n, _2n); const n2 = Fp.mul(n, _2n);
const v = Fp.pow(n2, c1); const v = Fp.pow(n2, c1);
const nv = Fp.mul(n, v); const nv = Fp.mul(n, v);
@@ -197,12 +203,8 @@ 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 export interface IField<T> {
// - 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 Field<T> {
ORDER: bigint; ORDER: bigint;
BYTES: number; BYTES: number;
BITS: number; BITS: number;
@@ -231,7 +233,8 @@ export interface Field<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;
@@ -248,7 +251,7 @@ const FIELD_FIELDS = [
'eql', 'add', 'sub', 'mul', 'pow', 'div', 'eql', 'add', 'sub', 'mul', 'pow', 'div',
'addN', 'subN', 'mulN', 'sqrN' 'addN', 'subN', 'mulN', 'sqrN'
] as const; ] as const;
export function validateField<T>(field: Field<T>) { export function validateField<T>(field: IField<T>) {
const initial = { const initial = {
ORDER: 'bigint', ORDER: 'bigint',
MASK: 'bigint', MASK: 'bigint',
@@ -263,7 +266,12 @@ export function validateField<T>(field: Field<T>) {
} }
// Generic field functions // Generic field functions
export function FpPow<T>(f: Field<T>, num: T, power: bigint): T {
/**
* Same as `pow` but for Fp: non-constant-time.
* Unsafe in some contexts: uses ladder, so can expose bigint bits.
*/
export function FpPow<T>(f: IField<T>, num: T, power: bigint): T {
// Should have same speed as pow for bigints // Should have same speed as pow for bigints
// TODO: benchmark! // TODO: benchmark!
if (power < _0n) throw new Error('Expected power > 0'); if (power < _0n) throw new Error('Expected power > 0');
@@ -274,12 +282,16 @@ export function FpPow<T>(f: Field<T>, num: T, power: bigint): T {
while (power > _0n) { while (power > _0n) {
if (power & _1n) p = f.mul(p, d); if (power & _1n) p = f.mul(p, d);
d = f.sqr(d); d = f.sqr(d);
power >>= 1n; power >>= _1n;
} }
return p; return p;
} }
export function FpInvertBatch<T>(f: Field<T>, nums: T[]): T[] { /**
* Efficiently invert an array of Field elements.
* `inv(0)` will return `undefined` here: make sure to throw an error.
*/
export function FpInvertBatch<T>(f: IField<T>, nums: T[]): T[] {
const tmp = new Array(nums.length); 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
const lastMultiplied = nums.reduce((acc, num, i) => { const lastMultiplied = nums.reduce((acc, num, i) => {
@@ -298,12 +310,12 @@ export function FpInvertBatch<T>(f: Field<T>, nums: T[]): T[] {
return tmp; return tmp;
} }
export function FpDiv<T>(f: Field<T>, lhs: T, rhs: T | bigint): T { export function FpDiv<T>(f: IField<T>, lhs: T, rhs: T | bigint): T {
return f.mul(lhs, typeof rhs === 'bigint' ? invert(rhs, f.ORDER) : f.inv(rhs)); return f.mul(lhs, typeof rhs === 'bigint' ? invert(rhs, f.ORDER) : f.inv(rhs));
} }
// This function returns True whenever the value x is a square in the field F. // This function returns True whenever the value x is a square in the field F.
export function FpIsSquare<T>(f: Field<T>) { export function FpIsSquare<T>(f: IField<T>) {
const legendreConst = (f.ORDER - _1n) / _2n; // Integer arithmetic const legendreConst = (f.ORDER - _1n) / _2n; // Integer arithmetic
return (x: T): boolean => { return (x: T): boolean => {
const p = f.pow(x, legendreConst); const p = f.pow(x, legendreConst);
@@ -319,18 +331,26 @@ export function nLength(n: bigint, nBitLength?: number) {
return { nBitLength: _nBitLength, nByteLength }; return { nBitLength: _nBitLength, nByteLength };
} }
// NOTE: very fragile, always bench. Major performance points: type FpField = IField<bigint> & Required<Pick<IField<bigint>, 'isOdd'>>;
// - NonNormalized ops /**
// - Object.freeze * Initializes a finite field over prime. **Non-primes are not supported.**
// - same shape of object (don't add/remove keys) * Do not init in loop: slow. Very fragile: always run a benchmark on a change.
type FpField = Field<bigint> & Required<Pick<Field<bigint>, 'isOdd'>>; * Major performance optimizations:
export function Fp( * * a) denormalized operations like mulN instead of mul
* * b) same object shape: never add or remove keys
* * c) Object.freeze
* @param ORDER prime positive bigint
* @param bitLen how many bits the field consumes
* @param isLE (def: false) if encoding / decoding should be in little-endian
* @param redef optional faster redefinitions of sqrt and other methods
*/
export function Field(
ORDER: bigint, ORDER: bigint,
bitLen?: number, bitLen?: number,
isLE = false, isLE = false,
redef: Partial<Field<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);
@@ -381,26 +401,23 @@ export function Fp(
return Object.freeze(f); return Object.freeze(f);
} }
export function FpSqrtOdd<T>(Fp: Field<T>, elm: T) { export function FpSqrtOdd<T>(Fp: IField<T>, elm: T) {
if (!Fp.isOdd) throw new Error(`Field doesn't have isOdd`); if (!Fp.isOdd) throw new Error(`Field doesn't have isOdd`);
const root = Fp.sqrt(elm); const root = Fp.sqrt(elm);
return Fp.isOdd(root) ? root : Fp.neg(root); return Fp.isOdd(root) ? root : Fp.neg(root);
} }
export function FpSqrtEven<T>(Fp: Field<T>, elm: T) { export function FpSqrtEven<T>(Fp: IField<T>, elm: T) {
if (!Fp.isOdd) throw new Error(`Field doesn't have isOdd`); if (!Fp.isOdd) throw new Error(`Field doesn't have isOdd`);
const root = Fp.sqrt(elm); const root = Fp.sqrt(elm);
return Fp.isOdd(root) ? Fp.neg(root) : root; return Fp.isOdd(root) ? Fp.neg(root) : root;
} }
/** /**
* 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,
@@ -415,3 +432,53 @@ export function hashToPrivateScalar(
const num = isLE ? bytesToNumberLE(hash) : bytesToNumberBE(hash); const num = isLE ? bytesToNumberLE(hash) : bytesToNumberBE(hash);
return mod(num, groupOrder - _1n) + _1n; return mod(num, groupOrder - _1n) + _1n;
} }
/**
* Returns total number of bytes consumed by the field element.
* For example, 32 bytes for usual 256-bit weierstrass curve.
* @param fieldOrder number of field elements, usually CURVE.n
* @returns byte length of field
*/
export function getFieldBytesLength(fieldOrder: bigint): number {
if (typeof fieldOrder !== 'bigint') throw new Error('field order must be bigint');
const bitLength = fieldOrder.toString(2).length;
return Math.ceil(bitLength / 8);
}
/**
* Returns minimal amount of bytes that can be safely reduced
* by field order.
* Should be 2^-128 for 128-bit curve such as P256.
* @param fieldOrder number of field elements, usually CURVE.n
* @returns byte length of target hash
*/
export function getMinHashLength(fieldOrder: bigint): number {
const length = getFieldBytesLength(fieldOrder);
return length + Math.ceil(length / 2);
}
/**
* "Constant-time" private key generation utility.
* Can take (n + n/2) or more bytes of uniform input e.g. from CSPRNG or KDF
* and convert them into private scalar, with the modulo bias being negligible.
* Needs at least 48 bytes of input for 32-byte private key.
* https://research.kudelskisecurity.com/2020/07/28/the-definitive-guide-to-modulo-bias-and-how-to-avoid-it/
* FIPS 186-5, A.2 https://csrc.nist.gov/publications/detail/fips/186/5/final
* RFC 9380, https://www.rfc-editor.org/rfc/rfc9380#section-5
* @param hash hash output from SHA3 or a similar function
* @param groupOrder size of subgroup - (e.g. secp256k1.CURVE.n)
* @param isLE interpret hash bytes as LE num
* @returns valid private scalar
*/
export function mapHashToField(key: Uint8Array, fieldOrder: bigint, isLE = false): Uint8Array {
const len = key.length;
const fieldLen = getFieldBytesLength(fieldOrder);
const minLen = getMinHashLength(fieldOrder);
// No small numbers: need to understand bias story. No huge numbers: easier to detect JS timings.
if (len < 16 || len < minLen || len > 1024)
throw new Error(`expected ${minLen}-1024 bytes of input, got ${len}`);
const num = isLE ? bytesToNumberBE(key) : bytesToNumberLE(key);
// `mod(x, 11)` can sometimes produce 0. `mod(x, 10) + 1` is the same, but no 0
const reduced = mod(num, fieldOrder - _1n) + _1n;
return isLE ? numberToBytesLE(reduced, fieldLen) : numberToBytesBE(reduced, fieldLen);
}

View File

@@ -11,25 +11,27 @@ export type CurveType = {
nByteLength: number; nByteLength: number;
adjustScalarBytes?: (bytes: Uint8Array) => Uint8Array; adjustScalarBytes?: (bytes: Uint8Array) => Uint8Array;
domain?: (data: Uint8Array, ctx: Uint8Array, phflag: boolean) => Uint8Array; domain?: (data: Uint8Array, ctx: Uint8Array, phflag: boolean) => Uint8Array;
a24: bigint; // Related to d, but cannot be derived from it a: bigint;
montgomeryBits: number; montgomeryBits: number;
powPminus2?: (x: bigint) => bigint; powPminus2?: (x: bigint) => bigint;
xyToU?: (x: bigint, y: bigint) => bigint; xyToU?: (x: bigint, y: bigint) => bigint;
Gu: string; Gu: bigint;
randomBytes?: (bytesLength?: number) => Uint8Array;
}; };
export type CurveFn = { export type CurveFn = {
scalarMult: (scalar: Hex, u: Hex) => Uint8Array; scalarMult: (scalar: Hex, u: Hex) => Uint8Array;
scalarMultBase: (scalar: Hex) => Uint8Array; scalarMultBase: (scalar: Hex) => Uint8Array;
getSharedSecret: (privateKeyA: Hex, publicKeyB: Hex) => Uint8Array; getSharedSecret: (privateKeyA: Hex, publicKeyB: Hex) => Uint8Array;
getPublicKey: (privateKey: Hex) => Uint8Array; getPublicKey: (privateKey: Hex) => Uint8Array;
Gu: string; utils: { randomPrivateKey: () => Uint8Array };
GuBytes: Uint8Array;
}; };
function validateOpts(curve: CurveType) { function validateOpts(curve: CurveType) {
validateObject( validateObject(
curve, curve,
{ {
a24: 'bigint', a: 'bigint',
}, },
{ {
montgomeryBits: 'isSafeInteger', montgomeryBits: 'isSafeInteger',
@@ -37,7 +39,7 @@ function validateOpts(curve: CurveType) {
adjustScalarBytes: 'function', adjustScalarBytes: 'function',
domain: 'function', domain: 'function',
powPminus2: 'function', powPminus2: 'function',
Gu: 'string', Gu: 'bigint',
} }
); );
// Set defaults // Set defaults
@@ -49,7 +51,7 @@ function validateOpts(curve: CurveType) {
export function montgomery(curveDef: CurveType): CurveFn { export function montgomery(curveDef: CurveType): CurveFn {
const CURVE = validateOpts(curveDef); const CURVE = validateOpts(curveDef);
const { P } = CURVE; const { P } = CURVE;
const modP = (a: bigint) => mod(a, P); const modP = (n: bigint) => mod(n, P);
const montgomeryBits = CURVE.montgomeryBits; const montgomeryBits = CURVE.montgomeryBits;
const montgomeryBytes = Math.ceil(montgomeryBits / 8); const montgomeryBytes = Math.ceil(montgomeryBits / 8);
const fieldLen = CURVE.nByteLength; const fieldLen = CURVE.nByteLength;
@@ -73,12 +75,15 @@ export function montgomery(curveDef: CurveType): CurveFn {
return [x_2, x_3]; return [x_2, x_3];
} }
// Accepts 0 as well
function assertFieldElement(n: bigint): bigint { function assertFieldElement(n: bigint): bigint {
if (typeof n === 'bigint' && _0n <= n && n < P) return n; if (typeof n === 'bigint' && _0n <= n && n < P) return n;
throw new Error('Expected valid scalar 0 < scalar < CURVE.P'); throw new Error('Expected valid scalar 0 < scalar < CURVE.P');
} }
// x25519 from 4 // x25519 from 4
// The constant a24 is (486662 - 2) / 4 = 121665 for curve25519/X25519
const a24 = (CURVE.a - BigInt(2)) / BigInt(4);
/** /**
* *
* @param pointU u coordinate (x) on Montgomery Curve 25519 * @param pointU u coordinate (x) on Montgomery Curve 25519
@@ -90,8 +95,6 @@ export function montgomery(curveDef: CurveType): CurveFn {
// Section 5: Implementations MUST accept non-canonical values and process them as // Section 5: Implementations MUST accept non-canonical values and process them as
// if they had been reduced modulo the field prime. // if they had been reduced modulo the field prime.
const k = assertFieldElement(scalar); const k = assertFieldElement(scalar);
// The constant a24 is (486662 - 2) / 4 = 121665 for curve25519/X25519
const a24 = CURVE.a24;
const x_1 = u; const x_1 = u;
let x_2 = _1n; let x_2 = _1n;
let z_2 = _0n; let z_2 = _0n;
@@ -147,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 {
@@ -170,8 +171,9 @@ export function montgomery(curveDef: CurveType): CurveFn {
return encodeUCoordinate(pu); return encodeUCoordinate(pu);
} }
// Computes public key from private. By doing scalar multiplication of base point. // Computes public key from private. By doing scalar multiplication of base point.
const GuBytes = encodeUCoordinate(CURVE.Gu);
function scalarMultBase(scalar: Hex): Uint8Array { function scalarMultBase(scalar: Hex): Uint8Array {
return scalarMult(scalar, CURVE.Gu); return scalarMult(scalar, GuBytes);
} }
return { return {
@@ -179,6 +181,7 @@ export function montgomery(curveDef: CurveType): CurveFn {
scalarMultBase, scalarMultBase,
getSharedSecret: (privateKey: Hex, publicKey: Hex) => scalarMult(privateKey, publicKey), getSharedSecret: (privateKey: Hex, publicKey: Hex) => scalarMult(privateKey, publicKey),
getPublicKey: (privateKey: Hex): Uint8Array => scalarMultBase(privateKey), getPublicKey: (privateKey: Hex): Uint8Array => scalarMultBase(privateKey),
Gu: CURVE.Gu, utils: { randomPrivateKey: () => CURVE.randomBytes!(CURVE.nByteLength) },
GuBytes: GuBytes,
}; };
} }

View File

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

View File

@@ -1,13 +1,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,22 @@ 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')
);
}
// 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'); if (!isBytes(bytes)) throw new Error('Uint8Array expected');
// 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 +51,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'); if (!isBytes(bytes)) throw new Error('Uint8Array expected');
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 +118,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 +131,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];
if (!isBytes(a)) throw new Error('Uint8Array expected');
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 const bitSet = (n: bigint, pos: number, value: boolean) => {
return n | ((value ? _1n : _0n) << BigInt(pos));
};
/**
* Calculate mask for N bits. Not using ** operator with bigints because of old engines.
* Same as BigInt(`0b${Array(i).fill('1').join('')}`)
*/
export const bitMask = (n: number) => (_2n << BigInt(n - 1)) - _1n; export const bitMask = (n: number) => (_2n << BigInt(n - 1)) - _1n;
// DRBG // DRBG
@@ -205,6 +273,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),

View File

@@ -58,6 +58,8 @@ export interface ProjPointType<T> extends Group<ProjPointType<T>> {
readonly px: T; readonly px: T;
readonly py: T; readonly py: T;
readonly pz: T; readonly pz: T;
get x(): T;
get y(): T;
multiply(scalar: bigint): ProjPointType<T>; multiply(scalar: bigint): ProjPointType<T>;
toAffine(iz?: T): AffinePoint<T>; toAffine(iz?: T): AffinePoint<T>;
isTorsionFree(): boolean; isTorsionFree(): boolean;
@@ -82,8 +84,8 @@ export interface ProjConstructor<T> extends GroupConstructor<ProjPointType<T>> {
export type CurvePointsType<T> = BasicWCurve<T> & { export type CurvePointsType<T> = BasicWCurve<T> & {
// Bytes // Bytes
fromBytes: (bytes: Uint8Array) => AffinePoint<T>; fromBytes?: (bytes: Uint8Array) => AffinePoint<T>;
toBytes: (c: ProjConstructor<T>, point: ProjPointType<T>, compressed: boolean) => Uint8Array; toBytes?: (c: ProjConstructor<T>, point: ProjPointType<T>, isCompressed: boolean) => Uint8Array;
}; };
function validatePointOpts<T>(curve: CurvePointsType<T>) { function validatePointOpts<T>(curve: CurvePointsType<T>) {
@@ -93,8 +95,6 @@ function validatePointOpts<T>(curve: CurvePointsType<T>) {
{ {
a: 'field', a: 'field',
b: 'field', b: 'field',
fromBytes: 'function',
toBytes: 'function',
}, },
{ {
allowedPrivateKeyLengths: 'array', allowedPrivateKeyLengths: 'array',
@@ -102,6 +102,8 @@ function validatePointOpts<T>(curve: CurvePointsType<T>) {
isTorsionFree: 'function', isTorsionFree: 'function',
clearCofactor: 'function', clearCofactor: 'function',
allowInfinityPoint: 'boolean', allowInfinityPoint: 'boolean',
fromBytes: 'function',
toBytes: 'function',
} }
); );
const { endo, Fp, a } = opts; const { endo, Fp, a } = opts;
@@ -121,15 +123,16 @@ 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>;
normalizePrivateKey: (key: PrivKey) => bigint; normPrivateKeyToScalar: (key: PrivKey) => bigint;
weierstrassEquation: (x: T) => T; weierstrassEquation: (x: T) => T;
isWithinCurveOrder: (num: bigint) => boolean; isWithinCurveOrder: (num: bigint) => boolean;
}; };
// ASN.1 DER encoding utilities // ASN.1 DER encoding utilities
const { bytesToNumberBE: b2n, hexToBytes: h2b } = ut; const { bytesToNumberBE: b2n, hexToBytes: h2b } = ut;
const DER = { export const DER = {
// asn.1 DER encoding utils // asn.1 DER encoding utils
Err: class DERErr extends Error { Err: class DERErr extends Error {
constructor(m = '') { constructor(m = '') {
@@ -142,16 +145,20 @@ const DER = {
const len = data[1]; const len = data[1];
const res = data.subarray(2, len + 2); const res = data.subarray(2, len + 2);
if (!len || res.length !== len) throw new E('Invalid signature integer: wrong length'); if (!len || res.length !== len) throw new E('Invalid signature integer: wrong length');
if (res[0] === 0x00 && res[1] <= 0x7f) // https://crypto.stackexchange.com/a/57734 Leftmost bit of first byte is 'negative' flag,
throw new E('Invalid signature integer: trailing length'); // since we always use positive integers here. It must always be empty:
// ^ Weird condition: not about length, but about first bytes of number. // - add zero byte if exists
// - if next byte doesn't have a flag, leading zero is not allowed (minimal encoding)
if (res[0] & 0b10000000) throw new E('Invalid signature integer: negative');
if (res[0] === 0x00 && !(res[1] & 0b10000000))
throw new E('Invalid signature integer: unnecessary leading zero');
return { d: b2n(res), l: data.subarray(len + 2) }; // d is data, l is left return { d: b2n(res), l: data.subarray(len + 2) }; // d is data, l is left
}, },
toSig(hex: string | Uint8Array): { r: bigint; s: bigint } { toSig(hex: string | Uint8Array): { r: bigint; s: bigint } {
// 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'); if (!ut.isBytes(data)) throw new Error('ui8a expected');
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');
@@ -161,7 +168,8 @@ const DER = {
return { r, s }; return { r, s };
}, },
hexFromSig(sig: { r: bigint; s: bigint }): string { hexFromSig(sig: { r: bigint; s: bigint }): string {
const slice = (s: string): string => (Number.parseInt(s[0], 16) >= 8 ? '00' + s : s); // slice DER // Add leading zero if first byte has negative bit enabled. More details in '_parseInt'
const slice = (s: string): string => (Number.parseInt(s[0], 16) & 0b1000 ? '00' + s : s);
const h = (num: number | bigint) => { const h = (num: number | bigint) => {
const hex = num.toString(16); const hex = num.toString(16);
return hex.length & 1 ? `0${hex}` : hex; return hex.length & 1 ? `0${hex}` : hex;
@@ -176,14 +184,31 @@ const DER = {
}, },
}; };
// Be friendly to bad ECMAScript parsers by not using bigint literals like 123n // Be friendly to bad ECMAScript parsers by not using bigint literals
const _0n = BigInt(0); // prettier-ignore
const _1n = BigInt(1); 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 =
CURVE.toBytes ||
((_c: ProjConstructor<T>, point: ProjPointType<T>, _isCompressed: boolean) => {
const a = point.toAffine();
return ut.concatBytes(Uint8Array.from([0x04]), Fp.toBytes(a.x), Fp.toBytes(a.y));
});
const fromBytes =
CURVE.fromBytes ||
((bytes: Uint8Array) => {
// const head = bytes[0];
const tail = bytes.subarray(1);
// if (head !== 0x04) throw new Error('Only non-compressed encoding is supported');
const x = Fp.fromBytes(tail.subarray(0, Fp.BYTES));
const y = Fp.fromBytes(tail.subarray(Fp.BYTES, 2 * Fp.BYTES));
return { x, y };
});
/** /**
* y² = x³ + ax + b: Short weierstrass curve formula * y² = x³ + ax + b: Short weierstrass curve formula
* @returns y² * @returns y²
@@ -194,6 +219,12 @@ export function weierstrassPoints<T>(opts: CurvePointsType<T>) {
const x3 = Fp.mul(x2, x); // x2 * x const x3 = Fp.mul(x2, x); // x2 * x
return Fp.add(Fp.add(x3, Fp.mul(x, a)), b); // x3 + a * x + b return Fp.add(Fp.add(x3, Fp.mul(x, a)), b); // x3 + a * x + b
} }
// Validate whether the passed curve params are valid.
// We check if curve equation works for generator point.
// `assertValidity()` won't work: `isTorsionFree()` is not available at this point in bls12-381.
// ProjectivePoint class has not been initialized yet.
if (!Fp.eql(Fp.sqr(CURVE.Gy), weierstrassEquation(CURVE.Gx)))
throw new Error('bad generator point: equation left != right');
// Valid group elements reside in range 1..n-1 // Valid group elements reside in range 1..n-1
function isWithinCurveOrder(num: bigint): boolean { function isWithinCurveOrder(num: bigint): boolean {
@@ -203,11 +234,11 @@ export function weierstrassPoints<T>(opts: CurvePointsType<T>) {
if (!isWithinCurveOrder(num)) throw new Error('Expected valid bigint: 0 < bigint < curve.n'); if (!isWithinCurveOrder(num)) throw new Error('Expected valid bigint: 0 < bigint < curve.n');
} }
// Validates if priv key is valid and converts it to bigint. // Validates if priv key is valid and converts it to bigint.
// Supports options CURVE.normalizePrivateKey and CURVE.wrapPrivateKey. // Supports options allowedPrivateKeyLengths and wrapPrivateKey.
function normalizePrivateKey(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');
@@ -239,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');
@@ -280,14 +315,14 @@ export function weierstrassPoints<T>(opts: CurvePointsType<T>) {
* @param hex short/long ECDSA hex * @param hex short/long ECDSA hex
*/ */
static fromHex(hex: Hex): Point { static fromHex(hex: Hex): Point {
const P = Point.fromAffine(CURVE.fromBytes(ensureBytes('pointHex', hex))); const P = Point.fromAffine(fromBytes(ensureBytes('pointHex', hex)));
P.assertValidity(); P.assertValidity();
return P; return P;
} }
// Multiplies generator point by privateKey. // Multiplies generator point by privateKey.
static fromPrivateKey(privateKey: PrivKey) { static fromPrivateKey(privateKey: PrivKey) {
return Point.BASE.multiply(normalizePrivateKey(privateKey)); return Point.BASE.multiply(normPrivateKeyToScalar(privateKey));
} }
// We calculate precomputes for elliptic curve point multiplication // We calculate precomputes for elliptic curve point multiplication
@@ -303,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`
@@ -348,7 +385,7 @@ export function weierstrassPoints<T>(opts: CurvePointsType<T>) {
// Cost: 8M + 3S + 3*a + 2*b3 + 15add. // Cost: 8M + 3S + 3*a + 2*b3 + 15add.
double() { double() {
const { a, b } = CURVE; const { a, b } = CURVE;
const b3 = Fp.mul(b, 3n); const b3 = Fp.mul(b, _3n);
const { px: X1, py: Y1, pz: Z1 } = this; const { px: X1, py: Y1, pz: Z1 } = this;
let X3 = Fp.ZERO, Y3 = Fp.ZERO, Z3 = Fp.ZERO; // prettier-ignore let X3 = Fp.ZERO, Y3 = Fp.ZERO, Z3 = Fp.ZERO; // prettier-ignore
let t0 = Fp.mul(X1, X1); // step 1 let t0 = Fp.mul(X1, X1); // step 1
@@ -395,7 +432,7 @@ export function weierstrassPoints<T>(opts: CurvePointsType<T>) {
const { px: X2, py: Y2, pz: Z2 } = other; const { px: X2, py: Y2, pz: Z2 } = other;
let X3 = Fp.ZERO, Y3 = Fp.ZERO, Z3 = Fp.ZERO; // prettier-ignore let X3 = Fp.ZERO, Y3 = Fp.ZERO, Z3 = Fp.ZERO; // prettier-ignore
const a = CURVE.a; const a = CURVE.a;
const b3 = Fp.mul(CURVE.b, 3n); const b3 = Fp.mul(CURVE.b, _3n);
let t0 = Fp.mul(X1, X2); // step 1 let t0 = Fp.mul(X1, X2); // step 1
let t1 = Fp.mul(Y1, Y2); let t1 = Fp.mul(Y1, Y2);
let t2 = Fp.mul(Z1, Z2); let t2 = Fp.mul(Z1, Z2);
@@ -488,8 +525,9 @@ export function weierstrassPoints<T>(opts: CurvePointsType<T>) {
* Constant time multiplication. * Constant time multiplication.
* Uses wNAF method. Windowed method may be 10% faster, * Uses wNAF method. Windowed method may be 10% faster,
* but takes 2x longer to generate and consumes 2x memory. * but takes 2x longer to generate and consumes 2x memory.
* Uses precomputes when available.
* Uses endomorphism for Koblitz curves.
* @param scalar by which the point would be multiplied * @param scalar by which the point would be multiplied
* @param affinePoint optional point ot save cached precompute windows on it
* @returns New point * @returns New point
*/ */
multiply(scalar: bigint): Point { multiply(scalar: bigint): Point {
@@ -517,6 +555,8 @@ export function weierstrassPoints<T>(opts: CurvePointsType<T>) {
/** /**
* Efficiently calculate `aP + bQ`. Unsafe, can expose private key, if used incorrectly. * Efficiently calculate `aP + bQ`. Unsafe, can expose private key, if used incorrectly.
* Not using Strauss-Shamir trick: precomputation tables are faster.
* The trick could be useful if both P and Q are not G (not in our case).
* @returns non-zero affine point * @returns non-zero affine point
*/ */
multiplyAndAddUnsafe(Q: Point, a: bigint, b: bigint): Point | undefined { multiplyAndAddUnsafe(Q: Point, a: bigint, b: bigint): Point | undefined {
@@ -560,7 +600,7 @@ export function weierstrassPoints<T>(opts: CurvePointsType<T>) {
toRawBytes(isCompressed = true): Uint8Array { toRawBytes(isCompressed = true): Uint8Array {
this.assertValidity(); this.assertValidity();
return CURVE.toBytes(Point, this, isCompressed); return toBytes(Point, this, isCompressed);
} }
toHex(isCompressed = true): string { toHex(isCompressed = true): string {
@@ -569,10 +609,11 @@ export function weierstrassPoints<T>(opts: CurvePointsType<T>) {
} }
const _bits = CURVE.nBitLength; const _bits = CURVE.nBitLength;
const wnaf = wNAF(Point, CURVE.endo ? Math.ceil(_bits / 2) : _bits); const wnaf = wNAF(Point, CURVE.endo ? Math.ceil(_bits / 2) : _bits);
// Validate if generator point is on curve
return { return {
CURVE,
ProjectivePoint: Point as ProjConstructor<T>, ProjectivePoint: Point as ProjConstructor<T>,
normalizePrivateKey, normPrivateKeyToScalar,
weierstrassEquation, weierstrassEquation,
isWithinCurveOrder, isWithinCurveOrder,
}; };
@@ -584,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>;
@@ -594,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;
@@ -635,14 +679,13 @@ 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;
utils: { utils: {
normPrivateKeyToScalar: (key: PrivKey) => bigint; normPrivateKeyToScalar: (key: PrivKey) => bigint;
isValidPrivateKey(privateKey: PrivKey): boolean; isValidPrivateKey(privateKey: PrivKey): boolean;
hashToPrivateKey: (hash: Hex) => Uint8Array;
randomPrivateKey: () => Uint8Array; randomPrivateKey: () => Uint8Array;
precompute: (windowSize?: number, point?: ProjPointType<bigint>) => ProjPointType<bigint>; precompute: (windowSize?: number, point?: ProjPointType<bigint>) => ProjPointType<bigint>;
}; };
@@ -650,8 +693,7 @@ export type CurveFn = {
export function weierstrass(curveDef: CurveType): CurveFn { export function weierstrass(curveDef: CurveType): CurveFn {
const CURVE = validateOpts(curveDef) as ReturnType<typeof validateOpts>; const CURVE = validateOpts(curveDef) as ReturnType<typeof validateOpts>;
const CURVE_ORDER = CURVE.n; const { Fp, n: CURVE_ORDER } = CURVE;
const Fp = CURVE.Fp;
const compressedLen = Fp.BYTES + 1; // e.g. 33 for 32 const compressedLen = Fp.BYTES + 1; // e.g. 33 for 32
const uncompressedLen = 2 * Fp.BYTES + 1; // e.g. 65 for 32 const uncompressedLen = 2 * Fp.BYTES + 1; // e.g. 65 for 32
@@ -667,17 +709,16 @@ export function weierstrass(curveDef: CurveType): CurveFn {
const { const {
ProjectivePoint: Point, ProjectivePoint: Point,
normalizePrivateKey, normPrivateKeyToScalar,
weierstrassEquation, weierstrassEquation,
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;
if (isCompressed) { if (isCompressed) {
// TODO: hasEvenY
return cat(Uint8Array.from([point.hasEvenY() ? 0x02 : 0x03]), x); return cat(Uint8Array.from([point.hasEvenY() ? 0x02 : 0x03]), x);
} else { } else {
return cat(Uint8Array.from([0x04]), x, Fp.toBytes(a.y)); return cat(Uint8Array.from([0x04]), x, Fp.toBytes(a.y));
@@ -692,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;
@@ -727,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();
} }
@@ -751,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 {
@@ -797,41 +848,39 @@ 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) {
try { try {
normalizePrivateKey(privateKey); normPrivateKeyToScalar(privateKey);
return true; return true;
} catch (error) { } catch (error) {
return false; return false;
} }
}, },
normPrivateKeyToScalar: normalizePrivateKey, normPrivateKeyToScalar: normPrivateKeyToScalar,
/** /**
* Converts some bytes to a valid private key. Needs at least (nBitLength+64) bytes. * Produces cryptographically secure private key from random of size
* (groupLen + ceil(groupLen / 2)) with modulo bias being negligible.
*/ */
hashToPrivateKey: (hash: Hex): Uint8Array => randomPrivateKey: (): Uint8Array => {
ut.numberToBytesBE(mod.hashToPrivateScalar(hash, CURVE_ORDER), CURVE.nByteLength), const length = mod.getMinHashLength(CURVE.n);
return mod.mapHashToField(CURVE.randomBytes(length), CURVE.n);
},
/** /**
* Produces cryptographically secure private key from random of size (nBitLength+64) * Creates precompute table for an arbitrary EC point. Makes point "cached".
* as per FIPS 186 B.4.1 with modulo bias being neglible. * Allows to massively speed-up `point.multiply(scalar)`.
*/
randomPrivateKey: (): Uint8Array => utils.hashToPrivateKey(CURVE.randomBytes(Fp.BYTES + 8)),
/**
* 1. Returns cached point which you can use to pass to `getSharedSecret` or `#multiply` by it.
* 2. Precomputes point multiplication table. Is done by default on first `getPublicKey()` call.
* If you want your first getPublicKey to take 0.16ms instead of 20ms, make sure to call
* utils.precompute() somewhere without arguments first.
* @param windowSize 2, 4, 8, 16
* @returns cached point * @returns cached point
* @example
* const fast = utils.precompute(8, ProjectivePoint.fromHex(someonesPubKey));
* fast.multiply(privKey); // much faster ECDH now
*/ */
precompute(windowSize = 8, point = Point.BASE): typeof Point.BASE { precompute(windowSize = 8, point = Point.BASE): typeof Point.BASE {
point._setWindowSize(windowSize); point._setWindowSize(windowSize);
point.multiply(BigInt(3)); point.multiply(BigInt(3)); // 3 is arbitrary, just need any number here
return point; return point;
}, },
}; };
@@ -850,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;
@@ -862,7 +911,8 @@ export function weierstrass(curveDef: CurveType): CurveFn {
/** /**
* ECDH (Elliptic Curve Diffie Hellman). * ECDH (Elliptic Curve Diffie Hellman).
* Computes shared public key from private key and public key. * Computes shared public key from private key and public key.
* Checks: 1) private key validity 2) shared key is on-curve * Checks: 1) private key validity 2) shared key is on-curve.
* Does NOT hash the result.
* @param privateA private key * @param privateA private key
* @param publicB different public key * @param publicB different public key
* @param isCompressed whether to return compact (default), or full key * @param isCompressed whether to return compact (default), or full key
@@ -872,7 +922,7 @@ export function weierstrass(curveDef: CurveType): CurveFn {
if (isProbPub(privateA)) throw new Error('first arg must be private key'); if (isProbPub(privateA)) throw new Error('first arg must be private key');
if (!isProbPub(publicB)) throw new Error('second arg must be public key'); if (!isProbPub(publicB)) throw new Error('second arg must be public key');
const b = Point.fromHex(publicB); // check for being on-curve const b = Point.fromHex(publicB); // check for being on-curve
return b.multiply(normalizePrivateKey(privateA)).toRawBytes(isCompressed); return b.multiply(normPrivateKeyToScalar(privateA)).toRawBytes(isCompressed);
} }
// RFC6979: ensure ECDSA msg is X bytes and < N. RFC suggests optional truncating via bits2octets. // RFC6979: ensure ECDSA msg is X bytes and < N. RFC suggests optional truncating via bits2octets.
@@ -895,10 +945,12 @@ export function weierstrass(curveDef: CurveType): CurveFn {
}; };
// NOTE: pads output with zero as per spec // NOTE: pads output with zero as per spec
const ORDER_MASK = ut.bitMask(CURVE.nBitLength); const ORDER_MASK = ut.bitMask(CURVE.nBitLength);
/**
* Converts to bytes. Checks if num in `[0..ORDER_MASK-1]` e.g.: `[0..2^256-1]`.
*/
function int2octets(num: bigint): Uint8Array { function int2octets(num: bigint): Uint8Array {
if (typeof num !== 'bigint') throw new Error('bigint expected'); if (typeof num !== 'bigint') throw new Error('bigint expected');
if (!(_0n <= num && num < ORDER_MASK)) if (!(_0n <= num && num < ORDER_MASK))
// n in [0..ORDER_MASK-1]
throw new Error(`bigint expected < 2^${CURVE.nBitLength}`); throw new Error(`bigint expected < 2^${CURVE.nBitLength}`);
// works with order, can have different size than numToField! // works with order, can have different size than numToField!
return ut.numberToBytesBE(num, CURVE.nByteLength); return ut.numberToBytesBE(num, CURVE.nByteLength);
@@ -922,18 +974,18 @@ export function weierstrass(curveDef: CurveType): CurveFn {
// with nBitLength % 8 !== 0. Because of that, we unwrap it here as int2octets call. // with nBitLength % 8 !== 0. Because of that, we unwrap it here as int2octets call.
// const bits2octets = (bits) => int2octets(bits2int_modN(bits)) // const bits2octets = (bits) => int2octets(bits2int_modN(bits))
const h1int = bits2int_modN(msgHash); const h1int = bits2int_modN(msgHash);
const d = normalizePrivateKey(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) {
// 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
@@ -941,16 +993,10 @@ export function weierstrass(curveDef: CurveType): CurveFn {
const q = Point.BASE.multiply(k).toAffine(); // q = Gk const q = Point.BASE.multiply(k).toAffine(); // q = Gk
const r = modN(q.x); // r = q.x mod n const r = modN(q.x); // r = q.x mod n
if (r === _0n) return; if (r === _0n) return;
// X blinding according to https://tches.iacr.org/index.php/TCHES/article/view/7337/6509 // Can use scalar blinding b^-1(bm + bdr) where b ∈ [1,q1] according to
// b * m + b * r * d ∈ [0,q1] exposed via side-channel, but d (private scalar) is not. // https://tches.iacr.org/index.php/TCHES/article/view/7337/6509. We've decided against it:
// NOTE: there is still probable some leak in multiplication, since it is not constant-time // a) dependency on CSPRNG b) 15% slowdown c) doesn't really help since bigints are not CT
const b = ut.bytesToNumberBE(utils.randomPrivateKey()); // random scalar, b ∈ [1,q1] const s = modN(ik * modN(m + r * d)); // Not using blinding here
const bi = invN(b); // b^-1
const bdr = modN(b * d * r); // b * d * r
const bm = modN(b * m); // b * m
const mrx = modN(bi * modN(bdr + bm)); // b^-1(bm + bdr) -> m + rd
const s = modN(ik * mrx); // s = k^-1(m + rd) mod n
if (s === _0n) return; if (s === _0n) return;
let recovery = (q.x === r ? 0 : 2) | Number(q.y & _1n); // recovery bit (2 or 3, when q.x > n) let recovery = (q.x === r ? 0 : 2) | Number(q.y & _1n); // recovery bit (2 or 3, when q.x > n)
let normS = s; let normS = s;
@@ -958,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 };
} }
@@ -966,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
} }
@@ -1013,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 {
@@ -1058,23 +1108,31 @@ export function weierstrass(curveDef: CurveType): CurveFn {
}; };
} }
// Implementation of the Shallue and van de Woestijne method for any Weierstrass curve /**
* 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? * TODO: check if there is a way to merge this with uvRatio in Edwards; move to modular.
// b = True and y = sqrt(u / v) if (u / v) is square in F, and * b = True and y = sqrt(u / v) if (u / v) is square in F, and
// b = False and y = sqrt(Z * (u / v)) otherwise. * b = False and y = sqrt(Z * (u / v)) otherwise.
export function SWUFpSqrtRatio<T>(Fp: mod.Field<T>, Z: T) { * @param Fp
* @param Z
* @returns
*/
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 <<.
const c3 = (c2 - 1n) / 2n; // 3. c3 = (c2 - 1) / 2 # Integer arithmetic // 2n ** c1 == 2n << (c1-1)
const c4 = 2n ** c1 - 1n; // 4. c4 = 2^c1 - 1 # Integer arithmetic const _2n_pow_c1_1 = _2n << (c1 - _1n - _1n);
const c5 = 2n ** (c1 - 1n); // 5. c5 = 2^(c1 - 1) # Integer arithmetic const _2n_pow_c1 = _2n_pow_c1_1 * _2n;
const c2 = (q - _1n) / _2n_pow_c1; // 2. c2 = (q - 1) / (2^c1) # Integer arithmetic
const c3 = (c2 - _1n) / _2n; // 3. c3 = (c2 - 1) / 2 # Integer arithmetic
const c4 = _2n_pow_c1 - _1n; // 4. c4 = 2^c1 - 1 # Integer arithmetic
const c5 = _2n_pow_c1_1; // 5. c5 = 2^(c1 - 1) # Integer arithmetic
const c6 = Fp.pow(Z, c2); // 6. c6 = Z^c2 const 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 } => {
let tv1 = c6; // 1. tv1 = c6 let tv1 = c6; // 1. tv1 = c6
let tv2 = Fp.pow(v, c4); // 2. tv2 = v^c4 let tv2 = Fp.pow(v, c4); // 2. tv2 = v^c4
@@ -1093,8 +1151,9 @@ export function SWUFpSqrtRatio<T>(Fp: mod.Field<T>, Z: T) {
tv3 = Fp.cmov(tv2, tv3, isQR); // 15. tv3 = CMOV(tv2, tv3, isQR) tv3 = Fp.cmov(tv2, tv3, isQR); // 15. tv3 = CMOV(tv2, tv3, isQR)
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 > 1; 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
@@ -1105,9 +1164,9 @@ export function SWUFpSqrtRatio<T>(Fp: mod.Field<T>, Z: T) {
} }
return { isValid: isQR, value: tv3 }; return { isValid: isQR, value: tv3 };
}; };
if (Fp.ORDER % 4n === 3n) { if (Fp.ORDER % _4n === _3n) {
// sqrt_ratio_3mod4(u, v) // sqrt_ratio_3mod4(u, v)
const c1 = (Fp.ORDER - 3n) / 4n; // 1. c1 = (q - 3) / 4 # Integer arithmetic const c1 = (Fp.ORDER - _3n) / _4n; // 1. c1 = (q - 3) / 4 # Integer arithmetic
const c2 = Fp.sqrt(Fp.neg(Z)); // 2. c2 = sqrt(-Z) const c2 = Fp.sqrt(Fp.neg(Z)); // 2. c2 = sqrt(-Z)
sqrtRatio = (u: T, v: T) => { sqrtRatio = (u: T, v: T) => {
let tv1 = Fp.sqr(v); // 1. tv1 = v^2 let tv1 = Fp.sqr(v); // 1. tv1 = v^2
@@ -1123,12 +1182,15 @@ export function SWUFpSqrtRatio<T>(Fp: mod.Field<T>, Z: T) {
}; };
} }
// No curves uses that // No curves uses that
// 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.Field<T>, Fp: mod.IField<T>,
opts: { opts: {
A: T; A: T;
B: T; B: T;

View File

@@ -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-10](https://tools.ietf.org/html/draft-irtf-cfrg-pairing-friendly-curves-10),
// [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,26 +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.
// //
// ### Resources // ### Compatibility and notes
// - [BLS12-381 for the rest of us](https://hackmd.io/@benjaminion/bls12-381) // 1. It is compatible with Algorand, Chia, Dfinity, Ethereum, Filecoin, ZEC
// - [Key concepts of pairings](https://medium.com/@alonmuroch_65570/bls-signatures-part-2-key-concepts-of-pairings-27a8a9533d0c) // Filecoin uses little endian byte arrays for private keys - make sure to reverse byte order.
// - Pairing over bls12-381: // 2. Some projects use G2 for public keys and G1 for signatures. It's called "short signature"
// [part 1](https://research.nccgroup.com/2020/07/06/pairing-over-bls12-381-part-1-fields/), // 3. Curve security level is about 120 bits as per Barbulescu-Duquesne 2017
// [part 2](https://research.nccgroup.com/2020/07/13/pairing-over-bls12-381-part-2-curves/), // https://hal.science/hal-01534101/file/main.pdf
// [part 3](https://research.nccgroup.com/2020/08/13/pairing-over-bls12-381-part-3-pairing/) // 4. Compatible with specs:
// - [Estimating the bit security of pairing-friendly curves](https://research.nccgroup.com/2022/02/03/estimating-the-bit-security-of-pairing-friendly-curves/) // [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),
// ### Differences from @noble/bls12-381 1.4 // [RFC 9380](https://www.rfc-editor.org/rfc/rfc9380).
// - PointG1 -> G1.Point
// - PointG2 -> G2.Point
// - PointG2.fromSignature -> Signature.decode
// - PointG2.toSignature -> Signature.encode
// - Fixed Fp2 ORDER
// - Points now have only two coordinates
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';
@@ -55,10 +40,10 @@ import {
numberToBytesBE, numberToBytesBE,
bytesToNumberBE, bytesToNumberBE,
bitLen, bitLen,
bitSet,
bitGet, bitGet,
Hex, Hex,
bitMask, bitMask,
bytesToHex,
} from './abstract/utils.js'; } from './abstract/utils.js';
// Types // Types
import { import {
@@ -69,16 +54,22 @@ import {
} from './abstract/weierstrass.js'; } from './abstract/weierstrass.js';
import { isogenyMap } from './abstract/hash-to-curve.js'; import { isogenyMap } from './abstract/hash-to-curve.js';
// Be friendly to bad ECMAScript parsers by not using bigint literals
// prettier-ignore
const _0n = BigInt(0), _1n = BigInt(1), _2n = BigInt(2), _3n = BigInt(3), _4n = BigInt(4);
// prettier-ignore
const _8n = BigInt(8), _16n = BigInt(16);
// CURVE FIELDS // CURVE FIELDS
// Finite field over p. // Finite field over p.
const Fp = const Fp_raw = BigInt(
mod.Fp( '0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab'
0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaabn
); );
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.
const Fr = mod.Fp(0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001n); const Fr = mod.Field(BigInt('0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001'));
// Fp₂ over complex plane // Fp₂ over complex plane
type BigintTuple = [bigint, bigint]; type BigintTuple = [bigint, bigint];
@@ -120,11 +111,9 @@ 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;
0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaabn **
2n;
const Fp2: mod.Field<Fp2> & Fp2Utils = { const Fp2: mod.IField<Fp2> & Fp2Utils = {
ORDER: FP2_ORDER, ORDER: FP2_ORDER,
BITS: bitLen(FP2_ORDER), BITS: bitLen(FP2_ORDER),
BYTES: Math.ceil(bitLen(FP2_ORDER) / 8), BYTES: Math.ceil(bitLen(FP2_ORDER) / 8),
@@ -175,7 +164,7 @@ const Fp2: mod.Field<Fp2> & Fp2Utils = {
// https://github.com/zkcrypto/bls12_381/blob/080eaa74ec0e394377caa1ba302c8c121df08b07/src/fp2.rs#L250 // https://github.com/zkcrypto/bls12_381/blob/080eaa74ec0e394377caa1ba302c8c121df08b07/src/fp2.rs#L250
// https://github.com/supranational/blst/blob/aae0c7d70b799ac269ff5edf29d8191dbd357876/src/exp2.c#L1 // https://github.com/supranational/blst/blob/aae0c7d70b799ac269ff5edf29d8191dbd357876/src/exp2.c#L1
// Inspired by https://github.com/dalek-cryptography/curve25519-dalek/blob/17698df9d4c834204f83a3574143abacb4fc81a5/src/field.rs#L99 // Inspired by https://github.com/dalek-cryptography/curve25519-dalek/blob/17698df9d4c834204f83a3574143abacb4fc81a5/src/field.rs#L99
const candidateSqrt = Fp2.pow(num, (Fp2.ORDER + 8n) / 16n); const candidateSqrt = Fp2.pow(num, (Fp2.ORDER + _8n) / _16n);
const check = Fp2.div(Fp2.sqr(candidateSqrt), num); // candidateSqrt.square().div(this); const check = Fp2.div(Fp2.sqr(candidateSqrt), num); // candidateSqrt.square().div(this);
const R = FP2_ROOTS_OF_UNITY; const R = FP2_ROOTS_OF_UNITY;
const divisor = [R[0], R[2], R[4], R[6]].find((r) => Fp2.eql(r, check)); const divisor = [R[0], R[2], R[4], R[6]].find((r) => Fp2.eql(r, check));
@@ -190,13 +179,13 @@ const Fp2: mod.Field<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;
const zero_0 = x0 === 0n; const zero_0 = x0 === _0n;
const sign_1 = x1 % 2n; const sign_1 = x1 % _2n;
return BigInt(sign_0 || (zero_0 && sign_1)) == 1n; return BigInt(sign_0 || (zero_0 && sign_1)) == _1n;
}, },
// Bytes util // Bytes util
fromBytes(b: Uint8Array): Fp2 { fromBytes(b: Uint8Array): Fp2 {
@@ -216,8 +205,8 @@ const Fp2: mod.Field<Fp2> & Fp2Utils = {
// multiply by u + 1 // multiply by u + 1
mulByNonresidue: ({ c0, c1 }) => ({ c0: Fp.sub(c0, c1), c1: Fp.add(c0, c1) }), mulByNonresidue: ({ c0, c1 }) => ({ c0: Fp.sub(c0, c1), c1: Fp.add(c0, c1) }),
multiplyByB: ({ c0, c1 }) => { multiplyByB: ({ c0, c1 }) => {
let t0 = Fp.mul(c0, 4n); // 4 * c0 let t0 = Fp.mul(c0, _4n); // 4 * c0
let t1 = Fp.mul(c1, 4n); // 4 * c1 let t1 = Fp.mul(c1, _4n); // 4 * c1
// (T0-T1) + (T0+T1)*i // (T0-T1) + (T0+T1)*i
return { c0: Fp.sub(t0, t1), c1: Fp.add(t0, t1) }; return { c0: Fp.sub(t0, t1), c1: Fp.add(t0, t1) };
}, },
@@ -234,33 +223,36 @@ const Fp2: mod.Field<Fp2> & Fp2Utils = {
// Finite extension field over irreducible polynominal. // Finite extension field over irreducible polynominal.
// Fp(u) / (u² - β) where β = -1 // Fp(u) / (u² - β) where β = -1
const FP2_FROBENIUS_COEFFICIENTS = [ const FP2_FROBENIUS_COEFFICIENTS = [
0x1n, BigInt('0x1'),
0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaaan, BigInt(
'0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaaa'
),
].map((item) => Fp.create(item)); ].map((item) => Fp.create(item));
// For Fp2 roots of unity. // For Fp2 roots of unity.
const rv1 = const rv1 = BigInt(
0x6af0e0437ff400b6831e36d6bd17ffe48395dabc2d3435e77f76e17009241c5ee67992f72ec05f4c81084fbede3cc09n; '0x6af0e0437ff400b6831e36d6bd17ffe48395dabc2d3435e77f76e17009241c5ee67992f72ec05f4c81084fbede3cc09'
);
// const ev1 = // const ev1 =
// 0x699be3b8c6870965e5bf892ad5d2cc7b0e85a117402dfd83b7f4a947e02d978498255a2aaec0ac627b5afbdf1bf1c90n; // BigInt('0x699be3b8c6870965e5bf892ad5d2cc7b0e85a117402dfd83b7f4a947e02d978498255a2aaec0ac627b5afbdf1bf1c90');
// const ev2 = // const ev2 =
// 0x8157cd83046453f5dd0972b6e3949e4288020b5b8a9cc99ca07e27089a2ce2436d965026adad3ef7baba37f2183e9b5n; // BigInt('0x8157cd83046453f5dd0972b6e3949e4288020b5b8a9cc99ca07e27089a2ce2436d965026adad3ef7baba37f2183e9b5');
// const ev3 = // const ev3 =
// 0xab1c2ffdd6c253ca155231eb3e71ba044fd562f6f72bc5bad5ec46a0b7a3b0247cf08ce6c6317f40edbc653a72dee17n; // BigInt('0xab1c2ffdd6c253ca155231eb3e71ba044fd562f6f72bc5bad5ec46a0b7a3b0247cf08ce6c6317f40edbc653a72dee17');
// const ev4 = // const ev4 =
// 0xaa404866706722864480885d68ad0ccac1967c7544b447873cc37e0181271e006df72162a3d3e0287bf597fbf7f8fc1n; // BigInt('0xaa404866706722864480885d68ad0ccac1967c7544b447873cc37e0181271e006df72162a3d3e0287bf597fbf7f8fc1');
// Eighth roots of unity, used for computing square roots in Fp2. // Eighth roots of unity, used for computing square roots in Fp2.
// To verify or re-calculate: // To verify or re-calculate:
// Array(8).fill(new Fp2([1n, 1n])).map((fp2, k) => fp2.pow(Fp2.ORDER * BigInt(k) / 8n)) // Array(8).fill(new Fp2([1n, 1n])).map((fp2, k) => fp2.pow(Fp2.ORDER * BigInt(k) / 8n))
const FP2_ROOTS_OF_UNITY = [ const FP2_ROOTS_OF_UNITY = [
[1n, 0n], [_1n, _0n],
[rv1, -rv1], [rv1, -rv1],
[0n, 1n], [_0n, _1n],
[rv1, rv1], [rv1, rv1],
[-1n, 0n], [-_1n, _0n],
[-rv1, rv1], [-rv1, rv1],
[0n, -1n], [_0n, -_1n],
[-rv1, -rv1], [-rv1, -rv1],
].map((pair) => Fp2.fromBigTuple(pair)); ].map((pair) => Fp2.fromBigTuple(pair));
// eta values, used for computing sqrt(g(X1(t))) // eta values, used for computing sqrt(g(X1(t)))
@@ -314,8 +306,8 @@ const Fp6Multiply = ({ c0, c1, c2 }: Fp6, rhs: Fp6 | bigint) => {
}; };
const Fp6Square = ({ c0, c1, c2 }: Fp6) => { const Fp6Square = ({ c0, c1, c2 }: Fp6) => {
let t0 = Fp2.sqr(c0); // c0² let t0 = Fp2.sqr(c0); // c0²
let t1 = Fp2.mul(Fp2.mul(c0, c1), 2n); // 2 * c0 * c1 let t1 = Fp2.mul(Fp2.mul(c0, c1), _2n); // 2 * c0 * c1
let t3 = Fp2.mul(Fp2.mul(c1, c2), 2n); // 2 * c1 * c2 let t3 = Fp2.mul(Fp2.mul(c1, c2), _2n); // 2 * c1 * c2
let t4 = Fp2.sqr(c2); // c2² let t4 = Fp2.sqr(c2); // c2²
return { return {
c0: Fp2.add(Fp2.mulByNonresidue(t3), t0), // T3 * (u + 1) + T0 c0: Fp2.add(Fp2.mulByNonresidue(t3), t0), // T3 * (u + 1) + T0
@@ -333,7 +325,7 @@ type Fp6Utils = {
multiplyByFp2(lhs: Fp6, rhs: Fp2): Fp6; multiplyByFp2(lhs: Fp6, rhs: Fp2): Fp6;
}; };
const Fp6: mod.Field<Fp6> & Fp6Utils = { const Fp6: mod.IField<Fp6> & Fp6Utils = {
ORDER: Fp2.ORDER, // TODO: unused, but need to verify ORDER: Fp2.ORDER, // TODO: unused, but need to verify
BITS: 3 * Fp2.BITS, BITS: 3 * Fp2.BITS,
BYTES: 3 * Fp2.BYTES, BYTES: 3 * Fp2.BYTES,
@@ -440,46 +432,64 @@ const Fp6: mod.Field<Fp6> & Fp6Utils = {
}; };
const FP6_FROBENIUS_COEFFICIENTS_1 = [ const FP6_FROBENIUS_COEFFICIENTS_1 = [
[0x1n, 0x0n], [BigInt('0x1'), BigInt('0x0')],
[ [
0x0n, BigInt('0x0'),
0x1a0111ea397fe699ec02408663d4de85aa0d857d89759ad4897d29650fb85f9b409427eb4f49fffd8bfd00000000aaacn, BigInt(
'0x1a0111ea397fe699ec02408663d4de85aa0d857d89759ad4897d29650fb85f9b409427eb4f49fffd8bfd00000000aaac'
),
], ],
[ [
0x00000000000000005f19672fdf76ce51ba69c6076a0f77eaddb3a93be6f89688de17d813620a00022e01fffffffefffen, BigInt(
0x0n, '0x00000000000000005f19672fdf76ce51ba69c6076a0f77eaddb3a93be6f89688de17d813620a00022e01fffffffefffe'
),
BigInt('0x0'),
], ],
[0x0n, 0x1n], [BigInt('0x0'), BigInt('0x1')],
[ [
0x1a0111ea397fe699ec02408663d4de85aa0d857d89759ad4897d29650fb85f9b409427eb4f49fffd8bfd00000000aaacn, BigInt(
0x0n, '0x1a0111ea397fe699ec02408663d4de85aa0d857d89759ad4897d29650fb85f9b409427eb4f49fffd8bfd00000000aaac'
),
BigInt('0x0'),
], ],
[ [
0x0n, BigInt('0x0'),
0x00000000000000005f19672fdf76ce51ba69c6076a0f77eaddb3a93be6f89688de17d813620a00022e01fffffffefffen, BigInt(
'0x00000000000000005f19672fdf76ce51ba69c6076a0f77eaddb3a93be6f89688de17d813620a00022e01fffffffefffe'
),
], ],
].map((pair) => Fp2.fromBigTuple(pair)); ].map((pair) => Fp2.fromBigTuple(pair));
const FP6_FROBENIUS_COEFFICIENTS_2 = [ const FP6_FROBENIUS_COEFFICIENTS_2 = [
[0x1n, 0x0n], [BigInt('0x1'), BigInt('0x0')],
[ [
0x1a0111ea397fe699ec02408663d4de85aa0d857d89759ad4897d29650fb85f9b409427eb4f49fffd8bfd00000000aaadn, BigInt(
0x0n, '0x1a0111ea397fe699ec02408663d4de85aa0d857d89759ad4897d29650fb85f9b409427eb4f49fffd8bfd00000000aaad'
),
BigInt('0x0'),
], ],
[ [
0x1a0111ea397fe699ec02408663d4de85aa0d857d89759ad4897d29650fb85f9b409427eb4f49fffd8bfd00000000aaacn, BigInt(
0x0n, '0x1a0111ea397fe699ec02408663d4de85aa0d857d89759ad4897d29650fb85f9b409427eb4f49fffd8bfd00000000aaac'
),
BigInt('0x0'),
], ],
[ [
0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaaan, BigInt(
0x0n, '0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaaa'
),
BigInt('0x0'),
], ],
[ [
0x00000000000000005f19672fdf76ce51ba69c6076a0f77eaddb3a93be6f89688de17d813620a00022e01fffffffefffen, BigInt(
0x0n, '0x00000000000000005f19672fdf76ce51ba69c6076a0f77eaddb3a93be6f89688de17d813620a00022e01fffffffefffe'
),
BigInt('0x0'),
], ],
[ [
0x00000000000000005f19672fdf76ce51ba69c6076a0f77eaddb3a93be6f89688de17d813620a00022e01fffffffeffffn, BigInt(
0x0n, '0x00000000000000005f19672fdf76ce51ba69c6076a0f77eaddb3a93be6f89688de17d813620a00022e01fffffffeffff'
),
BigInt('0x0'),
], ],
].map((pair) => Fp2.fromBigTuple(pair)); ].map((pair) => Fp2.fromBigTuple(pair));
@@ -488,7 +498,7 @@ const FP6_FROBENIUS_COEFFICIENTS_2 = [
// Fp₆(w) / (w² - γ) where γ = v // Fp₆(w) / (w² - γ) where γ = v
type Fp12 = { c0: Fp6; c1: Fp6 }; type Fp12 = { c0: Fp6; c1: Fp6 };
// The BLS parameter x for BLS12-381 // The BLS parameter x for BLS12-381
const BLS_X = 0xd201000000010000n; const BLS_X = BigInt('0xd201000000010000');
const BLS_X_LEN = bitLen(BLS_X); const BLS_X_LEN = bitLen(BLS_X);
// prettier-ignore // prettier-ignore
@@ -545,7 +555,7 @@ type Fp12Utils = {
_cyclotomicExp(num: Fp12, n: bigint): Fp12; _cyclotomicExp(num: Fp12, n: bigint): Fp12;
}; };
const Fp12: mod.Field<Fp12> & Fp12Utils = { const Fp12: mod.IField<Fp12> & Fp12Utils = {
ORDER: Fp2.ORDER, // TODO: unused, but need to verify ORDER: Fp2.ORDER, // TODO: unused, but need to verify
BITS: 2 * Fp2.BITS, BITS: 2 * Fp2.BITS,
BYTES: 2 * Fp2.BYTES, BYTES: 2 * Fp2.BYTES,
@@ -646,14 +656,14 @@ const Fp12: mod.Field<Fp12> & Fp12Utils = {
let t9 = Fp2.mulByNonresidue(t8); // T8 * (u + 1) let t9 = Fp2.mulByNonresidue(t8); // T8 * (u + 1)
return { return {
c0: Fp6.create({ c0: Fp6.create({
c0: Fp2.add(Fp2.mul(Fp2.sub(t3, c0c0), 2n), t3), // 2 * (T3 - c0c0) + T3 c0: Fp2.add(Fp2.mul(Fp2.sub(t3, c0c0), _2n), t3), // 2 * (T3 - c0c0) + T3
c1: Fp2.add(Fp2.mul(Fp2.sub(t5, c0c1), 2n), t5), // 2 * (T5 - c0c1) + T5 c1: Fp2.add(Fp2.mul(Fp2.sub(t5, c0c1), _2n), t5), // 2 * (T5 - c0c1) + T5
c2: Fp2.add(Fp2.mul(Fp2.sub(t7, c0c2), 2n), t7), c2: Fp2.add(Fp2.mul(Fp2.sub(t7, c0c2), _2n), t7),
}), // 2 * (T7 - c0c2) + T7 }), // 2 * (T7 - c0c2) + T7
c1: Fp6.create({ c1: Fp6.create({
c0: Fp2.add(Fp2.mul(Fp2.add(t9, c1c0), 2n), t9), // 2 * (T9 + c1c0) + T9 c0: Fp2.add(Fp2.mul(Fp2.add(t9, c1c0), _2n), t9), // 2 * (T9 + c1c0) + T9
c1: Fp2.add(Fp2.mul(Fp2.add(t4, c1c1), 2n), t4), // 2 * (T4 + c1c1) + T4 c1: Fp2.add(Fp2.mul(Fp2.add(t4, c1c1), _2n), t4), // 2 * (T4 + c1c1) + T4
c2: Fp2.add(Fp2.mul(Fp2.add(t6, c1c2), 2n), t6), c2: Fp2.add(Fp2.mul(Fp2.add(t6, c1c2), _2n), t6),
}), }),
}; // 2 * (T6 + c1c2) + T6 }; // 2 * (T6 + c1c2) + T6
}, },
@@ -688,58 +698,91 @@ const Fp12: mod.Field<Fp12> & Fp12Utils = {
}, },
}; };
const FP12_FROBENIUS_COEFFICIENTS = [ const FP12_FROBENIUS_COEFFICIENTS = [
[0x1n, 0x0n], [BigInt('0x1'), BigInt('0x0')],
[ [
0x1904d3bf02bb0667c231beb4202c0d1f0fd603fd3cbd5f4f7b2443d784bab9c4f67ea53d63e7813d8d0775ed92235fb8n, BigInt(
0x00fc3e2b36c4e03288e9e902231f9fb854a14787b6c7b36fec0c8ec971f63c5f282d5ac14d6c7ec22cf78a126ddc4af3n, '0x1904d3bf02bb0667c231beb4202c0d1f0fd603fd3cbd5f4f7b2443d784bab9c4f67ea53d63e7813d8d0775ed92235fb8'
),
BigInt(
'0x00fc3e2b36c4e03288e9e902231f9fb854a14787b6c7b36fec0c8ec971f63c5f282d5ac14d6c7ec22cf78a126ddc4af3'
),
], ],
[ [
0x00000000000000005f19672fdf76ce51ba69c6076a0f77eaddb3a93be6f89688de17d813620a00022e01fffffffeffffn, BigInt(
0x0n, '0x00000000000000005f19672fdf76ce51ba69c6076a0f77eaddb3a93be6f89688de17d813620a00022e01fffffffeffff'
),
BigInt('0x0'),
], ],
[ [
0x135203e60180a68ee2e9c448d77a2cd91c3dedd930b1cf60ef396489f61eb45e304466cf3e67fa0af1ee7b04121bdea2n, BigInt(
0x06af0e0437ff400b6831e36d6bd17ffe48395dabc2d3435e77f76e17009241c5ee67992f72ec05f4c81084fbede3cc09n, '0x135203e60180a68ee2e9c448d77a2cd91c3dedd930b1cf60ef396489f61eb45e304466cf3e67fa0af1ee7b04121bdea2'
),
BigInt(
'0x06af0e0437ff400b6831e36d6bd17ffe48395dabc2d3435e77f76e17009241c5ee67992f72ec05f4c81084fbede3cc09'
),
], ],
[ [
0x00000000000000005f19672fdf76ce51ba69c6076a0f77eaddb3a93be6f89688de17d813620a00022e01fffffffefffen, BigInt(
0x0n, '0x00000000000000005f19672fdf76ce51ba69c6076a0f77eaddb3a93be6f89688de17d813620a00022e01fffffffefffe'
),
BigInt('0x0'),
], ],
[ [
0x144e4211384586c16bd3ad4afa99cc9170df3560e77982d0db45f3536814f0bd5871c1908bd478cd1ee605167ff82995n, BigInt(
0x05b2cfd9013a5fd8df47fa6b48b1e045f39816240c0b8fee8beadf4d8e9c0566c63a3e6e257f87329b18fae980078116n, '0x144e4211384586c16bd3ad4afa99cc9170df3560e77982d0db45f3536814f0bd5871c1908bd478cd1ee605167ff82995'
),
BigInt(
'0x05b2cfd9013a5fd8df47fa6b48b1e045f39816240c0b8fee8beadf4d8e9c0566c63a3e6e257f87329b18fae980078116'
),
], ],
[ [
0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaaan, BigInt(
0x0n, '0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaaa'
),
BigInt('0x0'),
], ],
[ [
0x00fc3e2b36c4e03288e9e902231f9fb854a14787b6c7b36fec0c8ec971f63c5f282d5ac14d6c7ec22cf78a126ddc4af3n, BigInt(
0x1904d3bf02bb0667c231beb4202c0d1f0fd603fd3cbd5f4f7b2443d784bab9c4f67ea53d63e7813d8d0775ed92235fb8n, '0x00fc3e2b36c4e03288e9e902231f9fb854a14787b6c7b36fec0c8ec971f63c5f282d5ac14d6c7ec22cf78a126ddc4af3'
),
BigInt(
'0x1904d3bf02bb0667c231beb4202c0d1f0fd603fd3cbd5f4f7b2443d784bab9c4f67ea53d63e7813d8d0775ed92235fb8'
),
], ],
[ [
0x1a0111ea397fe699ec02408663d4de85aa0d857d89759ad4897d29650fb85f9b409427eb4f49fffd8bfd00000000aaacn, BigInt(
0x0n, '0x1a0111ea397fe699ec02408663d4de85aa0d857d89759ad4897d29650fb85f9b409427eb4f49fffd8bfd00000000aaac'
),
BigInt('0x0'),
], ],
[ [
0x06af0e0437ff400b6831e36d6bd17ffe48395dabc2d3435e77f76e17009241c5ee67992f72ec05f4c81084fbede3cc09n, BigInt(
0x135203e60180a68ee2e9c448d77a2cd91c3dedd930b1cf60ef396489f61eb45e304466cf3e67fa0af1ee7b04121bdea2n, '0x06af0e0437ff400b6831e36d6bd17ffe48395dabc2d3435e77f76e17009241c5ee67992f72ec05f4c81084fbede3cc09'
),
BigInt(
'0x135203e60180a68ee2e9c448d77a2cd91c3dedd930b1cf60ef396489f61eb45e304466cf3e67fa0af1ee7b04121bdea2'
),
], ],
[ [
0x1a0111ea397fe699ec02408663d4de85aa0d857d89759ad4897d29650fb85f9b409427eb4f49fffd8bfd00000000aaadn, BigInt(
0x0n, '0x1a0111ea397fe699ec02408663d4de85aa0d857d89759ad4897d29650fb85f9b409427eb4f49fffd8bfd00000000aaad'
),
BigInt('0x0'),
], ],
[ [
0x05b2cfd9013a5fd8df47fa6b48b1e045f39816240c0b8fee8beadf4d8e9c0566c63a3e6e257f87329b18fae980078116n, BigInt(
0x144e4211384586c16bd3ad4afa99cc9170df3560e77982d0db45f3536814f0bd5871c1908bd478cd1ee605167ff82995n, '0x05b2cfd9013a5fd8df47fa6b48b1e045f39816240c0b8fee8beadf4d8e9c0566c63a3e6e257f87329b18fae980078116'
),
BigInt(
'0x144e4211384586c16bd3ad4afa99cc9170df3560e77982d0db45f3536814f0bd5871c1908bd478cd1ee605167ff82995'
),
], ],
].map((n) => Fp2.fromBigTuple(n)); ].map((n) => Fp2.fromBigTuple(n));
// END OF CURVE FIELDS // END OF CURVE FIELDS
// 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,
[ [
@@ -887,19 +930,23 @@ const isogenyMapG1 = isogenyMap(
// SWU Map - Fp2 to G2': y² = x³ + 240i * x + 1012 + 1012i // SWU Map - Fp2 to G2': y² = x³ + 240i * x + 1012 + 1012i
const G2_SWU = mapToCurveSimpleSWU(Fp2, { const G2_SWU = mapToCurveSimpleSWU(Fp2, {
A: Fp2.create({ c0: Fp.create(0n), c1: Fp.create(240n) }), // A' = 240 * I A: Fp2.create({ c0: Fp.create(_0n), c1: Fp.create(BigInt(240)) }), // A' = 240 * I
B: Fp2.create({ c0: Fp.create(1012n), c1: Fp.create(1012n) }), // B' = 1012 * (1 + I) B: Fp2.create({ c0: Fp.create(BigInt(1012)), c1: Fp.create(BigInt(1012)) }), // B' = 1012 * (1 + I)
Z: Fp2.create({ c0: Fp.create(-2n), c1: Fp.create(-1n) }), // Z: -(2 + I) Z: Fp2.create({ c0: Fp.create(BigInt(-2)), c1: Fp.create(BigInt(-1)) }), // Z: -(2 + I)
}); });
// Optimized SWU Map - Fp to G1 // Optimized SWU Map - Fp to G1
const G1_SWU = mapToCurveSimpleSWU(Fp, { const G1_SWU = mapToCurveSimpleSWU(Fp, {
A: Fp.create( A: Fp.create(
0x144698a3b8e9433d693a02c96d4982b0ea985383ee66a8d8e8981aefd881ac98936f8da0e0f97f5cf428082d584c1dn BigInt(
'0x144698a3b8e9433d693a02c96d4982b0ea985383ee66a8d8e8981aefd881ac98936f8da0e0f97f5cf428082d584c1d'
)
), ),
B: Fp.create( B: Fp.create(
0x12e2908d11688030018b12e8753eee3b2016c1f0f24f4070a0b9c14fcef35ef55a23215a316ceaa5d1cc48e98e172be0n BigInt(
'0x12e2908d11688030018b12e8753eee3b2016c1f0f24f4070a0b9c14fcef35ef55a23215a316ceaa5d1cc48e98e172be0'
)
), ),
Z: Fp.create(11n), Z: Fp.create(BigInt(11)),
}); });
// Endomorphisms (for fast cofactor clearing) // Endomorphisms (for fast cofactor clearing)
@@ -922,8 +969,9 @@ function G2psi(c: ProjConstructor<Fp2>, P: ProjPointType<Fp2>) {
} }
// Ψ²(P) endomorphism // Ψ²(P) endomorphism
// 1 / F2(2)^((p-1)/3) in GF(p²) // 1 / F2(2)^((p-1)/3) in GF(p²)
const PSI2_C1 = const PSI2_C1 = BigInt(
0x1a0111ea397fe699ec02408663d4de85aa0d857d89759ad4897d29650fb85f9b409427eb4f49fffd8bfd00000000aaacn; '0x1a0111ea397fe699ec02408663d4de85aa0d857d89759ad4897d29650fb85f9b409427eb4f49fffd8bfd00000000aaac'
);
function psi2(x: Fp2, y: Fp2): [Fp2, Fp2] { function psi2(x: Fp2, y: Fp2): [Fp2, Fp2] {
return [Fp2.mul(x, PSI2_C1), Fp2.neg(y)]; return [Fp2.mul(x, PSI2_C1), Fp2.neg(y)];
@@ -938,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
@@ -970,14 +1018,62 @@ 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>) {
// NOTE: by some reasons it was missed in bls12-381, looks like bug
point.assertValidity();
const len = Fp.BYTES;
if (point.equals(bls12_381.G2.ProjectivePoint.ZERO))
return concatB(COMPRESSED_ZERO, numberToBytesBE(_0n, len));
const { x, y } = point.toAffine();
const { re: x0, im: x1 } = Fp2.reim(x);
const { re: y0, im: y1 } = Fp2.reim(y);
const tmp = y1 > _0n ? y1 * _2n : y0 * _2n;
const sort = Boolean((tmp / Fp.ORDER) & _1n);
const z2 = x0;
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:
@@ -988,27 +1084,31 @@ const COMPRESSED_ZERO = Fp.toBytes(bitSet(bitSet(0n, I_BIT_POS, true), S_BIT_POS
// Here goes constants && point encoding format // Here goes constants && point encoding format
export const bls12_381: CurveFn<Fp, Fp2, Fp6, Fp12> = bls({ export const bls12_381: CurveFn<Fp, Fp2, Fp6, Fp12> = bls({
// Fields // Fields
Fr, fields: {
Fp, Fp,
Fp2, Fp2,
Fp6, Fp6,
Fp12, Fp12,
// order; z⁴ z² + 1 Fr,
r: Fr.ORDER, // Same as N in other curves },
// G1 is the order-q subgroup of E1(Fp) : y² = x³ + 4, #E1(Fp) = h1q, where // G1 is the order-q subgroup of E1(Fp) : y² = x³ + 4, #E1(Fp) = h1q, where
// characteristic; z + (z⁴ - z² + 1)(z - 1)²/3 // characteristic; z + (z⁴ - z² + 1)(z - 1)²/3
G1: { G1: {
Fp, Fp,
// cofactor; (z - 1)²/3 // cofactor; (z - 1)²/3
h: 0x396c8c005555e1568c00aaab0000aaabn, h: BigInt('0x396c8c005555e1568c00aaab0000aaab'),
// generator's coordinates // generator's coordinates
// x = 3685416753713387016781088315183077757961620795782546409894578378688607592378376318836054947676345821548104185464507 // x = 3685416753713387016781088315183077757961620795782546409894578378688607592378376318836054947676345821548104185464507
// y = 1339506544944476473020471379941921221584933875938349620426543736416511423956333506472724655353366534992391756441569 // y = 1339506544944476473020471379941921221584933875938349620426543736416511423956333506472724655353366534992391756441569
Gx: 0x17f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bbn, Gx: BigInt(
Gy: 0x08b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e1n, '0x17f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb'
),
Gy: BigInt(
'0x08b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e1'
),
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.
@@ -1017,18 +1117,19 @@ export const bls12_381: CurveFn<Fp, Fp2, Fp6, Fp12> = bls({
// https://eprint.iacr.org/2021/1130.pdf // https://eprint.iacr.org/2021/1130.pdf
isTorsionFree: (c, point): boolean => { isTorsionFree: (c, point): boolean => {
// φ endomorphism // φ endomorphism
const cubicRootOfUnityModP = const cubicRootOfUnityModP = BigInt(
0x5f19672fdf76ce51ba69c6076a0f77eaddb3a93be6f89688de17d813620a00022e01fffffffefffen; '0x5f19672fdf76ce51ba69c6076a0f77eaddb3a93be6f89688de17d813620a00022e01fffffffefffe'
);
const phi = new c(Fp.mul(point.px, cubicRootOfUnityModP), point.py, point.pz); const phi = new c(Fp.mul(point.px, cubicRootOfUnityModP), point.py, point.pz);
// todo: unroll // todo: unroll
const xP = point.multiplyUnsafe(bls12_381.CURVE.x).negate(); // [x]P const xP = point.multiplyUnsafe(bls12_381.params.x).negate(); // [x]P
const u2P = xP.multiplyUnsafe(bls12_381.CURVE.x); // [u2]P const u2P = xP.multiplyUnsafe(bls12_381.params.x); // [u2]P
return u2P.equals(phi); return u2P.equals(phi);
// https://eprint.iacr.org/2019/814.pdf // https://eprint.iacr.org/2019/814.pdf
// (z² 1)/3 // (z² 1)/3
// const c1 = 0x396c8c005555e1560000000055555555n; // const c1 = BigInt('0x396c8c005555e1560000000055555555');
// const P = this; // const P = this;
// const S = P.sigma(); // const S = P.sigma();
// const Q = S.double(); // const Q = S.double();
@@ -1040,33 +1141,39 @@ 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.CURVE.x).add(point); // x*P + P return point.multiplyUnsafe(bls12_381.params.x).add(point); // x*P + P
}, },
mapToCurve: (scalars: bigint[]) => { mapToCurve: (scalars: bigint[]) => {
const { x, y } = G1_SWU(Fp.create(scalars[0])); const { x, y } = G1_SWU(Fp.create(scalars[0]));
return isogenyMapG1(x, y); return isogenyMapG1(x, y);
}, },
fromBytes: (bytes: Uint8Array): AffinePoint<Fp> => { fromBytes: (bytes: Uint8Array): AffinePoint<Fp> => {
if (bytes.length === 48) { const { compressed, infinity, sort, value } = parseMask(bytes);
if (value.length === 48 && compressed) {
// 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);
const right = Fp.add(Fp.pow(x, 3n), Fp.create(bls12_381.CURVE.G1.b)); // y² = x³ + b if (infinity) {
if (x !== _0n) throw new Error('G1: non-empty compressed point at infinity');
return { x: _0n, y: _0n };
}
const right = Fp.add(Fp.pow(x, _3n), Fp.create(bls12_381.params.G1b)); // y² = x³ + b
let y = Fp.sqrt(right); 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.slice(0, Fp.BYTES)); const y = bytesToNumberBE(value.subarray(Fp.BYTES));
const y = bytesToNumberBE(bytes.slice(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');
@@ -1078,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
@@ -1092,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
@@ -1100,21 +1229,33 @@ export const bls12_381: CurveFn<Fp, Fp2, Fp6, Fp12> = bls({
G2: { G2: {
Fp: Fp2, Fp: Fp2,
// cofactor // cofactor
h: 0x5d543a95414e7f1091d50792876a202cd91de4547085abaa68a205b2e5a7ddfa628f1cb4d9e82ef21537e293a6691ae1616ec6e786f0c70cf1c38e31c7238e5n, h: BigInt(
'0x5d543a95414e7f1091d50792876a202cd91de4547085abaa68a205b2e5a7ddfa628f1cb4d9e82ef21537e293a6691ae1616ec6e786f0c70cf1c38e31c7238e5'
),
Gx: Fp2.fromBigTuple([ Gx: Fp2.fromBigTuple([
0x024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb8n, BigInt(
0x13e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7en, '0x024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb8'
),
BigInt(
'0x13e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e'
),
]), ]),
// y = // y =
// 927553665492332455747201965776037880757740193453592970025027978793976877002675564980949289727957565575433344219582, // 927553665492332455747201965776037880757740193453592970025027978793976877002675564980949289727957565575433344219582,
// 1985150602287291935568054521177171638300868978215655730859378665066344726373823718423869104263333984641494340347905 // 1985150602287291935568054521177171638300868978215655730859378665066344726373823718423869104263333984641494340347905
Gy: Fp2.fromBigTuple([ Gy: Fp2.fromBigTuple([
0x0ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801n, BigInt(
0x0606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79ben, '0x0ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801'
),
BigInt(
'0x0606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be'
),
]), ]),
a: Fp2.ZERO, a: Fp2.ZERO,
b: Fp2.fromBigTuple([4n, 4n]), b: Fp2.fromBigTuple([_4n, _4n]),
hEff: 0xbc69f08f2ee75b3584c6a0ea91b352888e2a8e9145ad7689986ff031508ffe1329c2f178731db956d82bf015d1212b02ec0ec69d7477c1ae954cbc06689f6a359894c0adebbf6b4e8020005aaa95551n, hEff: BigInt(
'0xbc69f08f2ee75b3584c6a0ea91b352888e2a8e9145ad7689986ff031508ffe1329c2f178731db956d82bf015d1212b02ec0ec69d7477c1ae954cbc06689f6a359894c0adebbf6b4e8020005aaa95551'
),
htfDefaults: { ...htfDefaults }, htfDefaults: { ...htfDefaults },
wrapPrivateKey: true, wrapPrivateKey: true,
allowInfinityPoint: true, allowInfinityPoint: true,
@@ -1127,7 +1268,7 @@ export const bls12_381: CurveFn<Fp, Fp2, Fp6, Fp12> = bls({
// It returns false for shitty points. // It returns false for shitty points.
// https://eprint.iacr.org/2021/1130.pdf // https://eprint.iacr.org/2021/1130.pdf
isTorsionFree: (c, P): boolean => { isTorsionFree: (c, P): boolean => {
return P.multiplyUnsafe(bls12_381.CURVE.x).negate().equals(G2psi(c, P)); // ψ(P) == [u](P) return P.multiplyUnsafe(bls12_381.params.x).negate().equals(G2psi(c, P)); // ψ(P) == [u](P)
// Older version: https://eprint.iacr.org/2019/814.pdf // Older version: https://eprint.iacr.org/2019/814.pdf
// Ψ²(P) => Ψ³(P) => [z]Ψ³(P) where z = -x => [z]Ψ³(P) - Ψ²(P) + P == O // Ψ²(P) => Ψ³(P) => [z]Ψ³(P) where z = -x => [z]Ψ³(P) - Ψ²(P) + P == O
// return P.psi2().psi().mulNegX().subtract(psi2).add(P).isZero(); // return P.psi2().psi().mulNegX().subtract(psi2).add(P).isZero();
@@ -1137,7 +1278,7 @@ export const bls12_381: CurveFn<Fp, Fp2, Fp6, Fp12> = bls({
// https://eprint.iacr.org/2017/419.pdf // https://eprint.iacr.org/2017/419.pdf
// prettier-ignore // prettier-ignore
clearCofactor: (c, P) => { clearCofactor: (c, P) => {
const { x } = bls12_381.CURVE; const x = bls12_381.params.x;
let t1 = P.multiplyUnsafe(x).negate(); // [-x]P let t1 = P.multiplyUnsafe(x).negate(); // [-x]P
let t2 = G2psi(c, P); // Ψ(P) let t2 = G2psi(c, P); // Ψ(P)
let t3 = P.double(); // 2P let t3 = P.double(); // 2P
@@ -1151,90 +1292,89 @@ 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> => {
const m_byte = bytes[0] & 0xe0; const { compressed, infinity, sort, value } = parseMask(bytes);
if (m_byte === 0x20 || m_byte === 0x60 || m_byte === 0xe0) { if (
throw new Error('Invalid encoding flag: ' + m_byte); (!compressed && !infinity && sort) || // 00100000
(!compressed && infinity && sort) || // 01100000
(sort && infinity && compressed) // 11100000
) {
throw new Error('Invalid encoding flag: ' + (bytes[0] & 0b1110_0000));
} }
const bitC = m_byte & 0x80; // compression bit
const bitI = m_byte & 0x40; // point at infinity bit
const bitS = m_byte & 0x20; // sign bit
const L = Fp.BYTES; const 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.CURVE.G2; 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');
} }
}, },
toBytes: (c, point, isCompressed) => { toBytes: (c, point, isCompressed) => {
const { BYTES: len, ORDER: P } = Fp;
const isZero = point.equals(c.ZERO); const isZero = point.equals(c.ZERO);
const { x, y } = point.toAffine(); const { x, y } = point.toAffine();
if (isCompressed) { if (isCompressed) {
const P = Fp.ORDER; if (isZero) return concatB(COMPRESSED_ZERO, numberToBytesBE(_0n, len));
if (isZero) return concatB(COMPRESSED_ZERO, numberToBytesBE(0n, Fp.BYTES)); 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); return concatB(
// set compressed & sign bits (looks like different offsets than for G1/Fp?) setMask(numberToBytesBE(x.c1, len), { compressed: true, sort: flag }),
let x_1 = bitSet(x.c1, C_BIT_POS, flag); numberToBytesBE(x.c0, len)
x_1 = bitSet(x_1, S_BIT_POS, true); );
return concatB(numberToBytesBE(x_1, Fp.BYTES), numberToBytesBE(x.c0, Fp.BYTES));
} else { } else {
if (isZero) return concatB(new Uint8Array([0x40]), new Uint8Array(4 * Fp.BYTES - 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);
const { re: y0, im: y1 } = Fp2.reim(y); const { re: y0, im: y1 } = Fp2.reim(y);
return concatB( return concatB(
numberToBytesBE(x1, Fp.BYTES), numberToBytesBE(x1, len),
numberToBytesBE(x0, Fp.BYTES), numberToBytesBE(x0, len),
numberToBytesBE(y1, Fp.BYTES), numberToBytesBE(y1, len),
numberToBytesBE(y0, Fp.BYTES) numberToBytesBE(y0, len)
); );
} }
}, },
Signature: { Signature: {
// TODO: Optimize, it's very slow because of sqrt. // TODO: Optimize, it's very slow because of sqrt.
decode(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 = hex.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 });
const y2 = Fp2.add(Fp2.pow(x, 3n), bls12_381.CURVE.G2.b); // y² = x³ + 4 const y2 = Fp2.add(Fp2.pow(x, _3n), bls12_381.params.G2b); // y² = x³ + 4
// The slow part // The slow part
let y = Fp2.sqrt(y2); let y = Fp2.sqrt(y2);
if (!y) throw new Error('Failed to find a square root'); if (!y) throw new Error('Failed to find a square root');
@@ -1242,32 +1382,26 @@ 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);
const point = bls12_381.G2.ProjectivePoint.fromAffine({ x, y }); const point = bls12_381.G2.ProjectivePoint.fromAffine({ x, y });
point.assertValidity(); point.assertValidity();
return point; return point;
}, },
encode(point: ProjPointType<Fp2>) { toRawBytes(point: ProjPointType<Fp2>) {
// NOTE: by some reasons it was missed in bls12-381, looks like bug return signatureG2ToRawBytes(point);
point.assertValidity(); },
if (point.equals(bls12_381.G2.ProjectivePoint.ZERO)) toHex(point: ProjPointType<Fp2>) {
return concatB(COMPRESSED_ZERO, numberToBytesBE(0n, Fp.BYTES)); return bytesToHex(signatureG2ToRawBytes(point));
const a = point.toAffine();
const { re: x0, im: x1 } = Fp2.reim(a.x);
const { re: y0, im: y1 } = Fp2.reim(a.y);
const tmp = y1 > 0n ? y1 * 2n : y0 * 2n;
const aflag1 = Boolean((tmp / Fp.ORDER) & 1n);
const z1 = bitSet(bitSet(x1, 381, aflag1), S_BIT_POS, true);
const z2 = x0;
return concatB(numberToBytesBE(z1, Fp.BYTES), numberToBytesBE(z2, Fp.BYTES));
}, },
}, },
}, },
// The BLS parameter x for BLS12-381 params: {
x: BLS_X, x: BLS_X, // The BLS parameter x for BLS12-381
r: Fr.ORDER, // order; z⁴ z² + 1; CURVE.n from other curves
},
htfDefaults, htfDefaults,
hash: sha256, hash: sha256,
randomBytes, randomBytes,

View File

@@ -2,17 +2,18 @@
import { sha256 } from '@noble/hashes/sha256'; import { sha256 } from '@noble/hashes/sha256';
import { weierstrass } from './abstract/weierstrass.js'; import { weierstrass } from './abstract/weierstrass.js';
import { getHash } from './_shortw_utils.js'; import { getHash } from './_shortw_utils.js';
import { Fp } from './abstract/modular.js'; 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),
b: BigInt(3), b: BigInt(3),
Fp: Fp(BigInt('0x30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47')), Fp: Field(BigInt('0x30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47')),
n: BigInt('0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001'), n: BigInt('0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001'),
Gx: BigInt(1), Gx: BigInt(1),
Gy: BigInt(2), Gy: BigInt(2),

View File

@@ -1,18 +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, Fp as 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, Group } from './abstract/curve.js';
/** /**
* ed25519 Twisted Edwards curve with following addons: * ed25519 Twisted Edwards curve with following addons:
@@ -33,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;
@@ -50,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
@@ -60,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;
@@ -94,15 +98,15 @@ export const ED25519_TORSION_SUBGROUP = [
const Fp = Field(ED25519_P, undefined, true); const Fp = Field(ED25519_P, undefined, true);
const ED25519_DEF = { const ed25519Defaults = {
// Param: a // Param: a
a: BigInt(-1), a: BigInt(-1), // Fp.create(-1) is proper; our way still works and is faster
// 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 ed25519 has // Subgroup order: how many points curve has
// 2n**252n + 27742317777372353535851937790883648493n; // 2n**252n + 27742317777372353535851937790883648493n;
n: BigInt('7237005577332262213973186563042994240857116359379907606001950938285454250989'), n: BigInt('7237005577332262213973186563042994240857116359379907606001950938285454250989'),
// Cofactor // Cofactor
@@ -119,7 +123,8 @@ const ED25519_DEF = {
uvRatio, uvRatio,
} as const; } as const;
export const ed25519 = twistedEdwards(ED25519_DEF); 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(
@@ -129,19 +134,24 @@ function ed25519_domain(data: Uint8Array, ctx: Uint8Array, phflag: boolean) {
data data
); );
} }
export const ed25519ctx = twistedEdwards({ ...ED25519_DEF, domain: ed25519_domain });
export const ed25519ph = twistedEdwards({ export const ed25519ctx = /* @__PURE__ */ twistedEdwards({
...ED25519_DEF, ...ed25519Defaults,
domain: ed25519_domain, domain: ed25519_domain,
preHash: sha512, });
export const ed25519ph = /* @__PURE__ */ twistedEdwards({
...ed25519Defaults,
domain: ed25519_domain,
prehash: sha512,
}); });
export const x25519 = montgomery({ export const x25519 = /* @__PURE__ */ (() =>
montgomery({
P: ED25519_P, P: ED25519_P,
a24: BigInt('121665'), a: BigInt(486662),
montgomeryBits: 255, // n is 253 bits montgomeryBits: 255, // n is 253 bits
nByteLength: 32, nByteLength: 32,
Gu: '0900000000000000000000000000000000000000000000000000000000000000', Gu: BigInt(9),
powPminus2: (x: bigint): bigint => { powPminus2: (x: bigint): bigint => {
const P = ED25519_P; const P = ED25519_P;
// x^(p-2) aka x^(2^255-21) // x^(p-2) aka x^(2^255-21)
@@ -149,7 +159,36 @@ export const x25519 = montgomery({
return mod(pow2(pow_p_5_8, BigInt(3), P) * b2, P); return mod(pow2(pow_p_5_8, BigInt(3), P) * b2, P);
}, },
adjustScalarBytes, adjustScalarBytes,
}); randomBytes,
}))();
/**
* Converts ed25519 public key to x25519 public key. Uses formula:
* * `(u, v) = ((1+y)/(1-y), sqrt(-486664)*u/x)`
* * `(x, y) = (sqrt(-486664)*u/v, (u-1)/(u+1))`
* @example
* const someonesPub = ed25519.getPublicKey(ed25519.utils.randomPrivateKey());
* const aPriv = x25519.utils.randomPrivateKey();
* x25519.getSharedSecret(aPriv, edwardsToMontgomeryPub(someonesPub))
*/
export function edwardsToMontgomeryPub(edwardsPub: Hex): Uint8Array {
const { y } = ed25519.ExtendedPoint.fromHex(edwardsPub);
const _1n = BigInt(1);
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)
// NOTE: very important part is usage of FpSqrtEven for ELL2_C1_EDWARDS, since // NOTE: very important part is usage of FpSqrtEven for ELL2_C1_EDWARDS, since
@@ -202,12 +241,13 @@ function map_to_curve_elligator2_curve25519(u: bigint) {
let y = Fp.cmov(y2, y1, e3); // 36. y = CMOV(y2, y1, e3) # If e3, y = y1, else y = y2 let y = Fp.cmov(y2, y1, e3); // 36. y = CMOV(y2, y1, e3) # If e3, y = y1, else y = y2
let e4 = Fp.isOdd(y); // 37. e4 = sgn0(y) == 1 # Fix sign of y let e4 = Fp.isOdd(y); // 37. e4 = sgn0(y) == 1 # Fix sign of y
y = Fp.cmov(y, Fp.neg(y), e3 !== e4); // 38. y = CMOV(y, -y, e3 XOR e4) y = Fp.cmov(y, Fp.neg(y), e3 !== e4); // 38. y = CMOV(y, -y, e3 XOR e4)
return { xMn: xn, xMd: xd, yMn: y, yMd: 1n }; // 39. return (xn, xd, y, 1) return { xMn: xn, xMd: xd, yMn: y, yMd: _1n }; // 39. return (xn, xd, y, 1)
} }
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
@@ -223,7 +263,9 @@ function map_to_curve_elligator2_edwards25519(u: bigint) {
const inv = Fp.invertBatch([xd, yd]); // batch division const inv = Fp.invertBatch([xd, yd]); // batch division
return { x: Fp.mul(xn, inv[0]), y: Fp.mul(yn, inv[1]) }; // 13. return (xn, xd, yn, yd) return { x: Fp.mul(xn, inv[0]), y: Fp.mul(yn, inv[1]) }; // 13. return (xn, xd, yn, yd)
} }
const { hashToCurve, encodeToCurve } = htf.createHasher(
const htf = /* @__PURE__ */ (() =>
createHasher(
ed25519.ExtendedPoint, ed25519.ExtendedPoint,
(scalars: bigint[]) => map_to_curve_elligator2_edwards25519(scalars[0]), (scalars: bigint[]) => map_to_curve_elligator2_edwards25519(scalars[0]),
{ {
@@ -235,16 +277,16 @@ const { hashToCurve, encodeToCurve } = htf.createHasher(
expand: 'xmd', expand: 'xmd',
hash: sha512, hash: sha512,
} }
); ))();
export { hashToCurve, encodeToCurve }; export const hashToCurve = /* @__PURE__ */ (() => htf.hashToCurve)();
export const encodeToCurve = /* @__PURE__ */ (() => htf.encodeToCurve)();
function assertRstPoint(other: unknown) { function assertRstPoint(other: unknown) {
if (!(other instanceof RistrettoPoint)) throw new Error('RistrettoPoint expected'); if (!(other instanceof RistPoint)) throw new Error('RistrettoPoint expected');
} }
// √(-1) aka √(a) aka 2^((p-1)/4) // √(-1) aka √(a) aka 2^((p-1)/4)
const SQRT_M1 = BigInt( const SQRT_M1 = ED25519_SQRT_M1;
'19681161376707505956807079304988542015446066515923890162744021073123829784752'
);
// √(ad - 1) // √(ad - 1)
const SQRT_AD_MINUS_ONE = BigInt( const SQRT_AD_MINUS_ONE = BigInt(
'25063068953384623474111414158702152701244531502492656460079210482610430750235' '25063068953384623474111414158702152701244531502492656460079210482610430750235'
@@ -301,27 +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>) {
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));
} }
/** /**
@@ -329,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;
@@ -353,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));
} }
/** /**
@@ -397,10 +443,10 @@ 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 } = this.ep; const { ex: X2, ey: Y2 } = other.ep;
const mod = ed25519.CURVE.Fp.create; const mod = ed25519.CURVE.Fp.create;
// (x1 * y2 == y1 * x2) | (y1 * y2 == x1 * x2) // (x1 * y2 == y1 * x2) | (y1 * y2 == x1 * x2)
const one = mod(X1 * Y2) === mod(Y1 * X2); const one = mod(X1 * Y2) === mod(Y1 * X2);
@@ -408,21 +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;
})();
// Hashing to ristretto255. https://www.rfc-editor.org/rfc/rfc9380#appendix-B
export const hashToRistretto255 = (msg: Uint8Array, options: htfBasicOpts) => {
const d = options.DST;
const DST = typeof d === 'string' ? utf8ToBytes(d) : d;
const uniform_bytes = expand_message_xmd(msg, DST, 64, sha512);
const P = RistPoint.hashToCurve(uniform_bytes);
return P;
};
export const hash_to_ristretto255 = hashToRistretto255; // legacy

View File

@@ -1,14 +1,25 @@
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */ /*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
import { shake256 } from '@noble/hashes/sha3'; import { shake256 } from '@noble/hashes/sha3';
import { concatBytes, randomBytes, utf8ToBytes, wrapConstructor } from '@noble/hashes/utils'; import { concatBytes, randomBytes, utf8ToBytes, wrapConstructor } from '@noble/hashes/utils';
import { twistedEdwards } from './abstract/edwards.js'; import { ExtPointType, twistedEdwards } from './abstract/edwards.js';
import { mod, pow2, Fp as 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,6 +65,28 @@ 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 ED448_DEF = { const ED448_DEF = {
@@ -69,6 +103,7 @@ const ED448_DEF = {
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),
@@ -93,40 +128,21 @@ 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__ */ (() =>
a24: BigInt(39081), montgomery({
a: BigInt(156326),
// RFC 7748 has 56-byte keys, RFC 8032 has 57-byte keys
montgomeryBits: 448, montgomeryBits: 448,
nByteLength: 57, nByteLength: 56,
P: ed448P, P: ed448P,
Gu: '0500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', Gu: BigInt(5),
powPminus2: (x: bigint): bigint => { powPminus2: (x: bigint): bigint => {
const P = ed448P; const P = ed448P;
const Pminus3div4 = ed448_pow_Pminus3div4(x); const Pminus3div4 = ed448_pow_Pminus3div4(x);
@@ -134,25 +150,30 @@ export const x448 = montgomery({
return mod(Pminus3 * x, P); // Pminus3 * x = Pminus2 return mod(Pminus3 * x, P); // Pminus3 * x = Pminus2
}, },
adjustScalarBytes, adjustScalarBytes,
// The 4-isogeny maps between the Montgomery curve and this Edwards randomBytes,
// curve are: }))();
// (u, v) = (y^2/x^2, (2 - x^2 - y^2)*y/x^3)
// (x, y) = (4*v*(u^2 - 1)/(u^4 - 2*u^2 + 4*v^2 + 1), /**
// -(u^5 - 2*u^3 - 4*u*v^2 + u)/ * Converts edwards448 public key to x448 public key. Uses formula:
// (u^5 - 2*u^2*v^2 - 2*u^3 - 2*v^2 + u)) * * `(u, v) = ((y-1)/(y+1), sqrt(156324)*u/x)`
// xyToU: (p: PointType) => { * * `(x, y) = (sqrt(156324)*u/v, (1+u)/(1-u))`
// const P = ed448P; * @example
// const { x, y } = p; * const aPub = ed448.getPublicKey(utils.randomPrivateKey());
// if (x === _0n) throw new Error(`Point with x=0 doesn't have mapping`); * x448.getSharedSecret(edwardsToMontgomery(aPub), edwardsToMontgomery(someonesPub))
// const invX = invert(x * x, P); // x^2 */
// const u = mod(y * y * invX, P); // (y^2/x^2) export function edwardsToMontgomeryPub(edwardsPub: string | Uint8Array): Uint8Array {
// return numberToBytesLE(u, 56); const { y } = ed448.ExtendedPoint.fromHex(edwardsPub);
// }, const _1n = BigInt(1);
}); return Fp.toBytes(Fp.create((y - _1n) * Fp.inv(y + _1n)));
}
export const edwardsToMontgomery = edwardsToMontgomeryPub; // deprecated
// TODO: add edwardsToMontgomeryPriv, similar to ed25519 version
// Hash To Curve Elligator2 Map // 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
@@ -182,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
@@ -194,10 +216,10 @@ function map_to_curve_elligator2_edwards448(u: bigint) {
xEn = Fp.mul(xEn, xd2); // 9. xEn = xEn * xd2 xEn = Fp.mul(xEn, xd2); // 9. xEn = xEn * xd2
xEn = Fp.mul(xEn, yd); // 10. xEn = xEn * yd xEn = Fp.mul(xEn, yd); // 10. xEn = xEn * yd
xEn = Fp.mul(xEn, yn); // 11. xEn = xEn * yn xEn = Fp.mul(xEn, yn); // 11. xEn = xEn * yn
xEn = Fp.mul(xEn, 4n); // 12. xEn = xEn * 4 xEn = Fp.mul(xEn, _4n); // 12. xEn = xEn * 4
tv2 = Fp.mul(tv2, xn2); // 13. tv2 = tv2 * xn2 tv2 = Fp.mul(tv2, xn2); // 13. tv2 = tv2 * xn2
tv2 = Fp.mul(tv2, yd2); // 14. tv2 = tv2 * yd2 tv2 = Fp.mul(tv2, yd2); // 14. tv2 = tv2 * yd2
let tv3 = Fp.mul(yn2, 4n); // 15. tv3 = 4 * yn2 let tv3 = Fp.mul(yn2, _4n); // 15. tv3 = 4 * yn2
let tv1 = Fp.add(tv3, yd2); // 16. tv1 = tv3 + yd2 let tv1 = Fp.add(tv3, yd2); // 16. tv1 = tv3 + yd2
tv1 = Fp.mul(tv1, xd4); // 17. tv1 = tv1 * xd4 tv1 = Fp.mul(tv1, xd4); // 17. tv1 = tv1 * xd4
let xEd = Fp.add(tv1, tv2); // 18. xEd = tv1 + tv2 let xEd = Fp.add(tv1, tv2); // 18. xEd = tv1 + tv2
@@ -225,7 +247,8 @@ function map_to_curve_elligator2_edwards448(u: bigint) {
return { x: Fp.mul(xEn, inv[0]), y: Fp.mul(yEn, inv[1]) }; // 38. return (xEn, xEd, yEn, yEd) return { x: Fp.mul(xEn, inv[0]), y: Fp.mul(yEn, inv[1]) }; // 38. return (xEn, xEd, yEn, yEd)
} }
const { hashToCurve, encodeToCurve } = htf.createHasher( const htf = /* @__PURE__ */ (() =>
createHasher(
ed448.ExtendedPoint, ed448.ExtendedPoint,
(scalars: bigint[]) => map_to_curve_elligator2_edwards448(scalars[0]), (scalars: bigint[]) => map_to_curve_elligator2_edwards448(scalars[0]),
{ {
@@ -237,5 +260,221 @@ const { hashToCurve, encodeToCurve } = htf.createHasher(
expand: 'xof', expand: 'xof',
hash: shake256, 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

View File

@@ -3,7 +3,7 @@ import { sha512 } from '@noble/hashes/sha512';
import { concatBytes, randomBytes, utf8ToBytes } from '@noble/hashes/utils'; import { concatBytes, randomBytes, utf8ToBytes } from '@noble/hashes/utils';
import { twistedEdwards } from './abstract/edwards.js'; import { twistedEdwards } from './abstract/edwards.js';
import { blake2s } from '@noble/hashes/blake2s'; import { blake2s } from '@noble/hashes/blake2s';
import { Fp } from './abstract/modular.js'; import { Field } from './abstract/modular.js';
/** /**
* jubjub Twisted Edwards curve. * jubjub Twisted Edwards curve.
@@ -11,13 +11,13 @@ import { Fp } 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'),
// Finite field 𝔽p over which we'll do calculations // Finite field 𝔽p over which we'll do calculations
// Same value as bls12-381 Fr (not Fp) // Same value as bls12-381 Fr (not Fp)
Fp: Fp(BigInt('0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001')), Fp: Field(BigInt('0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001')),
// Subgroup order: how many points curve has // Subgroup order: how many points curve has
n: BigInt('0xe7db4ea6533afa906673b0101343b00a6682093ccc81082d0970e5ed6f72cb7'), n: BigInt('0xe7db4ea6533afa906673b0101343b00a6682093ccc81082d0970e5ed6f72cb7'),
// Cofactor // Cofactor

View File

@@ -1,25 +0,0 @@
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
import { createCurve } from './_shortw_utils.js';
import { sha256 } from '@noble/hashes/sha256';
import { Fp } from './abstract/modular.js';
// NIST secp192r1 aka P192
// https://www.secg.org/sec2-v2.pdf, https://neuromancer.sk/std/secg/secp192r1
export const P192 = createCurve(
{
// Params: a, b
a: BigInt('0xfffffffffffffffffffffffffffffffefffffffffffffffc'),
b: BigInt('0x64210519e59c80e70fa7e9ab72243049feb8deecc146b9b1'),
// Field over which we'll do calculations; 2n ** 192n - 2n ** 64n - 1n
Fp: Fp(BigInt('0xfffffffffffffffffffffffffffffffeffffffffffffffff')),
// Curve order, total count of valid points in the field.
n: BigInt('0xffffffffffffffffffffffff99def836146bc9b1b4d22831'),
// Base point (x, y) aka generator point
Gx: BigInt('0x188da80eb03090f67cbf20eb43a18800f4ff0afd82ff1012'),
Gy: BigInt('0x07192b95ffc8da78631011ed6b24cdd573f977a11e794811'),
h: BigInt(1),
lowS: false,
} as const,
sha256
);
export const secp192r1 = P192;

View File

@@ -1,25 +0,0 @@
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
import { createCurve } from './_shortw_utils.js';
import { sha224 } from '@noble/hashes/sha256';
import { Fp } from './abstract/modular.js';
// NIST secp224r1 aka P224
// https://www.secg.org/sec2-v2.pdf, https://neuromancer.sk/std/nist/P-224
export const P224 = createCurve(
{
// Params: a, b
a: BigInt('0xfffffffffffffffffffffffffffffffefffffffffffffffffffffffe'),
b: BigInt('0xb4050a850c04b3abf54132565044b0b7d7bfd8ba270b39432355ffb4'),
// Field over which we'll do calculations;
Fp: Fp(BigInt('0xffffffffffffffffffffffffffffffff000000000000000000000001')),
// Curve order, total count of valid points in the field
n: BigInt('0xffffffffffffffffffffffffffff16a2e0b8f03e13dd29455c5c2a3d'),
// Base point (x, y) aka generator point
Gx: BigInt('0xb70e0cbd6bb4bf7f321390b94a03c1d356c21122343280d6115c1d21'),
Gy: BigInt('0xbd376388b5f723fb4c22dfe6cd4375a05a07476444d5819985007e34'),
h: BigInt(1),
lowS: false,
} as const,
sha224
);
export const secp224r1 = P224;

View File

@@ -1,46 +1,41 @@
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */ /*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
import { createCurve } from './_shortw_utils.js'; import { createCurve } from './_shortw_utils.js';
import { sha256 } from '@noble/hashes/sha256'; import { sha256 } from '@noble/hashes/sha256';
import { Fp as 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
// Field over which we'll do calculations; 2n**224n * (2n**32n-1n) + 2n**192n + 2n**96n-1n
const Fp = Field(BigInt('0xffffffff00000001000000000000000000000000ffffffffffffffffffffffff')); const Fp = Field(BigInt('0xffffffff00000001000000000000000000000000ffffffffffffffffffffffff'));
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, { // prettier-ignore
A: CURVE_A, export const p256 = createCurve({
B: CURVE_B, a: CURVE_A, // Equation params: a, b
Z: Fp.create(BigInt('-10')),
});
export const P256 = createCurve(
{
// Params: a, b
a: CURVE_A,
b: CURVE_B, b: CURVE_B,
Fp, Fp, // Field: 2n**224n * (2n**32n-1n) + 2n**192n + 2n**96n-1n
// Curve order, total count of valid points in the field // Curve order, total count of valid points in the field
n: BigInt('0xffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551'), n: BigInt('0xffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551'),
// Base point (x, y) aka generator point // Base (generator) point (x, y)
Gx: BigInt('0x6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296'), Gx: BigInt('0x6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296'),
Gy: BigInt('0x4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5'), Gy: BigInt('0x4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5'),
h: BigInt(1), h: BigInt(1),
lowS: false, lowS: false,
} as const, } as const, sha256);
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,
@@ -48,6 +43,6 @@ const { hashToCurve, encodeToCurve } = htf.createHasher(
k: 128, k: 128,
expand: 'xmd', expand: 'xmd',
hash: sha256, hash: sha256,
} }))();
); export const hashToCurve = /* @__PURE__ */ (() => htf.hashToCurve)();
export { hashToCurve, encodeToCurve }; export const encodeToCurve = /* @__PURE__ */ (() => htf.encodeToCurve)();

View File

@@ -1,14 +1,14 @@
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */ /*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
import { createCurve } from './_shortw_utils.js'; import { createCurve } from './_shortw_utils.js';
import { sha384 } from '@noble/hashes/sha512'; import { sha384 } from '@noble/hashes/sha512';
import { Fp as 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
// Field over which we'll do calculations. 2n**384n - 2n**128n - 2n**96n + 2n**32n - 1n // Field over which we'll do calculations.
// prettier-ignore // prettier-ignore
const P = BigInt('0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffff0000000000000000ffffffff'); const P = BigInt('0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffff0000000000000000ffffffff');
const Fp = Field(P); const Fp = Field(P);
@@ -16,35 +16,30 @@ const CURVE_A = Fp.create(BigInt('-3'));
// prettier-ignore // prettier-ignore
const CURVE_B = BigInt('0xb3312fa7e23ee7e4988e056be3f82d19181d9c6efe8141120314088f5013875ac656398d8a2ed19d2a85c8edd3ec2aef'); const CURVE_B = BigInt('0xb3312fa7e23ee7e4988e056be3f82d19181d9c6efe8141120314088f5013875ac656398d8a2ed19d2a85c8edd3ec2aef');
const mapSWU = mapToCurveSimpleSWU(Fp, {
A: CURVE_A,
B: CURVE_B,
Z: Fp.create(BigInt('-12')),
});
// prettier-ignore // prettier-ignore
export const P384 = createCurve({ export const p384 = createCurve({
// Params: a, b a: CURVE_A, // Equation params: a, b
a: CURVE_A,
b: CURVE_B, b: CURVE_B,
// Field over which we'll do calculations. 2n**384n - 2n**128n - 2n**96n + 2n**32n - 1n Fp, // Field: 2n**384n - 2n**128n - 2n**96n + 2n**32n - 1n
Fp,
// Curve order, total count of valid points in the field. // Curve order, total count of valid points in the field.
n: BigInt('0xffffffffffffffffffffffffffffffffffffffffffffffffc7634d81f4372ddf581a0db248b0a77aecec196accc52973'), n: BigInt('0xffffffffffffffffffffffffffffffffffffffffffffffffc7634d81f4372ddf581a0db248b0a77aecec196accc52973'),
// Base point (x, y) aka generator point // Base (generator) point (x, y)
Gx: BigInt('0xaa87ca22be8b05378eb1c71ef320ad746e1d3b628ba79b9859f741e082542a385502f25dbf55296c3a545e3872760ab7'), Gx: BigInt('0xaa87ca22be8b05378eb1c71ef320ad746e1d3b628ba79b9859f741e082542a385502f25dbf55296c3a545e3872760ab7'),
Gy: BigInt('0x3617de4a96262c6f5d9e98bf9292dc29f8f41dbd289a147ce9da3113b5f0b8c00a60b1ce1d7e819d7a431d7c90ea0e5f'), Gy: BigInt('0x3617de4a96262c6f5d9e98bf9292dc29f8f41dbd289a147ce9da3113b5f0b8c00a60b1ce1d7e819d7a431d7c90ea0e5f'),
h: BigInt(1), h: BigInt(1),
lowS: false, lowS: false,
} as const, } as const, sha384);
sha384 export const secp384r1 = p384;
);
export const secp384r1 = P384;
const { hashToCurve, encodeToCurve } = htf.createHasher( const mapSWU = /* @__PURE__ */ (() =>
secp384r1.ProjectivePoint, mapToCurveSimpleSWU(Fp, {
(scalars: bigint[]) => mapSWU(scalars[0]), A: CURVE_A,
{ B: CURVE_B,
Z: Fp.create(BigInt('-12')),
}))();
const htf = /* @__PURE__ */ (() =>
createHasher(secp384r1.ProjectivePoint, (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,
@@ -52,6 +47,6 @@ const { hashToCurve, encodeToCurve } = htf.createHasher(
k: 192, k: 192,
expand: 'xmd', expand: 'xmd',
hash: sha384, hash: sha384,
} }))();
); export const hashToCurve = /* @__PURE__ */ (() => htf.hashToCurve)();
export { hashToCurve, encodeToCurve }; export const encodeToCurve = /* @__PURE__ */ (() => htf.encodeToCurve)();

View File

@@ -1,50 +1,61 @@
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */ /*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
import { createCurve } from './_shortw_utils.js'; import { createCurve } from './_shortw_utils.js';
import { sha512 } from '@noble/hashes/sha512'; import { sha512 } from '@noble/hashes/sha512';
import { Fp as 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.
// https://www.secg.org/sec2-v2.pdf, https://neuromancer.sk/std/nist/P-521 // https://www.secg.org/sec2-v2.pdf, https://neuromancer.sk/std/nist/P-521
// Field over which we'll do calculations; 2n**521n - 1n // Field over which we'll do calculations.
// prettier-ignore // prettier-ignore
const P = BigInt('0x1ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff'); const P = BigInt('0x1ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff');
const Fp = Field(P); const Fp = Field(P);
const CURVE_A = Fp.create(BigInt('-3')); const CURVE = {
// prettier-ignore a: Fp.create(BigInt('-3')),
const CURVE_B = BigInt('0x0051953eb9618e1c9a1f929a21a0b68540eea2da725b99b315f3b8b489918ef109e156193951ec7e937b1652c0bd3bb1bf073573df883d2c34f1ef451fd46b503f00'); b: BigInt(
'0x0051953eb9618e1c9a1f929a21a0b68540eea2da725b99b315f3b8b489918ef109e156193951ec7e937b1652c0bd3bb1bf073573df883d2c34f1ef451fd46b503f00'
const mapSWU = mapToCurveSimpleSWU(Fp, { ),
A: CURVE_A,
B: CURVE_B,
Z: Fp.create(BigInt('-4')),
});
// prettier-ignore
export const P521 = createCurve({
// Params: a, b
a: CURVE_A,
b: CURVE_B,
Fp, Fp,
// Curve order, total count of valid points in the field n: BigInt(
n: BigInt('0x01fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa51868783bf2f966b7fcc0148f709a5d03bb5c9b8899c47aebb6fb71e91386409'), '0x01fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa51868783bf2f966b7fcc0148f709a5d03bb5c9b8899c47aebb6fb71e91386409'
// Base point (x, y) aka generator point ),
Gx: BigInt('0x00c6858e06b70404e9cd9e3ecb662395b4429c648139053fb521f828af606b4d3dbaa14b5e77efe75928fe1dc127a2ffa8de3348b3c1856a429bf97e7e31c2e5bd66'), Gx: BigInt(
Gy: BigInt('0x011839296a789a3bc0045c8a5fb42c7d1bd998f54449579b446817afbd17273e662c97ee72995ef42640c550b9013fad0761353c7086a272c24088be94769fd16650'), '0x00c6858e06b70404e9cd9e3ecb662395b4429c648139053fb521f828af606b4d3dbaa14b5e77efe75928fe1dc127a2ffa8de3348b3c1856a429bf97e7e31c2e5bd66'
),
Gy: BigInt(
'0x011839296a789a3bc0045c8a5fb42c7d1bd998f54449579b446817afbd17273e662c97ee72995ef42640c550b9013fad0761353c7086a272c24088be94769fd16650'
),
h: BigInt(1), h: BigInt(1),
};
// prettier-ignore
export const p521 = createCurve({
a: CURVE.a, // Equation params: a, b
b: CURVE.b,
Fp, // Field: 2n**521n - 1n
// Curve order, total count of valid points in the field
n: CURVE.n,
Gx: CURVE.Gx, // Base point (x, y) aka generator point
Gy: CURVE.Gy,
h: CURVE.h,
lowS: false, lowS: false,
allowedPrivateKeyLengths: [130, 131, 132] // P521 keys are variable-length. Normalize to 132b allowedPrivateKeyLengths: [130, 131, 132] // P521 keys are variable-length. Normalize to 132b
} as const, sha512); } as const, sha512);
export const secp521r1 = P521; export const secp521r1 = p521;
const { hashToCurve, encodeToCurve } = htf.createHasher( const mapSWU = /* @__PURE__ */ (() =>
secp521r1.ProjectivePoint, mapToCurveSimpleSWU(Fp, {
(scalars: bigint[]) => mapSWU(scalars[0]), A: CURVE.a,
{ B: CURVE.b,
Z: Fp.create(BigInt('-4')),
}))();
const htf = /* @__PURE__ */ (() =>
createHasher(secp521r1.ProjectivePoint, (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,
@@ -52,6 +63,6 @@ const { hashToCurve, encodeToCurve } = htf.createHasher(
k: 256, k: 256,
expand: 'xmd', expand: 'xmd',
hash: sha512, hash: sha512,
} }))();
); export const hashToCurve = /* @__PURE__ */ (() => htf.hashToCurve)();
export { hashToCurve, encodeToCurve }; export const encodeToCurve = /* @__PURE__ */ (() => htf.encodeToCurve)();

View File

@@ -11,7 +11,7 @@ export const q = BigInt('0x40000000000000000000000000000000224698fc0994a8dd8c46e
export const pallas = weierstrass({ export const pallas = weierstrass({
a: BigInt(0), a: BigInt(0),
b: BigInt(5), b: BigInt(5),
Fp: mod.Fp(p), Fp: mod.Field(p),
n: q, n: q,
Gx: mod.mod(BigInt(-1), p), Gx: mod.mod(BigInt(-1), p),
Gy: BigInt(2), Gy: BigInt(2),
@@ -22,7 +22,7 @@ export const pallas = weierstrass({
export const vesta = weierstrass({ export const vesta = weierstrass({
a: BigInt(0), a: BigInt(0),
b: BigInt(5), b: BigInt(5),
Fp: mod.Fp(q), Fp: mod.Field(q),
n: p, n: p,
Gx: mod.mod(BigInt(-1), q), Gx: mod.mod(BigInt(-1), q),
Gy: BigInt(2), Gy: BigInt(2),

View File

@@ -1,11 +1,11 @@
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */ /*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
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 { Fp as Field, mod, pow2 } from './abstract/modular.js'; import { Field, mod, pow2, FpIsSquare } 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');
@@ -43,7 +43,6 @@ function sqrtMod(y: bigint): bigint {
} }
const Fp = Field(secp256k1P, undefined, undefined, { sqrt: sqrtMod }); const Fp = Field(secp256k1P, undefined, undefined, { sqrt: sqrtMod });
type Fp = bigint;
export const secp256k1 = createCurve( export const secp256k1 = createCurve(
{ {
@@ -107,6 +106,7 @@ function taggedHash(tag: string, ...messages: Uint8Array[]): Uint8Array {
return sha256(concatBytes(tagP, ...messages)); return sha256(concatBytes(tagP, ...messages));
} }
// ECDSA compact points are 33-byte. Schnorr is 32: we strip first byte 0x02 or 0x03
const pointToBytes = (point: PointType<bigint>) => point.toRawBytes(true).slice(1); const pointToBytes = (point: PointType<bigint>) => point.toRawBytes(true).slice(1);
const numTo32b = (n: bigint) => numberToBytesBE(n, 32); const numTo32b = (n: bigint) => numberToBytesBE(n, 32);
const modP = (x: bigint) => mod(x, secp256k1P); const modP = (x: bigint) => mod(x, secp256k1P);
@@ -114,22 +114,31 @@ const modN = (x: bigint) => mod(x, secp256k1N);
const Point = secp256k1.ProjectivePoint; const Point = secp256k1.ProjectivePoint;
const GmulAdd = (Q: PointType<bigint>, a: bigint, b: bigint) => const GmulAdd = (Q: PointType<bigint>, a: bigint, b: bigint) =>
Point.BASE.multiplyAndAddUnsafe(Q, a, b); Point.BASE.multiplyAndAddUnsafe(Q, a, b);
// Calculate point, scalar and bytes
function schnorrGetExtPubKey(priv: PrivKey) { function schnorrGetExtPubKey(priv: PrivKey) {
const d = secp256k1.utils.normPrivateKeyToScalar(priv); let d_ = secp256k1.utils.normPrivateKeyToScalar(priv); // same method executed in fromPrivateKey
const point = Point.fromPrivateKey(d); // P = d'⋅G; 0 < d' < n check is done inside let p = Point.fromPrivateKey(d_); // P = d'⋅G; 0 < d' < n check is done inside
const scalar = point.hasEvenY() ? d : modN(-d); // d = d' if has_even_y(P), otherwise d = n-d' const scalar = p.hasEvenY() ? d_ : modN(-d_);
return { point, scalar, bytes: pointToBytes(point) }; return { scalar: scalar, bytes: pointToBytes(p) };
} }
/**
* lift_x from BIP340. Convert 32-byte x coordinate to elliptic curve point.
* @returns valid point checked for being on-curve
*/
function lift_x(x: bigint): PointType<bigint> { function lift_x(x: bigint): PointType<bigint> {
if (!fe(x)) throw new Error('bad x: need 0 < x < p'); // Fail if x ≥ p. if (!fe(x)) throw new Error('bad x: need 0 < x < p'); // Fail if x ≥ p.
const xx = modP(x * x); const xx = modP(x * x);
const c = modP(xx * x + BigInt(7)); // Let c = x³ + 7 mod p. const c = modP(xx * x + BigInt(7)); // Let c = x³ + 7 mod p.
let y = sqrtMod(c); // Let y = c^(p+1)/4 mod p. let y = sqrtMod(c); // Let y = c^(p+1)/4 mod p.
if (y % 2n !== 0n) y = modP(-y); // Return the unique point P such that x(P) = x and if (y % _2n !== _0n) y = modP(-y); // Return the unique point P such that x(P) = x and
const p = new Point(x, y, _1n); // y(P) = y if y mod 2 = 0 or y(P) = p-y otherwise. const p = new Point(x, y, _1n); // y(P) = y if y mod 2 = 0 or y(P) = p-y otherwise.
p.assertValidity(); p.assertValidity();
return p; return p;
} }
/**
* Create tagged hash, convert it to bigint, reduce modulo-n.
*/
function challenge(...args: Uint8Array[]): bigint { function challenge(...args: Uint8Array[]): bigint {
return modN(bytesToNumberBE(taggedHash('BIP0340/challenge', ...args))); return modN(bytesToNumberBE(taggedHash('BIP0340/challenge', ...args)));
} }
@@ -157,10 +166,10 @@ function schnorrSign(
const rand = taggedHash('BIP0340/nonce', t, px, m); // Let rand = hash/nonce(t || bytes(P) || m) const rand = taggedHash('BIP0340/nonce', t, px, m); // Let rand = hash/nonce(t || bytes(P) || m)
const k_ = modN(bytesToNumberBE(rand)); // Let k' = int(rand) mod n const k_ = modN(bytesToNumberBE(rand)); // Let k' = int(rand) mod n
if (k_ === _0n) throw new Error('sign failed: k is zero'); // Fail if k' = 0. if (k_ === _0n) throw new Error('sign failed: k is zero'); // Fail if k' = 0.
const { point: R, bytes: rx, scalar: k } = schnorrGetExtPubKey(k_); // Let R = k'⋅G. const { bytes: rx, scalar: k } = schnorrGetExtPubKey(k_); // Let R = k'⋅G.
const e = challenge(rx, px, m); // Let e = int(hash/challenge(bytes(R) || bytes(P) || m)) mod n. const e = challenge(rx, px, m); // Let e = int(hash/challenge(bytes(R) || bytes(P) || m)) mod n.
const sig = new Uint8Array(64); // Let sig = bytes(R) || bytes((k + ed) mod n). const sig = new Uint8Array(64); // Let sig = bytes(R) || bytes((k + ed) mod n).
sig.set(numTo32b(R.px), 0); sig.set(rx, 0);
sig.set(numTo32b(modN(k + e * d)), 32); sig.set(numTo32b(modN(k + e * d)), 32);
// If Verify(bytes(P), m, sig) (see below) returns failure, abort // If Verify(bytes(P), m, sig) (see below) returns failure, abort
if (!schnorrVerify(sig, m, px)) throw new Error('sign: Invalid signature produced'); if (!schnorrVerify(sig, m, px)) throw new Error('sign: Invalid signature produced');
@@ -169,6 +178,7 @@ function schnorrSign(
/** /**
* Verifies Schnorr signature. * Verifies Schnorr signature.
* Will swallow errors & return false except for initial type validation of arguments.
*/ */
function schnorrVerify(signature: Hex, message: Hex, publicKey: Hex): boolean { function schnorrVerify(signature: Hex, message: Hex, publicKey: Hex): boolean {
const sig = ensureBytes('signature', signature, 64); const sig = ensureBytes('signature', signature, 64);
@@ -189,13 +199,12 @@ 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,
utils: { utils: {
randomPrivateKey: secp256k1.utils.randomPrivateKey, randomPrivateKey: secp256k1.utils.randomPrivateKey,
getExtendedPublicKey: schnorrGetExtPubKey,
lift_x, lift_x,
pointToBytes, pointToBytes,
numberToBytesBE, numberToBytesBE,
@@ -203,9 +212,10 @@ export const schnorr = {
taggedHash, taggedHash,
mod, mod,
}, },
}; }))();
const isoMap = htf.isogenyMap( const isoMap = /* @__PURE__ */ (() =>
isogenyMap(
Fp, Fp,
[ [
// xNum // xNum
@@ -235,14 +245,16 @@ const isoMap = htf.isogenyMap(
'0x6484aa716545ca2cf3a70c3fa8fe337e0a3d21162f0d6299a7bf8192bfd2a76f', '0x6484aa716545ca2cf3a70c3fa8fe337e0a3d21162f0d6299a7bf8192bfd2a76f',
'0x0000000000000000000000000000000000000000000000000000000000000001', // LAST 1 '0x0000000000000000000000000000000000000000000000000000000000000001', // LAST 1
], ],
].map((i) => i.map((j) => BigInt(j))) as [Fp[], Fp[], Fp[], Fp[]] ].map((i) => i.map((j) => BigInt(j))) as [bigint[], bigint[], bigint[], bigint[]]
); ))();
const mapSWU = mapToCurveSimpleSWU(Fp, { const mapSWU = /* @__PURE__ */ (() =>
mapToCurveSimpleSWU(Fp, {
A: BigInt('0x3f8731abdd661adca08a5558f0f5d272e953d363cb6f0e5d405447c01a444533'), A: BigInt('0x3f8731abdd661adca08a5558f0f5d272e953d363cb6f0e5d405447c01a444533'),
B: BigInt('1771'), B: BigInt('1771'),
Z: Fp.create(BigInt('-11')), Z: Fp.create(BigInt('-11')),
}); }))();
export const { hashToCurve, encodeToCurve } = htf.createHasher( const htf = /* @__PURE__ */ (() =>
createHasher(
secp256k1.ProjectivePoint, secp256k1.ProjectivePoint,
(scalars: bigint[]) => { (scalars: bigint[]) => {
const { x, y } = mapSWU(Fp.create(scalars[0])); const { x, y } = mapSWU(Fp.create(scalars[0]));
@@ -257,4 +269,132 @@ export const { hashToCurve, encodeToCurve } = htf.createHasher(
expand: 'xmd', expand: 'xmd',
hash: sha256, hash: sha256,
} }
))();
export const hashToCurve = /* @__PURE__ */ (() => htf.hashToCurve)();
export const encodeToCurve = /* @__PURE__ */ (() => htf.encodeToCurve)();
// ElligatorSwift: Schnorr-like x-only ECDH with public keys indistinguishable
// from uniformly random bytes.
// https://github.com/bitcoin/bips/blob/master/bip-0324.mediawiki,
// https://github.com/bitcoin/bitcoin/blob/master/src/secp256k1/doc/ellswift.md
// SwiftEC: Shallue-van de Woestijne Indifferentiable Function to Elliptic Curves.
// https://eprint.iacr.org/2022/759.pdf
//
// Curve25519 & P-521 are incompatible with SwiftEC. Differences from SwiftEC:
// - undefined inputs are remapped
// - y-parity is encoded in u/t values
const MINUS_3_SQRT = Fp.sqrt(Fp.create(BigInt(-3)));
const _3n = BigInt(3);
const _4n = BigInt(4);
const _7n = BigInt(7);
const isSquare = FpIsSquare(Fp);
const isValidX = (x: bigint) => isSquare(Fp.add(Fp.mul(Fp.mul(x, x), x), _7n));
const trySqrt = (x: bigint): bigint | void => {
try {
return Fp.sqrt(x);
} catch (_e) {}
};
export const elligatorSwift = /* @__PURE__ */ {
// (internal stuff, exported for tests only): decode(u, _inv(x, u)) = x
_inv: (x: bigint, u: bigint, ellCase: number): bigint | void => {
if (!Number.isSafeInteger(ellCase) || ellCase < 0 || ellCase > 7)
throw new Error(`elligatorSwift._inv: wrong case=${ellCase}`);
let v: bigint, s: bigint;
// Most rejections happens in 3 condition (in comments, ~33% each)
const u2 = Fp.mul(u, u); // u**2
const u3 = Fp.mul(u2, u); // u**3
if ((ellCase & 2) === 0) {
if (isValidX(Fp.sub(Fp.neg(x), u))) return; // [1 condition]
v = x;
s = Fp.div(Fp.neg(Fp.add(u3, _7n)), Fp.add(Fp.add(u2, Fp.mul(u, v)), Fp.mul(v, v))); // = -(u**3 + 7) / (u**2 + u*v + v**2)
} else {
s = Fp.sub(x, u); // x - u
if (Fp.is0(s)) return;
const t0 = Fp.add(u3, _7n); // (u**3 + 7)
const t1 = Fp.mul(Fp.mul(_3n, s), u2); // 3 * s * u**2
// r = (-s * (4 * (u**3 + 7) + 3 * s * u**2)).sqrt()
const r = trySqrt(Fp.mul(Fp.neg(s), Fp.add(Fp.mul(_4n, t0), t1)));
if (r === undefined) return; // [2 condition]
if (ellCase & 1 && Fp.is0(r)) return;
v = Fp.div(Fp.add(Fp.neg(u), Fp.div(r, s)), _2n); // v = (-u + r / s) / 2
}
const w = trySqrt(s);
if (w === undefined) return; // [3 condition]
const last = ellCase & 5; // ellCase = 0..8, last = 0,1,4,5
const t0 = last & 1 ? Fp.add(_1n, MINUS_3_SQRT) : Fp.sub(_1n, MINUS_3_SQRT);
const w0 = last === 0 || last === 5 ? Fp.neg(w) : w; // -w | w
// w0 * (u * t0 / 2 + v)
return Fp.mul(w0, Fp.add(Fp.div(Fp.mul(u, t0), _2n), v));
},
// Encode public key (point or x coordinate bigint) into 64-byte pseudorandom encoding
encode: (x: bigint | PointType<bigint>): Uint8Array => {
if (x instanceof secp256k1.ProjectivePoint) x = x.x;
if (typeof x !== 'bigint') {
throw new Error(
'elligatorSwift.encode: wrong public key. Should be Projective point or x coordinate (bigint)'
); );
}
// 200k test cycles per keygen: avg=4 max=48
// seems too much, but same as for reference implementation
while (true) {
// random scalar 1..Fp.ORDER
const u = Fp.create(Fp.fromBytes(secp256k1.utils.randomPrivateKey()));
const ellCase = randomBytes(1)[0] & 7; // [0..8)
const t = elligatorSwift._inv(x, u, ellCase);
if (!t) continue;
return concatBytes(numberToBytesBE(u, 32), numberToBytesBE(t, 32));
}
},
// Decode elligatorSwift point to xonly
decode: (data: Hex): Uint8Array => {
const _data = ensureBytes('data', data, 64);
let u = Fp.create(Fp.fromBytes(_data.subarray(0, 32)));
let t = Fp.create(Fp.fromBytes(_data.subarray(32, 64)));
if (Fp.is0(u)) u = Fp.create(_1n);
if (Fp.is0(t)) t = Fp.create(_1n);
const u3 = Fp.mul(Fp.mul(u, u), u); // u**3
const u3plus7 = Fp.add(u3, _7n);
// u**3 + t**2 + 7 == 0 -> t = 2 * t
if (Fp.is0(Fp.add(u3plus7, Fp.mul(t, t)))) t = Fp.add(t, t);
// X = (u**3 + 7 - t**2) / (2 * t)
const x = Fp.div(Fp.sub(u3plus7, Fp.mul(t, t)), Fp.add(t, t));
// Y = (X + t) / (MINUS_3_SQRT * u);
const y = Fp.div(Fp.add(x, t), Fp.mul(MINUS_3_SQRT, u));
// try different cases
let res = Fp.add(u, Fp.mul(Fp.mul(y, y), _4n)); // u + 4 * Y ** 2,
if (isValidX(res)) return numberToBytesBE(res, 32);
res = Fp.div(Fp.sub(Fp.div(Fp.neg(x), y), u), _2n); // (-X / Y - u) / 2
if (isValidX(res)) return numberToBytesBE(res, 32);
res = Fp.div(Fp.sub(Fp.div(x, y), u), _2n); // (X / Y - u) / 2
if (isValidX(res)) return numberToBytesBE(res, 32);
throw new Error('elligatorSwift: cannot decode public key');
},
// Generate pair (public key, secret key)
keygen: () => {
const privateKey = secp256k1.utils.randomPrivateKey();
const publicKey = elligatorSwift.encode(Point.fromPrivateKey(privateKey));
return { privateKey, publicKey };
},
// Generates shared secret between a pub key and a priv key
getSharedSecret: (privateKeyA: Hex, publicKeyB: Hex) => {
const pub = elligatorSwift.decode(publicKeyB);
const priv = ensureBytes('privKey', privateKeyA, 32);
const point = lift_x(Fp.fromBytes(pub));
const d = bytesToNumberBE(priv);
return numberToBytesBE(point.multiply(d).x, 32);
},
// BIP324 shared secret
getSharedSecretBip324: (
privateKeyOurs: Hex,
publicKeyTheirs: Hex,
publicKeyOurs: Hex,
initiating: boolean
) => {
const ours = ensureBytes('publicKeyOurs', publicKeyOurs);
const theirs = ensureBytes('publicKeyTheirs', publicKeyTheirs);
const ecdhPoint = elligatorSwift.getSharedSecret(privateKeyOurs, theirs);
const pubs = initiating ? [ours, theirs] : [theirs, ours];
return taggedHash('bip324_ellswift_xonly_ecdh', ...pubs, ecdhPoint);
},
};

View File

@@ -1,356 +0,0 @@
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
import { keccak_256 } from '@noble/hashes/sha3';
import { sha256 } from '@noble/hashes/sha256';
import { weierstrass, ProjPointType } from './abstract/weierstrass.js';
import * as cutils from './abstract/utils.js';
import { Fp, mod, Field, validateField } from './abstract/modular.js';
import { getHash } from './_shortw_utils.js';
import * as poseidon from './abstract/poseidon.js';
import { utf8ToBytes } from '@noble/hashes/utils';
type ProjectivePoint = ProjPointType<bigint>;
// Stark-friendly elliptic curve
// https://docs.starkware.co/starkex/stark-curve.html
const CURVE_N = BigInt(
'3618502788666131213697322783095070105526743751716087489154079457884512865583'
);
const nBitLength = 252;
// Copy-pasted from weierstrass.ts
function bits2int(bytes: Uint8Array): bigint {
const delta = bytes.length * 8 - nBitLength;
const num = cutils.bytesToNumberBE(bytes);
return delta > 0 ? num >> BigInt(delta) : num;
}
function bits2int_modN(bytes: Uint8Array): bigint {
return mod(bits2int(bytes), CURVE_N);
}
export const starkCurve = weierstrass({
// Params: a, b
a: BigInt(1),
b: BigInt('3141592653589793238462643383279502884197169399375105820974944592307816406665'),
// Field over which we'll do calculations; 2n**251n + 17n * 2n**192n + 1n
// There is no efficient sqrt for field (P%4==1)
Fp: Fp(BigInt('0x800000000000011000000000000000000000000000000000000000000000001')),
// Curve order, total count of valid points in the field.
n: CURVE_N,
nBitLength: nBitLength, // len(bin(N).replace('0b',''))
// Base point (x, y) aka generator point
Gx: BigInt('874739451078007766457464989774322083649278607533249481151382481072868806602'),
Gy: BigInt('152666792071518830868575557812948353041420400780739481342941381225525861407'),
h: BigInt(1),
// Default options
lowS: false,
...getHash(sha256),
// Custom truncation routines for stark curve
bits2int: (bytes: Uint8Array): bigint => {
while (bytes[0] === 0) bytes = bytes.subarray(1);
return bits2int(bytes);
},
bits2int_modN: (bytes: Uint8Array): bigint => {
let hashS = cutils.bytesToNumberBE(bytes).toString(16);
if (hashS.length === 63) {
hashS += '0';
bytes = hexToBytes0x(hashS);
}
// Truncate zero bytes on left (compat with elliptic)
while (bytes[0] === 0) bytes = bytes.subarray(1);
return bits2int_modN(bytes);
},
});
// Custom Starknet type conversion functions that can handle 0x and unpadded hex
function hexToBytes0x(hex: string): Uint8Array {
if (typeof hex !== 'string') {
throw new Error('hexToBytes: expected string, got ' + typeof hex);
}
hex = strip0x(hex);
if (hex.length & 1) hex = '0' + hex; // padding
if (hex.length % 2) throw new Error('hexToBytes: received invalid unpadded hex ' + hex.length);
const array = new Uint8Array(hex.length / 2);
for (let i = 0; i < array.length; i++) {
const j = i * 2;
const hexByte = hex.slice(j, j + 2);
const byte = Number.parseInt(hexByte, 16);
if (Number.isNaN(byte) || byte < 0) throw new Error('Invalid byte sequence');
array[i] = byte;
}
return array;
}
function hexToNumber0x(hex: string): bigint {
if (typeof hex !== 'string') {
throw new Error('hexToNumber: expected string, got ' + typeof hex);
}
// Big Endian
// TODO: strip vs no strip?
return BigInt(`0x${strip0x(hex)}`);
}
function bytesToNumber0x(bytes: Uint8Array): bigint {
return hexToNumber0x(cutils.bytesToHex(bytes));
}
function ensureBytes0x(hex: Hex): Uint8Array {
// Uint8Array.from() instead of hash.slice() because node.js Buffer
// is instance of Uint8Array, and its slice() creates **mutable** copy
return hex instanceof Uint8Array ? Uint8Array.from(hex) : hexToBytes0x(hex);
}
function normalizePrivateKey(privKey: Hex) {
return cutils.bytesToHex(ensureBytes0x(privKey)).padStart(64, '0');
}
function getPublicKey0x(privKey: Hex, isCompressed = false) {
return starkCurve.getPublicKey(normalizePrivateKey(privKey), isCompressed);
}
function getSharedSecret0x(privKeyA: Hex, pubKeyB: Hex) {
return starkCurve.getSharedSecret(normalizePrivateKey(privKeyA), pubKeyB);
}
function sign0x(msgHash: Hex, privKey: Hex, opts?: any) {
if (typeof privKey === 'string') privKey = strip0x(privKey).padStart(64, '0');
return starkCurve.sign(ensureBytes0x(msgHash), normalizePrivateKey(privKey), opts);
}
function verify0x(signature: Hex, msgHash: Hex, pubKey: Hex) {
const sig = signature instanceof Signature ? signature : ensureBytes0x(signature);
return starkCurve.verify(sig, ensureBytes0x(msgHash), ensureBytes0x(pubKey));
}
const { CURVE, ProjectivePoint, Signature } = starkCurve;
export const utils = starkCurve.utils;
export {
CURVE,
Signature,
ProjectivePoint,
getPublicKey0x as getPublicKey,
getSharedSecret0x as getSharedSecret,
sign0x as sign,
verify0x as verify,
};
const stripLeadingZeros = (s: string) => s.replace(/^0+/gm, '');
export const bytesToHexEth = (uint8a: Uint8Array): string =>
`0x${stripLeadingZeros(cutils.bytesToHex(uint8a))}`;
export const strip0x = (hex: string) => hex.replace(/^0x/i, '');
export const numberToHexEth = (num: bigint | number) => `0x${num.toString(16)}`;
// We accept hex strings besides Uint8Array for simplicity
type Hex = Uint8Array | string;
// 1. seed generation
function hashKeyWithIndex(key: Uint8Array, index: number) {
let indexHex = cutils.numberToHexUnpadded(index);
if (indexHex.length & 1) indexHex = '0' + indexHex;
return sha256Num(cutils.concatBytes(key, hexToBytes0x(indexHex)));
}
export function grindKey(seed: Hex) {
const _seed = ensureBytes0x(seed);
const sha256mask = 2n ** 256n;
const limit = sha256mask - mod(sha256mask, CURVE_N);
for (let i = 0; ; i++) {
const key = hashKeyWithIndex(_seed, i);
// key should be in [0, limit)
if (key < limit) return mod(key, CURVE_N).toString(16);
}
}
export function getStarkKey(privateKey: Hex) {
return bytesToHexEth(getPublicKey0x(privateKey, true).slice(1));
}
export function ethSigToPrivate(signature: string) {
signature = strip0x(signature.replace(/^0x/, ''));
if (signature.length !== 130) throw new Error('Wrong ethereum signature');
return grindKey(signature.substring(0, 64));
}
const MASK_31 = 2n ** 31n - 1n;
const int31 = (n: bigint) => Number(n & MASK_31);
export function getAccountPath(
layer: string,
application: string,
ethereumAddress: string,
index: number
) {
const layerNum = int31(sha256Num(layer));
const applicationNum = int31(sha256Num(application));
const eth = hexToNumber0x(ethereumAddress);
return `m/2645'/${layerNum}'/${applicationNum}'/${int31(eth)}'/${int31(eth >> 31n)}'/${index}`;
}
// https://docs.starkware.co/starkex/pedersen-hash-function.html
const PEDERSEN_POINTS_AFFINE = [
new ProjectivePoint(
2089986280348253421170679821480865132823066470938446095505822317253594081284n,
1713931329540660377023406109199410414810705867260802078187082345529207694986n,
1n
),
new ProjectivePoint(
996781205833008774514500082376783249102396023663454813447423147977397232763n,
1668503676786377725805489344771023921079126552019160156920634619255970485781n,
1n
),
new ProjectivePoint(
2251563274489750535117886426533222435294046428347329203627021249169616184184n,
1798716007562728905295480679789526322175868328062420237419143593021674992973n,
1n
),
new ProjectivePoint(
2138414695194151160943305727036575959195309218611738193261179310511854807447n,
113410276730064486255102093846540133784865286929052426931474106396135072156n,
1n
),
new ProjectivePoint(
2379962749567351885752724891227938183011949129833673362440656643086021394946n,
776496453633298175483985398648758586525933812536653089401905292063708816422n,
1n
),
];
// for (const p of PEDERSEN_POINTS) p._setWindowSize(8);
const PEDERSEN_POINTS = PEDERSEN_POINTS_AFFINE;
function pedersenPrecompute(p1: ProjectivePoint, p2: ProjectivePoint): ProjectivePoint[] {
const out: ProjectivePoint[] = [];
let p = p1;
for (let i = 0; i < 248; i++) {
out.push(p);
p = p.double();
}
// NOTE: we cannot use wNAF here, because last 4 bits will require full 248 bits multiplication
// We can add support for this to wNAF, but it will complicate wNAF.
p = p2;
for (let i = 0; i < 4; i++) {
out.push(p);
p = p.double();
}
return out;
}
const PEDERSEN_POINTS1 = pedersenPrecompute(PEDERSEN_POINTS[1], PEDERSEN_POINTS[2]);
const PEDERSEN_POINTS2 = pedersenPrecompute(PEDERSEN_POINTS[3], PEDERSEN_POINTS[4]);
type PedersenArg = Hex | bigint | number;
function pedersenArg(arg: PedersenArg): bigint {
let value: bigint;
if (typeof arg === 'bigint') value = arg;
else if (typeof arg === 'number') {
if (!Number.isSafeInteger(arg)) throw new Error(`Invalid pedersenArg: ${arg}`);
value = BigInt(arg);
} else value = bytesToNumber0x(ensureBytes0x(arg));
// [0..Fp)
if (!(0n <= value && value < starkCurve.CURVE.Fp.ORDER))
throw new Error(`PedersenArg should be 0 <= value < CURVE.P: ${value}`);
return value;
}
function pedersenSingle(point: ProjectivePoint, value: PedersenArg, constants: ProjectivePoint[]) {
let x = pedersenArg(value);
for (let j = 0; j < 252; j++) {
const pt = constants[j];
if (pt.px === point.px) throw new Error('Same point');
if ((x & 1n) !== 0n) point = point.add(pt);
x >>= 1n;
}
return point;
}
// shift_point + x_low * P_0 + x_high * P1 + y_low * P2 + y_high * P3
export function pedersen(x: PedersenArg, y: PedersenArg) {
let point: ProjectivePoint = PEDERSEN_POINTS[0];
point = pedersenSingle(point, x, PEDERSEN_POINTS1);
point = pedersenSingle(point, y, PEDERSEN_POINTS2);
return bytesToHexEth(point.toRawBytes(true).slice(1));
}
export function hashChain(data: PedersenArg[], fn = pedersen) {
if (!Array.isArray(data) || data.length < 1)
throw new Error('data should be array of at least 1 element');
if (data.length === 1) return numberToHexEth(pedersenArg(data[0]));
return Array.from(data)
.reverse()
.reduce((acc, i) => fn(i, acc));
}
// Same as hashChain, but computes hash even for single element and order is not revesed
export const computeHashOnElements = (data: PedersenArg[], fn = pedersen) =>
[0, ...data, data.length].reduce((x, y) => fn(x, y));
const MASK_250 = cutils.bitMask(250);
export const keccak = (data: Uint8Array): bigint => bytesToNumber0x(keccak_256(data)) & MASK_250;
const sha256Num = (data: Uint8Array | string): bigint => cutils.bytesToNumberBE(sha256(data));
// Poseidon hash
export const Fp253 = Fp(
BigInt('14474011154664525231415395255581126252639794253786371766033694892385558855681')
); // 2^253 + 2^199 + 1
export const Fp251 = Fp(
BigInt('3618502788666131213697322783095070105623107215331596699973092056135872020481')
); // 2^251 + 17 * 2^192 + 1
function poseidonRoundConstant(Fp: Field<bigint>, name: string, idx: number) {
const val = Fp.fromBytes(sha256(utf8ToBytes(`${name}${idx}`)));
return Fp.create(val);
}
// NOTE: doesn't check eiginvalues and possible can create unsafe matrix. But any filtration here will break compatibility with starknet
// Please use only if you really know what you doing.
// https://eprint.iacr.org/2019/458.pdf Section 2.3 (Avoiding Insecure Matrices)
export function _poseidonMDS(Fp: Field<bigint>, name: string, m: number, attempt = 0) {
const x_values: bigint[] = [];
const y_values: bigint[] = [];
for (let i = 0; i < m; i++) {
x_values.push(poseidonRoundConstant(Fp, `${name}x`, attempt * m + i));
y_values.push(poseidonRoundConstant(Fp, `${name}y`, attempt * m + i));
}
if (new Set([...x_values, ...y_values]).size !== 2 * m)
throw new Error('X and Y values are not distinct');
return x_values.map((x) => y_values.map((y) => Fp.inv(Fp.sub(x, y))));
}
const MDS_SMALL = [
[3, 1, 1],
[1, -1, 1],
[1, 1, -2],
].map((i) => i.map(BigInt));
export type PoseidonOpts = {
Fp: Field<bigint>;
rate: number;
capacity: number;
roundsFull: number;
roundsPartial: number;
};
export function poseidonBasic(opts: PoseidonOpts, mds: bigint[][]) {
validateField(opts.Fp);
if (!Number.isSafeInteger(opts.rate) || !Number.isSafeInteger(opts.capacity))
throw new Error(`Wrong poseidon opts: ${opts}`);
const m = opts.rate + opts.capacity;
const rounds = opts.roundsFull + opts.roundsPartial;
const roundConstants = [];
for (let i = 0; i < rounds; i++) {
const row = [];
for (let j = 0; j < m; j++) row.push(poseidonRoundConstant(opts.Fp, 'Hades', m * i + j));
roundConstants.push(row);
}
return poseidon.poseidon({
...opts,
t: m,
sboxPower: 3,
reversePartialPowIdx: true, // Why?!
mds,
roundConstants,
});
}
export function poseidonCreate(opts: PoseidonOpts, mdsAttempt = 0) {
const m = opts.rate + opts.capacity;
if (!Number.isSafeInteger(mdsAttempt)) throw new Error(`Wrong mdsAttempt=${mdsAttempt}`);
return poseidonBasic(opts, _poseidonMDS(opts.Fp, 'HadesMDS', m, mdsAttempt));
}
export const poseidonSmall = poseidonBasic(
{ Fp: Fp251, rate: 2, capacity: 1, roundsFull: 8, roundsPartial: 83 },
MDS_SMALL
);
export function poseidonHash(x: bigint, y: bigint, fn = poseidonSmall) {
return fn([x, y, 2n])[0];
}

View File

@@ -0,0 +1,44 @@
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
import { createCurve } from '../esm/_shortw_utils.js';
import { sha224, sha256 } from '@noble/hashes/sha256';
import { Field as Fp } from '../esm/abstract/modular.js';
// NIST secp192r1 aka p192
// https://www.secg.org/sec2-v2.pdf, https://neuromancer.sk/std/secg/secp192r1
export const p192 = createCurve(
{
// Params: a, b
a: BigInt('0xfffffffffffffffffffffffffffffffefffffffffffffffc'),
b: BigInt('0x64210519e59c80e70fa7e9ab72243049feb8deecc146b9b1'),
// Field over which we'll do calculations; 2n ** 192n - 2n ** 64n - 1n
Fp: Fp(BigInt('0xfffffffffffffffffffffffffffffffeffffffffffffffff')),
// Curve order, total count of valid points in the field.
n: BigInt('0xffffffffffffffffffffffff99def836146bc9b1b4d22831'),
// Base point (x, y) aka generator point
Gx: BigInt('0x188da80eb03090f67cbf20eb43a18800f4ff0afd82ff1012'),
Gy: BigInt('0x07192b95ffc8da78631011ed6b24cdd573f977a11e794811'),
h: BigInt(1),
lowS: false,
},
sha256
);
export const secp192r1 = p192;
export const p224 = createCurve(
{
// Params: a, b
a: BigInt('0xfffffffffffffffffffffffffffffffefffffffffffffffffffffffe'),
b: BigInt('0xb4050a850c04b3abf54132565044b0b7d7bfd8ba270b39432355ffb4'),
// Field over which we'll do calculations;
Fp: Fp(BigInt('0xffffffffffffffffffffffffffffffff000000000000000000000001')),
// Curve order, total count of valid points in the field
n: BigInt('0xffffffffffffffffffffffffffff16a2e0b8f03e13dd29455c5c2a3d'),
// Base point (x, y) aka generator point
Gx: BigInt('0xb70e0cbd6bb4bf7f321390b94a03c1d356c21122343280d6115c1d21'),
Gy: BigInt('0xbd376388b5f723fb4c22dfe6cd4375a05a07476444d5819985007e34'),
h: BigInt(1),
lowS: false,
},
sha224
);
export const secp224r1 = p224;

103
test/_poseidon.helpers.js Normal file
View File

@@ -0,0 +1,103 @@
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
import { sha256 } from '@noble/hashes/sha256';
import { utf8ToBytes } from '@noble/hashes/utils';
import { Field as Fp, validateField } from '../esm/abstract/modular.js';
import { poseidon } from '../esm/abstract/poseidon.js';
import * as u from '../esm/abstract/utils.js';
// Poseidon hash https://docs.starkware.co/starkex/stark-curve.html
export const Fp253 = Fp(
BigInt('14474011154664525231415395255581126252639794253786371766033694892385558855681')
); // 2^253 + 2^199 + 1
export const Fp251 = Fp(
BigInt('3618502788666131213697322783095070105623107215331596699973092056135872020481')
); // 2^251 + 17 * 2^192 + 1
function poseidonRoundConstant(Fp, name, idx) {
const val = Fp.fromBytes(sha256(utf8ToBytes(`${name}${idx}`)));
return Fp.create(val);
}
// NOTE: doesn't check eiginvalues and possible can create unsafe matrix. But any filtration here will break compatibility with starknet
// Please use only if you really know what you doing.
// https://eprint.iacr.org/2019/458.pdf Section 2.3 (Avoiding Insecure Matrices)
export function _poseidonMDS(Fp, name, m, attempt = 0) {
const x_values = [];
const y_values = [];
for (let i = 0; i < m; i++) {
x_values.push(poseidonRoundConstant(Fp, `${name}x`, attempt * m + i));
y_values.push(poseidonRoundConstant(Fp, `${name}y`, attempt * m + i));
}
if (new Set([...x_values, ...y_values]).size !== 2 * m)
throw new Error('X and Y values are not distinct');
return x_values.map((x) => y_values.map((y) => Fp.inv(Fp.sub(x, y))));
}
const MDS_SMALL = [
[3, 1, 1],
[1, -1, 1],
[1, 1, -2],
].map((i) => i.map(BigInt));
export function poseidonBasic(opts, mds) {
validateField(opts.Fp);
if (!Number.isSafeInteger(opts.rate) || !Number.isSafeInteger(opts.capacity))
throw new Error(`Wrong poseidon opts: ${opts}`);
const m = opts.rate + opts.capacity;
const rounds = opts.roundsFull + opts.roundsPartial;
const roundConstants = [];
for (let i = 0; i < rounds; i++) {
const row = [];
for (let j = 0; j < m; j++) row.push(poseidonRoundConstant(opts.Fp, 'Hades', m * i + j));
roundConstants.push(row);
}
const res = poseidon({
...opts,
t: m,
sboxPower: 3,
reversePartialPowIdx: true, // Why?!
mds,
roundConstants,
});
res.m = m;
res.rate = opts.rate;
res.capacity = opts.capacity;
return res;
}
export function poseidonCreate(opts, mdsAttempt = 0) {
const m = opts.rate + opts.capacity;
if (!Number.isSafeInteger(mdsAttempt)) throw new Error(`Wrong mdsAttempt=${mdsAttempt}`);
return poseidonBasic(opts, _poseidonMDS(opts.Fp, 'HadesMDS', m, mdsAttempt));
}
export const poseidonSmall = poseidonBasic(
{ Fp: Fp251, rate: 2, capacity: 1, roundsFull: 8, roundsPartial: 83 },
MDS_SMALL
);
export function poseidonHash(x, y, fn = poseidonSmall) {
return fn([x, y, 2n])[0];
}
export function poseidonHashFunc(x, y, fn = poseidonSmall) {
return u.numberToVarBytesBE(poseidonHash(u.bytesToNumberBE(x), u.bytesToNumberBE(y), fn));
}
export function poseidonHashSingle(x, fn = poseidonSmall) {
return fn([x, 0n, 1n])[0];
}
export function poseidonHashMany(values, fn = poseidonSmall) {
const { m, rate } = fn;
if (!Array.isArray(values)) throw new Error('bigint array expected in values');
const padded = Array.from(values); // copy
padded.push(1n);
while (padded.length % rate !== 0) padded.push(0n);
let state = new Array(m).fill(0n);
for (let i = 0; i < padded.length; i += rate) {
for (let j = 0; j < rate; j++) state[j] += padded[i + j];
state = fn(state);
}
return state[0];
}

View File

@@ -1,22 +1,24 @@
import { deepStrictEqual, throws } from 'assert'; 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 '../lib/esm/abstract/modular.js'; import * as mod from '../esm/abstract/modular.js';
import { bytesToHex as toHex } from '../lib/esm/abstract/utils.js'; import { bytesToHex as toHex } from '../esm/abstract/utils.js';
// Generic tests for all curves in package // Generic tests for all curves in package
import { secp192r1 } from '../lib/esm/p192.js'; import { secp192r1, secp224r1 } from './_more-curves.helpers.js';
import { secp224r1 } from '../lib/esm/p224.js'; import { secp256r1 } from '../esm/p256.js';
import { secp256r1 } from '../lib/esm/p256.js'; import { secp384r1 } from '../esm/p384.js';
import { secp384r1 } from '../lib/esm/p384.js'; import { secp521r1 } from '../esm/p521.js';
import { secp521r1 } from '../lib/esm/p521.js'; import { secp256k1 } from '../esm/secp256k1.js';
import { secp256k1 } from '../lib/esm/secp256k1.js'; import { ed25519, ed25519ctx, ed25519ph, x25519 } from '../esm/ed25519.js';
import { ed25519, ed25519ctx, ed25519ph, x25519 } from '../lib/esm/ed25519.js'; import { ed448, ed448ph } from '../esm/ed448.js';
import { ed448, ed448ph } from '../lib/esm/ed448.js'; import { pallas, vesta } from '../esm/pasta.js';
import { starkCurve } from '../lib/esm/stark.js'; import { bn254 } from '../esm/bn254.js';
import { pallas, vesta } from '../lib/esm/pasta.js'; import { jubjub } from '../esm/jubjub.js';
import { bn254 } from '../lib/esm/bn.js'; import { bls12_381 } from '../esm/bls12-381.js';
import { jubjub } from '../lib/esm/jubjub.js'; import { default as wyche_curves } from './wycheproof/ec_prime_order_curves_test.json' assert { type: 'json' };
import { bls12_381 } from '../lib/esm/bls12-381.js'; import { createCurve } from '../esm/_shortw_utils.js';
import { Field } from '../esm/abstract/modular.js';
import { sha256 } from '@noble/hashes/sha256';
// Fields tests // Fields tests
const FIELDS = { const FIELDS = {
@@ -25,7 +27,6 @@ const FIELDS = {
secp256r1: { Fp: [secp256r1.CURVE.Fp] }, secp256r1: { Fp: [secp256r1.CURVE.Fp] },
secp521r1: { Fp: [secp521r1.CURVE.Fp] }, secp521r1: { Fp: [secp521r1.CURVE.Fp] },
secp256k1: { Fp: [secp256k1.CURVE.Fp] }, secp256k1: { Fp: [secp256k1.CURVE.Fp] },
stark: { Fp: [starkCurve.CURVE.Fp] },
jubjub: { Fp: [jubjub.CURVE.Fp] }, jubjub: { Fp: [jubjub.CURVE.Fp] },
ed25519: { Fp: [ed25519.CURVE.Fp] }, ed25519: { Fp: [ed25519.CURVE.Fp] },
ed448: { Fp: [ed448.CURVE.Fp] }, ed448: { Fp: [ed448.CURVE.Fp] },
@@ -33,19 +34,19 @@ const FIELDS = {
pallas: { Fp: [pallas.CURVE.Fp] }, pallas: { Fp: [pallas.CURVE.Fp] },
vesta: { Fp: [vesta.CURVE.Fp] }, vesta: { Fp: [vesta.CURVE.Fp] },
bls12: { bls12: {
Fp: [bls12_381.CURVE.Fp], Fp: [bls12_381.fields.Fp],
Fp2: [ Fp2: [
bls12_381.CURVE.Fp2, bls12_381.fields.Fp2,
fc.array(fc.bigInt(1n, bls12_381.CURVE.Fp.ORDER - 1n), { fc.array(fc.bigInt(1n, bls12_381.fields.Fp.ORDER - 1n), {
minLength: 2, minLength: 2,
maxLength: 2, maxLength: 2,
}), }),
(Fp2, num) => Fp2.fromBigTuple([num[0], num[1]]), (Fp2, num) => Fp2.fromBigTuple([num[0], num[1]]),
], ],
// Fp6: [bls12_381.CURVE.Fp6], // Fp6: [bls12_381.fields.Fp6],
Fp12: [ Fp12: [
bls12_381.CURVE.Fp12, bls12_381.fields.Fp12,
fc.array(fc.bigInt(1n, bls12_381.CURVE.Fp.ORDER - 1n), { fc.array(fc.bigInt(1n, bls12_381.fields.Fp.ORDER - 1n), {
minLength: 12, minLength: 12,
maxLength: 12, maxLength: 12,
}), }),
@@ -224,7 +225,7 @@ for (const c in FIELDS) {
const isSquare = mod.FpIsSquare(Fp); const isSquare = mod.FpIsSquare(Fp);
// Not implemented // Not implemented
if (Fp !== bls12_381.CURVE.Fp12) { if (Fp !== bls12_381.fields.Fp12) {
should('multiply/sqrt', () => { should('multiply/sqrt', () => {
fc.assert( fc.assert(
fc.property(FC_BIGINT, (num) => { fc.property(FC_BIGINT, (num) => {
@@ -315,7 +316,6 @@ const CURVES = {
secp256k1, secp256k1,
ed25519, ed25519ctx, ed25519ph, ed25519, ed25519ctx, ed25519ph,
ed448, ed448ph, ed448, ed448ph,
starkCurve,
pallas, vesta, pallas, vesta,
bn254, bn254,
jubjub, jubjub,
@@ -557,6 +557,7 @@ for (const name in CURVES) {
}); });
} }
describe(name, () => { describe(name, () => {
if (['bn254', 'pallas', 'vesta'].includes(name)) return;
// Generic complex things (getPublicKey/sign/verify/getSharedSecret) // Generic complex things (getPublicKey/sign/verify/getSharedSecret)
should('.getPublicKey() type check', () => { should('.getPublicKey() type check', () => {
throws(() => C.getPublicKey(0), '0'); throws(() => C.getPublicKey(0), '0');
@@ -745,6 +746,54 @@ should('bigInt private keys', () => {
secp256k1.sign('', 123n); secp256k1.sign('', 123n);
}); });
describe('wycheproof curve creation', () => {
const VECTORS = wyche_curves.testGroups[0].tests;
for (const v of VECTORS) {
should(`${v.name}`, () => {
const CURVE = createCurve(
{
Fp: Field(BigInt(`0x${v.p}`)),
a: BigInt(`0x${v.a}`),
b: BigInt(`0x${v.b}`),
n: BigInt(`0x${v.n}`),
h: BigInt(v.h),
Gx: BigInt(`0x${v.gx}`),
Gy: BigInt(`0x${v.gy}`),
},
sha256
);
});
const CURVE = CURVES[v.name];
if (!CURVE) continue;
should(`${v.name} parms verify`, () => {
deepStrictEqual(CURVE.CURVE.Fp.ORDER, BigInt(`0x${v.p}`));
deepStrictEqual(CURVE.CURVE.a, BigInt(`0x${v.a}`));
deepStrictEqual(CURVE.CURVE.b, BigInt(`0x${v.b}`));
deepStrictEqual(CURVE.CURVE.n, BigInt(`0x${v.n}`));
deepStrictEqual(CURVE.CURVE.Gx, BigInt(`0x${v.gx}`));
deepStrictEqual(CURVE.CURVE.Gy, BigInt(`0x${v.gy}`));
deepStrictEqual(CURVE.CURVE.h, BigInt(v.h));
});
}
});
should('validate generator point is on curve', () => {
throws(() =>
createCurve(
{
Fp: Field(BigInt(`0x00c302f41d932a36cda7a3463093d18db78fce476de1a86297`)),
a: BigInt(`0x00c302f41d932a36cda7a3463093d18db78fce476de1a86294`),
b: BigInt(`0x13d56ffaec78681e68f9deb43b35bec2fb68542e27897b79`),
n: BigInt(`0x00c302f41d932a36cda7a3462f9e9e916b5be8f1029ac4acc1`),
h: BigInt(1),
Gx: BigInt(`0x3ae9e58c82f63c30282e1fe7bbf43fa72c446af6f4618129`),
Gy: BigInt(`0x097e2c5667c2223a902ab5ca449d0084b7e5b3de7ccc01c8`), // last 9 -> 8
},
sha256
)
);
});
// ESM is broken. // ESM is broken.
import url from 'url'; import url from 'url';
if (import.meta.url === url.pathToFileURL(process.argv[1]).href) { if (import.meta.url === url.pathToFileURL(process.argv[1]).href) {

View File

@@ -2,13 +2,19 @@ import { deepStrictEqual, notDeepStrictEqual, throws } from 'assert';
import * as fc from 'fast-check'; import * as fc from 'fast-check';
import { readFileSync } from 'fs'; import { readFileSync } from 'fs';
import { describe, should } from 'micro-should'; import { describe, should } from 'micro-should';
import { wNAF } from '../lib/esm/abstract/curve.js'; import { wNAF } from '../esm/abstract/curve.js';
import { bytesToHex, utf8ToBytes } from '../lib/esm/abstract/utils.js'; import { bytesToHex, utf8ToBytes } from '../esm/abstract/utils.js';
import { hash_to_field } from '../lib/esm/abstract/hash-to-curve.js'; import { hash_to_field } from '../esm/abstract/hash-to-curve.js';
import { bls12_381 as bls } from '../lib/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')
@@ -24,11 +30,10 @@ const SCALAR_VECTORS = readFileSync('./test/bls12-381/bls12-381-scalar-test-vect
const NUM_RUNS = Number(process.env.RUNS_COUNT || 10); // reduce to 1 to shorten test time const NUM_RUNS = Number(process.env.RUNS_COUNT || 10); // reduce to 1 to shorten test time
fc.configureGlobal({ numRuns: NUM_RUNS }); fc.configureGlobal({ numRuns: NUM_RUNS });
const { Fp2 } = bls;
const G1Point = bls.G1.ProjectivePoint; const G1Point = bls.G1.ProjectivePoint;
const G2Point = bls.G2.ProjectivePoint; const G2Point = bls.G2.ProjectivePoint;
const G1Aff = (x, y) => G1Point.fromAffine({ x, y }); const G1Aff = (x, y) => G1Point.fromAffine({ x, y });
const CURVE_ORDER = bls.CURVE.r; const CURVE_ORDER = bls.params.r;
const FC_MSG = fc.hexaString({ minLength: 64, maxLength: 64 }); const FC_MSG = fc.hexaString({ minLength: 64, maxLength: 64 });
const FC_MSG_5 = fc.array(FC_MSG, { minLength: 5, maxLength: 5 }); const FC_MSG_5 = fc.array(FC_MSG, { minLength: 5, maxLength: 5 });
@@ -38,14 +43,19 @@ 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})`);
} }
const { Fp, Fp2 } = bls.fields;
// Fp // Fp
describe('bls12-381 Fp', () => { describe('bls12-381 Fp', () => {
const Fp = bls.Fp;
const FC_BIGINT = fc.bigInt(1n, Fp.ORDER - 1n); const FC_BIGINT = fc.bigInt(1n, Fp.ORDER - 1n);
should('multiply/sqrt', () => { should('multiply/sqrt', () => {
@@ -60,8 +70,7 @@ describe('bls12-381 Fp', () => {
// Fp2 // Fp2
describe('bls12-381 Fp2', () => { describe('bls12-381 Fp2', () => {
const Fp = bls.Fp; const { Fp, Fp2 } = bls.fields;
const Fp2 = bls.Fp2;
const FC_BIGINT = fc.bigInt(1n, Fp.ORDER - 1n); const FC_BIGINT = fc.bigInt(1n, Fp.ORDER - 1n);
const FC_BIGINT_2 = fc.array(FC_BIGINT, { minLength: 2, maxLength: 2 }); const FC_BIGINT_2 = fc.array(FC_BIGINT, { minLength: 2, maxLength: 2 });
@@ -149,7 +158,7 @@ describe('bls12-381 Fp2', () => {
// Point // Point
describe('bls12-381 Point', () => { describe('bls12-381 Point', () => {
const Fp = bls.Fp; const { Fp } = bls.fields;
const FC_BIGINT = fc.bigInt(1n, Fp.ORDER - 1n); const FC_BIGINT = fc.bigInt(1n, Fp.ORDER - 1n);
const PointG1 = G1Point; const PointG1 = G1Point;
const PointG2 = G2Point; const PointG2 = G2Point;
@@ -557,9 +566,12 @@ describe('bls12-381 Point', () => {
]; ];
// Use wNAF allow scalars higher than CURVE.r // Use wNAF allow scalars higher than CURVE.r
const w = wNAF(G2Point, 1); const w = wNAF(G2Point, 1);
const hEff = BigInt(
'0xbc69f08f2ee75b3584c6a0ea91b352888e2a8e9145ad7689986ff031508ffe1329c2f178731db956d82bf015d1212b02ec0ec69d7477c1ae954cbc06689f6a359894c0adebbf6b4e8020005aaa95551'
);
for (let p of points) { for (let p of points) {
const ours = p.clearCofactor(); const ours = p.clearCofactor();
const shouldBe = w.unsafeLadder(p, bls.CURVE.G2.hEff); const shouldBe = w.unsafeLadder(p, hEff);
deepStrictEqual(ours.equals(shouldBe), true, 'clearLast'); deepStrictEqual(ours.equals(shouldBe), true, 'clearLast');
} }
}); });
@@ -577,12 +589,12 @@ describe('bls12-381/basic', () => {
deepStrictEqual(g1.x, G1Point.ZERO.x); deepStrictEqual(g1.x, G1Point.ZERO.x);
deepStrictEqual(g1.y, G1Point.ZERO.y); deepStrictEqual(g1.y, G1Point.ZERO.y);
// Test Non-Zero // Test Non-Zero
const x = bls.Fp.create( const x = Fp.create(
BigInt( BigInt(
'0x17f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb' '0x17f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb'
) )
); );
const y = bls.Fp.create( const y = Fp.create(
BigInt( BigInt(
'0x08b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e1' '0x08b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e1'
) )
@@ -603,12 +615,12 @@ describe('bls12-381/basic', () => {
deepStrictEqual(g1.x, G1Point.ZERO.x); deepStrictEqual(g1.x, G1Point.ZERO.x);
deepStrictEqual(g1.y, G1Point.ZERO.y); deepStrictEqual(g1.y, G1Point.ZERO.y);
// Test Non-Zero // Test Non-Zero
const x = bls.Fp.create( const x = Fp.create(
BigInt( BigInt(
'0x17f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb' '0x17f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb'
) )
); );
const y = bls.Fp.create( const y = Fp.create(
BigInt( BigInt(
'0x08b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e1' '0x08b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e1'
) )
@@ -689,12 +701,12 @@ describe('bls12-381/basic', () => {
// Test Zero // Test Zero
deepStrictEqual(G1Point.ZERO.toHex(false), B_192_40); deepStrictEqual(G1Point.ZERO.toHex(false), B_192_40);
// Test Non-Zero // Test Non-Zero
const x = bls.Fp.create( const x = Fp.create(
BigInt( BigInt(
'0x17f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb' '0x17f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb'
) )
); );
const y = bls.Fp.create( const y = Fp.create(
BigInt( BigInt(
'0x08b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e1' '0x08b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e1'
) )
@@ -710,12 +722,12 @@ describe('bls12-381/basic', () => {
// Test Zero // Test Zero
deepStrictEqual(G1Point.ZERO.toHex(false), B_192_40); deepStrictEqual(G1Point.ZERO.toHex(false), B_192_40);
// Test Non-Zero // Test Non-Zero
const x = bls.Fp.create( const x = Fp.create(
BigInt( BigInt(
'0x17f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb' '0x17f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb'
) )
); );
const y = bls.Fp.create( const y = Fp.create(
BigInt( BigInt(
'0x08b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e1' '0x08b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e1'
) )
@@ -801,32 +813,32 @@ describe('bls12-381/basic', () => {
throws(() => G2Point.fromPrivateKey(0n)); throws(() => G2Point.fromPrivateKey(0n));
}); });
const VALID_G1 = new G1Point( const VALID_G1 = new G1Point(
bls.Fp.create( Fp.create(
3609742242174788176010452839163620388872641749536604986743596621604118973777515189035770461528205168143692110933639n 3609742242174788176010452839163620388872641749536604986743596621604118973777515189035770461528205168143692110933639n
), ),
bls.Fp.create( Fp.create(
1619277690257184054444116778047375363103842303863153349133480657158810226683757397206929105479676799650932070320089n 1619277690257184054444116778047375363103842303863153349133480657158810226683757397206929105479676799650932070320089n
), ),
bls.Fp.create(1n) Fp.create(1n)
); );
const VALID_G1_2 = new G1Point( const VALID_G1_2 = new G1Point(
bls.Fp.create( Fp.create(
1206972466279728255044019580914616126536509750250979180256809997983196363639429409634110400978470384566664128085207n 1206972466279728255044019580914616126536509750250979180256809997983196363639429409634110400978470384566664128085207n
), ),
bls.Fp.create( Fp.create(
2991142246317096160788653339959532007292638191110818490939476869616372888657136539642598243964263069435065725313423n 2991142246317096160788653339959532007292638191110818490939476869616372888657136539642598243964263069435065725313423n
), ),
bls.Fp.create(1n) Fp.create(1n)
); );
const INVALID_G1 = new G1Point( const INVALID_G1 = new G1Point(
bls.Fp.create( Fp.create(
499001545268060011619089734015590154568173930614466321429631711131511181286230338880376679848890024401335766847607n 499001545268060011619089734015590154568173930614466321429631711131511181286230338880376679848890024401335766847607n
), ),
bls.Fp.create( Fp.create(
3934582309586258715640230772291917282844636728991757779640464479794033391537662970190753981664259511166946374555673n 3934582309586258715640230772291917282844636728991757779640464479794033391537662970190753981664259511166946374555673n
), ),
bls.Fp.create(1n) Fp.create(1n)
); );
should('aggregate pubkeys', () => { should('aggregate pubkeys', () => {
@@ -846,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;
@@ -855,15 +874,15 @@ describe('bls12-381/basic', () => {
}); });
should(`produce correct scalars (${SCALAR_VECTORS.length} vectors)`, () => { should(`produce correct scalars (${SCALAR_VECTORS.length} vectors)`, () => {
const options = { const options = {
p: bls.CURVE.r, p: bls.params.r,
m: 1, m: 1,
expand: false, expand: '_internal_pass',
}; };
for (let vector of SCALAR_VECTORS) { for (let vector of SCALAR_VECTORS) {
const [okmAscii, expectedHex] = vector; const [okmAscii, expectedHex] = vector;
const expected = BigInt('0x' + expectedHex); const expected = BigInt('0x' + expectedHex);
const okm = utf8ToBytes(okmAscii); const okm = utf8ToBytes(okmAscii);
const scalars = hash_to_field(okm, 1, Object.assign({}, bls.CURVE.htfDefaults, options)); const scalars = hash_to_field(okm, 1, Object.assign({}, bls.G2.CURVE.htfDefaults, options));
deepStrictEqual(scalars[0][0], expected); deepStrictEqual(scalars[0][0], expected);
} }
}); });
@@ -871,7 +890,8 @@ describe('bls12-381/basic', () => {
// Pairing // Pairing
describe('pairing', () => { describe('pairing', () => {
const { pairing, Fp12 } = bls; const { pairing } = bls;
const { Fp12 } = bls.fields;
const G1 = G1Point.BASE; const G1 = G1Point.BASE;
const G2 = G2Point.BASE; const G2 = G2Point.BASE;
@@ -968,7 +988,7 @@ describe('pairing', () => {
}); });
}); });
// hashToCurve // hashToCurve
describe('hash-to-curve', () => { describe('hash-to-curve (against Killic)', () => {
// Point G1 // Point G1
const VECTORS_G1 = [ const VECTORS_G1 = [
{ {
@@ -998,16 +1018,17 @@ describe('hash-to-curve', () => {
'047a85d6898416a0899e26219bca7c4f0fa682717199de196b02b95eaf9fb55456ac3b810e78571a1b7f5692b7c58ab6', '047a85d6898416a0899e26219bca7c4f0fa682717199de196b02b95eaf9fb55456ac3b810e78571a1b7f5692b7c58ab6',
}, },
]; ];
describe('hashToCurve G1', () => {
for (let i = 0; i < VECTORS_G1.length; i++) { for (let i = 0; i < VECTORS_G1.length; i++) {
const t = VECTORS_G1[i]; const t = VECTORS_G1[i];
should(`hashToCurve/G1 Killic (${i})`, () => { should(`${i}`, () => {
const p = bls.G1.hashToCurve(t.msg, { const p = bls.G1.hashToCurve(t.msg, {
DST: 'BLS12381G1_XMD:SHA-256_SSWU_RO_TESTGEN', DST: 'BLS12381G1_XMD:SHA-256_SSWU_RO_TESTGEN',
}); });
deepStrictEqual(p.toHex(false), t.expected); deepStrictEqual(p.toHex(false), t.expected);
}); });
} }
});
const VECTORS_ENCODE_G1 = [ const VECTORS_ENCODE_G1 = [
{ {
msg: utf8ToBytes(''), msg: utf8ToBytes(''),
@@ -1036,15 +1057,17 @@ describe('hash-to-curve', () => {
'094bfdfe3e552447433b5a00967498a3f1314b86ce7a7164c8a8f4131f99333b30a574607e301d5f774172c627fd0bca', '094bfdfe3e552447433b5a00967498a3f1314b86ce7a7164c8a8f4131f99333b30a574607e301d5f774172c627fd0bca',
}, },
]; ];
describe('encodeToCurve G1', () => {
for (let i = 0; i < VECTORS_ENCODE_G1.length; i++) { for (let i = 0; i < VECTORS_ENCODE_G1.length; i++) {
const t = VECTORS_ENCODE_G1[i]; const t = VECTORS_ENCODE_G1[i];
should(`hashToCurve/G1 (Killic, encodeToCurve) (${i})`, () => { should(`(${i})`, () => {
const p = bls.G1.encodeToCurve(t.msg, { const p = bls.G1.encodeToCurve(t.msg, {
DST: 'BLS12381G1_XMD:SHA-256_SSWU_NU_TESTGEN', DST: 'BLS12381G1_XMD:SHA-256_SSWU_NU_TESTGEN',
}); });
deepStrictEqual(p.toHex(false), t.expected); deepStrictEqual(p.toHex(false), t.expected);
}); });
} }
});
// Point G2 // Point G2
const VECTORS_G2 = [ const VECTORS_G2 = [
{ {
@@ -1082,16 +1105,17 @@ describe('hash-to-curve', () => {
'15c1d4f1a685bb63ee67ca1fd96155e3d091e852a684b78d085fd34f6091e5249ddddbdcf2e7ec82ce6c04c63647eeb7', '15c1d4f1a685bb63ee67ca1fd96155e3d091e852a684b78d085fd34f6091e5249ddddbdcf2e7ec82ce6c04c63647eeb7',
}, },
]; ];
describe('hashToCurve G2', () => {
for (let i = 0; i < VECTORS_G2.length; i++) { for (let i = 0; i < VECTORS_G2.length; i++) {
const t = VECTORS_G2[i]; const t = VECTORS_G2[i];
should(`hashToCurve/G2 Killic (${i})`, () => { should(`${i}`, () => {
const p = bls.G2.hashToCurve(t.msg, { const p = bls.G2.hashToCurve(t.msg, {
DST: 'BLS12381G2_XMD:SHA-256_SSWU_RO_TESTGEN', DST: 'BLS12381G2_XMD:SHA-256_SSWU_RO_TESTGEN',
}); });
deepStrictEqual(p.toHex(false), t.expected); deepStrictEqual(p.toHex(false), t.expected);
}); });
} }
});
const VECTORS_ENCODE_G2 = [ const VECTORS_ENCODE_G2 = [
{ {
msg: utf8ToBytes(''), msg: utf8ToBytes(''),
@@ -1128,9 +1152,10 @@ describe('hash-to-curve', () => {
'09e5c8242dd7281ad32c03fe4af3f19167770016255fb25ad9b67ec51d62fade31a1af101e8f6172ec2ee8857662be3a', '09e5c8242dd7281ad32c03fe4af3f19167770016255fb25ad9b67ec51d62fade31a1af101e8f6172ec2ee8857662be3a',
}, },
]; ];
describe('encodeToCurve G2', () => {
for (let i = 0; i < VECTORS_ENCODE_G2.length; i++) { for (let i = 0; i < VECTORS_ENCODE_G2.length; i++) {
const t = VECTORS_ENCODE_G2[i]; const t = VECTORS_ENCODE_G2[i];
should(`hashToCurve/G2 (Killic, encodeToCurve) (${i})`, () => { should(`${i}`, () => {
const p = bls.G2.encodeToCurve(t.msg, { const p = bls.G2.encodeToCurve(t.msg, {
DST: 'BLS12381G2_XMD:SHA-256_SSWU_NU_TESTGEN', DST: 'BLS12381G2_XMD:SHA-256_SSWU_NU_TESTGEN',
}); });
@@ -1138,6 +1163,7 @@ describe('hash-to-curve', () => {
}); });
} }
}); });
});
describe('verify()', () => { describe('verify()', () => {
should('verify signed message', () => { should('verify signed message', () => {
@@ -1169,6 +1195,35 @@ describe('verify()', () => {
deepStrictEqual(res, false); deepStrictEqual(res, 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}`);
}
});
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);
}
});
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);
}
});
describe('batch', () => { describe('batch', () => {
should('verify multi-signature', () => { should('verify multi-signature', () => {
fc.assert( fc.assert(
@@ -1226,6 +1281,7 @@ describe('verify()', () => {
should('verify multi-signature as simple signature', () => { should('verify multi-signature as simple signature', () => {
fc.assert( fc.assert(
fc.property(FC_MSG, FC_BIGINT_5, (message, privateKeys) => { fc.property(FC_MSG, FC_BIGINT_5, (message, privateKeys) => {
message = replaceZeroPoint(message);
const publicKey = privateKeys.map(getPubKey); const publicKey = privateKeys.map(getPubKey);
const signatures = privateKeys.map((privateKey) => bls.sign(message, privateKey)); const signatures = privateKeys.map((privateKey) => bls.sign(message, privateKey));
const aggregatedSignature = bls.aggregateSignatures(signatures); const aggregatedSignature = bls.aggregateSignatures(signatures);
@@ -1237,6 +1293,7 @@ describe('verify()', () => {
should('not verify wrong multi-signature as simple signature', () => { should('not verify wrong multi-signature as simple signature', () => {
fc.assert( fc.assert(
fc.property(FC_MSG, FC_MSG, FC_BIGINT_5, (message, wrongMessage, privateKeys) => { fc.property(FC_MSG, FC_MSG, FC_BIGINT_5, (message, wrongMessage, privateKeys) => {
message = replaceZeroPoint(message);
const publicKey = privateKeys.map(getPubKey); const publicKey = privateKeys.map(getPubKey);
const signatures = privateKeys.map((privateKey) => bls.sign(message, privateKey)); const signatures = privateKeys.map((privateKey) => bls.sign(message, privateKey));
const aggregatedSignature = bls.aggregateSignatures(signatures); const aggregatedSignature = bls.aggregateSignatures(signatures);
@@ -1259,7 +1316,7 @@ describe('bls12-381 deterministic', () => {
.reverse() .reverse()
.reduce((acc, i) => acc + i); .reduce((acc, i) => acc + i);
const Fp12 = bls.Fp12; const { Fp12 } = bls.fields;
should('Killic based/Pairing', () => { should('Killic based/Pairing', () => {
const t = bls.pairing(G1Point.BASE, G2Point.BASE); const t = bls.pairing(G1Point.BASE, G2Point.BASE);
@@ -1360,6 +1417,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.

View File

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

View File

@@ -1,14 +1,18 @@
import { sha512 } from '@noble/hashes/sha512'; import { sha512 } from '@noble/hashes/sha512';
import { hexToBytes, bytesToHex, randomBytes } from '@noble/hashes/utils'; import { bytesToHex as hex, hexToBytes } from '@noble/hashes/utils';
import { deepStrictEqual, strictEqual, throws } from 'assert'; import { deepStrictEqual, throws } from 'assert';
import { describe, should } from 'micro-should'; import { describe, should } from 'micro-should';
import { numberToBytesLE } from '../lib/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 '../lib/esm/ed25519.js'; import {
ed25519,
// const ed = ed25519; ed25519ctx,
const hex = bytesToHex; ed25519ph,
// const Point = ed.ExtendedPoint; edwardsToMontgomeryPub,
edwardsToMontgomeryPriv,
RistrettoPoint,
x25519,
} from '../esm/ed25519.js';
const VECTORS_RFC8032_CTX = [ const VECTORS_RFC8032_CTX = [
{ {
@@ -62,8 +66,14 @@ describe('RFC8032ctx', () => {
const v = VECTORS_RFC8032_CTX[i]; const v = VECTORS_RFC8032_CTX[i];
should(`${i}`, () => { should(`${i}`, () => {
deepStrictEqual(hex(ed25519ctx.getPublicKey(v.secretKey)), v.publicKey); deepStrictEqual(hex(ed25519ctx.getPublicKey(v.secretKey)), v.publicKey);
deepStrictEqual(hex(ed25519ctx.sign(v.message, v.secretKey, v.context)), v.signature); deepStrictEqual(
deepStrictEqual(ed25519ctx.verify(v.signature, v.message, v.publicKey, v.context), true); hex(ed25519ctx.sign(v.message, v.secretKey, { context: v.context })),
v.signature
);
deepStrictEqual(
ed25519ctx.verify(v.signature, v.message, v.publicKey, { context: v.context }),
true
);
}); });
} }
}); });
@@ -93,14 +103,7 @@ describe('RFC8032ph', () => {
}); });
// x25519 // x25519
should('X25519 base point', () => { describe('RFC7748 X25519 ECDH', () => {
const { y } = ed25519ph.ExtendedPoint.BASE;
const { Fp } = ed25519ph.CURVE;
const u = Fp.create((y + 1n) * Fp.inv(1n - y));
deepStrictEqual(hex(numberToBytesLE(u, 32)), x25519.Gu);
});
describe('RFC7748', () => {
const rfc7748Mul = [ const rfc7748Mul = [
{ {
scalar: 'a546e36bf0527c9d3b16154b82465edd62144c0ac1fc5a18506a2244ba449ac4', scalar: 'a546e36bf0527c9d3b16154b82465edd62144c0ac1fc5a18506a2244ba449ac4',
@@ -127,8 +130,8 @@ describe('RFC7748', () => {
]; ];
for (let i = 0; i < rfc7748Iter.length; i++) { for (let i = 0; i < rfc7748Iter.length; i++) {
const { scalar, iters } = rfc7748Iter[i]; const { scalar, iters } = rfc7748Iter[i];
should(`scalarMult iteration (${i})`, () => { should(`scalarMult iteration x${iters}`, () => {
let k = x25519.Gu; let k = x25519.GuBytes;
for (let i = 0, u = k; i < iters; i++) [k, u] = [x25519.scalarMult(k, u), k]; for (let i = 0, u = k; i < iters; i++) [k, u] = [x25519.scalarMult(k, u), k];
deepStrictEqual(hex(k), scalar); deepStrictEqual(hex(k), scalar);
}); });
@@ -145,10 +148,66 @@ describe('RFC7748', () => {
deepStrictEqual(hex(x25519.scalarMult(alicePrivate, bobPublic)), shared); deepStrictEqual(hex(x25519.scalarMult(alicePrivate, bobPublic)), shared);
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;
}
}
}); });
describe('Wycheproof', () => {
should('edwardsToMontgomery should produce correct output', () => {
const edSecret = hexToBytes('77076d0a7318a57d3c16c17251b26645df4c2f87ebc0992ab177fba51db92c2a');
const edPublic = ed25519.getPublicKey(edSecret);
const xPrivate = edwardsToMontgomeryPriv(edSecret);
deepStrictEqual(
hex(xPrivate),
'a8cd44eb8e93319c0570bc11005c0e0189d34ff02f6c17773411ad191293c94f'
);
const xPublic = edwardsToMontgomeryPub(edPublic);
deepStrictEqual(
hex(xPublic),
'ed7749b4d989f6957f3bfde6c56767e988e21c9f8784d91d610011cd553f9b06'
);
});
should('edwardsToMontgomery should produce correct keyPair', () => {
const edSecret = ed25519.utils.randomPrivateKey();
const edPublic = ed25519.getPublicKey(edSecret);
const xSecret = edwardsToMontgomeryPriv(edSecret);
const expectedXPublic = x25519.getPublicKey(xSecret);
const xPublic = edwardsToMontgomeryPub(edPublic);
deepStrictEqual(xPublic, expectedXPublic);
});
should('ECDH through edwardsToMontgomery should be commutative', () => {
const edSecret1 = ed25519.utils.randomPrivateKey();
const edPublic1 = ed25519.getPublicKey(edSecret1);
const edSecret2 = ed25519.utils.randomPrivateKey();
const edPublic2 = ed25519.getPublicKey(edSecret2);
deepStrictEqual(
x25519.getSharedSecret(edwardsToMontgomeryPriv(edSecret1), edwardsToMontgomeryPub(edPublic2)),
x25519.getSharedSecret(edwardsToMontgomeryPriv(edSecret2), edwardsToMontgomeryPub(edPublic1))
);
});
should('base point', () => {
const { y } = ed25519ph.ExtendedPoint.BASE;
const { Fp } = ed25519ph.CURVE;
const u = Fp.create((y + 1n) * Fp.inv(1n - y));
deepStrictEqual(numberToBytesLE(u, 32), x25519.GuBytes);
});
const group = x25519vectors.testGroups[0]; const group = x25519vectors.testGroups[0];
should(`X25519`, () => { should('wycheproof', () => {
for (let i = 0; i < group.tests.length; i++) { for (let i = 0; i < group.tests.length; i++) {
const v = group.tests[i]; const v = group.tests[i];
const comment = `(${i}, ${v.result}) ${v.comment}`; const comment = `(${i}, ${v.result}) ${v.comment}`;
@@ -281,10 +340,23 @@ describe('ristretto255', () => {
deepStrictEqual(point.toHex(), encodedHashToPoints[i]); deepStrictEqual(point.toHex(), encodedHashToPoints[i]);
} }
}); });
should('have proper equality testing', () => {
const MAX_255B = BigInt('0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff');
const bytes255ToNumberLE = (bytes) =>
ed25519ctx.CURVE.Fp.create(bytesToNumberLE(bytes) & MAX_255B);
const priv = new Uint8Array([
198, 101, 65, 165, 93, 120, 37, 238, 16, 133, 10, 35, 253, 243, 161, 246, 229, 135, 12, 137,
202, 114, 222, 139, 146, 123, 4, 125, 152, 173, 1, 7,
]);
const pub = RistrettoPoint.BASE.multiply(bytes255ToNumberLE(priv));
deepStrictEqual(pub.equals(RistrettoPoint.ZERO), false);
});
}); });
// 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) {
should.run(); should.run();
} }

View File

@@ -1 +1,2 @@
export { ed25519, ED25519_TORSION_SUBGROUP } from '../lib/esm/ed25519.js'; export { numberToBytesLE } from '../esm/abstract/utils.js';
export { ed25519, ED25519_TORSION_SUBGROUP } from '../esm/ed25519.js';

View File

@@ -1,14 +1,19 @@
import { deepStrictEqual, strictEqual, throws } from 'assert'; import { deepStrictEqual, strictEqual, throws } from 'assert';
import { readFileSync } from 'fs'; import { readFileSync } from 'fs';
import { hexToBytes, bytesToHex, 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 } from './ed25519.helpers.js'; import { ed25519 as ed, ED25519_TORSION_SUBGROUP, numberToBytesLE } from './ed25519.helpers.js';
import { default as ed25519vectors } from './wycheproof/eddsa_test.json' assert { type: 'json' }; // Old vectors allow to test sign() because they include private key
import { default as ed25519vectors_OLD } from './ed25519/ed25519_test_OLD.json' assert { type: 'json' };
import { default as ed25519vectors } from './wycheproof/ed25519_test.json' assert { type: 'json' };
import { default as zip215 } from './ed25519/zip215.json' assert { type: 'json' }; import { default as 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;
@@ -17,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', () => {
@@ -305,7 +303,8 @@ describe('ed25519', () => {
// https://zips.z.cash/zip-0215 // https://zips.z.cash/zip-0215
// Vectors from https://gist.github.com/hdevalence/93ed42d17ecab8e42138b213812c8cc7 // Vectors from https://gist.github.com/hdevalence/93ed42d17ecab8e42138b213812c8cc7
should('ZIP-215 compliance tests/should pass all of them', () => { describe('ZIP215', () => {
should('pass all compliance tests', () => {
const str = utf8ToBytes('Zcash'); const str = utf8ToBytes('Zcash');
for (let v of zip215) { for (let v of zip215) {
let noble = false; let noble = false;
@@ -317,11 +316,12 @@ describe('ed25519', () => {
deepStrictEqual(noble, v.valid_zip215, JSON.stringify(v)); deepStrictEqual(noble, v.valid_zip215, JSON.stringify(v));
} }
}); });
should('ZIP-215 compliance tests/disallows sig.s >= CURVE.n', () => { should('disallow sig.s >= CURVE.n', () => {
// sig.R = BASE, sig.s = N+1 // sig.R = BASE, sig.s = N+1
const sig = const sig =
'5866666666666666666666666666666666666666666666666666666666666666eed3f55c1a631258d69cf7a2def9de1400000000000000000000000000000010'; '5866666666666666666666666666666666666666666666666666666666666666eed3f55c1a631258d69cf7a2def9de1400000000000000000000000000000010';
throws(() => ed.verify(sig, 'deadbeef', Point.BASE)); deepStrictEqual(ed.verify(sig, 'deadbeef', Point.BASE), false);
});
}); });
// should('X25519/getSharedSecret() should be commutative', () => { // should('X25519/getSharedSecret() should be commutative', () => {
@@ -346,9 +346,9 @@ describe('ed25519', () => {
// ); // );
// }); // });
should(`Wycheproof/ED25519`, () => { should(`wycheproof/ED25519 (OLD)`, () => {
for (let g = 0; g < ed25519vectors.testGroups.length; g++) { for (let g = 0; g < ed25519vectors_OLD.testGroups.length; g++) {
const group = ed25519vectors.testGroups[g]; const group = ed25519vectors_OLD.testGroups[g];
const key = group.key; const key = group.key;
deepStrictEqual(hex(ed.getPublicKey(key.sk)), key.pk, `(${g}, public)`); deepStrictEqual(hex(ed.getPublicKey(key.sk)), key.pk, `(${g}, public)`);
for (let i = 0; i < group.tests.length; i++) { for (let i = 0; i < group.tests.length; i++) {
@@ -370,7 +370,29 @@ describe('ed25519', () => {
} }
}); });
should('Property test issue #1', () => { should(`wycheproof/ED25519`, () => {
for (let g = 0; g < ed25519vectors.testGroups.length; g++) {
const group = ed25519vectors.testGroups[g];
const key = group.publicKey;
for (let i = 0; i < group.tests.length; i++) {
const v = group.tests[i];
const comment = `(${g}/${i}, ${v.result}): ${v.comment}`;
if (v.result === 'valid' || v.result === 'acceptable') {
deepStrictEqual(ed.verify(v.sig, v.msg, key.pk), true, comment);
} else if (v.result === 'invalid') {
let failed = false;
try {
failed = !ed.verify(v.sig, v.msg, key.pk);
} catch (error) {
failed = true;
}
deepStrictEqual(failed, true, comment);
} else throw new Error('unknown test result');
}
}
});
should('not mutate inputs', () => {
const message = new Uint8Array([12, 12, 12]); const message = new Uint8Array([12, 12, 12]);
const signature = ed.sign(message, to32Bytes(1n)); const signature = ed.sign(message, to32Bytes(1n));
const publicKey = ed.getPublicKey(to32Bytes(1n)); // <- was 1n const publicKey = ed.getPublicKey(to32Bytes(1n)); // <- was 1n
@@ -387,16 +409,43 @@ describe('ed25519', () => {
strictEqual(cleared.isTorsionFree(), true, `cleared must be torsionFree: ${hex}`); strictEqual(cleared.isTorsionFree(), true, `cleared must be torsionFree: ${hex}`);
} }
}); });
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('ed25519 bug', () => { should('not verify when sig.s >= CURVE.n', () => {
const privateKey = ed.utils.randomPrivateKey();
const message = Uint8Array.from([0xab, 0xbc, 0xcd, 0xde]);
const publicKey = ed.getPublicKey(privateKey);
const signature = ed.sign(message, privateKey);
const R = signature.slice(0, 32);
let s = signature.slice(32, 64);
s = bytesToHex(s.slice().reverse());
s = BigInt('0x' + s);
s = s + ed.CURVE.n;
s = numberToBytesLE(s, 32);
const sig_invalid = concatBytes(R, s);
deepStrictEqual(ed.verify(sig_invalid, message, publicKey), false);
});
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();
// point2.toAffine(); // crash! // point2.toAffine(); // crash!
}); });
});
// ESM is broken. // ESM is broken.
import url from 'url'; import url from 'url';

View File

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

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

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

View File

@@ -1,9 +1,11 @@
import { deepStrictEqual, throws } from 'assert'; import { deepStrictEqual, throws } from 'assert';
import { describe, should } from 'micro-should'; import { describe, should } from 'micro-should';
import * as fc from 'fast-check'; import * as fc from 'fast-check';
import { ed448, ed448ph, x448 } from '../lib/esm/ed448.js'; import { ed448, ed448ph, x448 } from '../esm/ed448.js';
import { hexToBytes, bytesToHex, randomBytes } from '@noble/hashes/utils'; import { bytesToHex, concatBytes, hexToBytes, randomBytes } from '@noble/hashes/utils';
import { numberToBytesLE } from '../lib/esm/abstract/utils.js'; import { numberToBytesLE } from '../esm/abstract/utils.js';
// Old vectors allow to test sign() because they include private key
import { default as ed448vectorsOld } from './ed448/ed448_test_OLD.json' assert { type: 'json' };
import { default as ed448vectors } from './wycheproof/ed448_test.json' assert { type: 'json' }; import { default as ed448vectors } from './wycheproof/ed448_test.json' assert { type: 'json' };
import { default as x448vectors } from './wycheproof/x448_test.json' assert { type: 'json' }; import { default as x448vectors } from './wycheproof/x448_test.json' assert { type: 'json' };
@@ -439,9 +441,9 @@ describe('ed448', () => {
} }
}); });
describe('wycheproof', () => { describe('wycheproof (OLD)', () => {
for (let g = 0; g < ed448vectors.testGroups.length; g++) { for (let g = 0; g < ed448vectorsOld.testGroups.length; g++) {
const group = ed448vectors.testGroups[g]; const group = ed448vectorsOld.testGroups[g];
const key = group.key; const key = group.key;
should(`ED448(${g}, public)`, () => { should(`ED448(${g}, public)`, () => {
deepStrictEqual(hex(ed.getPublicKey(key.sk)), key.pk); deepStrictEqual(hex(ed.getPublicKey(key.sk)), key.pk);
@@ -467,92 +469,20 @@ describe('ed448', () => {
} }
}); });
// ECDH
const rfc7748Mul = [
{
scalar:
'3d262fddf9ec8e88495266fea19a34d28882acef045104d0d1aae121700a779c984c24f8cdd78fbff44943eba368f54b29259a4f1c600ad3',
u: '06fce640fa3487bfda5f6cf2d5263f8aad88334cbd07437f020f08f9814dc031ddbdc38c19c6da2583fa5429db94ada18aa7a7fb4ef8a086',
outputU:
'ce3e4ff95a60dc6697da1db1d85e6afbdf79b50a2412d7546d5f239fe14fbaadeb445fc66a01b0779d98223961111e21766282f73dd96b6f',
},
{
scalar:
'203d494428b8399352665ddca42f9de8fef600908e0d461cb021f8c538345dd77c3e4806e25f46d3315c44e0a5b4371282dd2c8d5be3095f',
u: '0fbcc2f993cd56d3305b0b7d9e55d4c1a8fb5dbb52f8e9a1e9b6201b165d015894e56c4d3570bee52fe205e28a78b91cdfbde71ce8d157db',
outputU:
'884a02576239ff7a2f2f63b2db6a9ff37047ac13568e1e30fe63c4a7ad1b3ee3a5700df34321d62077e63633c575c1c954514e99da7c179d',
},
];
describe('RFC7748', () => {
for (let i = 0; i < rfc7748Mul.length; i++) {
const v = rfc7748Mul[i];
should(`scalarMult (${i})`, () => {
deepStrictEqual(hex(x448.scalarMult(v.scalar, v.u)), v.outputU);
});
}
});
const rfc7748Iter = [
{
scalar:
'3f482c8a9f19b01e6c46ee9711d9dc14fd4bf67af30765c2ae2b846a4d23a8cd0db897086239492caf350b51f833868b9bc2b3bca9cf4113',
iters: 1,
},
{
scalar:
'aa3b4749d55b9daf1e5b00288826c467274ce3ebbdd5c17b975e09d4af6c67cf10d087202db88286e2b79fceea3ec353ef54faa26e219f38',
iters: 1000,
},
// { scalar: '077f453681caca3693198420bbe515cae0002472519b3e67661a7e89cab94695c8f4bcd66e61b9b9c946da8d524de3d69bd9d9d66b997e37', iters: 1000000 },
];
for (let i = 0; i < rfc7748Iter.length; i++) {
const { scalar, iters } = rfc7748Iter[i];
should(`RFC7748: scalarMult iteration (${i})`, () => {
let k = x448.Gu;
for (let i = 0, u = k; i < iters; i++) [k, u] = [x448.scalarMult(k, u), k];
deepStrictEqual(hex(k), scalar);
});
}
should('RFC7748 getSharedKey', () => {
const alicePrivate =
'9a8f4925d1519f5775cf46b04b5800d4ee9ee8bae8bc5565d498c28dd9c9baf574a9419744897391006382a6f127ab1d9ac2d8c0a598726b';
const alicePublic =
'9b08f7cc31b7e3e67d22d5aea121074a273bd2b83de09c63faa73d2c22c5d9bbc836647241d953d40c5b12da88120d53177f80e532c41fa0';
const bobPrivate =
'1c306a7ac2a0e2e0990b294470cba339e6453772b075811d8fad0d1d6927c120bb5ee8972b0d3e21374c9c921b09d1b0366f10b65173992d';
const bobPublic =
'3eb7a829b0cd20f5bcfc0b599b6feccf6da4627107bdb0d4f345b43027d8b972fc3e34fb4232a13ca706dcb57aec3dae07bdc1c67bf33609';
const shared =
'07fff4181ac6cc95ec1c16a94a0f74d12da232ce40a77552281d282bb60c0b56fd2464c335543936521c24403085d59a449a5037514a879d';
deepStrictEqual(alicePublic, hex(x448.getPublicKey(alicePrivate)));
deepStrictEqual(bobPublic, hex(x448.getPublicKey(bobPrivate)));
deepStrictEqual(hex(x448.scalarMult(alicePrivate, bobPublic)), shared);
deepStrictEqual(hex(x448.scalarMult(bobPrivate, alicePublic)), shared);
});
describe('wycheproof', () => { describe('wycheproof', () => {
const group = x448vectors.testGroups[0]; for (let g = 0; g < ed448vectors.testGroups.length; g++) {
should(`X448`, () => { const group = ed448vectors.testGroups[g];
const key = group.publicKey;
should(`ED448`, () => {
for (let i = 0; i < group.tests.length; i++) { for (let i = 0; i < group.tests.length; i++) {
const v = group.tests[i]; const v = group.tests[i];
const index = `(${i}, ${v.result}) ${v.comment}`; const index = `${g}/${i} ${v.comment}`;
if (v.result === 'valid' || v.result === 'acceptable') { if (v.result === 'valid' || v.result === 'acceptable') {
try { deepStrictEqual(ed.verify(v.sig, v.msg, key.pk), true, index);
const shared = hex(x448.scalarMult(v.private, v.public));
deepStrictEqual(shared, v.shared, index);
} catch (e) {
// We are more strict
if (e.message.includes('Expected valid scalar')) return;
if (e.message.includes('Invalid private or public key received')) return;
if (e.message.includes('Expected 56 bytes')) return;
throw e;
}
} else if (v.result === 'invalid') { } else if (v.result === 'invalid') {
let failed = false; let failed = false;
try { try {
x448.scalarMult(v.private, v.public); failed = !ed.verify(v.sig, v.msg, key.pk);
} catch (error) { } catch (error) {
failed = true; failed = true;
} }
@@ -560,8 +490,8 @@ describe('ed448', () => {
} else throw new Error('unknown test result'); } else throw new Error('unknown test result');
} }
}); });
}
}); });
// should('X448: should convert base point to montgomery using fromPoint', () => { // should('X448: should convert base point to montgomery using fromPoint', () => {
// deepStrictEqual( // deepStrictEqual(
// hex(ed.montgomeryCurve.UfromPoint(Point.BASE)), // hex(ed.montgomeryCurve.UfromPoint(Point.BASE)),
@@ -584,6 +514,7 @@ describe('ed448', () => {
// } // }
// }); // });
describe('ed448ctx', () => {
const VECTORS_RFC8032_CTX = [ const VECTORS_RFC8032_CTX = [
{ {
secretKey: secretKey:
@@ -603,16 +534,20 @@ describe('ed448', () => {
'3c00', '3c00',
}, },
]; ];
for (let i = 0; i < VECTORS_RFC8032_CTX.length; i++) { for (let i = 0; i < VECTORS_RFC8032_CTX.length; i++) {
const v = VECTORS_RFC8032_CTX[i]; const v = VECTORS_RFC8032_CTX[i];
should(`RFC8032ctx/${i}`, () => { should(`${i}`, () => {
deepStrictEqual(hex(ed.getPublicKey(v.secretKey)), v.publicKey); deepStrictEqual(hex(ed.getPublicKey(v.secretKey)), v.publicKey);
deepStrictEqual(hex(ed.sign(v.message, v.secretKey, v.context)), v.signature); deepStrictEqual(hex(ed.sign(v.message, v.secretKey, { context: v.context })), v.signature);
deepStrictEqual(ed.verify(v.signature, v.message, v.publicKey, v.context), true); deepStrictEqual(
ed.verify(v.signature, v.message, v.publicKey, { context: v.context }),
true
);
}); });
} }
});
describe('ed448ph', () => {
const VECTORS_RFC8032_PH = [ const VECTORS_RFC8032_PH = [
{ {
secretKey: secretKey:
@@ -648,23 +583,154 @@ describe('ed448', () => {
'2100', '2100',
}, },
]; ];
for (let i = 0; i < VECTORS_RFC8032_PH.length; i++) { for (let i = 0; i < VECTORS_RFC8032_PH.length; i++) {
const v = VECTORS_RFC8032_PH[i]; const v = VECTORS_RFC8032_PH[i];
should(`RFC8032ph/${i}`, () => { should(`${i}`, () => {
deepStrictEqual(hex(ed448ph.getPublicKey(v.secretKey)), v.publicKey); deepStrictEqual(hex(ed448ph.getPublicKey(v.secretKey)), v.publicKey);
deepStrictEqual(hex(ed448ph.sign(v.message, v.secretKey, v.context)), v.signature); deepStrictEqual(
deepStrictEqual(ed448ph.verify(v.signature, v.message, v.publicKey, v.context), true); hex(ed448ph.sign(v.message, v.secretKey, { context: v.context })),
v.signature
);
deepStrictEqual(
ed448ph.verify(v.signature, v.message, v.publicKey, { context: v.context }),
true
);
});
}
});
should('not verify when sig.s >= CURVE.n', () => {
function get56bSig() {
const privateKey = ed448.utils.randomPrivateKey();
const message = Uint8Array.from([0xab, 0xbc, 0xcd, 0xde]);
const publicKey = ed448.getPublicKey(privateKey);
const signature = ed448.sign(message, privateKey);
const R = signature.slice(0, 56);
let s = signature.slice(56, 112);
s = bytesToHex(s.slice().reverse());
s = BigInt('0x' + s);
s = s + ed448.CURVE.n;
s = numberToBytesLE(s, 56);
const sig_invalid = concatBytes(R, s);
return { sig_invalid, message, publicKey };
}
let sig;
while (true) {
try {
sig = get56bSig();
break;
} catch (error) {
// non-56b sig was generated, try again
}
}
throws(() => {
ed448.verify(sig.sig_invalid, sig.message, sig.publicKey);
});
});
describe('RFC7748 X448 ECDH', () => {
// ECDH
const rfc7748Mul = [
{
scalar:
'3d262fddf9ec8e88495266fea19a34d28882acef045104d0d1aae121700a779c984c24f8cdd78fbff44943eba368f54b29259a4f1c600ad3',
u: '06fce640fa3487bfda5f6cf2d5263f8aad88334cbd07437f020f08f9814dc031ddbdc38c19c6da2583fa5429db94ada18aa7a7fb4ef8a086',
outputU:
'ce3e4ff95a60dc6697da1db1d85e6afbdf79b50a2412d7546d5f239fe14fbaadeb445fc66a01b0779d98223961111e21766282f73dd96b6f',
},
{
scalar:
'203d494428b8399352665ddca42f9de8fef600908e0d461cb021f8c538345dd77c3e4806e25f46d3315c44e0a5b4371282dd2c8d5be3095f',
u: '0fbcc2f993cd56d3305b0b7d9e55d4c1a8fb5dbb52f8e9a1e9b6201b165d015894e56c4d3570bee52fe205e28a78b91cdfbde71ce8d157db',
outputU:
'884a02576239ff7a2f2f63b2db6a9ff37047ac13568e1e30fe63c4a7ad1b3ee3a5700df34321d62077e63633c575c1c954514e99da7c179d',
},
];
for (let i = 0; i < rfc7748Mul.length; i++) {
const v = rfc7748Mul[i];
should(`scalarMult (${i})`, () => {
deepStrictEqual(hex(x448.scalarMult(v.scalar, v.u)), v.outputU);
}); });
} }
should('X448 base point', () => { const rfc7748Iter = [
{
scalar:
'3f482c8a9f19b01e6c46ee9711d9dc14fd4bf67af30765c2ae2b846a4d23a8cd0db897086239492caf350b51f833868b9bc2b3bca9cf4113',
iters: 1,
},
{
scalar:
'aa3b4749d55b9daf1e5b00288826c467274ce3ebbdd5c17b975e09d4af6c67cf10d087202db88286e2b79fceea3ec353ef54faa26e219f38',
iters: 1000,
},
// { scalar: '077f453681caca3693198420bbe515cae0002472519b3e67661a7e89cab94695c8f4bcd66e61b9b9c946da8d524de3d69bd9d9d66b997e37', iters: 1000000 },
];
for (let i = 0; i < rfc7748Iter.length; i++) {
const { scalar, iters } = rfc7748Iter[i];
should(`scalarMult iterated ${iters}x`, () => {
let k = x448.GuBytes;
for (let i = 0, u = k; i < iters; i++) [k, u] = [x448.scalarMult(k, u), k];
deepStrictEqual(hex(k), scalar);
});
}
should('getSharedKey', () => {
const alicePrivate =
'9a8f4925d1519f5775cf46b04b5800d4ee9ee8bae8bc5565d498c28dd9c9baf574a9419744897391006382a6f127ab1d9ac2d8c0a598726b';
const alicePublic =
'9b08f7cc31b7e3e67d22d5aea121074a273bd2b83de09c63faa73d2c22c5d9bbc836647241d953d40c5b12da88120d53177f80e532c41fa0';
const bobPrivate =
'1c306a7ac2a0e2e0990b294470cba339e6453772b075811d8fad0d1d6927c120bb5ee8972b0d3e21374c9c921b09d1b0366f10b65173992d';
const bobPublic =
'3eb7a829b0cd20f5bcfc0b599b6feccf6da4627107bdb0d4f345b43027d8b972fc3e34fb4232a13ca706dcb57aec3dae07bdc1c67bf33609';
const shared =
'07fff4181ac6cc95ec1c16a94a0f74d12da232ce40a77552281d282bb60c0b56fd2464c335543936521c24403085d59a449a5037514a879d';
deepStrictEqual(alicePublic, hex(x448.getPublicKey(alicePrivate)));
deepStrictEqual(bobPublic, hex(x448.getPublicKey(bobPrivate)));
deepStrictEqual(hex(x448.scalarMult(alicePrivate, bobPublic)), shared);
deepStrictEqual(hex(x448.scalarMult(bobPrivate, alicePublic)), shared);
});
should('wycheproof', () => {
const group = x448vectors.testGroups[0];
for (let i = 0; i < group.tests.length; i++) {
const v = group.tests[i];
const index = `(${i}, ${v.result}) ${v.comment}`;
if (v.result === 'valid' || v.result === 'acceptable') {
try {
const shared = hex(x448.scalarMult(v.private, v.public));
deepStrictEqual(shared, v.shared, index);
} catch (e) {
// We are more strict
if (e.message.includes('Expected valid scalar')) return;
if (e.message.includes('Invalid private or public key received')) return;
if (e.message.includes('Expected 56 bytes')) return;
throw e;
}
} else if (v.result === 'invalid') {
let failed = false;
try {
x448.scalarMult(v.private, v.public);
} catch (error) {
failed = true;
}
deepStrictEqual(failed, true, index);
} else throw new Error('unknown test result');
}
});
should('have proper base point', () => {
const { x, y } = Point.BASE; const { x, y } = Point.BASE;
const { Fp } = ed448.CURVE; const { Fp } = ed448.CURVE;
// const invX = Fp.invert(x * x); // x² // const invX = Fp.invert(x * x); // x²
const u = Fp.div(Fp.create(y * y), Fp.create(x * x)); // (y²/x²) const u = Fp.div(Fp.create(y * y), Fp.create(x * x)); // (y²/x²)
// const u = Fp.create(y * y * invX); // const u = Fp.create(y * y * invX);
deepStrictEqual(hex(numberToBytesLE(u, 56)), x448.Gu); deepStrictEqual(numberToBytesLE(u, 56), x448.GuBytes);
});
}); });
}); });

View File

@@ -0,0 +1,908 @@
{
"algorithm" : "EDDSA",
"generatorVersion" : "0.8r12",
"numberOfTests" : 86,
"header" : [
"Test vectors of type EddsaVerify are intended for testing",
"the verification of Eddsa signatures."
],
"notes" : {
"SignatureMalleability" : "EdDSA signatures are non-malleable, if implemented accordingly. Failing to check the range of S allows to modify signatures. See RFC 8032, Section 5.2.7 and Section 8.4."
},
"schema" : "eddsa_verify_schema.json",
"testGroups" : [
{
"jwk" : {
"crv" : "Ed448",
"d" : "iDAeB2UY01N_kwLuD1Ij5LY-HwFgB9PC69_sX3CZfoEZxrrQrnuAP0h5HKjsVJqiobhi96UVkLnV",
"kid" : "none",
"kty" : "OKP",
"x" : "QZYQpTSvEn9YOwSBjNt_D_MAsCXy4BaCvK4z_Wkc7gOVEd8M3caQ7peEJuizjlDOWvfc-6UPcEwA"
},
"key" : {
"curve" : "edwards448",
"keySize" : 448,
"pk" : "419610a534af127f583b04818cdb7f0ff300b025f2e01682bcae33fd691cee039511df0cddc690ee978426e8b38e50ce5af7dcfba50f704c00",
"sk" : "88301e076518d3537f9302ee0f5223e4b63e1f016007d3c2ebdfec5f70997e8119c6bad0ae7b803f48791ca8ec549aa2a1b862f7a51590b9d5",
"type" : "EDDSAKeyPair"
},
"keyDer" : "3043300506032b6571033a00419610a534af127f583b04818cdb7f0ff300b025f2e01682bcae33fd691cee039511df0cddc690ee978426e8b38e50ce5af7dcfba50f704c00",
"keyPem" : "-----BEGIN PUBLIC KEY-----\nMEMwBQYDK2VxAzoAQZYQpTSvEn9YOwSBjNt/D/MAsCXy4BaCvK4z/Wkc7gOVEd8M3caQ7peEJuizjlDOWvfc+6UPcEwA\n-----END PUBLIC KEY-----\n",
"type" : "EddsaVerify",
"tests" : [
{
"tcId" : 1,
"comment" : "",
"msg" : "",
"sig" : "cf7953007666e12f73af9ec92e3e018da5ee5a8d5b17f5100a354c58f1d5f4bb37ab835c52f72374c72d612689149cf6d36a70db6dc5a6c400b597348e0e31e51e65bb144e63c892a367b4c055c036aa6cd7e728cdd2a098963bda863903e6dd025b5a5d891209f4e28537694804e50b0800",
"result" : "valid",
"flags" : []
},
{
"tcId" : 2,
"comment" : "",
"msg" : "78",
"sig" : "c56e94d5c9ca860c244f33db556bf6b3cec38b024b77604a35d6a07211b1316b9a027133c374b86f72665cc45ce01583a2e0f2775c6172da801acef168717cab1196cddfb149359dfef589756257cc2d6b02fc516d8d41b4adaa3f11428f41410ef0dc3c1b008d3d052173d4389508ed0100",
"result" : "valid",
"flags" : []
},
{
"tcId" : 3,
"comment" : "",
"msg" : "54657374",
"sig" : "5d053ff5b71f6ec3284525d35d77933178c8e19879886d08eccc6c7d27e9e5b5e02537dbc4d4723506e8d171fc1733857573dd02d18f48f28031d67d699a188a9ca46b4eabe2107aef237ca609cb462e24c91d25d286402b6ef7862b78a386950246ff38d6d2f458136d12e3c97fdd982600",
"result" : "valid",
"flags" : []
},
{
"tcId" : 4,
"comment" : "",
"msg" : "48656c6c6f",
"sig" : "442e33780f199dd7bc71d1335f74df7f3a0ec789e21a175c1bffddb6e50091998d969ac8194b3acefb7702f6c222f84f7eeca3b80406f1fe80687915e7925bf52deb47b6b779e26d30eec7c5fef03580f280a089eefd0bacc9fbbb6a4d73a591d1671d192e6bbcfdb79ad3db5673a1263000",
"result" : "valid",
"flags" : []
},
{
"tcId" : 5,
"comment" : "",
"msg" : "313233343030",
"sig" : "5db94c53101f521f6c1f43b60ea4d7e06fbd49c2e8afaf4fcc289e645e0880a87b8e55858df4cf2291a7303ffda446b82a117b4dd408cff28060a05236fc9c1682b0e55b60a082c9a57bffe61ef4dda5ce65df539805122b3a09a05976d41ad68ab52df85428152c57da93531e5d16920e00",
"result" : "valid",
"flags" : []
},
{
"tcId" : 6,
"comment" : "",
"msg" : "000000000000000000000000",
"sig" : "a8ca64d1ab00eae77fd2854d8422db3ae12fca91c14f274f30a44df98590786ec4cbb96a9564fc1b9b16c22d2bd00aa65f0876323729f5ac809fb0b89a4d3f27afbabb596851d835173d60ea34e0875359f3d6adb13cef1395b7eaa5f9147583ff38b4deb183062874915bf194ae61072300",
"result" : "valid",
"flags" : []
},
{
"tcId" : 7,
"comment" : "",
"msg" : "6161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161",
"sig" : "b205d3e24ccef64c1e86f15f48ddfa682453503489475188b04a8f55860b3c8a9c01e6de820bb7d9b15daff8de25a4a870e987157a115ec1802da0d0606da12842ea7eab658b5eea6dd1f3a641a5174425578003cd318b8d6b8dcb4de954b5078d1912c578ad8281515d6df3672b94173f00",
"result" : "valid",
"flags" : []
},
{
"tcId" : 8,
"comment" : "",
"msg" : "202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f60",
"sig" : "3492ef66e5fdf1503e9e206c5c2f0d4b7891aad793575527d2251e0df1b97c2feac188bc382ce3c92c4bc36ba2695f32bedadd480eaa932300d0db1f9a9c60844d2ea5aea64933c7be46c4f9d21cb48b39eae23d08496de7ce9501197185cc5d4ff8aa4b018ce7ad321f6a7d778c4a070400",
"result" : "valid",
"flags" : []
},
{
"tcId" : 9,
"comment" : "",
"msg" : "ffffffffffffffffffffffffffffffff",
"sig" : "545e1905af1b5886552eaf78e17304c6f83fcfb3444df2d1ea056486db615e3bb29131bb0c1fd295364dc515dae581967148eb23c6c9012e806d3623baff00548c648e3cb3756aaaaf659f2fb7dd2e71c7611448593ca63f2a98913ab7f182e6820eaf1334e2745e0e7bc0dccab98de71600",
"result" : "valid",
"flags" : []
},
{
"tcId" : 10,
"comment" : "special values for r and s",
"msg" : "3f",
"sig" : "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
"result" : "invalid",
"flags" : []
},
{
"tcId" : 11,
"comment" : "special values for r and s",
"msg" : "3f",
"sig" : "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
"result" : "invalid",
"flags" : []
},
{
"tcId" : 12,
"comment" : "special values for r and s",
"msg" : "3f",
"sig" : "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f24458ab92c27823558fc58d72c26c219036d6ae49db4ec4e923ca7cffffffffffffffffffffffffffffffffffffffffffffffffffffff3f",
"result" : "invalid",
"flags" : []
},
{
"tcId" : 13,
"comment" : "special values for r and s",
"msg" : "3f",
"sig" : "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f34458ab92c27823558fc58d72c26c219036d6ae49db4ec4e923ca7cffffffffffffffffffffffffffffffffffffffffffffffffffffff3f",
"result" : "invalid",
"flags" : []
},
{
"tcId" : 14,
"comment" : "special values for r and s",
"msg" : "3f",
"sig" : "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffffffffffffffffffffffffffffffffffffffffffffffffff",
"result" : "invalid",
"flags" : []
},
{
"tcId" : 15,
"comment" : "special values for r and s",
"msg" : "3f",
"sig" : "01000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
"result" : "invalid",
"flags" : []
},
{
"tcId" : 16,
"comment" : "special values for r and s",
"msg" : "3f",
"sig" : "01000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
"result" : "invalid",
"flags" : []
},
{
"tcId" : 17,
"comment" : "special values for r and s",
"msg" : "3f",
"sig" : "0100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f24458ab92c27823558fc58d72c26c219036d6ae49db4ec4e923ca7cffffffffffffffffffffffffffffffffffffffffffffffffffffff3f",
"result" : "invalid",
"flags" : []
},
{
"tcId" : 18,
"comment" : "special values for r and s",
"msg" : "3f",
"sig" : "0100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f34458ab92c27823558fc58d72c26c219036d6ae49db4ec4e923ca7cffffffffffffffffffffffffffffffffffffffffffffffffffffff3f",
"result" : "invalid",
"flags" : []
},
{
"tcId" : 19,
"comment" : "special values for r and s",
"msg" : "3f",
"sig" : "0100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffffffffffffffffffffffffffffffffffffffffffffffffff",
"result" : "invalid",
"flags" : []
},
{
"tcId" : 20,
"comment" : "special values for r and s",
"msg" : "3f",
"sig" : "f34458ab92c27823558fc58d72c26c219036d6ae49db4ec4e923ca7cffffffffffffffffffffffffffffffffffffffffffffffffffffff3f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
"result" : "invalid",
"flags" : []
},
{
"tcId" : 21,
"comment" : "special values for r and s",
"msg" : "3f",
"sig" : "f34458ab92c27823558fc58d72c26c219036d6ae49db4ec4e923ca7cffffffffffffffffffffffffffffffffffffffffffffffffffffff3f0100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
"result" : "invalid",
"flags" : []
},
{
"tcId" : 22,
"comment" : "special values for r and s",
"msg" : "3f",
"sig" : "f34458ab92c27823558fc58d72c26c219036d6ae49db4ec4e923ca7cffffffffffffffffffffffffffffffffffffffffffffffffffffff3ff24458ab92c27823558fc58d72c26c219036d6ae49db4ec4e923ca7cffffffffffffffffffffffffffffffffffffffffffffffffffffff3f",
"result" : "invalid",
"flags" : []
},
{
"tcId" : 23,
"comment" : "special values for r and s",
"msg" : "3f",
"sig" : "f34458ab92c27823558fc58d72c26c219036d6ae49db4ec4e923ca7cffffffffffffffffffffffffffffffffffffffffffffffffffffff3ff34458ab92c27823558fc58d72c26c219036d6ae49db4ec4e923ca7cffffffffffffffffffffffffffffffffffffffffffffffffffffff3f",
"result" : "invalid",
"flags" : []
},
{
"tcId" : 24,
"comment" : "special values for r and s",
"msg" : "3f",
"sig" : "f34458ab92c27823558fc58d72c26c219036d6ae49db4ec4e923ca7cffffffffffffffffffffffffffffffffffffffffffffffffffffff3ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffffffffffffffffffffffffffffffffffffffffffffffffff",
"result" : "invalid",
"flags" : []
},
{
"tcId" : 25,
"comment" : "special values for r and s",
"msg" : "3f",
"sig" : "fffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
"result" : "invalid",
"flags" : []
},
{
"tcId" : 26,
"comment" : "special values for r and s",
"msg" : "3f",
"sig" : "fffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffffffffffffffffffffffffffffffffffffffffffffffffff0100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
"result" : "invalid",
"flags" : []
},
{
"tcId" : 27,
"comment" : "special values for r and s",
"msg" : "3f",
"sig" : "fffffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffffffffffffffffffffffffffffffffffffffffffffffffffff24458ab92c27823558fc58d72c26c219036d6ae49db4ec4e923ca7cffffffffffffffffffffffffffffffffffffffffffffffffffffff3f",
"result" : "invalid",
"flags" : []
},
{
"tcId" : 28,
"comment" : "special values for r and s",
"msg" : "3f",
"sig" : "fffffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffffffffffffffffffffffffffffffffffffffffffffffffffff34458ab92c27823558fc58d72c26c219036d6ae49db4ec4e923ca7cffffffffffffffffffffffffffffffffffffffffffffffffffffff3f",
"result" : "invalid",
"flags" : []
},
{
"tcId" : 29,
"comment" : "special values for r and s",
"msg" : "3f",
"sig" : "fffffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffffffffffffffffffffffffffffffffffffffffffffffffff",
"result" : "invalid",
"flags" : []
},
{
"tcId" : 30,
"comment" : "empty signature",
"msg" : "54657374",
"sig" : "",
"result" : "invalid",
"flags" : []
},
{
"tcId" : 31,
"comment" : "s missing",
"msg" : "54657374",
"sig" : "5d053ff5b71f6ec3284525d35d77933178c8e19879886d08eccc6c7d27e9e5b5e02537dbc4d4723506e8d171fc1733857573dd02d18f48f280",
"result" : "invalid",
"flags" : []
},
{
"tcId" : 32,
"comment" : "signature too short",
"msg" : "54657374",
"sig" : "5d053ff5b71f6ec3284525d35d77933178c8e19879886d08eccc6c7d27e9e5b5e02537dbc4d4723506e8d171fc1733857573dd02d18f48f28031d67d699a188a9ca46b4eabe2107aef237ca609cb462e24c91d25d286402b6ef7862b78a386950246ff38d6d2f458136d12e3c97fdd98",
"result" : "invalid",
"flags" : []
},
{
"tcId" : 33,
"comment" : "signature too long",
"msg" : "54657374",
"sig" : "5d053ff5b71f6ec3284525d35d77933178c8e19879886d08eccc6c7d27e9e5b5e02537dbc4d4723506e8d171fc1733857573dd02d18f48f28031d67d699a188a9ca46b4eabe2107aef237ca609cb462e24c91d25d286402b6ef7862b78a386950246ff38d6d2f458136d12e3c97fdd9826002020",
"result" : "invalid",
"flags" : []
},
{
"tcId" : 34,
"comment" : "include pk in signature",
"msg" : "54657374",
"sig" : "5d053ff5b71f6ec3284525d35d77933178c8e19879886d08eccc6c7d27e9e5b5e02537dbc4d4723506e8d171fc1733857573dd02d18f48f28031d67d699a188a9ca46b4eabe2107aef237ca609cb462e24c91d25d286402b6ef7862b78a386950246ff38d6d2f458136d12e3c97fdd982600419610a534af127f583b04818cdb7f0ff300b025f2e01682bcae33fd691cee039511df0cddc690ee978426e8b38e50ce5af7dcfba50f704c00",
"result" : "invalid",
"flags" : []
},
{
"tcId" : 35,
"comment" : "prepending 0 byte to signature",
"msg" : "54657374",
"sig" : "005d053ff5b71f6ec3284525d35d77933178c8e19879886d08eccc6c7d27e9e5b5e02537dbc4d4723506e8d171fc1733857573dd02d18f48f28031d67d699a188a9ca46b4eabe2107aef237ca609cb462e24c91d25d286402b6ef7862b78a386950246ff38d6d2f458136d12e3c97fdd982600",
"result" : "invalid",
"flags" : []
},
{
"tcId" : 36,
"comment" : "prepending 0 byte to s",
"msg" : "54657374",
"sig" : "5d053ff5b71f6ec3284525d35d77933178c8e19879886d08eccc6c7d27e9e5b5e02537dbc4d4723506e8d171fc1733857573dd02d18f48f2800031d67d699a188a9ca46b4eabe2107aef237ca609cb462e24c91d25d286402b6ef7862b78a386950246ff38d6d2f458136d12e3c97fdd982600",
"result" : "invalid",
"flags" : []
},
{
"tcId" : 37,
"comment" : "appending 0 byte to signature",
"msg" : "54657374",
"sig" : "5d053ff5b71f6ec3284525d35d77933178c8e19879886d08eccc6c7d27e9e5b5e02537dbc4d4723506e8d171fc1733857573dd02d18f48f28031d67d699a188a9ca46b4eabe2107aef237ca609cb462e24c91d25d286402b6ef7862b78a386950246ff38d6d2f458136d12e3c97fdd98260000",
"result" : "invalid",
"flags" : []
},
{
"tcId" : 38,
"comment" : "removing 0 byte from signature",
"msg" : "5465737430",
"sig" : "dbd6384516ab6b0eb2d609414564ec217383b66040dfb0676128251ae24c1d7c179c21a9ee307dc13f8fe6550bc40187f093da85617bcf5d009d3ee8b798ad978b6e683bc4e911940ea82ea0b7e95dc24fe0b29e44663211892c2aaa3451379d22c289b94378f11fb700f1689d4a00d73e",
"result" : "invalid",
"flags" : []
},
{
"tcId" : 39,
"comment" : "removing 0 byte from signature",
"msg" : "546573743535",
"sig" : "ce2b2fff0bf445a36813cf2a76e0cc5619a4f16ee53f0fe3cd46fc0414db7248b32fbda54bbb37e708d6238076ea12bf850b964b044520bb80fbaf0e1d1ed3bcab261462df5e7f2de73ac9cbae26dfa29015039acf90575961fc9b91b9ca276dae7d5fa805bd202c5579a0f4c66e801400",
"result" : "invalid",
"flags" : []
},
{
"tcId" : 40,
"comment" : "dropping byte from signature",
"msg" : "546573743633",
"sig" : "c283ed36d78c275a5d02f7939aed2c4ef68320ae1bf6fc25e834b758046a6d52a480216a942dfe771f3bd307f4ce7d3f446e0824961bd5de80cda42b5cc38e6ec3d53f386978b9877d3c98a28ac8fc66630ffd178933a18de1aee23cab5011c9ff4c9277311b4c6c33acb8e82b8c693c00",
"result" : "invalid",
"flags" : []
},
{
"tcId" : 41,
"comment" : "removing leading 0 byte from signature",
"msg" : "54657374333631",
"sig" : "62e629bd2b8f595df401c362c766216d45de89fceecd99c69d323b5c53ad5ac3ea7224963feba2f2895551d94f548248ef8597d2a959f880d59934a5e8f07847834d66ba1a6b09de5dba692172b13f768f0c29e8196144c130d2353445d63cbd0b690794fdad30a48e8bb7cc2504f80700",
"result" : "invalid",
"flags" : []
},
{
"tcId" : 42,
"comment" : "modified bit 0 in R",
"msg" : "313233343030",
"sig" : "5cb94c53101f521f6c1f43b60ea4d7e06fbd49c2e8afaf4fcc289e645e0880a87b8e55858df4cf2291a7303ffda446b82a117b4dd408cff280afc33a525116cc12e0d1c3a1fde6de518a6544f360d0fe18d5be7770b057a2bf792db4b7648fa84a6eaecae909e33fa59c5dfe4804ba2623",
"result" : "invalid",
"flags" : []
},
{
"tcId" : 43,
"comment" : "modified bit 1 in R",
"msg" : "313233343030",
"sig" : "5fb94c53101f521f6c1f43b60ea4d7e06fbd49c2e8afaf4fcc289e645e0880a87b8e55858df4cf2291a7303ffda446b82a117b4dd408cff280f91386c3e9dd9e7c9af7ca6bbef8b7a44ae3d68eeade449d7dfbb31de8419eb943e2ecbcdd06df5227e82b9ded519a56e70f0a1c0fc17b06",
"result" : "invalid",
"flags" : []
},
{
"tcId" : 44,
"comment" : "modified bit 2 in R",
"msg" : "313233343030",
"sig" : "59b94c53101f521f6c1f43b60ea4d7e06fbd49c2e8afaf4fcc289e645e0880a87b8e55858df4cf2291a7303ffda446b82a117b4dd408cff280f1aab07b4ad069dfafc01b4532e1e44cbf7177e1bdda197fc87434046db5b935afd9114ac5e1138eaead23c3b59dba9026d2da4a86fe800b",
"result" : "invalid",
"flags" : []
},
{
"tcId" : 45,
"comment" : "modified bit 7 in R",
"msg" : "313233343030",
"sig" : "ddb94c53101f521f6c1f43b60ea4d7e06fbd49c2e8afaf4fcc289e645e0880a87b8e55858df4cf2291a7303ffda446b82a117b4dd408cff2807668402b7b093fc754019324077c1f842a7d2e35adf7b87094115cec459ad5419e162988ef42b1988d9b944d9d5a7ce09c6f342afa500839",
"result" : "invalid",
"flags" : []
},
{
"tcId" : 46,
"comment" : "modified bit 8 in R",
"msg" : "313233343030",
"sig" : "5db84c53101f521f6c1f43b60ea4d7e06fbd49c2e8afaf4fcc289e645e0880a87b8e55858df4cf2291a7303ffda446b82a117b4dd408cff280279b70338586b9e13e669191cc0dfc2a937d50a6118758de04a4ca41f4877abdb971afa87fe4b83bc243b8dfd2cb368aa389a4cb11e83e31",
"result" : "invalid",
"flags" : []
},
{
"tcId" : 47,
"comment" : "modified bit 16 in R",
"msg" : "313233343030",
"sig" : "5db94d53101f521f6c1f43b60ea4d7e06fbd49c2e8afaf4fcc289e645e0880a87b8e55858df4cf2291a7303ffda446b82a117b4dd408cff280c7b847556b3a6f9447483899ab730a23004c695054dd57b1c3214fa87f632f39c8ff1471f0532b8eee4154930e1ca30d574b8f9e85b0432b",
"result" : "invalid",
"flags" : []
},
{
"tcId" : 48,
"comment" : "modified bit 31 in R",
"msg" : "313233343030",
"sig" : "5db94cd3101f521f6c1f43b60ea4d7e06fbd49c2e8afaf4fcc289e645e0880a87b8e55858df4cf2291a7303ffda446b82a117b4dd408cff2800b017917472b130a1cc1c8e995a252617d5ddaf1f3d48930b4876fa0d2cfedec90a8c85c8274892a1ca3b6cfce63ebfebc307210b844ae0c",
"result" : "invalid",
"flags" : []
},
{
"tcId" : 49,
"comment" : "modified bit 32 in R",
"msg" : "313233343030",
"sig" : "5db94c53111f521f6c1f43b60ea4d7e06fbd49c2e8afaf4fcc289e645e0880a87b8e55858df4cf2291a7303ffda446b82a117b4dd408cff2805f38f6371860fcc4f2ec515afd35cb05d8941e2448cc469a15b8537e758b16d46b123581613462c2bb20d8a07299ab795d0998e1e4277931",
"result" : "invalid",
"flags" : []
},
{
"tcId" : 50,
"comment" : "modified bit 63 in R",
"msg" : "313233343030",
"sig" : "5db94c53101f529f6c1f43b60ea4d7e06fbd49c2e8afaf4fcc289e645e0880a87b8e55858df4cf2291a7303ffda446b82a117b4dd408cff28017111ba6fefd45e2490f1d53a184007fa073470706d7f4a9606fcad2954e74c32116ba7701d225b76e55164e64df3245c1031f0df734bd31",
"result" : "invalid",
"flags" : []
},
{
"tcId" : 51,
"comment" : "modified bit 64 in R",
"msg" : "313233343030",
"sig" : "5db94c53101f521f6d1f43b60ea4d7e06fbd49c2e8afaf4fcc289e645e0880a87b8e55858df4cf2291a7303ffda446b82a117b4dd408cff2808d7d0aa1fd81d0e31789921771c654338f96f0b557b615e3da55670271608a0e022e4e8cf393e309f8f6412281b6147e7fce42b089eb1e0c",
"result" : "invalid",
"flags" : []
},
{
"tcId" : 52,
"comment" : "modified bit 97 in R",
"msg" : "313233343030",
"sig" : "5db94c53101f521f6c1f43b60ca4d7e06fbd49c2e8afaf4fcc289e645e0880a87b8e55858df4cf2291a7303ffda446b82a117b4dd408cff280b08d3be6ebf4e60bf6d74e105ea2fa9b965c62816bbd22ea3bb0c1acfd12300523ca76f94b6f789488a957fbeb212d713baccf95fd594f3d",
"result" : "invalid",
"flags" : []
},
{
"tcId" : 53,
"comment" : "modified bit 127 in R",
"msg" : "313233343030",
"sig" : "5db94c53101f521f6c1f43b60ea4d7606fbd49c2e8afaf4fcc289e645e0880a87b8e55858df4cf2291a7303ffda446b82a117b4dd408cff280a23f54857e9b0f72b2ef90d2768834590464d75933ed08c454faa762b3702a2b631c33c339d05b2e24c20a8214f99af31f93f80f416a1129",
"result" : "invalid",
"flags" : []
},
{
"tcId" : 54,
"comment" : "modified bit 240 in R",
"msg" : "313233343030",
"sig" : "5db94c53101f521f6c1f43b60ea4d7e06fbd49c2e8afaf4fcc289e645e0881a87b8e55858df4cf2291a7303ffda446b82a117b4dd408cff280734bdc399273d3403d934ceaae16e87a68c6bff6b77d8037ff41c97922498a58e704c29ab519d41bab70735f71fc26f589361e2b21754300",
"result" : "invalid",
"flags" : []
},
{
"tcId" : 55,
"comment" : "modified bit 247 in R",
"msg" : "313233343030",
"sig" : "5db94c53101f521f6c1f43b60ea4d7e06fbd49c2e8afaf4fcc289e645e0800a87b8e55858df4cf2291a7303ffda446b82a117b4dd408cff280ba961cc8d0765c99d57470ee1c0c77f0a562a198fd0175eddb0c033e0fb8525328c5e2c516e2b00f73609c7f769195eb1a02ff54090d781f",
"result" : "invalid",
"flags" : []
},
{
"tcId" : 56,
"comment" : "modified bit 248 in R",
"msg" : "313233343030",
"sig" : "5db94c53101f521f6c1f43b60ea4d7e06fbd49c2e8afaf4fcc289e645e0880a97b8e55858df4cf2291a7303ffda446b82a117b4dd408cff280e72685907da9e5a64e4142ed02fc0c6bf95763201db5942aac055fa87e6fdd32e483fd21ed4110d5d7ef619b740fef2ad8a71fe821e42a2a",
"result" : "invalid",
"flags" : []
},
{
"tcId" : 57,
"comment" : "modified bit 253 in R",
"msg" : "313233343030",
"sig" : "5db94c53101f521f6c1f43b60ea4d7e06fbd49c2e8afaf4fcc289e645e0880887b8e55858df4cf2291a7303ffda446b82a117b4dd408cff280500646d67c74f13471f0ad034da530f7238fe7897e532af8ec2977643a410b1d054934df567e170276389e66b3f3ccb3c15aed239d04f72b",
"result" : "invalid",
"flags" : []
},
{
"tcId" : 58,
"comment" : "modified bit 254 in R",
"msg" : "313233343030",
"sig" : "5db94c53101f521f6c1f43b60ea4d7e06fbd49c2e8afaf4fcc289e645e0880e87b8e55858df4cf2291a7303ffda446b82a117b4dd408cff2807bb153b8e350aa736a91c921217578539600c1299ab76522ef8f6902d79c93f274073ee6beafe6200ecaf59f7cd11bb1c833f24bf30ed52d",
"result" : "invalid",
"flags" : []
},
{
"tcId" : 59,
"comment" : "modified bit 255 in R",
"msg" : "313233343030",
"sig" : "5db94c53101f521f6c1f43b60ea4d7e06fbd49c2e8afaf4fcc289e645e0880287b8e55858df4cf2291a7303ffda446b82a117b4dd408cff2804a67b22be599d6433b87ea961c82c457ab50f64ac6b7efb0b2f90988927f83742303c278f8248e02d5679b41ed505aba0fb51110d0def810",
"result" : "invalid",
"flags" : []
},
{
"tcId" : 60,
"comment" : "modified bit 440 in R",
"msg" : "313233343030",
"sig" : "5db94c53101f521f6c1f43b60ea4d7e06fbd49c2e8afaf4fcc289e645e0880a87b8e55858df4cf2291a7303ffda446b82a117b4dd408cff3807f452efb0cd97dab5506028b7b876830dee02a9c0cbd140dcde509638d4d546c30856b2151bdf79930df5bbb11f2beb66bcdc25ad75f2116",
"result" : "invalid",
"flags" : []
},
{
"tcId" : 61,
"comment" : "modified bit 441 in R",
"msg" : "313233343030",
"sig" : "5db94c53101f521f6c1f43b60ea4d7e06fbd49c2e8afaf4fcc289e645e0880a87b8e55858df4cf2291a7303ffda446b82a117b4dd408cff0808d78231bb3c9a87c5b8d168fe05f8197503a3d73a6d700f436b5a76ab866388baa6930191a077aca7970058932c88b7f9e6ecb13c89dcd1d",
"result" : "invalid",
"flags" : []
},
{
"tcId" : 62,
"comment" : "modified bit 447 in R",
"msg" : "313233343030",
"sig" : "5db94c53101f521f6c1f43b60ea4d7e06fbd49c2e8afaf4fcc289e645e0880a87b8e55858df4cf2291a7303ffda446b82a117b4dd408cf72809e5a8406063fb3545f0fb627f841b2e3a85ad5d378018e8b58fe58e14ee5520d57abc9140e9c5a75a8b09ac3334dd0cad69b48771284321d",
"result" : "invalid",
"flags" : []
},
{
"tcId" : 63,
"comment" : "modified bit 448 in R",
"msg" : "313233343030",
"sig" : "5db94c53101f521f6c1f43b60ea4d7e06fbd49c2e8afaf4fcc289e645e0880a87b8e55858df4cf2291a7303ffda446b82a117b4dd408cff2811adf92201088e051ee48b57aecf46edfc68e5baeed5ae4910ba5681d370f75ab593811e18293ef0808581c254196bcbf2b4c454136a6711b",
"result" : "invalid",
"flags" : []
},
{
"tcId" : 64,
"comment" : "modified bit 449 in R",
"msg" : "313233343030",
"sig" : "5db94c53101f521f6c1f43b60ea4d7e06fbd49c2e8afaf4fcc289e645e0880a87b8e55858df4cf2291a7303ffda446b82a117b4dd408cff2825e06c3999e8308be439c40940b0075d3e4f65147c1608cbe6e9c432e33bed6686f9393ae2568f0ad60febcb4b6179c0d90d034e7c3c46810",
"result" : "invalid",
"flags" : []
},
{
"tcId" : 65,
"comment" : "modified bit 454 in R",
"msg" : "313233343030",
"sig" : "5db94c53101f521f6c1f43b60ea4d7e06fbd49c2e8afaf4fcc289e645e0880a87b8e55858df4cf2291a7303ffda446b82a117b4dd408cff2c02456bbd141df048dbf1843be6d5fef402483314c2af547b361a09f3319489eaede43404df9faf634c1298d678b5261c808b0be3726013e39",
"result" : "invalid",
"flags" : []
},
{
"tcId" : 66,
"comment" : "modified bit 455 in R",
"msg" : "313233343030",
"sig" : "5db94c53101f521f6c1f43b60ea4d7e06fbd49c2e8afaf4fcc289e645e0880a87b8e55858df4cf2291a7303ffda446b82a117b4dd408cff2007106d2a896a7fec6dee53eea272d9b6e738c340295416b50f39a9463a5635450b9f93c4c06737affd42ae06cee5879c96c0bd58a91345503",
"result" : "invalid",
"flags" : []
},
{
"tcId" : 67,
"comment" : "R==0",
"msg" : "313233343030",
"sig" : "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000027ab98ab862e4e7ec3361a45ac1993e9b47d9ac40db91faed752399cee0413122b47346594fd7d2c8949b43e4cabaf17d8339ea0e307023f",
"result" : "invalid",
"flags" : []
},
{
"tcId" : 68,
"comment" : "invalid R",
"msg" : "313233343030",
"sig" : "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd11bae33a0999fd3fd2bed6fa5577685e8fd595e79c006e58fd35f69f91b1d853553fb4006019a07725aa37773883dbe12253812887ac828",
"result" : "invalid",
"flags" : []
},
{
"tcId" : 69,
"comment" : "all bits flipped in R",
"msg" : "313233343030",
"sig" : "a246b3acefe0ade093e0bc49f15b281f9042b63d175050b033d7619ba1f77f578471aa7a720b30dd6e58cfc0025bb947d5ee84b22bf7300d7f334e48141af0fade1469f5dedb851c9e725d27bd65012bada05e70cde641aad9ce0bea4983164f73816b6f13095e6b93eb03e850cad0cf0d",
"result" : "invalid",
"flags" : []
},
{
"tcId" : 70,
"comment" : "checking malleability ",
"msg" : "54657374",
"sig" : "5d053ff5b71f6ec3284525d35d77933178c8e19879886d08eccc6c7d27e9e5b5e02537dbc4d4723506e8d171fc1733857573dd02d18f48f280241bd6142ddb02c0f9fa133955d3e610b4b27cb814227de8b241ef4e86402b6ef7862b78a386950246ff38d6d2f458136d12e3c97fdd9866",
"result" : "invalid",
"flags" : [
"SignatureMalleability"
]
},
{
"tcId" : 71,
"comment" : "checking malleability ",
"msg" : "54657374",
"sig" : "5d053ff5b71f6ec3284525d35d77933178c8e19879886d08eccc6c7d27e9e5b5e02537dbc4d4723506e8d171fc1733857573dd02d18f48f28017602ec0bf9d7be34e8ad9c6c795533244e952675efdcbac9c65b9cb85402b6ef7862b78a386950246ff38d6d2f458136d12e3c97fdd98a6",
"result" : "invalid",
"flags" : [
"SignatureMalleability"
]
},
{
"tcId" : 72,
"comment" : "checking malleability ",
"msg" : "54657374",
"sig" : "5d053ff5b71f6ec3284525d35d77933178c8e19879886d08eccc6c7d27e9e5b5e02537dbc4d4723506e8d171fc1733857573dd02d18f48f280fde9de16e5226d2af9a864e2ac1a2d756456ffc4f1b3693570ad4dc584402b6ef7862b78a386950246ff38d6d2f458136d12e3c97fdd9826",
"result" : "invalid",
"flags" : [
"SignatureMalleability"
]
},
{
"tcId" : 73,
"comment" : "checking malleability ",
"msg" : "54657374",
"sig" : "5d053ff5b71f6ec3284525d35d77933178c8e19879886d08eccc6c7d27e9e5b5e02537dbc4d4723506e8d171fc1733857573dd02d18f48f280c9fd3fc42f2d50b84de67a197724e0faa43058801821a546173d76b882402b6ef7862b78a386950246ff38d6d2f458136d12e3c97fdd9826",
"result" : "invalid",
"flags" : [
"SignatureMalleability"
]
},
{
"tcId" : 74,
"comment" : "checking malleability ",
"msg" : "54657374",
"sig" : "5d053ff5b71f6ec3284525d35d77933178c8e19879886d08eccc6c7d27e9e5b5e02537dbc4d4723506e8d171fc1733857573dd02d18f48f28031d67d699a188a9ca46b4eabe2107aef237ca609cb462e24c91d25d286402b6ef7862b78a386950246ff38d6d2f458136d12e3c97fdd9866",
"result" : "invalid",
"flags" : [
"SignatureMalleability"
]
},
{
"tcId" : 75,
"comment" : "checking malleability ",
"msg" : "54657374",
"sig" : "5d053ff5b71f6ec3284525d35d77933178c8e19879886d08eccc6c7d27e9e5b5e02537dbc4d4723506e8d171fc1733857573dd02d18f48f28031d67d699a188a9ca46b4eabe2107aef237ca609cb462e24c91d25d286402b6ef7862b78a386950246ff38d6d2f458136d12e3c97fdd98a6",
"result" : "invalid",
"flags" : [
"SignatureMalleability"
]
},
{
"tcId" : 76,
"comment" : "checking malleability ",
"msg" : "54657374",
"sig" : "5d053ff5b71f6ec3284525d35d77933178c8e19879886d08eccc6c7d27e9e5b5e02537dbc4d4723506e8d171fc1733857573dd02d18f48f28031d67d699a188a9ca46b4eabe2107aef237ca609cb462e24c91d25d286402b6ef7862b78a386950246ff38d6d2f458136d12e3c97fdd9826",
"result" : "invalid",
"flags" : [
"SignatureMalleability"
]
},
{
"tcId" : 77,
"comment" : "checking malleability ",
"msg" : "54657374",
"sig" : "5d053ff5b71f6ec3284525d35d77933178c8e19879886d08eccc6c7d27e9e5b5e02537dbc4d4723506e8d171fc1733857573dd02d18f48f28030d67d699a188a9ca46b4eabe2107aef237ca609cb462e24c91d25d285402b6ef7862b78a386950246ff38d6d2f458136d12e3c97fdd9826",
"result" : "invalid",
"flags" : [
"SignatureMalleability"
]
}
]
},
{
"jwk" : {
"crv" : "Ed448",
"d" : "bIKlYsuAjRDWMr6JyFE-v2ySnzTd-oyfY8mWDvbjSKNSjIo_zC8ETjmj_FuUSS-PAy51SaIAmPlb",
"kid" : "none",
"kty" : "OKP",
"x" : "X9dEm1m0Yf0s54fsYWrUah2hNCSFpw4fig6nXYDpZ3jt8SR2m0bHBhvWeD3x5Q9s0foavq_oJWGA"
},
"key" : {
"curve" : "edwards448",
"keySize" : 448,
"pk" : "5fd7449b59b461fd2ce787ec616ad46a1da1342485a70e1f8a0ea75d80e96778edf124769b46c7061bd6783df1e50f6cd1fa1abeafe8256180",
"sk" : "6c82a562cb808d10d632be89c8513ebf6c929f34ddfa8c9f63c9960ef6e348a3528c8a3fcc2f044e39a3fc5b94492f8f032e7549a20098f95b",
"type" : "EDDSAKeyPair"
},
"keyDer" : "3043300506032b6571033a005fd7449b59b461fd2ce787ec616ad46a1da1342485a70e1f8a0ea75d80e96778edf124769b46c7061bd6783df1e50f6cd1fa1abeafe8256180",
"keyPem" : "-----BEGIN PUBLIC KEY-----\nMEMwBQYDK2VxAzoAX9dEm1m0Yf0s54fsYWrUah2hNCSFpw4fig6nXYDpZ3jt8SR2m0bHBhvWeD3x5Q9s0foavq/oJWGA\n-----END PUBLIC KEY-----\n",
"type" : "EddsaVerify",
"tests" : [
{
"tcId" : 78,
"comment" : "RFC 8032",
"msg" : "",
"sig" : "533a37f6bbe457251f023c0d88f976ae2dfb504a843e34d2074fd823d41a591f2b233f034f628281f2fd7a22ddd47d7828c59bd0a21bfd3980ff0d2028d4b18a9df63e006c5d1c2d345b925d8dc00b4104852db99ac5c7cdda8530a113a0f4dbb61149f05a7363268c71d95808ff2e652600",
"result" : "valid",
"flags" : []
}
]
},
{
"jwk" : {
"crv" : "Ed448",
"d" : "xOqwXTVwB8Yy89u0hImSTVUrCP4MNToNSh8ArNosRjr76mfF6NKHfF47w5emWZSe-AIelU4KEidO",
"kid" : "none",
"kty" : "OKP",
"x" : "Q7oo9DDN_0Vq5TFUX37NCsg0pV2TWMA3K_oMbGeYwIZq6gHrAHQoArhDjqTLghacI1FgYntMOpSA"
},
"key" : {
"curve" : "edwards448",
"keySize" : 448,
"pk" : "43ba28f430cdff456ae531545f7ecd0ac834a55d9358c0372bfa0c6c6798c0866aea01eb00742802b8438ea4cb82169c235160627b4c3a9480",
"sk" : "c4eab05d357007c632f3dbb48489924d552b08fe0c353a0d4a1f00acda2c463afbea67c5e8d2877c5e3bc397a659949ef8021e954e0a12274e",
"type" : "EDDSAKeyPair"
},
"keyDer" : "3043300506032b6571033a0043ba28f430cdff456ae531545f7ecd0ac834a55d9358c0372bfa0c6c6798c0866aea01eb00742802b8438ea4cb82169c235160627b4c3a9480",
"keyPem" : "-----BEGIN PUBLIC KEY-----\nMEMwBQYDK2VxAzoAQ7oo9DDN/0Vq5TFUX37NCsg0pV2TWMA3K/oMbGeYwIZq6gHrAHQoArhDjqTLghacI1FgYntMOpSA\n-----END PUBLIC KEY-----\n",
"type" : "EddsaVerify",
"tests" : [
{
"tcId" : 79,
"comment" : "RFC 8032: 1 octet",
"msg" : "03",
"sig" : "26b8f91727bd62897af15e41eb43c377efb9c610d48f2335cb0bd0087810f4352541b143c4b981b7e18f62de8ccdf633fc1bf037ab7cd779805e0dbcc0aae1cbcee1afb2e027df36bc04dcecbf154336c19f0af7e0a6472905e799f1953d2a0ff3348ab21aa4adafd1d234441cf807c03a00",
"result" : "valid",
"flags" : []
},
{
"tcId" : 80,
"comment" : "RFC 8032: 1 octet with context",
"msg" : "03",
"sig" : "d4f8f6131770dd46f40867d6fd5d5055de43541f8c5e35abbcd001b32a89f7d2151f7647f11d8ca2ae279fb842d607217fce6e042f6815ea000c85741de5c8da1144a6a1aba7f96de42505d7a7298524fda538fccbbb754f578c1cad10d54d0d5428407e85dcbc98a49155c13764e66c3c00",
"result" : "invalid",
"flags" : []
}
]
},
{
"jwk" : {
"crv" : "Ed448",
"d" : "zSPST3FCdOdENDI3uTKQ9RH2Ql-Y5kRZ_yA-iYUIP_32BQBVOrwOBc0CGEvbicTM1n4YeVEmfrMo",
"kid" : "none",
"kty" : "OKP",
"x" : "3OqeePNaG_NJmoMbELhskKrAHNhLZ6AQm1WjbpMoseNl_OFh1xznExpUPqTLX36fHYsAaWRHABQA"
},
"key" : {
"curve" : "edwards448",
"keySize" : 448,
"pk" : "dcea9e78f35a1bf3499a831b10b86c90aac01cd84b67a0109b55a36e9328b1e365fce161d71ce7131a543ea4cb5f7e9f1d8b00696447001400",
"sk" : "cd23d24f714274e744343237b93290f511f6425f98e64459ff203e8985083ffdf60500553abc0e05cd02184bdb89c4ccd67e187951267eb328",
"type" : "EDDSAKeyPair"
},
"keyDer" : "3043300506032b6571033a00dcea9e78f35a1bf3499a831b10b86c90aac01cd84b67a0109b55a36e9328b1e365fce161d71ce7131a543ea4cb5f7e9f1d8b00696447001400",
"keyPem" : "-----BEGIN PUBLIC KEY-----\nMEMwBQYDK2VxAzoA3OqeePNaG/NJmoMbELhskKrAHNhLZ6AQm1WjbpMoseNl/OFh1xznExpUPqTLX36fHYsAaWRHABQA\n-----END PUBLIC KEY-----\n",
"type" : "EddsaVerify",
"tests" : [
{
"tcId" : 81,
"comment" : "RFC 8032: 11 bytes",
"msg" : "0c3e544074ec63b0265e0c",
"sig" : "1f0a8888ce25e8d458a21130879b840a9089d999aaba039eaf3e3afa090a09d389dba82c4ff2ae8ac5cdfb7c55e94d5d961a29fe0109941e00b8dbdeea6d3b051068df7254c0cdc129cbe62db2dc957dbb47b51fd3f213fb8698f064774250a5028961c9bf8ffd973fe5d5c206492b140e00",
"result" : "valid",
"flags" : []
}
]
},
{
"jwk" : {
"crv" : "Ed448",
"d" : "JYzdStoy7Zyf9U5jdWrlgvuPqyrHIfLI5nanJ2hRPZOfY93bVWCRM_Ka34bsmSncy1LBxf0v9-Ib",
"kid" : "none",
"kty" : "OKP",
"x" : "O6FtoMbyzB8wGHdAdW9eeY1rxfwBXXxjzJUQ7j_UStwk2OlotuRub5TRm5RTYXJr114UnvCYF_WA"
},
"key" : {
"curve" : "edwards448",
"keySize" : 448,
"pk" : "3ba16da0c6f2cc1f30187740756f5e798d6bc5fc015d7c63cc9510ee3fd44adc24d8e968b6e46e6f94d19b945361726bd75e149ef09817f580",
"sk" : "258cdd4ada32ed9c9ff54e63756ae582fb8fab2ac721f2c8e676a72768513d939f63dddb55609133f29adf86ec9929dccb52c1c5fd2ff7e21b",
"type" : "EDDSAKeyPair"
},
"keyDer" : "3043300506032b6571033a003ba16da0c6f2cc1f30187740756f5e798d6bc5fc015d7c63cc9510ee3fd44adc24d8e968b6e46e6f94d19b945361726bd75e149ef09817f580",
"keyPem" : "-----BEGIN PUBLIC KEY-----\nMEMwBQYDK2VxAzoAO6FtoMbyzB8wGHdAdW9eeY1rxfwBXXxjzJUQ7j/UStwk2OlotuRub5TRm5RTYXJr114UnvCYF/WA\n-----END PUBLIC KEY-----\n",
"type" : "EddsaVerify",
"tests" : [
{
"tcId" : 82,
"comment" : "RFC 8032: 12 bytes",
"msg" : "64a65f3cdedcdd66811e2915",
"sig" : "7eeeab7c4e50fb799b418ee5e3197ff6bf15d43a14c34389b59dd1a7b1b85b4ae90438aca634bea45e3a2695f1270f07fdcdf7c62b8efeaf00b45c2c96ba457eb1a8bf075a3db28e5c24f6b923ed4ad747c3c9e03c7079efb87cb110d3a99861e72003cbae6d6b8b827e4e6c143064ff3c00",
"result" : "valid",
"flags" : []
}
]
},
{
"jwk" : {
"crv" : "Ed448",
"d" : "fvToRUQjZ1L7tWuPMaI6EOQoFPX1XKA3zcwRxkyaOylJwbtgcAMUYRcypsL-qY7rwCZqEak5cBAO",
"kid" : "none",
"kty" : "OKP",
"x" : "s9oHmwqkk6V3ICnwRnuuvuWoES2dOiJTI2HaKU97s4FcXcWeF2tNnzgcoJOOE8bAexdL5l36V46A"
},
"key" : {
"curve" : "edwards448",
"keySize" : 448,
"pk" : "b3da079b0aa493a5772029f0467baebee5a8112d9d3a22532361da294f7bb3815c5dc59e176b4d9f381ca0938e13c6c07b174be65dfa578e80",
"sk" : "7ef4e84544236752fbb56b8f31a23a10e42814f5f55ca037cdcc11c64c9a3b2949c1bb60700314611732a6c2fea98eebc0266a11a93970100e",
"type" : "EDDSAKeyPair"
},
"keyDer" : "3043300506032b6571033a00b3da079b0aa493a5772029f0467baebee5a8112d9d3a22532361da294f7bb3815c5dc59e176b4d9f381ca0938e13c6c07b174be65dfa578e80",
"keyPem" : "-----BEGIN PUBLIC KEY-----\nMEMwBQYDK2VxAzoAs9oHmwqkk6V3ICnwRnuuvuWoES2dOiJTI2HaKU97s4FcXcWeF2tNnzgcoJOOE8bAexdL5l36V46A\n-----END PUBLIC KEY-----\n",
"type" : "EddsaVerify",
"tests" : [
{
"tcId" : 83,
"comment" : "RFC 8032: 13 bytes",
"msg" : "64a65f3cdedcdd66811e2915e7",
"sig" : "6a12066f55331b6c22acd5d5bfc5d71228fbda80ae8dec26bdd306743c5027cb4890810c162c027468675ecf645a83176c0d7323a2ccde2d80efe5a1268e8aca1d6fbc194d3f77c44986eb4ab4177919ad8bec33eb47bbb5fc6e28196fd1caf56b4e7e0ba5519234d047155ac727a1053100",
"result" : "valid",
"flags" : []
}
]
},
{
"jwk" : {
"crv" : "Ed448",
"d" : "1l3zQa0T4AhWdoi67dqOnc3BfcAkl06ltCJ7ZTDjOb_yH5nmjKaWjzzKbf4PufT6tPoTXVVC6j8B",
"kid" : "none",
"kty" : "OKP",
"x" : "35cF9Y7bq4Asf4Njz-VWCrHGEywgqfHdFjSDom-KxTo51oCL9KHfvSYbCZuwOz-1CQbLKL2KCB8A"
},
"key" : {
"curve" : "edwards448",
"keySize" : 448,
"pk" : "df9705f58edbab802c7f8363cfe5560ab1c6132c20a9f1dd163483a26f8ac53a39d6808bf4a1dfbd261b099bb03b3fb50906cb28bd8a081f00",
"sk" : "d65df341ad13e008567688baedda8e9dcdc17dc024974ea5b4227b6530e339bff21f99e68ca6968f3cca6dfe0fb9f4fab4fa135d5542ea3f01",
"type" : "EDDSAKeyPair"
},
"keyDer" : "3043300506032b6571033a00df9705f58edbab802c7f8363cfe5560ab1c6132c20a9f1dd163483a26f8ac53a39d6808bf4a1dfbd261b099bb03b3fb50906cb28bd8a081f00",
"keyPem" : "-----BEGIN PUBLIC KEY-----\nMEMwBQYDK2VxAzoA35cF9Y7bq4Asf4Njz+VWCrHGEywgqfHdFjSDom+KxTo51oCL9KHfvSYbCZuwOz+1CQbLKL2KCB8A\n-----END PUBLIC KEY-----\n",
"type" : "EddsaVerify",
"tests" : [
{
"tcId" : 84,
"comment" : "RFC 8032: 64 bytes",
"msg" : "bd0f6a3747cd561bdddf4640a332461a4a30a12a434cd0bf40d766d9c6d458e5512204a30c17d1f50b5079631f64eb3112182da3005835461113718d1a5ef944",
"sig" : "554bc2480860b49eab8532d2a533b7d578ef473eeb58c98bb2d0e1ce488a98b18dfde9b9b90775e67f47d4a1c3482058efc9f40d2ca033a0801b63d45b3b722ef552bad3b4ccb667da350192b61c508cf7b6b5adadc2c8d9a446ef003fb05cba5f30e88e36ec2703b349ca229c2670833900",
"result" : "valid",
"flags" : []
}
]
},
{
"jwk" : {
"crv" : "Ed448",
"d" : "LsX-PBcEWr2xNqXmqRPjKrda5otT0vwUm3flBBMtN1abfnZrp0oZvWFiNDohyFkKqc68qQFMY231",
"kid" : "none",
"kty" : "OKP",
"x" : "eXVvAU3P4gefXdnnGL5BceLvJIagjyUYb2v_Q6mTa5v-EkArCK5leYo9geIunsgOdpCGLvPU7ToA"
},
"key" : {
"curve" : "edwards448",
"keySize" : 448,
"pk" : "79756f014dcfe2079f5dd9e718be4171e2ef2486a08f25186f6bff43a9936b9bfe12402b08ae65798a3d81e22e9ec80e7690862ef3d4ed3a00",
"sk" : "2ec5fe3c17045abdb136a5e6a913e32ab75ae68b53d2fc149b77e504132d37569b7e766ba74a19bd6162343a21c8590aa9cebca9014c636df5",
"type" : "EDDSAKeyPair"
},
"keyDer" : "3043300506032b6571033a0079756f014dcfe2079f5dd9e718be4171e2ef2486a08f25186f6bff43a9936b9bfe12402b08ae65798a3d81e22e9ec80e7690862ef3d4ed3a00",
"keyPem" : "-----BEGIN PUBLIC KEY-----\nMEMwBQYDK2VxAzoAeXVvAU3P4gefXdnnGL5BceLvJIagjyUYb2v/Q6mTa5v+EkArCK5leYo9geIunsgOdpCGLvPU7ToA\n-----END PUBLIC KEY-----\n",
"type" : "EddsaVerify",
"tests" : [
{
"tcId" : 85,
"comment" : "RFC 8032: 256 bytes",
"msg" : "15777532b0bdd0d1389f636c5f6b9ba734c90af572877e2d272dd078aa1e567cfa80e12928bb542330e8409f3174504107ecd5efac61ae7504dabe2a602ede89e5cca6257a7c77e27a702b3ae39fc769fc54f2395ae6a1178cab4738e543072fc1c177fe71e92e25bf03e4ecb72f47b64d0465aaea4c7fad372536c8ba516a6039c3c2a39f0e4d832be432dfa9a706a6e5c7e19f397964ca4258002f7c0541b590316dbc5622b6b2a6fe7a4abffd96105eca76ea7b98816af0748c10df048ce012d901015a51f189f3888145c03650aa23ce894c3bd889e030d565071c59f409a9981b51878fd6fc110624dcbcde0bf7a69ccce38fabdf86f3bef6044819de11",
"sig" : "c650ddbb0601c19ca11439e1640dd931f43c518ea5bea70d3dcde5f4191fe53f00cf966546b72bcc7d58be2b9badef28743954e3a44a23f880e8d4f1cfce2d7a61452d26da05896f0a50da66a239a8a188b6d825b3305ad77b73fbac0836ecc60987fd08527c1a8e80d5823e65cafe2a3d00",
"result" : "valid",
"flags" : []
}
]
},
{
"jwk" : {
"crv" : "Ed448",
"d" : "hy0JN4D103MN98ISZks3uKDyT1aBDaqDgs1Po_d2NOxE3FTxwu2b6ob6-3Yy2L4ZnqFl9a1V3Zzo",
"kid" : "none",
"kty" : "OKP",
"x" : "qBsuinClrJT_28ybrfw_6wgB8lhXi7EUrUTs4ewOeZ2gjv-4HF1oXAxW9k7srvjN8RzDhzeDjPQA"
},
"key" : {
"curve" : "edwards448",
"keySize" : 448,
"pk" : "a81b2e8a70a5ac94ffdbcc9badfc3feb0801f258578bb114ad44ece1ec0e799da08effb81c5d685c0c56f64eecaef8cdf11cc38737838cf400",
"sk" : "872d093780f5d3730df7c212664b37b8a0f24f56810daa8382cd4fa3f77634ec44dc54f1c2ed9bea86fafb7632d8be199ea165f5ad55dd9ce8",
"type" : "EDDSAKeyPair"
},
"keyDer" : "3043300506032b6571033a00a81b2e8a70a5ac94ffdbcc9badfc3feb0801f258578bb114ad44ece1ec0e799da08effb81c5d685c0c56f64eecaef8cdf11cc38737838cf400",
"keyPem" : "-----BEGIN PUBLIC KEY-----\nMEMwBQYDK2VxAzoAqBsuinClrJT/28ybrfw/6wgB8lhXi7EUrUTs4ewOeZ2gjv+4HF1oXAxW9k7srvjN8RzDhzeDjPQA\n-----END PUBLIC KEY-----\n",
"type" : "EddsaVerify",
"tests" : [
{
"tcId" : 86,
"comment" : "RFC 8032: 1023 bytes",
"msg" : "6ddf802e1aae4986935f7f981ba3f0351d6273c0a0c22c9c0e8339168e675412a3debfaf435ed651558007db4384b650fcc07e3b586a27a4f7a00ac8a6fec2cd86ae4bf1570c41e6a40c931db27b2faa15a8cedd52cff7362c4e6e23daec0fbc3a79b6806e316efcc7b68119bf46bc76a26067a53f296dafdbdc11c77f7777e972660cf4b6a9b369a6665f02e0cc9b6edfad136b4fabe723d2813db3136cfde9b6d044322fee2947952e031b73ab5c603349b307bdc27bc6cb8b8bbd7bd323219b8033a581b59eadebb09b3c4f3d2277d4f0343624acc817804728b25ab797172b4c5c21a22f9c7839d64300232eb66e53f31c723fa37fe387c7d3e50bdf9813a30e5bb12cf4cd930c40cfb4e1fc622592a49588794494d56d24ea4b40c89fc0596cc9ebb961c8cb10adde976a5d602b1c3f85b9b9a001ed3c6a4d3b1437f52096cd1956d042a597d561a596ecd3d1735a8d570ea0ec27225a2c4aaff26306d1526c1af3ca6d9cf5a2c98f47e1c46db9a33234cfd4d81f2c98538a09ebe76998d0d8fd25997c7d255c6d66ece6fa56f11144950f027795e653008f4bd7ca2dee85d8e90f3dc315130ce2a00375a318c7c3d97be2c8ce5b6db41a6254ff264fa6155baee3b0773c0f497c573f19bb4f4240281f0b1f4f7be857a4e59d416c06b4c50fa09e1810ddc6b1467baeac5a3668d11b6ecaa901440016f389f80acc4db977025e7f5924388c7e340a732e554440e76570f8dd71b7d640b3450d1fd5f0410a18f9a3494f707c717b79b4bf75c98400b096b21653b5d217cf3565c9597456f70703497a078763829bc01bb1cbc8fa04eadc9a6e3f6699587a9e75c94e5bab0036e0b2e711392cff0047d0d6b05bd2a588bc109718954259f1d86678a579a3120f19cfb2963f177aeb70f2d4844826262e51b80271272068ef5b3856fa8535aa2a88b2d41f2a0e2fda7624c2850272ac4a2f561f8f2f7a318bfd5caf9696149e4ac824ad3460538fdc25421beec2cc6818162d06bbed0c40a387192349db67a118bada6cd5ab0140ee273204f628aad1c135f770279a651e24d8c14d75a6059d76b96a6fd857def5e0b354b27ab937a5815d16b5fae407ff18222c6d1ed263be68c95f32d908bd895cd76207ae726487567f9a67dad79abec316f683b17f2d02bf07e0ac8b5bc6162cf94697b3c27cd1fea49b27f23ba2901871962506520c392da8b6ad0d99f7013fbc06c2c17a569500c8a7696481c1cd33e9b14e40b82e79a5f5db82571ba97bae3ad3e0479515bb0e2b0f3bfcd1fd33034efc6245eddd7ee2086ddae2600d8ca73e214e8c2b0bdb2b047c6a464a562ed77b73d2d841c4b34973551257713b753632efba348169abc90a68f42611a40126d7cb21b58695568186f7e569d2ff0f9e745d0487dd2eb997cafc5abf9dd102e62ff66cba87",
"sig" : "e301345a41a39a4d72fff8df69c98075a0cc082b802fc9b2b6bc503f926b65bddf7f4c8f1cb49f6396afc8a70abe6d8aef0db478d4c6b2970076c6a0484fe76d76b3a97625d79f1ce240e7c576750d295528286f719b413de9ada3e8eb78ed573603ce30d8bb761785dc30dbc320869e1a00",
"result" : "valid",
"flags" : []
}
]
}
]
}

View File

@@ -1,107 +0,0 @@
[
{
"curve": "P192",
"q": "FFFFFFFFFFFFFFFFFFFFFFFF99DEF836146BC9B1B4D22831",
"private": "6FAB034934E4C0FC9AE67F5B5659A9D7D1FEFD187EE09FD4",
"Ux": "AC2C77F529F91689FEA0EA5EFEC7F210D8EEA0B9E047ED56",
"Uy": "3BC723E57670BD4887EBC732C523063D0A7C957BC97C1C43",
"cases": [
{
"k": "32B1B6D7D42A05CB449065727A84804FB1A3E34D8F261496",
"message": "sample",
"r": "4B0B8CE98A92866A2820E20AA6B75B56382E0F9BFD5ECB55",
"s": "CCDB006926EA9565CBADC840829D8C384E06DE1F1E381B85"
},
{
"k": "5C4CE89CF56D9E7C77C8585339B006B97B5F0680B4306C6C",
"message": "test",
"r": "3A718BD8B4926C3B52EE6BBE67EF79B18CB6EB62B1AD97AE",
"s": "5662E6848A4A19B1F1AE2F72ACD4B8BBE50F1EAC65D9124F"
}
]
},
{
"curve": "P224",
"q": "FFFFFFFFFFFFFFFFFFFFFFFFFFFF16A2E0B8F03E13DD29455C5C2A3D",
"private": "F220266E1105BFE3083E03EC7A3A654651F45E37167E88600BF257C1",
"Ux": "00CF08DA5AD719E42707FA431292DEA11244D64FC51610D94B130D6C",
"Uy": "EEAB6F3DEBE455E3DBF85416F7030CBD94F34F2D6F232C69F3C1385A",
"cases": [
{
"k": "C1D1F2F10881088301880506805FEB4825FE09ACB6816C36991AA06D",
"message": "sample",
"r": "1CDFE6662DDE1E4A1EC4CDEDF6A1F5A2FB7FBD9145C12113E6ABFD3E",
"s": "A6694FD7718A21053F225D3F46197CA699D45006C06F871808F43EBC"
},
{
"k": "DF8B38D40DCA3E077D0AC520BF56B6D565134D9B5F2EAE0D34900524",
"message": "test",
"r": "C441CE8E261DED634E4CF84910E4C5D1D22C5CF3B732BB204DBEF019",
"s": "902F42847A63BDC5F6046ADA114953120F99442D76510150F372A3F4"
}
]
},
{
"curve": "P256",
"q": "FFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551",
"private": "C9AFA9D845BA75166B5C215767B1D6934E50C3DB36E89B127B8A622B120F6721",
"Ux": "60FED4BA255A9D31C961EB74C6356D68C049B8923B61FA6CE669622E60F29FB6",
"Uy": "7903FE1008B8BC99A41AE9E95628BC64F2F1B20C2D7E9F5177A3C294D4462299",
"cases": [
{
"k": "A6E3C57DD01ABE90086538398355DD4C3B17AA873382B0F24D6129493D8AAD60",
"message": "sample",
"r": "EFD48B2AACB6A8FD1140DD9CD45E81D69D2C877B56AAF991C34D0EA84EAF3716",
"s": "F7CB1C942D657C41D436C7A1B6E29F65F3E900DBB9AFF4064DC4AB2F843ACDA8"
},
{
"k": "D16B6AE827F17175E040871A1C7EC3500192C4C92677336EC2537ACAEE0008E0",
"message": "test",
"r": "F1ABB023518351CD71D881567B1EA663ED3EFCF6C5132B354F28D3B0B7D38367",
"s": "019F4113742A2B14BD25926B49C649155F267E60D3814B4C0CC84250E46F0083"
}
]
},
{
"curve": "P384",
"q": "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC7634D81F4372DDF581A0DB248B0A77AECEC196ACCC52973",
"private": "6B9D3DAD2E1B8C1C05B19875B6659F4DE23C3B667BF297BA9AA47740787137D896D5724E4C70A825F872C9EA60D2EDF5",
"Ux": "EC3A4E415B4E19A4568618029F427FA5DA9A8BC4AE92E02E06AAE5286B300C64DEF8F0EA9055866064A254515480BC13",
"Uy": "8015D9B72D7D57244EA8EF9AC0C621896708A59367F9DFB9F54CA84B3F1C9DB1288B231C3AE0D4FE7344FD2533264720",
"cases": [
{
"k": "94ED910D1A099DAD3254E9242AE85ABDE4BA15168EAF0CA87A555FD56D10FBCA2907E3E83BA95368623B8C4686915CF9",
"message": "sample",
"r": "94EDBB92A5ECB8AAD4736E56C691916B3F88140666CE9FA73D64C4EA95AD133C81A648152E44ACF96E36DD1E80FABE46",
"s": "99EF4AEB15F178CEA1FE40DB2603138F130E740A19624526203B6351D0A3A94FA329C145786E679E7B82C71A38628AC8"
},
{
"k": "015EE46A5BF88773ED9123A5AB0807962D193719503C527B031B4C2D225092ADA71F4A459BC0DA98ADB95837DB8312EA",
"message": "test",
"r": "8203B63D3C853E8D77227FB377BCF7B7B772E97892A80F36AB775D509D7A5FEB0542A7F0812998DA8F1DD3CA3CF023DB",
"s": "DDD0760448D42D8A43AF45AF836FCE4DE8BE06B485E9B61B827C2F13173923E06A739F040649A667BF3B828246BAA5A5"
}
]
},
{
"curve": "P521",
"q": "1FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA51868783BF2F966B7FCC0148F709A5D03BB5C9B8899C47AEBB6FB71E91386409",
"private": "0FAD06DAA62BA3B25D2FB40133DA757205DE67F5BB0018FEE8C86E1B68C7E75CAA896EB32F1F47C70855836A6D16FCC1466F6D8FBEC67DB89EC0C08B0E996B83538",
"Ux": "1894550D0785932E00EAA23B694F213F8C3121F86DC97A04E5A7167DB4E5BCD371123D46E45DB6B5D5370A7F20FB633155D38FFA16D2BD761DCAC474B9A2F5023A4",
"Uy": "0493101C962CD4D2FDDF782285E64584139C2F91B47F87FF82354D6630F746A28A0DB25741B5B34A828008B22ACC23F924FAAFBD4D33F81EA66956DFEAA2BFDFCF5",
"cases": [
{
"k": "1DAE2EA071F8110DC26882D4D5EAE0621A3256FC8847FB9022E2B7D28E6F10198B1574FDD03A9053C08A1854A168AA5A57470EC97DD5CE090124EF52A2F7ECBFFD3",
"message": "sample",
"r": "0C328FAFCBD79DD77850370C46325D987CB525569FB63C5D3BC53950E6D4C5F174E25A1EE9017B5D450606ADD152B534931D7D4E8455CC91F9B15BF05EC36E377FA",
"s": "0617CCE7CF5064806C467F678D3B4080D6F1CC50AF26CA209417308281B68AF282623EAA63E5B5C0723D8B8C37FF0777B1A20F8CCB1DCCC43997F1EE0E44DA4A67A"
},
{
"k": "16200813020EC986863BEDFC1B121F605C1215645018AEA1A7B215A564DE9EB1B38A67AA1128B80CE391C4FB71187654AAA3431027BFC7F395766CA988C964DC56D",
"message": "test",
"r": "13E99020ABF5CEE7525D16B69B229652AB6BDF2AFFCAEF38773B4B7D08725F10CDB93482FDCC54EDCEE91ECA4166B2A7C6265EF0CE2BD7051B7CEF945BABD47EE6D",
"s": "1FBD0013C674AA79CB39849527916CE301C66EA7CE8B80682786AD60F98F7E78A19CA69EFF5C57400E3B3A0AD66CE0978214D13BAF4E9AC60752F7B155E2DE4DCE3"
}
]
}
]

View File

@@ -5,15 +5,15 @@ import { bytesToHex } from '@noble/hashes/utils';
import { sha256 } from '@noble/hashes/sha256'; import { sha256 } from '@noble/hashes/sha256';
import { sha512 } from '@noble/hashes/sha512'; import { sha512 } from '@noble/hashes/sha512';
import { shake128, shake256 } from '@noble/hashes/sha3'; import { shake128, shake256 } from '@noble/hashes/sha3';
import * as secp256r1 from '../lib/esm/p256.js'; import * as secp256r1 from '../esm/p256.js';
import * as secp384r1 from '../lib/esm/p384.js'; import * as secp384r1 from '../esm/p384.js';
import * as secp521r1 from '../lib/esm/p521.js'; import * as secp521r1 from '../esm/p521.js';
import * as ed25519 from '../lib/esm/ed25519.js'; import * as ed25519 from '../esm/ed25519.js';
import * as ed448 from '../lib/esm/ed448.js'; import * as ed448 from '../esm/ed448.js';
import * as secp256k1 from '../lib/esm/secp256k1.js'; import * as secp256k1 from '../esm/secp256k1.js';
import { bls12_381 } from '../lib/esm/bls12-381.js'; import { bls12_381 } from '../esm/bls12-381.js';
import { expand_message_xmd, expand_message_xof } from '../lib/esm/abstract/hash-to-curve.js'; import { expand_message_xmd, expand_message_xof } from '../esm/abstract/hash-to-curve.js';
import { utf8ToBytes } from '../lib/esm/abstract/utils.js'; import { utf8ToBytes } from '../esm/abstract/utils.js';
// XMD // XMD
import { default as xmd_sha256_38 } from './hash-to-curve/expand_message_xmd_SHA256_38.json' assert { type: 'json' }; import { default as xmd_sha256_38 } from './hash-to-curve/expand_message_xmd_SHA256_38.json' assert { type: 'json' };
import { default as xmd_sha256_256 } from './hash-to-curve/expand_message_xmd_SHA256_256.json' assert { type: 'json' }; import { default as xmd_sha256_256 } from './hash-to-curve/expand_message_xmd_SHA256_256.json' assert { type: 'json' };
@@ -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}`, () => {

View File

@@ -4,12 +4,15 @@ 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 './stark/index.test.js'; import './secp256k1-bip-0324.test.js';
import './jubjub.test.js'; import './jubjub.test.js';
import './bls12-381.test.js';
import './hash-to-curve.test.js'; import './hash-to-curve.test.js';
import './poseidon.test.js';
import './bls12-381.test.js';
should.run(); should.run();

View File

@@ -1,4 +1,4 @@
import { jubjub, findGroupHash } from '../lib/esm/jubjub.js'; import { jubjub, findGroupHash } from '../esm/jubjub.js';
import { describe, should } from 'micro-should'; import { describe, should } from 'micro-should';
import { deepStrictEqual, throws } from 'assert'; import { deepStrictEqual, throws } from 'assert';
const Point = jubjub.ExtendedPoint; const Point = jubjub.ExtendedPoint;

View File

@@ -1,15 +1,16 @@
import { deepStrictEqual, throws } from 'assert'; import { deepStrictEqual, throws } from 'assert';
import { describe, should } from 'micro-should'; import { describe, should } from 'micro-should';
import { secp192r1, P192 } from '../lib/esm/p192.js'; import { secp192r1, secp224r1, p192, p224 } from './_more-curves.helpers.js';
import { secp224r1, P224 } from '../lib/esm/p224.js'; import { DER } from '../esm/abstract/weierstrass.js';
import { secp256r1, P256 } from '../lib/esm/p256.js'; import { secp256r1, p256 } from '../esm/p256.js';
import { secp384r1, P384 } from '../lib/esm/p384.js'; import { secp384r1, p384 } from '../esm/p384.js';
import { secp521r1, P521 } from '../lib/esm/p521.js'; import { secp521r1, p521 } from '../esm/p521.js';
import { secp256k1 } from '../lib/esm/secp256k1.js'; import { secp256k1 } from '../esm/secp256k1.js';
import { hexToBytes, bytesToHex } from '../lib/esm/abstract/utils.js'; 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 './fixtures/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' };
@@ -23,38 +24,59 @@ import { default as secp224r1_sha3_224_test } from './wycheproof/ecdsa_secp224r1
import { default as secp224r1_sha3_256_test } from './wycheproof/ecdsa_secp224r1_sha3_256_test.json' assert { type: 'json' }; import { default as secp224r1_sha3_256_test } from './wycheproof/ecdsa_secp224r1_sha3_256_test.json' assert { type: 'json' };
import { default as secp224r1_sha3_512_test } from './wycheproof/ecdsa_secp224r1_sha3_512_test.json' assert { type: 'json' }; import { default as secp224r1_sha3_512_test } from './wycheproof/ecdsa_secp224r1_sha3_512_test.json' assert { type: 'json' };
import { default as secp224r1_sha512_test } from './wycheproof/ecdsa_secp224r1_sha512_test.json' assert { type: 'json' }; import { default as secp224r1_sha512_test } from './wycheproof/ecdsa_secp224r1_sha512_test.json' assert { type: 'json' };
import { default as secp224r1_shake128_test } from './wycheproof/ecdsa_secp224r1_shake128_test.json' assert { type: 'json' };
import { default as secp256k1_sha256_bitcoin_test } from './wycheproof/ecdsa_secp256k1_sha256_bitcoin_test.json' assert { type: 'json' };
import { default as secp256k1_sha256_test } from './wycheproof/ecdsa_secp256k1_sha256_test.json' assert { type: 'json' }; import { default as secp256k1_sha256_test } from './wycheproof/ecdsa_secp256k1_sha256_test.json' assert { type: 'json' };
import { default as secp256k1_sha3_256_test } from './wycheproof/ecdsa_secp256k1_sha3_256_test.json' assert { type: 'json' }; import { default as secp256k1_sha3_256_test } from './wycheproof/ecdsa_secp256k1_sha3_256_test.json' assert { type: 'json' };
import { default as secp256k1_sha3_512_test } from './wycheproof/ecdsa_secp256k1_sha3_512_test.json' assert { type: 'json' }; import { default as secp256k1_sha3_512_test } from './wycheproof/ecdsa_secp256k1_sha3_512_test.json' assert { type: 'json' };
import { default as secp256k1_sha512_test } from './wycheproof/ecdsa_secp256k1_sha512_test.json' assert { type: 'json' }; import { default as secp256k1_sha512_test } from './wycheproof/ecdsa_secp256k1_sha512_test.json' assert { type: 'json' };
import { default as secp256k1_shake128_test } from './wycheproof/ecdsa_secp256k1_shake128_test.json' assert { type: 'json' };
import { default as secp256k1_shake256_test } from './wycheproof/ecdsa_secp256k1_shake256_test.json' assert { type: 'json' };
import { default as secp256r1_sha256_test } from './wycheproof/ecdsa_secp256r1_sha256_test.json' assert { type: 'json' }; import { default as secp256r1_sha256_test } from './wycheproof/ecdsa_secp256r1_sha256_test.json' assert { type: 'json' };
import { default as secp256r1_sha3_256_test } from './wycheproof/ecdsa_secp256r1_sha3_256_test.json' assert { type: 'json' }; import { default as secp256r1_sha3_256_test } from './wycheproof/ecdsa_secp256r1_sha3_256_test.json' assert { type: 'json' };
import { default as secp256r1_sha3_512_test } from './wycheproof/ecdsa_secp256r1_sha3_512_test.json' assert { type: 'json' }; import { default as secp256r1_sha3_512_test } from './wycheproof/ecdsa_secp256r1_sha3_512_test.json' assert { type: 'json' };
import { default as secp256r1_sha512_test } from './wycheproof/ecdsa_secp256r1_sha512_test.json' assert { type: 'json' }; import { default as secp256r1_sha512_test } from './wycheproof/ecdsa_secp256r1_sha512_test.json' assert { type: 'json' };
import { default as secp256r1_shake128_test } from './wycheproof/ecdsa_secp256r1_shake128_test.json' assert { type: 'json' };
import { default as secp384r1_sha384_test } from './wycheproof/ecdsa_secp384r1_sha384_test.json' assert { type: 'json' }; import { default as secp384r1_sha384_test } from './wycheproof/ecdsa_secp384r1_sha384_test.json' assert { type: 'json' };
import { default as secp384r1_sha3_384_test } from './wycheproof/ecdsa_secp384r1_sha3_384_test.json' assert { type: 'json' }; import { default as secp384r1_sha3_384_test } from './wycheproof/ecdsa_secp384r1_sha3_384_test.json' assert { type: 'json' };
import { default as secp384r1_sha3_512_test } from './wycheproof/ecdsa_secp384r1_sha3_512_test.json' assert { type: 'json' }; import { default as secp384r1_sha3_512_test } from './wycheproof/ecdsa_secp384r1_sha3_512_test.json' assert { type: 'json' };
import { default as secp384r1_sha512_test } from './wycheproof/ecdsa_secp384r1_sha512_test.json' assert { type: 'json' }; import { default as secp384r1_sha512_test } from './wycheproof/ecdsa_secp384r1_sha512_test.json' assert { type: 'json' };
import { default as secp384r1_shake256_test } from './wycheproof/ecdsa_secp384r1_shake256_test.json' assert { type: 'json' };
import { default as secp521r1_sha3_512_test } from './wycheproof/ecdsa_secp521r1_sha3_512_test.json' assert { type: 'json' }; import { default as secp521r1_sha3_512_test } from './wycheproof/ecdsa_secp521r1_sha3_512_test.json' assert { type: 'json' };
import { default as secp521r1_sha512_test } from './wycheproof/ecdsa_secp521r1_sha512_test.json' assert { type: 'json' }; import { default as secp521r1_sha512_test } from './wycheproof/ecdsa_secp521r1_sha512_test.json' assert { type: 'json' };
import { default as secp521r1_shake256_test } from './wycheproof/ecdsa_secp521r1_shake256_test.json' assert { type: 'json' };
import { sha3_224, sha3_256, sha3_384, sha3_512 } from '@noble/hashes/sha3'; import { sha3_224, sha3_256, sha3_384, sha3_512, shake128, shake256 } from '@noble/hashes/sha3';
import { sha512, sha384 } from '@noble/hashes/sha512'; import { sha512, sha384 } from '@noble/hashes/sha512';
import { sha224, sha256 } from '@noble/hashes/sha256'; import { sha224, sha256 } from '@noble/hashes/sha256';
// TODO: maybe add to noble-hashes?
const wrapShake = (shake, dkLen) => {
const hashC = (msg) => shake(msg, { dkLen });
hashC.outputLen = dkLen;
hashC.blockLen = shake.blockLen;
hashC.create = () => shake.create({ dkLen });
return hashC;
};
const shake128_224 = wrapShake(shake128, 224 / 8);
const shake128_256 = wrapShake(shake128, 256 / 8);
const shake256_256 = wrapShake(shake256, 256 / 8);
const shake256_384 = wrapShake(shake256, 384 / 8);
const shake256_512 = wrapShake(shake256, 512 / 8);
const hex = bytesToHex; const hex = bytesToHex;
// prettier-ignore // prettier-ignore
const NIST = { const NIST = {
secp192r1, P192, secp192r1, P192: p192,
secp224r1, P224, secp224r1, P224: p224,
secp256r1, P256, secp256r1, P256: p256,
secp384r1, P384, secp384r1, P384: p384,
secp521r1, P521, secp521r1, P521: p521,
secp256k1, secp256k1,
}; };
@@ -73,79 +95,32 @@ 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]);
}); });
describe('wycheproof ECDH', () => { // We don't support ASN.1 encoding of points. For tests we've implemented quick
for (const group of ecdh.testGroups) { // and dirty parser: take X last bytes of ASN.1 encoded sequence.
// // Tested in secp256k1.test.js // If that doesn't work, we ignore such vector.
// if (group.key.curve === 'secp256k1') continue; function verifyECDHVector(test, curve) {
// We don't have SHA-224 if (test.flags.includes('InvalidAsn')) return; // Ignore invalid ASN
const CURVE = NIST[group.curve];
if (!CURVE) continue;
should(group.curve, () => {
for (const test of group.tests) {
if (test.result === 'valid' || test.result === 'acceptable') { 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 { try {
const pub = CURVE.ProjectivePoint.fromHex(test.public); curve.ProjectivePoint.fromHex(pubB);
} catch (e) { } catch (e) {
// Our strict validation filter doesn't let weird-length DER vectors if (e.message.startsWith('Point of length')) return; // Ignore
if (e.message.startsWith('Point of length')) continue;
throw e; throw e;
} }
const shared = CURVE.getSharedSecret(test.private, test.public); const shared = curve.getSharedSecret(privA, pubB).subarray(1);
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');
}
});
}
// More per curve tests
const WYCHEPROOF_ECDH = {
P224: {
curve: P224,
tests: [ecdh_secp224r1_test],
},
P256: {
curve: P256,
tests: [ecdh_secp256r1_test],
},
secp256k1: {
curve: secp256k1,
tests: [ecdh_secp256k1_test],
},
P384: {
curve: P384,
tests: [ecdh_secp384r1_test],
},
P521: {
curve: P521,
tests: [ecdh_secp521r1_test],
},
};
for (const name in WYCHEPROOF_ECDH) {
const { curve, tests } = WYCHEPROOF_ECDH[name];
for (let i = 0; i < tests.length; i++) {
const test = tests[i];
for (let j = 0; j < test.testGroups.length; j++) {
const group = test.testGroups[j];
should(`additional ${name} (${i}/${j})`, () => {
for (const test of group.tests) {
if (test.result === 'valid' || test.result === 'acceptable') {
try {
const pub = curve.ProjectivePoint.fromHex(test.public);
} catch (e) {
// Our strict validation filter doesn't let weird-length DER vectors
if (e.message.includes('Point of length')) continue;
throw e;
}
const shared = curve.getSharedSecret(test.private, test.public);
deepStrictEqual(hex(shared), test.shared, 'valid'); deepStrictEqual(hex(shared), test.shared, 'valid');
} else if (test.result === 'invalid') { } else if (test.result === 'invalid') {
let failed = false; let failed = false;
@@ -157,6 +132,52 @@ describe('wycheproof ECDH', () => {
deepStrictEqual(failed, true, 'invalid'); deepStrictEqual(failed, true, 'invalid');
} else throw new Error('unknown test result'); } else throw new Error('unknown test result');
} }
describe('wycheproof ECDH', () => {
for (const group of ecdh.testGroups) {
const curve = NIST[group.curve];
if (!curve) continue;
should(group.curve, () => {
for (const test of group.tests) {
verifyECDHVector(test, curve);
}
});
}
// More per curve tests
const WYCHEPROOF_ECDH = {
p224: {
curve: p224,
tests: [ecdh_secp224r1_test],
},
p256: {
curve: p256,
tests: [ecdh_secp256r1_test],
},
secp256k1: {
curve: secp256k1,
tests: [ecdh_secp256k1_test],
},
p384: {
curve: p384,
tests: [ecdh_secp384r1_test],
},
p521: {
curve: p521,
tests: [ecdh_secp521r1_test],
},
};
for (const name in WYCHEPROOF_ECDH) {
const { curve, tests } = WYCHEPROOF_ECDH[name];
for (let i = 0; i < tests.length; i++) {
const curveTests = tests[i];
for (let j = 0; j < curveTests.testGroups.length; j++) {
const group = curveTests.testGroups[j];
should(`additional ${name} (${group.tests.length})`, () => {
for (const test of group.tests) {
verifyECDHVector(test, curve);
}
}); });
} }
} }
@@ -164,8 +185,8 @@ describe('wycheproof ECDH', () => {
}); });
const WYCHEPROOF_ECDSA = { const WYCHEPROOF_ECDSA = {
P224: { p224: {
curve: P224, curve: p224,
hashes: { hashes: {
sha224: { sha224: {
hash: sha224, hash: sha224,
@@ -191,6 +212,10 @@ const WYCHEPROOF_ECDSA = {
hash: sha512, hash: sha512,
tests: [secp224r1_sha512_test], tests: [secp224r1_sha512_test],
}, },
shake128: {
hash: shake128_224,
tests: [secp224r1_shake128_test],
},
}, },
}, },
secp256k1: { secp256k1: {
@@ -198,7 +223,7 @@ const WYCHEPROOF_ECDSA = {
hashes: { hashes: {
sha256: { sha256: {
hash: sha256, hash: sha256,
tests: [secp256k1_sha256_test], tests: [secp256k1_sha256_test, secp256k1_sha256_bitcoin_test],
}, },
sha3_256: { sha3_256: {
hash: sha3_256, hash: sha3_256,
@@ -212,10 +237,18 @@ const WYCHEPROOF_ECDSA = {
hash: sha512, hash: sha512,
tests: [secp256k1_sha512_test], tests: [secp256k1_sha512_test],
}, },
shake128: {
hash: shake128_256,
tests: [secp256k1_shake128_test],
},
shake256: {
hash: shake256_256,
tests: [secp256k1_shake256_test],
}, },
}, },
P256: { },
curve: P256, p256: {
curve: p256,
hashes: { hashes: {
sha256: { sha256: {
hash: sha256, hash: sha256,
@@ -233,10 +266,14 @@ const WYCHEPROOF_ECDSA = {
hash: sha512, hash: sha512,
tests: [secp256r1_sha512_test], tests: [secp256r1_sha512_test],
}, },
shake128: {
hash: shake128_256,
tests: [secp256r1_shake128_test],
}, },
}, },
P384: { },
curve: P384, p384: {
curve: p384,
hashes: { hashes: {
sha384: { sha384: {
hash: sha384, hash: sha384,
@@ -254,10 +291,14 @@ const WYCHEPROOF_ECDSA = {
hash: sha512, hash: sha512,
tests: [secp384r1_sha512_test], tests: [secp384r1_sha512_test],
}, },
shake256: {
hash: shake256_384,
tests: [secp384r1_shake256_test],
}, },
}, },
P521: { },
curve: P521, p521: {
curve: p521,
hashes: { hashes: {
sha3_512: { sha3_512: {
hash: sha3_512, hash: sha3_512,
@@ -267,19 +308,23 @@ const WYCHEPROOF_ECDSA = {
hash: sha512, hash: sha512,
tests: [secp521r1_sha512_test], tests: [secp521r1_sha512_test],
}, },
shake256: {
hash: shake256_512,
tests: [secp521r1_shake256_test],
},
}, },
}, },
}; };
function runWycheproof(name, CURVE, group, index) { function runWycheproof(name, CURVE, group, index) {
const pubKey = CURVE.ProjectivePoint.fromHex(group.key.uncompressed); const key = group.publicKey;
deepStrictEqual(pubKey.x, BigInt(`0x${group.key.wx}`)); const pubKey = CURVE.ProjectivePoint.fromHex(key.uncompressed);
deepStrictEqual(pubKey.y, BigInt(`0x${group.key.wy}`)); deepStrictEqual(pubKey.x, BigInt(`0x${key.wx}`));
deepStrictEqual(pubKey.y, BigInt(`0x${key.wy}`));
const pubR = pubKey.toRawBytes(); const pubR = pubKey.toRawBytes();
for (const test of group.tests) { for (const test of group.tests) {
const m = CURVE.CURVE.hash(hexToBytes(test.msg)); const m = CURVE.CURVE.hash(hexToBytes(test.msg));
const { sig } = test; const { sig } = test;
if (test.result === 'valid' || test.result === 'acceptable') { if (test.result === 'valid' || test.result === 'acceptable') {
try { try {
CURVE.Signature.fromDER(sig); CURVE.Signature.fromDER(sig);
@@ -311,7 +356,6 @@ describe('wycheproof ECDSA', () => {
should('generic', () => { should('generic', () => {
for (const group of ecdsa.testGroups) { for (const group of ecdsa.testGroups) {
// Tested in secp256k1.test.js // Tested in secp256k1.test.js
if (group.key.curve === 'secp256k1') continue;
let CURVE = NIST[group.key.curve]; let CURVE = NIST[group.key.curve];
if (!CURVE) continue; if (!CURVE) continue;
if (group.key.curve === 'secp224r1' && group.sha !== 'SHA-224') { if (group.key.curve === 'secp224r1' && group.sha !== 'SHA-224') {
@@ -324,6 +368,9 @@ describe('wycheproof ECDSA', () => {
if (['Hash weaker than DL-group'].includes(test.comment)) { if (['Hash weaker than DL-group'].includes(test.comment)) {
continue; continue;
} }
// These old Wycheproof vectors which still accept missing zero, new one is not.
if (test.flags.includes('MissingZero') && test.result === 'acceptable')
test.result = 'invalid';
const m = CURVE.CURVE.hash(hexToBytes(test.msg)); const m = CURVE.CURVE.hash(hexToBytes(test.msg));
if (test.result === 'valid' || test.result === 'acceptable') { if (test.result === 'valid' || test.result === 'acceptable') {
try { try {
@@ -334,7 +381,12 @@ describe('wycheproof ECDSA', () => {
throw e; throw e;
} }
const verified = CURVE.verify(test.sig, m, pubKey.toHex()); const verified = CURVE.verify(test.sig, m, pubKey.toHex());
deepStrictEqual(verified, true, 'valid'); if (group.key.curve === 'secp256k1') {
// lowS: true for secp256k1
deepStrictEqual(verified, !CURVE.Signature.fromDER(test.sig).hasHighS(), `valid`);
} else {
deepStrictEqual(verified, true, `valid`);
}
} else if (test.result === 'invalid') { } else if (test.result === 'invalid') {
let failed = false; let failed = false;
try { try {
@@ -389,6 +441,43 @@ describe('RFC6979', () => {
} }
}); });
should('properly add leading zero to DER', () => {
// Valid DER
deepStrictEqual(
DER.toSig(
'303c021c70049af31f8348673d56cece2b27e587a402f2a48f0b21a7911a480a021c2840bf24f6f66be287066b7cbf38788e1b7770b18fd1aa6a26d7c6dc'
),
{
r: 11796871166002955884468185727465595477481802908758874298363724580874n,
s: 4239126896857047637966364941684493209162496401998708914961872570076n,
}
);
// Invalid DER (missing trailing zero)
throws(() =>
DER.toSig(
'303c021c70049af31f8348673d56cece2b27e587a402f2a48f0b21a7911a480a021cd7bf40db0909941d78f9948340c69e14c5417f8c840b7edb35846361'
)
);
// Correctly adds trailing zero
deepStrictEqual(
DER.hexFromSig({
r: 11796871166002955884468185727465595477481802908758874298363724580874n,
s: 22720819770293592156700650145335132731295311312425682806720849797985n,
}),
'303d021c70049af31f8348673d56cece2b27e587a402f2a48f0b21a7911a480a021d00d7bf40db0909941d78f9948340c69e14c5417f8c840b7edb35846361'
);
});
should('have proper GLV endomorphism logic in secp256k1', () => {
const Point = secp256k1.ProjectivePoint;
for (let item of endoVectors) {
const point = Point.fromAffine({ x: BigInt(item.ax), y: BigInt(item.ay) });
const c = point.multiplyUnsafe(BigInt(item.scalar)).toAffine();
deepStrictEqual(c.x, BigInt(item.cx));
deepStrictEqual(c.y, BigInt(item.cy));
}
});
// ESM is broken. // ESM is broken.
import url from 'url'; import url from 'url';
if (import.meta.url === url.pathToFileURL(process.argv[1]).href) { if (import.meta.url === url.pathToFileURL(process.argv[1]).href) {

View File

@@ -1,8 +1,8 @@
import { deepStrictEqual, throws } from 'assert'; import { deepStrictEqual, throws } from 'assert';
import { should, describe } from 'micro-should'; import { should, describe } from 'micro-should';
import * as poseidon from '../lib/esm/abstract/poseidon.js'; import * as poseidon from '../esm/abstract/poseidon.js';
import * as stark from '../lib/esm/stark.js'; import * as stark from './_poseidon.helpers.js';
import * as mod from '../lib/esm/abstract/modular.js'; import * as mod from '../esm/abstract/modular.js';
import { default as pvectors } from './vectors/poseidon.json' assert { type: 'json' }; import { default as pvectors } from './vectors/poseidon.json' assert { type: 'json' };
const { st1, st2, st3, st4 } = pvectors; const { st1, st2, st3, st4 } = pvectors;
@@ -132,7 +132,9 @@ describe('Stark', () => {
// Official vectors: https://extgit.iaik.tugraz.at/krypto/hadeshash/-/blob/master/code/test_vectors.txt // Official vectors: https://extgit.iaik.tugraz.at/krypto/hadeshash/-/blob/master/code/test_vectors.txt
should('poseidonperm_x5_255_3', () => { should('poseidonperm_x5_255_3', () => {
const Fp = mod.Fp(BigInt('0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001')); const Fp = mod.Field(
BigInt('0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001')
);
const mds = [ const mds = [
[ [
@@ -161,6 +163,7 @@ should('poseidonperm_x5_255_3', () => {
t, t,
roundsFull: 8, roundsFull: 8,
roundsPartial: 57, roundsPartial: 57,
sboxPower: 5,
mds, mds,
roundConstants, roundConstants,
}); });
@@ -179,7 +182,7 @@ should('poseidonperm_x5_255_3', () => {
}); });
should('poseidonperm_x5_255_5', () => { should('poseidonperm_x5_255_5', () => {
const Fp = mod.Fp(0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001n); const Fp = mod.Field(0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001n);
const t = 5; const t = 5;
const mds = [ const mds = [
@@ -227,6 +230,7 @@ should('poseidonperm_x5_255_5', () => {
t, t,
roundsFull: 8, roundsFull: 8,
roundsPartial: 60, roundsPartial: 60,
sboxPower: 5,
mds, mds,
roundConstants, roundConstants,
}); });
@@ -250,7 +254,7 @@ should('poseidonperm_x5_255_5', () => {
}); });
should('poseidonperm_x5_254_3', () => { should('poseidonperm_x5_254_3', () => {
const Fp = mod.Fp(0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001n); const Fp = mod.Field(0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001n);
const t = 3; const t = 3;
const mds = [ const mds = [
@@ -278,6 +282,7 @@ should('poseidonperm_x5_254_3', () => {
t, t,
roundsFull: 8, roundsFull: 8,
roundsPartial: 57, roundsPartial: 57,
sboxPower: 5,
mds, mds,
roundConstants, roundConstants,
}); });
@@ -297,7 +302,7 @@ should('poseidonperm_x5_254_3', () => {
}); });
should('poseidonperm_x5_254_5', () => { should('poseidonperm_x5_254_5', () => {
const Fp = mod.Fp(0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001n); const Fp = mod.Field(0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001n);
const t = 5; const t = 5;
const mds = [ const mds = [
@@ -345,6 +350,7 @@ should('poseidonperm_x5_254_5', () => {
t, t,
roundsFull: 8, roundsFull: 8,
roundsPartial: 60, roundsPartial: 60,
sboxPower: 5,
mds, mds,
roundConstants, roundConstants,
}); });

View File

@@ -0,0 +1,122 @@
import { deepStrictEqual } from 'assert';
import { should, describe } from 'micro-should';
import * as fs from 'fs';
import {
hexToBytes,
hexToNumber,
concatBytes,
bytesToHex as toHex,
} from '../esm/abstract/utils.js';
// Generic tests for all curves in package
import { secp256k1, elligatorSwift } from '../esm/secp256k1.js';
// ESM is broken.
import { dirname } from 'path';
import { fileURLToPath } from 'url';
export const __dirname = dirname(fileURLToPath(import.meta.url));
// https://eprint.iacr.org/2022/759
const parseCSV = (path) => {
const data = fs.readFileSync(`${__dirname}/vectors/secp256k1/${path}`, 'utf8');
const lines = data.split('\n').filter((i) => !!i);
const rows = lines.map((i) => i.trim().split(','));
const lengths = new Set(rows.map((i) => i.length));
if (lengths.size !== 1) throw new Error('wrong dimensions');
if (rows.length < 2) throw new Error('wrong rows length');
const [head, ...rest] = rows;
return rest.map((row) => Object.fromEntries(row.map((cell, j) => [head[j], cell])));
};
describe('ElligatorSwift', () => {
should('packet_encoding_test_vectors', () => {
for (const t of parseCSV('bip-0324/packet_encoding_test_vectors.csv')) {
const inPriv = hexToNumber(t['in_priv_ours']);
const pubX = secp256k1.ProjectivePoint.BASE.multiply(inPriv)
.x.toString(16)
.padStart(2 * 32, '0');
deepStrictEqual(pubX, t['mid_x_ours']);
const bytesOurs = hexToBytes(t['in_ellswift_ours']);
const decoded = elligatorSwift.decode(bytesOurs);
deepStrictEqual(toHex(decoded), t['mid_x_ours']);
const bytesTheirs = hexToBytes(t['in_ellswift_theirs']);
deepStrictEqual(toHex(elligatorSwift.decode(bytesTheirs)), t['mid_x_theirs']);
const xShared = elligatorSwift.getSharedSecret(t['in_priv_ours'], bytesTheirs);
deepStrictEqual(toHex(xShared), t['mid_x_shared']);
const sharedSecret = elligatorSwift.getSharedSecretBip324(
t['in_priv_ours'],
t['in_ellswift_theirs'],
t['in_ellswift_ours'],
t['in_initiating'] === '1'
);
deepStrictEqual(toHex(sharedSecret), t['mid_shared_secret']);
}
});
should('xswiftec_inv_test_vectors', () => {
for (const t of parseCSV('bip-0324/xswiftec_inv_test_vectors.csv')) {
const Fp = secp256k1.CURVE.Fp;
const u = Fp.create(Fp.fromBytes(hexToBytes(t['u'])));
const x = Fp.create(Fp.fromBytes(hexToBytes(t['x'])));
for (let c = 0; c < 8; c++) {
const name = `case${c}_t`;
const ret = elligatorSwift._inv(x, u, c);
if (!ret) deepStrictEqual(t[name], '', 'empty case');
else {
deepStrictEqual(toHex(Fp.toBytes(ret)), t[name], 'real case');
deepStrictEqual(
elligatorSwift.decode(concatBytes(Fp.toBytes(u), Fp.toBytes(ret))),
Fp.toBytes(x)
);
}
}
}
});
should('ellswift_decode_test_vectors', () => {
for (const t of parseCSV('bip-0324/ellswift_decode_test_vectors.csv')) {
deepStrictEqual(toHex(elligatorSwift.decode(t['ellswift'])), t['x']);
}
});
should('Example', () => {
// random, so test more.
for (let i = 0; i < 100; i++) {
const alice = elligatorSwift.keygen();
const bob = elligatorSwift.keygen();
// ECDH
const sharedAlice = elligatorSwift.getSharedSecret(alice.privateKey, bob.publicKey);
const sharedBob = elligatorSwift.getSharedSecret(bob.privateKey, alice.publicKey);
deepStrictEqual(sharedAlice, sharedBob);
// ECDH BIP324
const sharedAlice2 = elligatorSwift.getSharedSecretBip324(
alice.privateKey,
bob.publicKey,
alice.publicKey,
true
);
const sharedBob2 = elligatorSwift.getSharedSecretBip324(
bob.privateKey,
alice.publicKey,
bob.publicKey,
false
);
deepStrictEqual(sharedAlice2, sharedBob2);
// pubKey decoding
for (const k of [alice, bob]) {
deepStrictEqual(
toHex(elligatorSwift.decode(k.publicKey)),
toHex(secp256k1.getPublicKey(k.privateKey, true).subarray(1))
);
}
}
});
});
// ESM is broken.
import url from 'url';
if (import.meta.url === url.pathToFileURL(process.argv[1]).href) {
should.run();
}

View File

@@ -2,8 +2,8 @@ import { deepStrictEqual, throws } from 'assert';
import { readFileSync } from 'fs'; import { readFileSync } from 'fs';
import { should, describe } from 'micro-should'; import { should, describe } from 'micro-should';
import { bytesToHex as hex } from '@noble/hashes/utils'; import { bytesToHex as hex } from '@noble/hashes/utils';
import { schnorr } from '../lib/esm/secp256k1.js'; import { schnorr } from '../esm/secp256k1.js';
const schCsv = readFileSync('./test/vectors/schnorr.csv', 'utf-8'); const schCsv = readFileSync('./test/vectors/secp256k1/schnorr.csv', 'utf-8');
describe('schnorr.sign()', () => { describe('schnorr.sign()', () => {
// index,secret key,public key,aux_rand,message,signature,verification result,comment // index,secret key,public key,aux_rand,message,signature,verification result,comment

View File

@@ -1,14 +1,11 @@
// @ts-ignore // @ts-ignore
export { secp256k1 as secp } from '../lib/esm/secp256k1.js'; export { secp256k1 as secp } from '../esm/secp256k1.js';
import { secp256k1 as _secp } from '../lib/esm/secp256k1.js'; import { secp256k1 as _secp } from '../esm/secp256k1.js';
export { bytesToNumberBE, numberToBytesBE } from '../lib/esm/abstract/utils.js'; export { bytesToNumberBE, numberToBytesBE } from '../esm/abstract/utils.js';
export { mod } from '../lib/esm/abstract/modular.js'; export { mod } from '../esm/abstract/modular.js';
export const sigFromDER = (der) => { export const sigFromDER = (der) => {
return _secp.Signature.fromDER(der); return _secp.Signature.fromDER(der);
}; };
export const sigToDER = (sig) => sig.toDERHex(); export const sigToDER = (sig) => sig.toDERHex();
export const selectHash = (secp) => secp.CURVE.hash; export const selectHash = (secp) => secp.CURVE.hash;
export const normVerifySig = (s) => _secp.Signature.fromDER(s); export const normVerifySig = (s) => _secp.Signature.fromDER(s);
// export const bytesToNumberBE = secp256k1.utils.bytesToNumberBE;
// export const numberToBytesBE = secp256k1.utils.numberToBytesBE;
// export const mod = mod_;

View File

@@ -8,14 +8,17 @@ import {
secp, sigFromDER, sigToDER, selectHash, normVerifySig, mod, bytesToNumberBE, numberToBytesBE secp, sigFromDER, sigToDER, selectHash, normVerifySig, mod, bytesToNumberBE, numberToBytesBE
} from './secp256k1.helpers.js'; } from './secp256k1.helpers.js';
import { default as ecdsa } from './vectors/ecdsa.json' assert { type: 'json' }; import { default as ecdsa } from './vectors/secp256k1/ecdsa.json' assert { type: 'json' };
import { default as ecdh } from './vectors/ecdh.json' assert { type: 'json' }; import { default as ecdh } from './wycheproof/ecdh_secp256k1_test.json' assert { type: 'json' };
import { default as privates } from './vectors/privates.json' assert { type: 'json' }; import { default as privates } from './vectors/secp256k1/privates.json' assert { type: 'json' };
import { default as points } from './vectors/points.json' assert { type: 'json' }; import { default as points } from './vectors/secp256k1/points.json' assert { type: 'json' };
import { default as wp } from './vectors/wychenproof.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/privates-2.txt', 'utf-8'); const privatesTxt = readFileSync('./test/vectors/secp256k1/privates-2.txt', 'utf-8');
const FC_BIGINT = fc.bigInt(1n + 1n, secp.CURVE.n - 1n); const FC_BIGINT = fc.bigInt(1n + 1n, secp.CURVE.n - 1n);
// prettier-ignore // prettier-ignore
@@ -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()', () => {
@@ -500,14 +530,24 @@ describe('secp256k1', () => {
should('wycheproof vectors', () => { should('wycheproof vectors', () => {
for (let group of wp.testGroups) { for (let group of wp.testGroups) {
// const pubKey = Point.fromHex().toRawBytes(); // const pubKey = Point.fromHex().toRawBytes();
const pubKey = group.key.uncompressed; const key = group.publicKey;
const pubKey = key.uncompressed;
for (let test of group.tests) { for (let test of group.tests) {
const h = selectHash(secp); const h = selectHash(secp);
const m = h(hexToBytes(test.msg)); const m = h(hexToBytes(test.msg));
if (test.result === 'valid' || test.result === 'acceptable') { if (test.result === 'valid' || test.result === 'acceptable') {
let sig;
try {
sig = sigFromDER(test.sig);
} catch (e) {
// These old Wycheproof vectors which allows invalid behaviour of DER parser
if (e.message === 'Invalid signature integer: negative') continue;
throw e;
}
const verified = secp.verify(normVerifySig(test.sig), m, pubKey); const verified = secp.verify(normVerifySig(test.sig), m, pubKey);
if (sigFromDER(test.sig).hasHighS()) { if (sig.hasHighS()) {
deepStrictEqual(verified, false); deepStrictEqual(verified, false);
} else { } else {
deepStrictEqual(verified, true); deepStrictEqual(verified, true);

View File

@@ -1,201 +0,0 @@
import { deepStrictEqual, throws } from 'assert';
import { describe, should } from 'micro-should';
import * as starknet from '../../lib/esm/stark.js';
import { default as issue2 } from './fixtures/issue2.json' assert { type: 'json' };
import * as bip32 from '@scure/bip32';
import * as bip39 from '@scure/bip39';
describe('starknet basic', () => {
should('Basic elliptic sanity check', () => {
const g1 = starknet.ProjectivePoint.BASE;
deepStrictEqual(
g1.toAffine().x.toString(16),
'1ef15c18599971b7beced415a40f0c7deacfd9b0d1819e03d723d8bc943cfca'
);
deepStrictEqual(
g1.toAffine().y.toString(16),
'5668060aa49730b7be4801df46ec62de53ecd11abe43a32873000c36e8dc1f'
);
const g2 = g1.double();
deepStrictEqual(
g2.toAffine().x.toString(16),
'759ca09377679ecd535a81e83039658bf40959283187c654c5416f439403cf5'
);
deepStrictEqual(
g2.toAffine().y.toString(16),
'6f524a3400e7708d5c01a28598ad272e7455aa88778b19f93b562d7a9646c41'
);
const g3 = g2.add(g1);
deepStrictEqual(
g3.toAffine().x.toString(16),
'411494b501a98abd8262b0da1351e17899a0c4ef23dd2f96fec5ba847310b20'
);
deepStrictEqual(
g3.toAffine().y.toString(16),
'7e1b3ebac08924d2c26f409549191fcf94f3bf6f301ed3553e22dfb802f0686'
);
const g32 = g1.multiply(3n);
deepStrictEqual(
g32.toAffine().x.toString(16),
'411494b501a98abd8262b0da1351e17899a0c4ef23dd2f96fec5ba847310b20'
);
deepStrictEqual(
g32.toAffine().y.toString(16),
'7e1b3ebac08924d2c26f409549191fcf94f3bf6f301ed3553e22dfb802f0686'
);
const minus1 = g1.multiply(starknet.CURVE.n - 1n);
deepStrictEqual(
minus1.toAffine().x.toString(16),
'1ef15c18599971b7beced415a40f0c7deacfd9b0d1819e03d723d8bc943cfca'
);
deepStrictEqual(
minus1.toAffine().y.toString(16),
'7a997f9f55b68e04841b7fe20b9139d21ac132ee541bc5cd78cfff3c91723e2'
);
});
should('Pedersen', () => {
deepStrictEqual(
starknet.pedersen(2, 3),
'0x5774fa77b3d843ae9167abd61cf80365a9b2b02218fc2f628494b5bdc9b33b8'
);
deepStrictEqual(
starknet.pedersen(1, 2),
'0x5bb9440e27889a364bcb678b1f679ecd1347acdedcbf36e83494f857cc58026'
);
deepStrictEqual(
starknet.pedersen(3, 4),
'0x262697b88544f733e5c6907c3e1763131e9f14c51ee7951258abbfb29415fbf'
);
});
should('Hash chain', () => {
deepStrictEqual(
starknet.hashChain([1, 2, 3]),
'0x5d9d62d4040b977c3f8d2389d494e4e89a96a8b45c44b1368f1cc6ec5418915'
);
});
should('Pedersen hash edgecases', () => {
// >>> pedersen_hash(0,0)
const zero = '0x49ee3eba8c1600700ee1b87eb599f16716b0b1022947733551fde4050ca6804';
deepStrictEqual(starknet.pedersen(0, 0), zero);
deepStrictEqual(starknet.pedersen(0n, 0n), zero);
deepStrictEqual(starknet.pedersen('0', '0'), zero);
deepStrictEqual(starknet.pedersen('0x0', '0x0'), zero);
// >>> pedersen_hash(3618502788666131213697322783095070105623107215331596699973092056135872020475,3618502788666131213697322783095070105623107215331596699973092056135872020475)
// 3226051580231087455100099637526672350308978851161639703631919449959447036451
const big = 3618502788666131213697322783095070105623107215331596699973092056135872020475n;
const bigExp = '0x721e167a36655994e88efa865e2ed8a0488d36db4d988fec043cda755728223';
deepStrictEqual(starknet.pedersen(big, big), bigExp);
// >= FIELD
const big2 = 36185027886661312136973227830950701056231072153315966999730920561358720204751n;
throws(() => starknet.pedersen(big2, big2), 'big2');
// FIELD -1
const big3 = 3618502788666131213697322783095070105623107215331596699973092056135872020480n;
const big3exp = '0x7258fccaf3371fad51b117471d9d888a1786c5694c3e6099160477b593a576e';
deepStrictEqual(starknet.pedersen(big3, big3), big3exp, 'big3');
// FIELD
const big4 = 3618502788666131213697322783095070105623107215331596699973092056135872020481n;
throws(() => starknet.pedersen(big4, big4), 'big4');
throws(() => starknet.pedersen(-1, -1), 'neg');
throws(() => starknet.pedersen(false, false), 'false');
throws(() => starknet.pedersen(true, true), 'true');
throws(() => starknet.pedersen(10.1, 10.1), 'float');
});
should('hashChain edgecases', () => {
deepStrictEqual(starknet.hashChain([32312321312321312312312321n]), '0x1aba6672c014b4838cc201');
deepStrictEqual(
starknet.hashChain([1n, 2n]),
'0x5bb9440e27889a364bcb678b1f679ecd1347acdedcbf36e83494f857cc58026'
);
deepStrictEqual(
starknet.hashChain([1, 2]),
'0x5bb9440e27889a364bcb678b1f679ecd1347acdedcbf36e83494f857cc58026'
);
throws(() => starknet.hashChain([]));
throws(() => starknet.hashChain('123'));
deepStrictEqual(
starknet.hashChain([1, 2]),
'0x5bb9440e27889a364bcb678b1f679ecd1347acdedcbf36e83494f857cc58026'
);
});
should('Pedersen hash, issue #2', () => {
// Verified with starnet.js
deepStrictEqual(
starknet.computeHashOnElements(issue2),
'0x22064462ea33a6ce5272a295e0f551c5da3834f80d8444e7a4df68190b1bc42'
);
deepStrictEqual(
starknet.computeHashOnElements([]),
'0x49ee3eba8c1600700ee1b87eb599f16716b0b1022947733551fde4050ca6804'
);
deepStrictEqual(
starknet.computeHashOnElements([1]),
'0x78d74f61aeaa8286418fd34b3a12a610445eba11d00ecc82ecac2542d55f7a4'
);
});
should('Seed derivation (example)', () => {
const layer = 'starkex';
const application = 'starkdeployement';
const mnemonic =
'range mountain blast problem vibrant void vivid doctor cluster enough melody ' +
'salt layer language laptop boat major space monkey unit glimpse pause change vibrant';
const ethAddress = '0xa4864d977b944315389d1765ffa7e66F74ee8cd7';
const hdKey = bip32.HDKey.fromMasterSeed(bip39.mnemonicToSeedSync(mnemonic)).derive(
starknet.getAccountPath(layer, application, ethAddress, 0)
);
deepStrictEqual(
starknet.grindKey(hdKey.privateKey),
'6cf0a8bf113352eb863157a45c5e5567abb34f8d32cddafd2c22aa803f4892c'
);
});
should('Compressed keys', () => {
const G = starknet.ProjectivePoint.BASE;
const half = starknet.CURVE.n / 2n;
const last = starknet.CURVE.n;
const vectors = [
1n,
2n,
3n,
4n,
5n,
half - 5n,
half - 4n,
half - 3n,
half - 2n,
half - 1n,
half,
half + 1n,
half + 2n,
half + 3n,
half + 4n,
half + 5n,
last - 5n,
last - 4n,
last - 3n,
last - 2n,
last - 1n,
].map((i) => G.multiply(i));
const fixPoint = (pt) => pt.toAffine();
for (const v of vectors) {
const uncompressed = v.toHex();
const compressed = v.toHex(true);
const exp = fixPoint(v);
deepStrictEqual(fixPoint(starknet.ProjectivePoint.fromHex(uncompressed)), exp);
deepStrictEqual(fixPoint(starknet.ProjectivePoint.fromHex(compressed)), exp);
deepStrictEqual(starknet.ProjectivePoint.fromHex(compressed).toHex(), uncompressed);
}
});
});
// ESM is broken.
import url from 'url';
if (import.meta.url === url.pathToFileURL(process.argv[1]).href) {
should.run();
}

View File

@@ -1,57 +0,0 @@
import * as microStark from '../../../lib/esm/stark.js';
import * as starkwareCrypto from '@starkware-industries/starkware-crypto-utils';
import * as bench from 'micro-bmark';
const { run, mark } = bench; // or bench.mark
const privateKey = '2dccce1da22003777062ee0870e9881b460a8b7eca276870f57c601f182136c';
const msgHash = 'c465dd6b1bbffdb05442eb17f5ca38ad1aa78a6f56bf4415bdee219114a47';
const keyPair = starkwareCrypto.default.ec.keyFromPrivate(privateKey, 'hex');
const publicKeyStark = starkwareCrypto.default.ec.keyFromPublic(
keyPair.getPublic(true, 'hex'),
'hex'
);
const publicKeyMicro = microStark.getPublicKey(privateKey);
const FNS = {
pedersenHash: {
samples: 250,
starkware: () =>
starkwareCrypto.default.pedersen([
'3d937c035c878245caf64531a5756109c53068da139362728feb561405371cb',
'208a0a10250e382e1e4bbe2880906c2791bf6275695e02fbbc6aeff9cd8b31a',
]),
'micro-starknet': () =>
microStark.pedersen(
'3d937c035c878245caf64531a5756109c53068da139362728feb561405371cb',
'208a0a10250e382e1e4bbe2880906c2791bf6275695e02fbbc6aeff9cd8b31a'
),
},
signVerify: {
samples: 500,
starkware: () =>
starkwareCrypto.default.verify(
publicKeyStark,
msgHash,
starkwareCrypto.default.sign(keyPair, msgHash)
),
'micro-starknet': () =>
microStark.verify(microStark.sign(msgHash, privateKey), msgHash, publicKeyMicro),
},
};
const main = () =>
run(async () => {
for (let [k, libs] of Object.entries(FNS)) {
console.log(`==== ${k} ====`);
for (const [lib, fn] of Object.entries(libs)) {
if (lib === 'samples') continue;
let title = `${k} (${lib})`;
await mark(title, libs.samples, () => fn());
}
console.log();
}
// Log current RAM
bench.logMem();
});
main();

View File

@@ -1,19 +0,0 @@
{
"name": "benchmark",
"private": true,
"version": "0.1.0",
"description": "benchmarks",
"main": "index.js",
"type": "module",
"scripts": {
"bench": "node index.js"
},
"keywords": [],
"author": "",
"license": "MIT",
"devDependencies": {
"@starkware-industries/starkware-crypto-utils": "^0.0.2",
"micro-bmark": "0.2.0",
"micro-should": "0.2.0"
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,32 +0,0 @@
{
"0x1": "0x1ef15c18599971b7beced415a40f0c7deacfd9b0d1819e03d723d8bc943cfca",
"0x2": "0x759ca09377679ecd535a81e83039658bf40959283187c654c5416f439403cf5",
"0x3": "0x411494b501a98abd8262b0da1351e17899a0c4ef23dd2f96fec5ba847310b20",
"0x4": "0xa7da05a4d664859ccd6e567b935cdfbfe3018c7771cb980892ef38878ae9bc",
"0x5": "0x788435d61046d3eec54d77d25bd194525f4fa26ebe6575536bc6f656656b74c",
"0x6": "0x1efc3d7c9649900fcbd03f578a8248d095bc4b6a13b3c25f9886ef971ff96fa",
"0x7": "0x743829e0a179f8afe223fc8112dfc8d024ab6b235fd42283c4f5970259ce7b7",
"0x8": "0x6eeee2b0c71d681692559735e08a2c3ba04e7347c0c18d4d49b83bb89771591",
"0x9": "0x216b4f076ff47e03a05032d1c6ee17933d8de8b2b4c43eb5ad5a7e1b25d3849",
"0x800000000000000000000000000000000000000000000000000000000000000": "0x5c79074e7f7b834c12c81a9bb0d46691a5e7517767a849d9d98cb84e2176ed2",
"0x800000000000000000000000000000000000000000000000000000000000001": "0x1c4f24e3bd16db0e2457bc005a9d61965105a535554c6b338871e34cb8e2d3a",
"0x800000000000000000000000000000000000000000000000000000000000002": "0xdfbb89b39288a9ddacf3942b4481b04d4fa2f8ed3c424757981cc6357f27ac",
"0x800000000000000000000000000000000000000000000000000000000000003": "0x41bef28265fd750b102f4f2d1e0231de7f4a33900a214f191a63d4fec4e72f4",
"0x800000000000000000000000000000000000000000000000000000000000004": "0x24de66eb164797d4b414e81ded0cfa1a592ef0a9363ebbcb440d4d03cb18af1",
"0x800000000000000000000000000000000000000000000000000000000000005": "0x5efb18c3bc9b69003746acc85fb6ee0cfbdc6adfb982f089cc63e1e5495daad",
"0x800000000000000000000000000000000000000000000000000000000000006": "0x10dc71f00918a8ebfe4085c834d41dd22b251b9f81eef8b9a4fab77e7e1afe9",
"0x800000000000000000000000000000000000000000000000000000000000007": "0x4267ebfd379b1c8caae73febc5920b0c95bd6f9f3536f47c5ddad1259c332ff",
"0x800000000000000000000000000000000000000000000000000000000000008": "0x6da515118c8e01fd5b2e96b814ee95bad7d60be4d2ba6b47e0d283f579d9671",
"0x800000000000000000000000000000000000000000000000000000000000009": "0x7a5b4797f4e56ed1473876bc2693fbe3f2fef7e050717cbae924ff23d426052",
"0x2e9c99d8382fa004dcbbee720aef8a97002de0e991f6a8344e6dc636a71b59e": "0x1ff6803ae740e7e596504ac5c6afbea472e53679361e214f12be0155b13e25d",
"0x8620458785138df8722214e073a91b8f55076ea78197cf41007692dd27fd90": "0x5967da40b90d7ca1e36dc4024381d7d4b403c6ac1a0ab358b0743984934a805",
"0x1b920e7dfb49ba5ada673882af5342e7448d3e9335e0ac37feb6280cd7289ce": "0x78c7ab46333968fbde3201cf512c1eeb5529360259072c459a158dee4449b57",
"0x704170dbfd5dc63caef69d2ce6dfc2b2dbb2af6e75851242bbe79fb6e62a118": "0x534bd8d6ebe4bb2f6992e2d7c19ef3146247e10c2849f357e44eddd283b2af6",
"0x4b58bf4228f39550eca59b5c96a0cb606036cc9495eef9a546f24f01b1b7829": "0x1097a8c5a46d94596f1c8e70ca66941f2bb11e3c8d4fd58fdc4589f09965be8",
"0x2e93226c90fb7a2381a24e940a94b98433e3553dcbf745d3f54d62963c75604": "0x369f0e8c8e984f244290267393a004dba435a4df091767ad5063fece7b1884c",
"0x4615f94598cd756ad1a551d7e57fd725916adfd0054eb773ceb482eef87d0b2": "0x1ee5b8d612102a2408cde59ce52a6498d2e38fe8789bb26d400dea310684ec9",
"0x6ade54b7debd7ca1d4e8e932f9545f8fa4024d73be1efcc86df86367fc333f8": "0x37de3bf52412b2fb9b0030d232ca9dd921cd8f71fd67975cdc62546826e121",
"0x618e7467dd24c2a3449c4df640439c12cdd0f8ea779afcee6e252b2cf494354": "0x71c2b578c432f2d305d3808bb645ecc46dd670cb43d4f4a076f75ccbff74fbc",
"0x7eae185e1f41ec76d214d763f0592f194933622a9dd5f3d52d0209f71619c1a": "0x2b0160052e70176e5b0ff2a6eff90896ae07b732fc27219e36e077735abd57e",
"0x178047D3869489C055D7EA54C014FFB834A069C9595186ABE04EA4D1223A03F": "0x1895a6a77ae14e7987b9cb51329a5adfb17bd8e7c638f92d6892d76e51cebcf"
}

View File

@@ -1,57 +0,0 @@
{
"private_key": "0x3c1e9550e66958296d11b60f8e8e7a7ad990d07fa65d5f7652c4a6c87d4e3cc",
"messages": [
{
"hash": "0x1",
"r": "3162358736122783857144396205516927012128897537504463716197279730251407200037",
"s": "1447067116407676619871126378936374427636662490882969509559888874644844560850"
},
{
"hash": "0x11",
"r": "2282960348362869237018441985726545922711140064809058182483721438101695251648",
"s": "2905868291002627709651322791912000820756370440695830310841564989426104902684"
},
{
"hash": "0x223",
"r": "2851492577225522862152785068304516872062840835882746625971400995051610132955",
"s": "2227464623243182122770469099770977514100002325017609907274766387592987135410"
},
{
"hash": "0x9999",
"r": "3551214266795401081823453828727326248401688527835302880992409448142527576296",
"s": "2580950807716503852408066180369610390914312729170066679103651110985466032285"
},
{
"hash": "0x387e76d1667c4454bfb835144120583af836f8e32a516765497d23eabe16b3f",
"r": "3518448914047769356425227827389998721396724764083236823647519654917215164512",
"s": "3042321032945513635364267149196358883053166552342928199041742035443537684462"
},
{
"hash": "0x3a7e76d1697c4455bfb835144120283af236f8e32a516765497d23eabe16b2",
"r": "2261926635950780594216378185339927576862772034098248230433352748057295357217",
"s": "2708700003762962638306717009307430364534544393269844487939098184375356178572"
},
{
"hash": "0xfa5f0cd1ebff93c9e6474379a213ba111f9e42f2f1cb361b0327e0737203",
"r": "3016953906936760149710218073693613509330129567629289734816320774638425763370",
"s": "306146275372136078470081798635201810092238376869367156373203048583896337506"
},
{
"hash": "0x4c1e9550e66958296d11b60f8e8e7f7ae99dd0cfa6bd5fa652c1a6c87d4e2cc",
"r": "3562728603055564208884290243634917206833465920158600288670177317979301056463",
"s": "1958799632261808501999574190111106370256896588537275453140683641951899459876"
},
{
"hash": "0x6362b40c218fb4c8a8bd42ca482145e8513b78e00faa0de76a98ba14fc37ae8",
"r": "3485557127492692423490706790022678621438670833185864153640824729109010175518",
"s": "897592218067946175671768586886915961592526001156186496738437723857225288280"
}
]
}

View File

@@ -1,4 +0,0 @@
import './basic.test.js';
import './stark.test.js';
import './property.test.js';
import './poseidon.test.js';

View File

@@ -1,114 +0,0 @@
import { deepStrictEqual, throws } from 'assert';
import { describe, should } from 'micro-should';
import * as starknet from '../../lib/esm/stark.js';
import * as fs from 'fs';
function parseTest(path) {
let data = fs.readFileSync(path, 'ascii');
// Remove whitespaces
data = data.replace(/[ |\t]/g, '');
const pattern =
'Rate=(\\d+)\n' +
'Capacity=(\\d+)\n' +
'FullRounds=(\\d+)\n' +
'PartialRounds=(\\d+)\n' +
'MDS=\\[(.+)\\]\n' +
'RoundKeys=\\(?\n?\\[\n?(.+)\n?\\]\n?\\)?';
const r = data.match(new RegExp(pattern, 'ms'));
function parseArray(s) {
// Remove new lines
s = s.replace(/\n/gms, '');
return s.match(/(\[.+?\])/g).map((i) =>
i
.replace(/^\[(.+)\]$/, '$1')
.split(',')
.filter((i) => !!i)
);
}
const res = {
rate: +r[1],
capacity: +r[2],
roundsFull: +r[3],
roundsPartial: +r[4],
MDS: parseArray(r[5]).map((i) => i.map((j) => BigInt(j))),
roundConstants: parseArray(r[6]).map((i) => i.map((j) => BigInt(j))),
};
return res;
}
function mapPoseidon(parsed) {
return starknet.poseidonBasic(
{
Fp: starknet.Fp251,
rate: parsed.rate,
capacity: parsed.capacity,
roundsFull: parsed.roundsFull,
roundsPartial: parsed.roundsPartial,
},
parsed.MDS
);
}
const parsed = {
poseidon3: parseTest('./test/stark/poseidon/poseidon3.txt'),
poseidon4: parseTest('./test/stark/poseidon/poseidon4.txt'),
poseidon5: parseTest('./test/stark/poseidon/poseidon5.txt'),
poseidon9: parseTest('./test/stark/poseidon/poseidon9.txt'),
};
function poseidonTest(name, parsed) {
should(`${name}`, () => {
const fn = mapPoseidon(parsed);
deepStrictEqual(fn.roundConstants, parsed.roundConstants);
});
}
describe('poseidon txt vectors', () => {
poseidonTest('poseidon3', parsed.poseidon3);
poseidonTest('poseidon4', parsed.poseidon4);
poseidonTest('poseidon5', parsed.poseidon5);
poseidonTest('poseidon9', parsed.poseidon9);
});
should('Poseidon examples', () => {
const p3 = mapPoseidon(parsed.poseidon3);
deepStrictEqual(p3([0n, 0n, 0n]), [
3446325744004048536138401612021367625846492093718951375866996507163446763827n,
1590252087433376791875644726012779423683501236913937337746052470473806035332n,
867921192302518434283879514999422690776342565400001269945778456016268852423n,
]);
const p4 = mapPoseidon(parsed.poseidon4);
deepStrictEqual(p4([0n, 0n, 0n, 0n]), [
535071095200566880914603862188010633478042591441142518549720701573192347548n,
3567335813488551850156302853280844225974867890860330236555401145692518003968n,
229995103310401763929738317978722680640995513996113588430855556460153357543n,
3513983790849716360905369754287999509206472929684378838050290392634812839312n,
]);
const p5 = mapPoseidon(parsed.poseidon5);
deepStrictEqual(p5([0n, 0n, 0n, 0n, 0n]), [
2337689130971531876049206831496963607805116499042700598724344149414565980684n,
3230969295497815870174763682436655274044379544854667759151474216427142025631n,
3297330512217530111610698859408044542971696143761201570393504997742535648562n,
2585480844700786541432072704002477919020588246983274666988914431019064343941n,
3595308260654382824623573767385493361624474708214823462901432822513585995028n,
]);
const p9 = mapPoseidon(parsed.poseidon9);
deepStrictEqual(p9([0n, 0n, 0n, 0n, 0n, 0n, 0n, 0n, 0n]), [
1534116856660032929112709488204491699743182428465681149262739677337223235050n,
1710856073207389764546990138116985223517553616229641666885337928044617114700n,
3165864635055638516987240200217592641540231237468651257819894959934472989427n,
1003007637710164252047715558598366312649052908276423203724288341354608811559n,
68117303579957054409211824649914588822081700129416361923518488718489651489n,
1123395637839379807713801282868237406546107732595903195840754789810160564711n,
478590974834311070537087181212389392308746075734019180430422247431982932503n,
835322726024358888065061514739954009068852229059154336727219387089732433787n,
3129703030204995742174502162918848446737407262178341733578946634564864233056n,
]);
});
// ESM is broken.
import url from 'url';
if (import.meta.url === url.pathToFileURL(process.argv[1]).href) {
should.run();
}

Some files were not shown because too many files have changed in this diff Show More