249 Commits
0.5.0 ... 1.1.0

Author SHA1 Message Date
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
Paul Miller
3486bbf6b8 Release 0.7.0. 2023-02-14 23:45:53 +01:00
Paul Miller
0d7a8296c5 gitignore update 2023-02-14 23:45:39 +01:00
Paul Miller
0f1e7a5a43 Move output from lib to root. React Native does not support pkg.json#exports 2023-02-14 23:43:28 +01:00
Paul Miller
3da48cf899 bump bmark 2023-02-14 23:24:11 +01:00
Paul Miller
4ec46dd65d Remove scure-base from top-level dep 2023-02-14 18:00:11 +01:00
Paul Miller
7073f63c6b drbg: move from weierstrass to utils 2023-02-14 17:54:57 +01:00
Paul Miller
80966cbd03 hash-to-curve: more type checks. Rename method to createHasher 2023-02-14 17:39:56 +01:00
Paul Miller
98ea15dca4 edwards: improve hex errors 2023-02-14 17:35:19 +01:00
Paul Miller
e1910e85ea mod, utils, weierstrass, secp: improve hex errors. secp: improve verify() logic and schnorr 2023-02-14 17:34:31 +01:00
Paul Miller
4d311d7294 Emit source maps 2023-02-14 17:23:51 +01:00
Paul Miller
c36d90cae6 bump lockfile, add comment to shortw 2023-02-13 23:55:58 +01:00
Paul Miller
af5aa8424f readme: supply chain attacks 2023-02-13 23:32:49 +01:00
Paul Miller
67b99652fc BLS: add docs 2023-02-12 22:25:36 +01:00
Paul Miller
c8d292976b README 2023-02-12 22:25:22 +01:00
Paul Miller
daffaa2339 README: more docs 2023-02-12 21:37:27 +01:00
Paul Miller
a462fc5779 readme updates 2023-02-12 11:30:55 +01:00
Paul Miller
fe3491c5aa Release 0.6.4. 2023-02-09 23:19:15 +01:00
Paul Miller
c0877ba69a Fix weierstrass type 2023-02-09 23:18:32 +01:00
Paul Miller
8e449cc78c ed25519 tests: unify with noble-ed25519 2023-02-09 21:26:24 +01:00
Paul Miller
1b6071cabd weierstrass: rename normPrivKey util. tests: prepare for unification w old noble pkg 2023-02-09 20:26:20 +01:00
Paul Miller
debb9d9709 Release 0.6.3. 2023-02-09 16:19:08 +01:00
Paul Miller
d2c6459756 Update wnaf comments 2023-02-09 15:45:21 +01:00
Paul Miller
47533b6336 Add more tests for weierstrass, etc 2023-02-09 13:29:19 +01:00
Paul Miller
00b73b68d3 hash-to-curve small refactor 2023-02-06 20:50:52 +01:00
Paul Miller
cef4b52d12 Update hashes to 1.2, scure devdeps, add lockfile 2023-02-06 20:50:41 +01:00
Paul Miller
47ce547dcf README update 2023-02-06 20:50:23 +01:00
Paul Miller
e2a7594eae Release 0.6.2. 2023-01-30 08:18:07 +01:00
Paul Miller
823149ecd9 Clarify comment 2023-01-30 08:17:08 +01:00
Paul Miller
e57aec63d8 Fix edwards assertValidity 2023-01-30 08:04:36 +01:00
Paul Miller
837aca98c9 Fix bugs 2023-01-30 06:10:56 +01:00
Paul Miller
dbb16b0e5e edwards: add assertValidity 2023-01-30 06:10:08 +01:00
Paul Miller
e14af67254 utils: fix hexToNumber, improve validateObject 2023-01-30 06:07:53 +01:00
Paul Miller
4780850748 montgomery: fix fieldLen 2023-01-30 05:56:07 +01:00
Paul Miller
3374a70f47 README update 2023-01-30 05:55:36 +01:00
Paul Miller
131f88b504 Release 0.6.1. 2023-01-29 05:14:10 +01:00
Paul Miller
4333e9a686 README 2023-01-29 05:12:58 +01:00
Paul Miller
a60d15ff05 Upgrading guide from other noble libraries 2023-01-29 05:10:58 +01:00
Paul Miller
ceffbc69da More Schnorr utils 2023-01-29 04:46:38 +01:00
Paul Miller
c75129e629 Use declarative curve field validation 2023-01-28 03:19:46 +01:00
Paul Miller
f39fb80c52 weierstrass: rename normalizePrivateKey to allowedPrivateKeyLengths 2023-01-27 23:45:55 +01:00
Paul Miller
fcd422d246 README updates 2023-01-27 03:48:53 +01:00
Paul Miller
ed9bf89038 stark: isCompressed=false. Update benchmarks 2023-01-27 03:43:18 +01:00
Paul Miller
7262b4219f Bump micro-should 2023-01-26 08:26:07 +01:00
Paul Miller
02b0b25147 New schnorr exports. Simplify RFC6979 k gen, privkey checks 2023-01-26 08:16:00 +01:00
Paul Miller
79100c2d47 Release 0.6.0. 2023-01-26 06:31:16 +01:00
Paul Miller
4ef2cad685 hash-to-curve: assertValidity 2023-01-26 06:14:12 +01:00
Paul Miller
69b3ab5a57 Shuffle code 2023-01-26 05:46:14 +01:00
Paul Miller
9465e60d30 More refactoring 2023-01-26 05:24:41 +01:00
Paul Miller
0fb78b7097 Rename group to curve. More refactoring 2023-01-26 04:14:21 +01:00
Paul Miller
be0b2a32a5 Fp rename. Edwards refactor. Weierstrass Fn instead of mod 2023-01-26 03:07:45 +01:00
Paul Miller
3d77422731 Restructure tests 2023-01-26 03:06:28 +01:00
Paul Miller
c46914f1bc weierstrass: remove most private utils 2023-01-25 08:21:48 +01:00
Paul Miller
f250f355e8 Schnorr: remove all private methods 2023-01-25 08:14:53 +01:00
Paul Miller
c095d74673 More schnorr updates 2023-01-25 08:10:05 +01:00
Paul Miller
ac52fea952 Another schnorr adjustment 2023-01-25 07:55:21 +01:00
Paul Miller
f2ee24bee4 schnorr: remove packSig 2023-01-25 07:54:00 +01:00
Paul Miller
cffea91061 Schnorr, weierstrass: refactor 2023-01-25 07:48:53 +01:00
Paul Miller
5fc38fc0e7 weierstrass: prehash option in sign/verify. Remove _normalizePublicKey 2023-01-25 05:45:49 +01:00
Paul Miller
849dc38f3c Change TypeError to Error 2023-01-25 05:24:22 +01:00
Paul Miller
0422e6ef38 p.x, p.y are now getters executing toAffine() 2023-01-25 04:51:08 +01:00
Paul Miller
21d2438a33 BLS: fix tests. Poseidon: more tests 2023-01-25 00:30:53 +01:00
Paul Miller
cea4696599 BLS tests: remove async 2023-01-25 00:13:39 +01:00
Paul Miller
f14b8d2be5 More AffinePoint fixes 2023-01-25 00:07:25 +01:00
Paul Miller
2ed27da8eb weierstrass: remove affine Point 2023-01-24 06:42:44 +01:00
Paul Miller
17e5be5f1b edwards: affine Point removal tests 2023-01-24 05:37:53 +01:00
Paul Miller
a49f0d266e edwards: remove affine Point, Signature. Stricter types 2023-01-24 05:34:56 +01:00
Paul Miller
bfbcf733e6 Update tests 2023-01-24 04:02:45 +01:00
Paul Miller
7fda6de619 weierstrass: make points compressed by def. Rewrite drbg, k generation. 2023-01-24 04:02:38 +01:00
Paul Miller
2b908ad602 edwards: simplify bounds check 2023-01-24 04:01:28 +01:00
Paul Miller
ceb3f67faa stark: switch to new weierstrass methods 2023-01-23 23:07:21 +01:00
Paul Miller
a2c87f9c2f weierstrass: simplify bits2int, remove truncateHash 2023-01-23 23:06:43 +01:00
Paul Miller
e1fd346279 utils: small improvements 2023-01-23 23:06:24 +01:00
Paul Miller
11e78aadbf Edwards: prohibit number scalars, only allow bigints 2023-01-23 20:28:01 +01:00
Paul Miller
055147f1be Add poseidon252 snark-friendly hash 2023-01-23 19:41:19 +01:00
Paul Miller
6f99f6042e weierstrass: bits2int, int2octets, truncateHash now comply with standard 2023-01-21 19:03:39 +01:00
Paul Miller
1e47bf2372 Bump prettier to 2.8.3 because it fails to parse bls 2023-01-21 19:02:58 +01:00
Paul Miller
40530eae0c hash-to-curve: decrease coupling, improve tree shaking support 2023-01-21 19:02:46 +01:00
Paul Miller
b9482bb17d Release 0.5.2. 2023-01-13 16:23:52 +01:00
Paul Miller
74475dca68 Fix lint 2023-01-13 16:02:07 +01:00
Paul Miller
f4cf21b9c8 tests: Use describe() 2023-01-13 16:00:13 +01:00
Paul Miller
5312d92b2c edwards: Fix isTorsionFree() 2023-01-13 15:58:04 +01:00
Paul Miller
d1770c0ac7 Rename test 2023-01-13 01:29:54 +01:00
Paul Miller
2d37edf7d1 Remove utils.mod(), utils.invert() 2023-01-13 01:26:00 +01:00
Paul Miller
36998fede8 Fix sqrt 2023-01-13 01:21:51 +01:00
Paul Miller
83960d445d Refactor: weierstrass assertValidity and others 2023-01-12 21:18:51 +01:00
Paul Miller
23cc2aa5d1 edwards, montgomery, weierstrass: refactor 2023-01-12 20:40:16 +01:00
Paul Miller
e45d7c2d25 utils: new util; ed448: small adjustment 2023-01-12 20:39:43 +01:00
Paul Miller
bfe929aac3 modular: Tonneli-Shanks refactoring 2023-01-12 20:38:42 +01:00
Paul Miller
069452dbe7 BLS, jubjub refactoring 2023-01-12 20:38:10 +01:00
Paul Miller
2e81f31d2e ECDSA: signUnhashed(), support for key recovery from bits 2/3 2023-01-08 20:02:04 +01:00
Paul Miller
9f7df0f13b ECDSA adjustments 2023-01-08 18:46:55 +01:00
Paul Miller
5600629bca Refactor 2023-01-08 18:02:54 +01:00
Paul Miller
2bd5e9ac16 Release 0.5.1. 2022-12-31 10:31:10 +01:00
Paul Miller
6890c26091 Fix readme toc 2022-12-31 10:29:25 +01:00
Paul Miller
a15e3a93a9 Docs 2022-12-31 10:00:29 +01:00
Paul Miller
910c508da9 hash-to-curve: elligator in 25519, 448. Stark: adjust type 2022-12-31 07:51:29 +01:00
Paul Miller
12da04a2bb Improve modular math 2022-12-31 07:49:42 +01:00
Paul Miller
cc2c84f040 Improve field tests 2022-12-31 07:49:09 +01:00
Paul Miller
5d42549acc hash-to-curve: add xmd/xof support 2022-12-31 07:48:13 +01:00
133 changed files with 273969 additions and 131065 deletions

View File

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

23
.github/workflows/publish-npm.yml vendored Normal file
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 }}

14
.gitignore vendored
View File

@@ -1,7 +1,13 @@
build/
node_modules/
coverage/
/lib/**/*.js
/lib/**/*.ts
/lib/**/*.d.ts.map
/curve-definitions/lib
/*.js
/*.ts
/*.js.map
/*.d.ts.map
/esm/*.js
/esm/*.ts
/esm/*.js.map
/esm/*.d.ts.map
/esm/abstract
/abstract/

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

1068
README.md

File diff suppressed because it is too large Load Diff

View File

@@ -4,8 +4,8 @@
| Version | Supported |
| ------- | ------------------ |
| >=0.5.0 | :white_check_mark: |
| <0.5.0 | :x: |
| >=1.0.0 | :white_check_mark: |
| <1.0.0 | :x: |
## Reporting a Vulnerability

Binary file not shown.

11
audit/README.md Normal file
View File

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

7
benchmark/_shared.js Normal file
View File

@@ -0,0 +1,7 @@
export function generateData(curve) {
const priv = curve.utils.randomPrivateKey();
const pub = curve.getPublicKey(priv);
const msg = curve.utils.randomPrivateKey();
const sig = curve.sign(msg, priv);
return { priv, pub, msg, sig };
}

67
benchmark/bls.js Normal file
View File

@@ -0,0 +1,67 @@
import { readFileSync } from 'fs';
import { mark, run } from 'micro-bmark';
import { bls12_381 as bls } from '../bls12-381.js';
const G2_VECTORS = readFileSync('../test/bls12-381/bls12-381-g2-test-vectors.txt', 'utf-8')
.trim()
.split('\n')
.map((l) => l.split(':'));
run(async () => {
console.log(`\x1b[36mbls12-381\x1b[0m`);
let p1, p2, sig;
await mark('init', 1, () => {
p1 =
bls.G1.ProjectivePoint.BASE.multiply(
0x28b90deaf189015d3a325908c5e0e4bf00f84f7e639b056ff82d7e70b6eede4cn
);
p2 =
bls.G2.ProjectivePoint.BASE.multiply(
0x28b90deaf189015d3a325908c5e0e4bf00f84f7e639b056ff82d7e70b6eede4dn
);
bls.pairing(p1, p2);
});
const priv = '28b90deaf189015d3a325908c5e0e4bf00f84f7e639b056ff82d7e70b6eede4c';
sig = bls.sign('09', priv);
const pubs = G2_VECTORS.map((v) => bls.getPublicKey(v[0]));
const sigs = G2_VECTORS.map((v) => v[2]);
const pub = bls.getPublicKey(priv);
const pub512 = pubs.slice(0, 512); // .map(bls.PointG1.fromHex)
const pub32 = pub512.slice(0, 32);
const pub128 = pub512.slice(0, 128);
const pub2048 = pub512.concat(pub512, pub512, pub512);
const sig512 = sigs.slice(0, 512); // .map(bls.PointG2.fromSignature);
const sig32 = sig512.slice(0, 32);
const sig128 = sig512.slice(0, 128);
const sig2048 = sig512.concat(sig512, sig512, sig512);
await mark('getPublicKey 1-bit', 1000, () => bls.getPublicKey('2'.padStart(64, '0')));
await mark('getPublicKey', 1000, () => bls.getPublicKey(priv));
await mark('sign', 50, () => bls.sign('09', priv));
await mark('verify', 50, () => bls.verify(sig, '09', pub));
await mark('pairing', 100, () => bls.pairing(p1, p2));
const scalars1 = Array(4096).fill(0).map(i => 2n ** 235n - BigInt(i));
const scalars2 = Array(4096).fill(0).map(i => 2n ** 241n + BigInt(i));
const points = scalars1.map(s => bls.G1.ProjectivePoint.BASE.multiply(s));
await mark('MSM 4096 scalars x points', 1, () => {
// naive approach, not using multi-scalar-multiplication
let sum = bls.G1.ProjectivePoint.ZERO;
for (let i = 0; i < 4096; i++) {
const scalar = scalars2[i];
const G1 = points[i];
const mutliplied = G1.multiplyUnsafe(scalar);
sum = sum.add(mutliplied);
}
});
await mark('aggregatePublicKeys/8', 100, () => bls.aggregatePublicKeys(pubs.slice(0, 8)));
await mark('aggregatePublicKeys/32', 50, () => bls.aggregatePublicKeys(pub32));
await mark('aggregatePublicKeys/128', 20, () => bls.aggregatePublicKeys(pub128));
await mark('aggregatePublicKeys/512', 10, () => bls.aggregatePublicKeys(pub512));
await mark('aggregatePublicKeys/2048', 5, () => bls.aggregatePublicKeys(pub2048));
await mark('aggregateSignatures/8', 100, () => bls.aggregateSignatures(sigs.slice(0, 8)));
await mark('aggregateSignatures/32', 50, () => bls.aggregateSignatures(sig32));
await mark('aggregateSignatures/128', 20, () => bls.aggregateSignatures(sig128));
await mark('aggregateSignatures/512', 10, () => bls.aggregateSignatures(sig512));
await mark('aggregateSignatures/2048', 5, () => bls.aggregateSignatures(sig2048));
});

23
benchmark/curves.js Normal file
View File

@@ -0,0 +1,23 @@
import { run, mark, utils } from 'micro-bmark';
import { generateData } from './_shared.js';
import { p256 } from '../p256.js';
import { p384 } from '../p384.js';
import { p521 } from '../p521.js';
import { ed25519 } from '../ed25519.js';
import { ed448 } from '../ed448.js';
run(async () => {
const RAM = false
for (let kv of Object.entries({ ed25519, ed448, p256, p384, p521 })) {
const [name, curve] = kv;
console.log();
console.log(`\x1b[36m${name}\x1b[0m`);
if (RAM) utils.logMem();
await mark('init', 1, () => curve.utils.precompute(8));
const d = generateData(curve);
await mark('getPublicKey', 5000, () => curve.getPublicKey(d.priv));
await mark('sign', 5000, () => curve.sign(d.msg, d.priv));
await mark('verify', 500, () => curve.verify(d.sig, d.msg, d.pub));
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,29 @@
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 } from '../ed25519.js';
import { hashToCurve as ed448 } 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));
}
});

View File

@@ -1,398 +0,0 @@
import * as bench from 'micro-bmark';
const { run, mark } = bench; // or bench.mark
import { readFileSync } from 'fs';
// Curves
import { secp256k1 } from '../lib/secp256k1.js';
import { P256 } from '../lib/p256.js';
import { P384 } from '../lib/p384.js';
import { P521 } from '../lib/p521.js';
import { ed25519 } from '../lib/ed25519.js';
import { ed448 } from '../lib/ed448.js';
import { bls12_381 as bls } from '../lib/bls12-381.js';
// Others
import { hmac } from '@noble/hashes/hmac';
import { sha256 } from '@noble/hashes/sha256';
import { sha512 } from '@noble/hashes/sha512';
import * as old_secp from '@noble/secp256k1';
import * as old_bls from '@noble/bls12-381';
import { concatBytes, hexToBytes } from '@noble/hashes/utils';
import * as starkwareCrypto from '@starkware-industries/starkware-crypto-utils';
import * as stark from '../lib/stark.js';
old_secp.utils.sha256Sync = (...msgs) =>
sha256
.create()
.update(concatBytes(...msgs))
.digest();
old_secp.utils.hmacSha256Sync = (key, ...msgs) =>
hmac
.create(sha256, key)
.update(concatBytes(...msgs))
.digest();
import * as noble_ed25519 from '@noble/ed25519';
noble_ed25519.utils.sha512Sync = (...m) => sha512(concatBytes(...m));
// BLS
const G2_VECTORS = readFileSync('../test/bls12-381/bls12-381-g2-test-vectors.txt', 'utf-8')
.trim()
.split('\n')
.map((l) => l.split(':'));
let p1, p2, oldp1, oldp2;
// /BLS
for (let item of [secp256k1, ed25519, ed448, P256, P384, P521, old_secp, noble_ed25519]) {
item.utils.precompute(8);
}
const ONLY_NOBLE = process.argv[2] === 'noble';
function generateData(namespace) {
const priv = namespace.utils.randomPrivateKey();
const pub = namespace.getPublicKey(priv);
const msg = namespace.utils.randomPrivateKey();
const sig = namespace.sign(msg, priv);
return { priv, pub, msg, sig };
}
export const CURVES = {
secp256k1: {
data: () => {
return generateData(secp256k1);
},
getPublicKey1: {
samples: 10000,
secp256k1_old: () => old_secp.getPublicKey(3n),
secp256k1: () => secp256k1.getPublicKey(3n),
},
getPublicKey255: {
samples: 10000,
secp256k1_old: () => old_secp.getPublicKey(2n ** 255n - 1n),
secp256k1: () => secp256k1.getPublicKey(2n ** 255n - 1n),
},
sign: {
samples: 5000,
secp256k1_old: ({ msg, priv }) => old_secp.signSync(msg, priv),
secp256k1: ({ msg, priv }) => secp256k1.sign(msg, priv),
},
verify: {
samples: 1000,
secp256k1_old: ({ sig, msg, pub }) => {
return old_secp.verify(new old_secp.Signature(sig.r, sig.s), msg, pub);
},
secp256k1: ({ sig, msg, pub }) => secp256k1.verify(sig, msg, pub),
},
getSharedSecret: {
samples: 1000,
secp256k1_old: ({ pub, priv }) => old_secp.getSharedSecret(priv, pub),
secp256k1: ({ pub, priv }) => secp256k1.getSharedSecret(priv, pub),
},
recoverPublicKey: {
samples: 1000,
secp256k1_old: ({ sig, msg }) =>
old_secp.recoverPublicKey(msg, new old_secp.Signature(sig.r, sig.s), sig.recovery),
secp256k1: ({ sig, msg }) => sig.recoverPublicKey(msg),
},
},
ed25519: {
data: () => {
function to32Bytes(numOrStr) {
const hex = typeof numOrStr === 'string' ? numOrStr : numOrStr.toString(16);
return hexToBytes(hex.padStart(64, '0'));
}
const priv = to32Bytes(0x9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60n);
const pub = noble_ed25519.sync.getPublicKey(priv);
const msg = to32Bytes('deadbeefdeadbeefdeadbeefdeadbeefdeadbeef');
const sig = noble_ed25519.sync.sign(msg, priv);
return { pub, priv, msg, sig };
},
getPublicKey: {
samples: 10000,
old: () => noble_ed25519.sync.getPublicKey(noble_ed25519.utils.randomPrivateKey()),
noble: () => ed25519.getPublicKey(ed25519.utils.randomPrivateKey()),
},
sign: {
samples: 5000,
old: ({ msg, priv }) => noble_ed25519.sync.sign(msg, priv),
noble: ({ msg, priv }) => ed25519.sign(msg, priv),
},
verify: {
samples: 1000,
old: ({ sig, msg, pub }) => noble_ed25519.sync.verify(sig, msg, pub),
noble: ({ sig, msg, pub }) => ed25519.verify(sig, msg, pub),
},
},
ed448: {
data: () => {
const priv = ed448.utils.randomPrivateKey();
const pub = ed448.getPublicKey(priv);
const msg = ed448.utils.randomPrivateKey();
const sig = ed448.sign(msg, priv);
return { priv, pub, msg, sig };
},
getPublicKey: {
samples: 5000,
noble: () => ed448.getPublicKey(ed448.utils.randomPrivateKey()),
},
sign: {
samples: 2500,
noble: ({ msg, priv }) => ed448.sign(msg, priv),
},
verify: {
samples: 500,
noble: ({ sig, msg, pub }) => ed448.verify(sig, msg, pub),
},
},
nist: {
data: () => {
return { p256: generateData(P256), p384: generateData(P384), p521: generateData(P521) };
},
getPublicKey: {
samples: 2500,
P256: () => P256.getPublicKey(P256.utils.randomPrivateKey()),
P384: () => P384.getPublicKey(P384.utils.randomPrivateKey()),
P521: () => P521.getPublicKey(P521.utils.randomPrivateKey()),
},
sign: {
samples: 1000,
P256: ({ p256: { msg, priv } }) => P256.sign(msg, priv),
P384: ({ p384: { msg, priv } }) => P384.sign(msg, priv),
P521: ({ p521: { msg, priv } }) => P521.sign(msg, priv),
},
verify: {
samples: 250,
P256: ({ p256: { sig, msg, pub } }) => P256.verify(sig, msg, pub),
P384: ({ p384: { sig, msg, pub } }) => P384.verify(sig, msg, pub),
P521: ({ p521: { sig, msg, pub } }) => P521.verify(sig, msg, pub),
},
},
stark: {
data: () => {
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 };
},
pedersen: {
samples: 500,
old: () => {
return starkwareCrypto.default.pedersen([
'3d937c035c878245caf64531a5756109c53068da139362728feb561405371cb',
'208a0a10250e382e1e4bbe2880906c2791bf6275695e02fbbc6aeff9cd8b31a',
]);
},
noble: () => {
return stark.pedersen(
'3d937c035c878245caf64531a5756109c53068da139362728feb561405371cb',
'208a0a10250e382e1e4bbe2880906c2791bf6275695e02fbbc6aeff9cd8b31a'
);
},
},
verify: {
samples: 500,
old: ({ publicKeyStark, msgHash, keyPair }) => {
return starkwareCrypto.default.verify(
publicKeyStark,
msgHash,
starkwareCrypto.default.sign(keyPair, msgHash)
);
},
noble: ({ priv, msg, pub }) => {
return stark.verify(stark.sign(msg, priv), msg, pub);
},
},
},
'bls12-381': {
data: async () => {
const priv = '28b90deaf189015d3a325908c5e0e4bf00f84f7e639b056ff82d7e70b6eede4c';
const pubs = G2_VECTORS.map((v) => bls.getPublicKey(v[0]));
const sigs = G2_VECTORS.map((v) => v[2]);
const pub = bls.getPublicKey(priv);
const pub512 = pubs.slice(0, 512); // .map(bls.PointG1.fromHex)
const pub32 = pub512.slice(0, 32);
const pub128 = pub512.slice(0, 128);
const pub2048 = pub512.concat(pub512, pub512, pub512);
const sig512 = sigs.slice(0, 512); // .map(bls.PointG2.fromSignature);
const sig32 = sig512.slice(0, 32);
const sig128 = sig512.slice(0, 128);
const sig2048 = sig512.concat(sig512, sig512, sig512);
return {
priv,
pubs,
sigs,
pub,
pub512,
pub32,
pub128,
pub2048,
sig32,
sig128,
sig512,
sig2048,
};
},
init: {
samples: 1,
old: () => {
oldp1 =
old_bls.PointG1.BASE.multiply(
0x28b90deaf189015d3a325908c5e0e4bf00f84f7e639b056ff82d7e70b6eede4cn
);
oldp2 =
old_bls.PointG2.BASE.multiply(
0x28b90deaf189015d3a325908c5e0e4bf00f84f7e639b056ff82d7e70b6eede4dn
);
old_bls.pairing(oldp1, oldp2);
},
noble: () => {
p1 =
bls.G1.Point.BASE.multiply(
0x28b90deaf189015d3a325908c5e0e4bf00f84f7e639b056ff82d7e70b6eede4cn
);
p2 =
bls.G2.Point.BASE.multiply(
0x28b90deaf189015d3a325908c5e0e4bf00f84f7e639b056ff82d7e70b6eede4dn
);
bls.pairing(p1, p2);
},
},
'getPublicKey (1-bit)': {
samples: 1000,
old: () => old_bls.getPublicKey('2'.padStart(64, '0')),
noble: () => bls.getPublicKey('2'.padStart(64, '0')),
},
getPublicKey: {
samples: 1000,
old: ({ priv }) => old_bls.getPublicKey(priv),
noble: ({ priv }) => bls.getPublicKey(priv),
},
sign: {
samples: 50,
old: ({ priv }) => old_bls.sign('09', priv),
noble: ({ priv }) => bls.sign('09', priv),
},
verify: {
samples: 50,
old: ({ pub }) =>
old_bls.verify(
'8647aa9680cd0cdf065b94e818ff2bb948cc97838bcee987b9bc1b76d0a0a6e0d85db4e9d75aaedfc79d4ea2733a21ae0579014de7636dd2943d45b87c82b1c66a289006b0b9767921bb8edd3f6c5c5dec0d54cd65f61513113c50cc977849e5',
'09',
pub
),
noble: ({ pub }) =>
bls.verify(
'8647aa9680cd0cdf065b94e818ff2bb948cc97838bcee987b9bc1b76d0a0a6e0d85db4e9d75aaedfc79d4ea2733a21ae0579014de7636dd2943d45b87c82b1c66a289006b0b9767921bb8edd3f6c5c5dec0d54cd65f61513113c50cc977849e5',
'09',
pub
),
},
pairing: {
samples: 100,
old: () => old_bls.pairing(oldp1, oldp2),
noble: () => bls.pairing(p1, p2),
},
'hashToCurve/G1': {
samples: 500,
old: () => old_bls.PointG1.hashToCurve('abcd'),
noble: () => bls.G1.Point.hashToCurve('abcd'),
},
'hashToCurve/G2': {
samples: 200,
old: () => old_bls.PointG2.hashToCurve('abcd'),
noble: () => bls.G2.Point.hashToCurve('abcd'),
},
// SLOW PART
// Requires points which we cannot init before (data fn same for all)
// await mark('sign/nc', 30, () => bls.sign(msgp, priv));
// await mark('verify/nc', 30, () => bls.verify(sigp, msgp, pubp));
'aggregatePublicKeys/8': {
samples: 100,
old: ({ pubs }) => old_bls.aggregatePublicKeys(pubs.slice(0, 8)),
noble: ({ pubs }) => bls.aggregatePublicKeys(pubs.slice(0, 8)),
},
'aggregatePublicKeys/32': {
samples: 50,
old: ({ pub32 }) => old_bls.aggregatePublicKeys(pub32.map(old_bls.PointG1.fromHex)),
noble: ({ pub32 }) => bls.aggregatePublicKeys(pub32.map(bls.G1.Point.fromHex)),
},
'aggregatePublicKeys/128': {
samples: 20,
old: ({ pub128 }) => old_bls.aggregatePublicKeys(pub128.map(old_bls.PointG1.fromHex)),
noble: ({ pub128 }) => bls.aggregatePublicKeys(pub128.map(bls.G1.Point.fromHex)),
},
'aggregatePublicKeys/512': {
samples: 10,
old: ({ pub512 }) => old_bls.aggregatePublicKeys(pub512.map(old_bls.PointG1.fromHex)),
noble: ({ pub512 }) => bls.aggregatePublicKeys(pub512.map(bls.G1.Point.fromHex)),
},
'aggregatePublicKeys/2048': {
samples: 5,
old: ({ pub2048 }) => old_bls.aggregatePublicKeys(pub2048.map(old_bls.PointG1.fromHex)),
noble: ({ pub2048 }) => bls.aggregatePublicKeys(pub2048.map(bls.G1.Point.fromHex)),
},
'aggregateSignatures/8': {
samples: 50,
old: ({ sigs }) => old_bls.aggregateSignatures(sigs.slice(0, 8)),
noble: ({ sigs }) => bls.aggregateSignatures(sigs.slice(0, 8)),
},
'aggregateSignatures/32': {
samples: 10,
old: ({ sig32 }) => old_bls.aggregateSignatures(sig32.map(old_bls.PointG2.fromSignature)),
noble: ({ sig32 }) => bls.aggregateSignatures(sig32.map(bls.Signature.decode)),
},
'aggregateSignatures/128': {
samples: 5,
old: ({ sig128 }) => old_bls.aggregateSignatures(sig128.map(old_bls.PointG2.fromSignature)),
noble: ({ sig128 }) => bls.aggregateSignatures(sig128.map(bls.Signature.decode)),
},
'aggregateSignatures/512': {
samples: 3,
old: ({ sig512 }) => old_bls.aggregateSignatures(sig512.map(old_bls.PointG2.fromSignature)),
noble: ({ sig512 }) => bls.aggregateSignatures(sig512.map(bls.Signature.decode)),
},
'aggregateSignatures/2048': {
samples: 2,
old: ({ sig2048 }) => old_bls.aggregateSignatures(sig2048.map(old_bls.PointG2.fromSignature)),
noble: ({ sig2048 }) => bls.aggregateSignatures(sig2048.map(bls.Signature.decode)),
},
},
};
const main = () =>
run(async () => {
for (const [name, curve] of Object.entries(CURVES)) {
console.log(`==== ${name} ====`);
const data = await curve.data();
for (const [fnName, libs] of Object.entries(curve)) {
if (fnName === 'data') continue;
const samples = libs.samples;
console.log(` - ${fnName} (samples: ${samples})`);
for (const [lib, fn] of Object.entries(libs)) {
if (lib === 'samples') continue;
if (ONLY_NOBLE && lib !== 'noble') continue;
await mark(` ${lib}`, samples, () => fn(data));
}
}
}
// Log current RAM
bench.logMem();
});
// ESM is broken.
import url from 'url';
if (import.meta.url === url.pathToFileURL(process.argv[1]).href) {
main();
}

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

@@ -12,13 +12,10 @@
"author": "",
"license": "MIT",
"devDependencies": {
"micro-bmark": "0.2.0"
"micro-bmark": "0.3.0"
},
"dependencies": {
"@noble/bls12-381": "^1.4.0",
"@noble/ed25519": "^1.7.1",
"@noble/hashes": "^1.1.5",
"@noble/secp256k1": "^1.7.0",
"@starkware-industries/starkware-crypto-utils": "^0.0.2"
"elliptic": "^6.5.4"
}
}

22
benchmark/secp256k1.js Normal file
View File

@@ -0,0 +1,22 @@
import { run, mark, utils } from 'micro-bmark';
import { secp256k1, schnorr } from '../secp256k1.js';
import { generateData } from './_shared.js';
run(async () => {
const RAM = false;
if (RAM) utils.logMem();
console.log(`\x1b[36msecp256k1\x1b[0m`);
await mark('init', 1, () => secp256k1.utils.precompute(8));
const d = generateData(secp256k1);
await mark('getPublicKey', 10000, () => secp256k1.getPublicKey(d.priv));
await mark('sign', 10000, () => secp256k1.sign(d.msg, d.priv));
await mark('verify', 1000, () => secp256k1.verify(d.sig, d.msg, d.pub));
const pub2 = secp256k1.getPublicKey(secp256k1.utils.randomPrivateKey());
await mark('getSharedSecret', 1000, () => secp256k1.getSharedSecret(d.priv, pub2));
await mark('recoverPublicKey', 1000, () => d.sig.recoverPublicKey(d.msg));
const s = schnorr.sign(d.msg, d.priv);
const spub = schnorr.getPublicKey(d.priv);
await mark('schnorr.sign', 1000, () => schnorr.sign(d.msg, d.priv));
await mark('schnorr.verify', 1000, () => schnorr.verify(s, d.msg, spub));
if (RAM) utils.logMem();
});

7
build/README.md Normal file
View File

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

11
build/input.js Normal file
View File

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

18
build/package.json Normal file
View File

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

View File

@@ -2,6 +2,6 @@
"type": "module",
"browser": {
"crypto": false,
"./crypto": "./esm/cryptoBrowser.js"
"./crypto": "./esm/crypto.js"
}
}

109
package-lock.json generated Normal file
View File

@@ -0,0 +1,109 @@
{
"name": "@noble/curves",
"version": "1.1.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@noble/curves",
"version": "1.1.0",
"license": "MIT",
"dependencies": {
"@noble/hashes": "1.3.1"
},
"devDependencies": {
"fast-check": "3.0.0",
"micro-bmark": "0.3.1",
"micro-should": "0.4.0",
"prettier": "2.8.4",
"typescript": "5.0.2"
},
"funding": {
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/@noble/hashes": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.1.tgz",
"integrity": "sha512-EbqwksQwz9xDRGfDST86whPBgM65E0OH/pCgqW0GBVzO22bNE+NuIbeTb714+IfSjU3aRk47EUvXIb5bTsenKA==",
"engines": {
"node": ">= 16"
},
"funding": {
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/fast-check": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/fast-check/-/fast-check-3.0.0.tgz",
"integrity": "sha512-uujtrFJEQQqnIMO52ARwzPcuV4omiL1OJBUBLE9WnNFeu0A97sREXDOmCIHY+Z6KLVcemUf09rWr0q0Xy/Y/Ew==",
"dev": true,
"dependencies": {
"pure-rand": "^5.0.1"
},
"engines": {
"node": ">=8.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/fast-check"
}
},
"node_modules/micro-bmark": {
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/micro-bmark/-/micro-bmark-0.3.1.tgz",
"integrity": "sha512-bNaKObD4yPAAPrpEqp5jO6LJ2sEFgLoFSmRjEY809mJ62+2AehI/K3+RlVpN3Oo92RHpgC2RQhj6b1Tb4dmo+w==",
"dev": true
},
"node_modules/micro-should": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/micro-should/-/micro-should-0.4.0.tgz",
"integrity": "sha512-Vclj8yrngSYc9Y3dL2C+AdUlTkyx/syWc4R7LYfk4h7+icfF0DoUBGjjUIaEDzZA19RzoI+Hg8rW9IRoNGP0tQ==",
"dev": true
},
"node_modules/prettier": {
"version": "2.8.4",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.4.tgz",
"integrity": "sha512-vIS4Rlc2FNh0BySk3Wkd6xmwxB0FpOndW5fisM5H8hsZSxU2VWVB5CWIkIjWvrHjIhxk2g3bfMKM87zNTrZddw==",
"dev": true,
"bin": {
"prettier": "bin-prettier.js"
},
"engines": {
"node": ">=10.13.0"
},
"funding": {
"url": "https://github.com/prettier/prettier?sponsor=1"
}
},
"node_modules/pure-rand": {
"version": "5.0.5",
"resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-5.0.5.tgz",
"integrity": "sha512-BwQpbqxSCBJVpamI6ydzcKqyFmnd5msMWUGvzXLm1aXvusbbgkbOto/EUPM00hjveJEaJtdbhUjKSzWRhQVkaw==",
"dev": true,
"funding": [
{
"type": "individual",
"url": "https://github.com/sponsors/dubzzz"
},
{
"type": "opencollective",
"url": "https://opencollective.com/fast-check"
}
]
},
"node_modules/typescript": {
"version": "5.0.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.0.2.tgz",
"integrity": "sha512-wVORMBGO/FAs/++blGNeAVdbNKtIh1rbBL2EyQ1+J9lClJ93KiiKe8PmFIVdXhHcyv44SL9oglmfeSsndo0jRw==",
"dev": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
},
"engines": {
"node": ">=12.20"
}
}
}
}

View File

@@ -1,14 +1,21 @@
{
"name": "@noble/curves",
"version": "0.5.0",
"description": "Minimal, auditable JS implementation of elliptic curve cryptography",
"version": "1.1.0",
"description": "Audited & minimal JS implementation of elliptic curve cryptography",
"files": [
"lib"
"abstract",
"esm",
"src",
"*.js",
"*.js.map",
"*.d.ts",
"*.d.ts.map"
],
"scripts": {
"bench": "node benchmark/index.js",
"bench": "cd benchmark; node secp256k1.js; node curves.js; node ecdh.js; node hash-to-curve.js; node modular.js; node bls.js",
"build": "tsc && tsc -p tsconfig.esm.json",
"build:release": "rollup -c rollup.config.js",
"build:clean": "rm *.{js,d.ts,d.ts.map,js.map} esm/*.{js,d.ts,d.ts.map,js.map} 2> /dev/null",
"lint": "prettier --check 'src/**/*.{js,ts}' 'test/*.js'",
"format": "prettier --write 'src/**/*.{js,ts}' 'test/*.js'",
"test": "node test/index.test.js"
@@ -21,142 +28,126 @@
},
"license": "MIT",
"dependencies": {
"@noble/hashes": "1.1.5"
"@noble/hashes": "1.3.1"
},
"devDependencies": {
"@rollup/plugin-node-resolve": "13.3.0",
"@scure/base": "~1.1.1",
"@scure/bip32": "~1.1.1",
"@scure/bip39": "~1.1.0",
"@types/node": "18.11.3",
"fast-check": "3.0.0",
"micro-bmark": "0.2.0",
"micro-should": "0.2.0",
"prettier": "2.6.2",
"rollup": "2.75.5",
"typescript": "4.7.3"
"micro-bmark": "0.3.1",
"micro-should": "0.4.0",
"prettier": "2.8.4",
"typescript": "5.0.2"
},
"main": "index.js",
"exports": {
".": {
"types": "./lib/index.d.ts",
"import": "./lib/esm/index.js",
"default": "./lib/index.js"
"types": "./index.d.ts",
"import": "./esm/index.js",
"default": "./index.js"
},
"./abstract/edwards": {
"types": "./lib/abstract/edwards.d.ts",
"import": "./lib/esm/abstract/edwards.js",
"default": "./lib/abstract/edwards.js"
"types": "./abstract/edwards.d.ts",
"import": "./esm/abstract/edwards.js",
"default": "./abstract/edwards.js"
},
"./abstract/modular": {
"types": "./lib/abstract/modular.d.ts",
"import": "./lib/esm/abstract/modular.js",
"default": "./lib/abstract/modular.js"
"types": "./abstract/modular.d.ts",
"import": "./esm/abstract/modular.js",
"default": "./abstract/modular.js"
},
"./abstract/montgomery": {
"types": "./lib/abstract/montgomery.d.ts",
"import": "./lib/esm/abstract/montgomery.js",
"default": "./lib/abstract/montgomery.js"
"types": "./abstract/montgomery.d.ts",
"import": "./esm/abstract/montgomery.js",
"default": "./abstract/montgomery.js"
},
"./abstract/weierstrass": {
"types": "./lib/abstract/weierstrass.d.ts",
"import": "./lib/esm/abstract/weierstrass.js",
"default": "./lib/abstract/weierstrass.js"
"types": "./abstract/weierstrass.d.ts",
"import": "./esm/abstract/weierstrass.js",
"default": "./abstract/weierstrass.js"
},
"./abstract/bls": {
"types": "./lib/abstract/bls.d.ts",
"import": "./lib/esm/abstract/bls.js",
"default": "./lib/abstract/bls.js"
"types": "./abstract/bls.d.ts",
"import": "./esm/abstract/bls.js",
"default": "./abstract/bls.js"
},
"./abstract/hash-to-curve": {
"types": "./lib/abstract/hash-to-curve.d.ts",
"import": "./lib/esm/abstract/hash-to-curve.js",
"default": "./lib/abstract/hash-to-curve.js"
"types": "./abstract/hash-to-curve.d.ts",
"import": "./esm/abstract/hash-to-curve.js",
"default": "./abstract/hash-to-curve.js"
},
"./abstract/group": {
"types": "./lib/abstract/group.d.ts",
"import": "./lib/esm/abstract/group.js",
"default": "./lib/abstract/group.js"
"./abstract/curve": {
"types": "./abstract/curve.d.ts",
"import": "./esm/abstract/curve.js",
"default": "./abstract/curve.js"
},
"./abstract/utils": {
"types": "./lib/abstract/utils.d.ts",
"import": "./lib/esm/abstract/utils.js",
"default": "./lib/abstract/utils.js"
"types": "./abstract/utils.d.ts",
"import": "./esm/abstract/utils.js",
"default": "./abstract/utils.js"
},
"./abstract/poseidon": {
"types": "./abstract/poseidon.d.ts",
"import": "./esm/abstract/poseidon.js",
"default": "./abstract/poseidon.js"
},
"./_shortw_utils": {
"types": "./lib/_shortw_utils.d.ts",
"import": "./lib/esm/_shortw_utils.js",
"default": "./lib/_shortw_utils.js"
"types": "./_shortw_utils.d.ts",
"import": "./esm/_shortw_utils.js",
"default": "./_shortw_utils.js"
},
"./bls12-381": {
"types": "./lib/bls12-381.d.ts",
"import": "./lib/esm/bls12-381.js",
"default": "./lib/bls12-381.js"
"types": "./bls12-381.d.ts",
"import": "./esm/bls12-381.js",
"default": "./bls12-381.js"
},
"./bn": {
"types": "./lib/bn.d.ts",
"import": "./lib/esm/bn.js",
"default": "./lib/bn.js"
"./bn254": {
"types": "./bn254.d.ts",
"import": "./esm/bn254.js",
"default": "./bn254.js"
},
"./ed25519": {
"types": "./lib/ed25519.d.ts",
"import": "./lib/esm/ed25519.js",
"default": "./lib/ed25519.js"
"types": "./ed25519.d.ts",
"import": "./esm/ed25519.js",
"default": "./ed25519.js"
},
"./ed448": {
"types": "./lib/ed448.d.ts",
"import": "./lib/esm/ed448.js",
"default": "./lib/ed448.js"
"types": "./ed448.d.ts",
"import": "./esm/ed448.js",
"default": "./ed448.js"
},
"./index": {
"types": "./lib/index.d.ts",
"import": "./lib/esm/index.js",
"default": "./lib/index.js"
"types": "./index.d.ts",
"import": "./esm/index.js",
"default": "./index.js"
},
"./jubjub": {
"types": "./lib/jubjub.d.ts",
"import": "./lib/esm/jubjub.js",
"default": "./lib/jubjub.js"
},
"./p192": {
"types": "./lib/p192.d.ts",
"import": "./lib/esm/p192.js",
"default": "./lib/p192.js"
},
"./p224": {
"types": "./lib/p224.d.ts",
"import": "./lib/esm/p224.js",
"default": "./lib/p224.js"
"types": "./jubjub.d.ts",
"import": "./esm/jubjub.js",
"default": "./jubjub.js"
},
"./p256": {
"types": "./lib/p256.d.ts",
"import": "./lib/esm/p256.js",
"default": "./lib/p256.js"
"types": "./p256.d.ts",
"import": "./esm/p256.js",
"default": "./p256.js"
},
"./p384": {
"types": "./lib/p384.d.ts",
"import": "./lib/esm/p384.js",
"default": "./lib/p384.js"
"types": "./p384.d.ts",
"import": "./esm/p384.js",
"default": "./p384.js"
},
"./p521": {
"types": "./lib/p521.d.ts",
"import": "./lib/esm/p521.js",
"default": "./lib/p521.js"
"types": "./p521.d.ts",
"import": "./esm/p521.js",
"default": "./p521.js"
},
"./pasta": {
"types": "./lib/pasta.d.ts",
"import": "./lib/esm/pasta.js",
"default": "./lib/pasta.js"
"types": "./pasta.d.ts",
"import": "./esm/pasta.js",
"default": "./pasta.js"
},
"./secp256k1": {
"types": "./lib/secp256k1.d.ts",
"import": "./lib/esm/secp256k1.js",
"default": "./lib/secp256k1.js"
},
"./stark": {
"types": "./lib/stark.d.ts",
"import": "./lib/esm/stark.js",
"default": "./lib/stark.js"
"types": "./secp256k1.d.ts",
"import": "./esm/secp256k1.js",
"default": "./secp256k1.js"
}
},
"keywords": [
@@ -173,20 +164,17 @@
"secp256k1",
"ed25519",
"ed448",
"x25519",
"ed25519",
"bls12-381",
"bn254",
"pasta",
"bls",
"nist",
"noble",
"ecc",
"ecdsa",
"eddsa",
"schnorr"
],
"funding": [
{
"type": "individual",
"url": "https://paulmillr.com/funding/"
}
]
"funding": "https://paulmillr.com/funding/"
}

View File

@@ -4,6 +4,7 @@ import { concatBytes, randomBytes } from '@noble/hashes/utils';
import { weierstrass, CurveType } from './abstract/weierstrass.js';
import { CHash } from './abstract/utils.js';
// connects noble-curves to noble-hashes
export function getHash(hash: CHash) {
return {
hash,

View File

@@ -1,110 +1,133 @@
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
// Barreto-Lynn-Scott Curves. A family of pairing friendly curves, with embedding degree = 12 or 24
// NOTE: only 12 supported for now
// Constructed from pair of weierstrass curves, based pairing logic
import * as mod from './modular.js';
import { ensureBytes, numberToBytesBE, bytesToNumberBE, bitLen, bitGet } from './utils.js';
import * as utils from './utils.js';
// Types
import { hexToBytes, bytesToHex, Hex, PrivKey } from './utils.js';
import { htfOpts, stringToBytes, hash_to_field, expand_message_xmd } from './hash-to-curve.js';
import { CurvePointsType, PointType, CurvePointsRes, weierstrassPoints } from './weierstrass.js';
/**
* BLS (Barreto-Lynn-Scott) family of pairing-friendly curves.
* Implements BLS (Boneh-Lynn-Shacham) signatures.
* Consists of two curves: G1 and G2:
* - G1 is a subgroup of (x, y) E(Fq) over y² = x³ + 4.
* - G2 is a subgroup of ((x₁, x₂+i), (y₁, y₂+i)) E(Fq²) over y² = x³ + 4(1 + i) where i is √-1
* - Gt, created by bilinear (ate) pairing e(G1, G2), consists of p-th roots of unity in
* Fq^k where k is embedding degree. Only degree 12 is currently supported, 24 is not.
* Pairing is used to aggregate and verify signatures.
* We are using Fp for private keys (shorter) and Fp₂ for signatures (longer).
* Some projects may prefer to swap this relation, it is not supported for now.
*/
import { AffinePoint } from './curve.js';
import { IField, hashToPrivateScalar } from './modular.js';
import { Hex, PrivKey, CHash, bitLen, bitGet, ensureBytes } from './utils.js';
import * as htf from './hash-to-curve.js';
import {
CurvePointsType,
ProjPointType as ProjPointType,
CurvePointsRes,
weierstrassPoints,
} from './weierstrass.js';
type Fp = bigint; // Can be different field?
// prettier-ignore
const _2n = BigInt(2), _3n = BigInt(3);
export type SignatureCoder<Fp2> = {
decode(hex: Hex): PointType<Fp2>;
encode(point: PointType<Fp2>): Uint8Array;
fromHex(hex: Hex): ProjPointType<Fp2>;
toRawBytes(point: ProjPointType<Fp2>): Uint8Array;
toHex(point: ProjPointType<Fp2>): string;
};
export type CurveType<Fp, Fp2, Fp6, Fp12> = {
r: bigint;
G1: Omit<CurvePointsType<Fp>, 'n'>;
G1: Omit<CurvePointsType<Fp>, 'n'> & {
mapToCurve: htf.MapToCurve<Fp>;
htfDefaults: htf.Opts;
};
G2: Omit<CurvePointsType<Fp2>, 'n'> & {
Signature: SignatureCoder<Fp2>;
mapToCurve: htf.MapToCurve<Fp2>;
htfDefaults: htf.Opts;
};
x: bigint;
Fp: mod.Field<Fp>;
Fr: mod.Field<bigint>;
Fp2: mod.Field<Fp2> & {
fields: {
Fp: IField<Fp>;
Fr: IField<bigint>;
Fp2: IField<Fp2> & {
reim: (num: Fp2) => { re: bigint; im: bigint };
multiplyByB: (num: Fp2) => Fp2;
frobeniusMap(num: Fp2, power: number): Fp2;
};
Fp6: mod.Field<Fp6>;
Fp12: mod.Field<Fp12> & {
Fp6: IField<Fp6>;
Fp12: IField<Fp12> & {
frobeniusMap(num: Fp12, power: number): Fp12;
multiplyBy014(num: Fp12, o0: Fp2, o1: Fp2, o4: Fp2): Fp12;
conjugate(num: Fp12): Fp12;
finalExponentiate(num: Fp12): Fp12;
};
htfDefaults: htfOpts;
hash: utils.CHash; // Because we need outputLen for DRBG
};
params: {
x: bigint;
r: bigint;
};
htfDefaults: htf.Opts;
hash: CHash; // Because we need outputLen for DRBG
randomBytes: (bytesLength?: number) => Uint8Array;
};
export type CurveFn<Fp, Fp2, Fp6, Fp12> = {
CURVE: CurveType<Fp, Fp2, Fp6, Fp12>;
Fr: mod.Field<bigint>;
Fp: mod.Field<Fp>;
Fp2: mod.Field<Fp2>;
Fp6: mod.Field<Fp6>;
Fp12: mod.Field<Fp12>;
G1: CurvePointsRes<Fp>;
G2: CurvePointsRes<Fp2>;
Signature: SignatureCoder<Fp2>;
millerLoop: (ell: [Fp2, Fp2, Fp2][], g1: [Fp, Fp]) => Fp12;
calcPairingPrecomputes: (x: Fp2, y: Fp2) => [Fp2, Fp2, Fp2][];
pairing: (P: PointType<Fp>, Q: PointType<Fp2>, withFinalExponent?: boolean) => Fp12;
getPublicKey: (privateKey: PrivKey) => Uint8Array;
sign: {
(message: Hex, privateKey: PrivKey): Uint8Array;
(message: PointType<Fp2>, privateKey: PrivKey): PointType<Fp2>;
(message: ProjPointType<Fp2>, privateKey: PrivKey): ProjPointType<Fp2>;
};
verify: (
signature: Hex | PointType<Fp2>,
message: Hex | PointType<Fp2>,
publicKey: Hex | PointType<Fp>
signature: Hex | ProjPointType<Fp2>,
message: Hex | ProjPointType<Fp2>,
publicKey: Hex | ProjPointType<Fp>
) => boolean;
verifyBatch: (
signature: Hex | ProjPointType<Fp2>,
messages: (Hex | ProjPointType<Fp2>)[],
publicKeys: (Hex | ProjPointType<Fp>)[]
) => boolean;
aggregatePublicKeys: {
(publicKeys: Hex[]): Uint8Array;
(publicKeys: PointType<Fp>[]): PointType<Fp>;
(publicKeys: ProjPointType<Fp>[]): ProjPointType<Fp>;
};
aggregateSignatures: {
(signatures: Hex[]): Uint8Array;
(signatures: PointType<Fp2>[]): PointType<Fp2>;
(signatures: ProjPointType<Fp2>[]): ProjPointType<Fp2>;
};
millerLoop: (ell: [Fp2, Fp2, Fp2][], g1: [Fp, Fp]) => Fp12;
pairing: (P: ProjPointType<Fp>, Q: ProjPointType<Fp2>, withFinalExponent?: boolean) => Fp12;
G1: CurvePointsRes<Fp> & ReturnType<typeof htf.createHasher<Fp>>;
G2: CurvePointsRes<Fp2> & ReturnType<typeof htf.createHasher<Fp2>>;
Signature: SignatureCoder<Fp2>;
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>;
};
verifyBatch: (
signature: Hex | PointType<Fp2>,
messages: (Hex | PointType<Fp2>)[],
publicKeys: (Hex | PointType<Fp>)[]
) => boolean;
utils: {
bytesToHex: typeof utils.bytesToHex;
hexToBytes: typeof utils.hexToBytes;
stringToBytes: typeof stringToBytes;
hashToField: typeof hash_to_field;
expandMessageXMD: typeof expand_message_xmd;
mod: typeof mod.mod;
getDSTLabel: () => string;
setDSTLabel(newLabel: string): void;
randomPrivateKey: () => Uint8Array;
calcPairingPrecomputes: (p: AffinePoint<Fp2>) => [Fp2, Fp2, Fp2][];
};
};
export function bls<Fp2, Fp6, Fp12>(
CURVE: CurveType<Fp, Fp2, Fp6, Fp12>
): CurveFn<Fp, Fp2, Fp6, Fp12> {
// Fields looks pretty specific for curve, so for now we need to pass them with options
const Fp = CURVE.Fp;
const Fr = CURVE.Fr;
const Fp2 = CURVE.Fp2;
const Fp6 = CURVE.Fp6;
const Fp12 = CURVE.Fp12;
const BLS_X_LEN = bitLen(CURVE.x);
// Fields are specific for curve, so for now we'll need to pass them with opts
const { Fp, Fr, Fp2, Fp6, Fp12 } = CURVE.fields;
const BLS_X_LEN = bitLen(CURVE.params.x);
const groupLen = 32; // TODO: calculate; hardcoded for now
// Pre-compute coefficients for sparse multiplication
// Point addition and point double calculations is reused for coefficients
function calcPairingPrecomputes(x: Fp2, y: Fp2) {
function calcPairingPrecomputes(p: AffinePoint<Fp2>) {
const { x, y } = p;
// prettier-ignore
const Qx = x, Qy = y, Qz = Fp2.ONE;
// prettier-ignore
@@ -112,32 +135,32 @@ export function bls<Fp2, Fp6, Fp12>(
let ell_coeff: [Fp2, Fp2, Fp2][] = [];
for (let i = BLS_X_LEN - 2; i >= 0; i--) {
// Double
let t0 = Fp2.square(Ry); // Ry²
let t1 = Fp2.square(Rz); // Rz²
let t2 = Fp2.multiplyByB(Fp2.mul(t1, 3n)); // 3 * T1 * B
let t3 = Fp2.mul(t2, 3n); // 3 * T2
let t4 = Fp2.sub(Fp2.sub(Fp2.square(Fp2.add(Ry, Rz)), t1), t0); // (Ry + Rz)² - T1 - T0
let t0 = Fp2.sqr(Ry); // Ry²
let t1 = Fp2.sqr(Rz); // Rz²
let t2 = Fp2.multiplyByB(Fp2.mul(t1, _3n)); // 3 * T1 * B
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
ell_coeff.push([
Fp2.sub(t2, t0), // T2 - T0
Fp2.mul(Fp2.square(Rx), 3n), // 3 * Rx²
Fp2.negate(t4), // -T4
Fp2.mul(Fp2.sqr(Rx), _3n), // 3 * Rx²
Fp2.neg(t4), // -T4
]);
Rx = Fp2.div(Fp2.mul(Fp2.mul(Fp2.sub(t0, t3), Rx), Ry), 2n); // ((T0 - T3) * Rx * Ry) / 2
Ry = Fp2.sub(Fp2.square(Fp2.div(Fp2.add(t0, t3), 2n)), Fp2.mul(Fp2.square(t2), 3n)); // ((T0 + T3) / 2)² - 3 * T2²
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²
Rz = Fp2.mul(t0, t4); // T0 * T4
if (bitGet(CURVE.x, i)) {
if (bitGet(CURVE.params.x, i)) {
// Addition
let t0 = Fp2.sub(Ry, Fp2.mul(Qy, Rz)); // Ry - Qy * Rz
let t1 = Fp2.sub(Rx, Fp2.mul(Qx, Rz)); // Rx - Qx * Rz
ell_coeff.push([
Fp2.sub(Fp2.mul(t0, Qx), Fp2.mul(t1, Qy)), // T0 * Qx - T1 * Qy
Fp2.negate(t0), // -T0
Fp2.neg(t0), // -T0
t1, // T1
]);
let t2 = Fp2.square(t1); // T1²
let t2 = Fp2.sqr(t1); // T1²
let t3 = Fp2.mul(t2, t1); // T2 * T1
let t4 = Fp2.mul(t2, Rx); // T2 * Rx
let t5 = Fp2.add(Fp2.sub(t3, Fp2.mul(t4, 2n)), Fp2.mul(Fp2.square(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
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
@@ -147,102 +170,39 @@ export function bls<Fp2, Fp6, Fp12>(
}
function millerLoop(ell: [Fp2, Fp2, Fp2][], g1: [Fp, Fp]): Fp12 {
const { x } = CURVE.params;
const Px = g1[0];
const Py = g1[1];
let f12 = Fp12.ONE;
for (let j = 0, i = BLS_X_LEN - 2; i >= 0; i--, j++) {
const E = ell[j];
f12 = Fp12.multiplyBy014(f12, E[0], Fp2.mul(E[1], Px), Fp2.mul(E[2], Py));
if (bitGet(CURVE.x, i)) {
if (bitGet(x, i)) {
j += 1;
const F = ell[j];
f12 = Fp12.multiplyBy014(f12, F[0], Fp2.mul(F[1], Px), Fp2.mul(F[2], Py));
}
if (i !== 0) f12 = Fp12.square(f12);
if (i !== 0) f12 = Fp12.sqr(f12);
}
return Fp12.conjugate(f12);
}
// bls12-381 is a construction of two curves:
// 1. Fp: (x, y)
// 2. Fp₂: ((x₁, x₂+i), (y₁, y₂+i)) - (complex numbers)
//
// Bilinear Pairing (ate pairing) is used to combine both elements into a paired one:
// Fp₁₂ = e(Fp, Fp2)
// where Fp₁₂ = 12-degree polynomial
// Pairing is used to verify signatures.
//
// We are using Fp for private keys (shorter) and Fp2 for signatures (longer).
// Some projects may prefer to swap this relation, it is not supported for now.
const htfDefaults = { ...CURVE.htfDefaults };
function isWithinCurveOrder(num: bigint): boolean {
return 0 < num && num < CURVE.r;
}
const utils = {
hexToBytes: hexToBytes,
bytesToHex: bytesToHex,
mod: mod.mod,
stringToBytes,
// TODO: do we need to export it here?
hashToField: (msg: Uint8Array, count: number, options: Partial<typeof htfDefaults> = {}) =>
hash_to_field(msg, count, { ...CURVE.htfDefaults, ...options }),
expandMessageXMD: (msg: Uint8Array, DST: Uint8Array, lenInBytes: number, H = CURVE.hash) =>
expand_message_xmd(msg, DST, lenInBytes, H),
/**
* Can take 40 or more bytes of uniform input e.g. from CSPRNG or KDF
* and convert them into private key, with the modulo bias being negligible.
* As per FIPS 186 B.1.1.
* https://research.kudelskisecurity.com/2020/07/28/the-definitive-guide-to-modulo-bias-and-how-to-avoid-it/
* @param hash hash output from sha512, or a similar function
* @returns valid private key
*/
hashToPrivateKey: (hash: Hex): Uint8Array => {
hash = ensureBytes(hash);
if (hash.length < 40 || hash.length > 1024)
throw new Error('Expected 40-1024 bytes of private key as per FIPS 186');
// hashToPrivateScalar(hash, CURVE.r)
// NOTE: doesn't add +/-1
const num = mod.mod(bytesToNumberBE(hash), CURVE.r);
// This should never happen
if (num === 0n || num === 1n) throw new Error('Invalid private key');
return numberToBytesBE(num, 32);
},
randomBytes: (bytesLength: number = 32): Uint8Array => CURVE.randomBytes(bytesLength),
// NIST SP 800-56A rev 3, section 5.6.1.2.2
// https://research.kudelskisecurity.com/2020/07/28/the-definitive-guide-to-modulo-bias-and-how-to-avoid-it/
randomPrivateKey: (): Uint8Array => utils.hashToPrivateKey(utils.randomBytes(40)),
getDSTLabel: () => htfDefaults.DST,
setDSTLabel(newLabel: string) {
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-3.1
if (typeof newLabel !== 'string' || newLabel.length > 2048 || newLabel.length === 0) {
throw new TypeError('Invalid DST');
}
htfDefaults.DST = newLabel;
randomPrivateKey: (): Uint8Array => {
return Fr.toBytes(hashToPrivateScalar(CURVE.randomBytes(groupLen + 8), CURVE.params.r));
},
calcPairingPrecomputes,
};
function normalizePrivKey(key: PrivKey): bigint {
let int: bigint;
if (key instanceof Uint8Array && key.length === 32) int = bytesToNumberBE(key);
else if (typeof key === 'string' && key.length === 64) int = BigInt(`0x${key}`);
else if (typeof key === 'number' && key > 0 && Number.isSafeInteger(key)) int = BigInt(key);
else if (typeof key === 'bigint' && key > 0n) int = key;
else throw new TypeError('Expected valid private key');
int = mod.mod(int, CURVE.r);
if (!isWithinCurveOrder(int)) throw new Error('Private key must be 0 < key < CURVE.r');
return int;
}
// 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(
G1_,
htf.createHasher(G1_.ProjectivePoint, CURVE.G1.mapToCurve, {
...CURVE.htfDefaults,
...CURVE.G1.htfDefaults,
})
);
// Sparse multiplication against precomputed coefficients
// TODO: replace with weakmap?
@@ -250,83 +210,92 @@ export function bls<Fp2, Fp6, Fp12>(
function pairingPrecomputes(point: G2): [Fp2, Fp2, Fp2][] {
const p = point as G2 & withPairingPrecomputes;
if (p._PPRECOMPUTES) return p._PPRECOMPUTES;
p._PPRECOMPUTES = calcPairingPrecomputes(p.x, p.y);
p._PPRECOMPUTES = calcPairingPrecomputes(point.toAffine());
return p._PPRECOMPUTES;
}
function clearPairingPrecomputes(point: G2) {
const p = point as G2 & withPairingPrecomputes;
p._PPRECOMPUTES = undefined;
}
clearPairingPrecomputes;
function millerLoopG1(Q: G1, P: G2): Fp12 {
return millerLoop(pairingPrecomputes(P), [Q.x, Q.y]);
}
// TODO: export
// function clearPairingPrecomputes(point: G2) {
// const p = point as G2 & withPairingPrecomputes;
// p._PPRECOMPUTES = undefined;
// }
// Point on G2 curve (complex numbers): (x₁, x₂+i), (y₁, y₂+i)
const G2 = weierstrassPoints({
n: Fr.ORDER,
...CURVE.G2,
});
const G2_ = weierstrassPoints({ n: Fr.ORDER, ...CURVE.G2 });
const G2 = Object.assign(
G2_,
htf.createHasher(G2_.ProjectivePoint as htf.H2CPointConstructor<Fp2>, CURVE.G2.mapToCurve, {
...CURVE.htfDefaults,
...CURVE.G2.htfDefaults,
})
);
const { Signature } = CURVE.G2;
// Calculates bilinear pairing
function pairing(P: G1, Q: G2, withFinalExponent: boolean = true): Fp12 {
if (P.equals(G1.Point.ZERO) || Q.equals(G2.Point.ZERO))
throw new Error('No pairings at point of Infinity');
P.assertValidity();
function pairing(Q: G1, P: G2, withFinalExponent: boolean = true): Fp12 {
if (Q.equals(G1.ProjectivePoint.ZERO) || P.equals(G2.ProjectivePoint.ZERO))
throw new Error('pairing is not available for ZERO point');
Q.assertValidity();
P.assertValidity();
// Performance: 9ms for millerLoop and ~14ms for exp.
const looped = millerLoopG1(P, Q);
const Qa = Q.toAffine();
const looped = millerLoop(pairingPrecomputes(P), [Qa.x, Qa.y]);
return withFinalExponent ? Fp12.finalExponentiate(looped) : looped;
}
type G1 = typeof G1.Point.BASE;
type G2 = typeof G2.Point.BASE;
type G1 = typeof G1.ProjectivePoint.BASE;
type G2 = typeof G2.ProjectivePoint.BASE;
type G1Hex = Hex | G1;
type G2Hex = Hex | G2;
function normP1(point: G1Hex): G1 {
return point instanceof G1.Point ? (point as G1) : G1.Point.fromHex(point);
return point instanceof G1.ProjectivePoint ? (point as G1) : G1.ProjectivePoint.fromHex(point);
}
function normP2(point: G2Hex): G2 {
return point instanceof G2.Point ? point : Signature.decode(point);
return point instanceof G2.ProjectivePoint ? point : Signature.fromHex(point);
}
function normP2Hash(point: G2Hex): G2 {
return point instanceof G2.Point ? point : G2.Point.hashToCurve(point);
function normP2Hash(point: G2Hex, htfOpts?: htf.htfBasicOpts): G2 {
return point instanceof G2.ProjectivePoint
? point
: (G2.hashToCurve(ensureBytes('point', point), htfOpts) as G2);
}
// Multiplies generator by private key.
// P = pk x G
function getPublicKey(privateKey: PrivKey): Uint8Array {
return G1.Point.fromPrivateKey(privateKey).toRawBytes(true);
return G1.ProjectivePoint.fromPrivateKey(privateKey).toRawBytes(true);
}
// Executes `hashToCurve` on the message and then multiplies the result by private key.
// S = pk x H(m)
function sign(message: Hex, privateKey: PrivKey): Uint8Array;
function sign(message: G2, privateKey: PrivKey): G2;
function sign(message: G2Hex, privateKey: PrivKey): Uint8Array | G2 {
const msgPoint = normP2Hash(message);
function sign(message: Hex, privateKey: PrivKey, htfOpts?: htf.htfBasicOpts): Uint8Array;
function sign(message: G2, privateKey: PrivKey, htfOpts?: htf.htfBasicOpts): G2;
function sign(message: G2Hex, privateKey: PrivKey, htfOpts?: htf.htfBasicOpts): Uint8Array | G2 {
const msgPoint = normP2Hash(message, htfOpts);
msgPoint.assertValidity();
const sigPoint = msgPoint.multiply(normalizePrivKey(privateKey));
if (message instanceof G2.Point) return sigPoint;
return Signature.encode(sigPoint);
const sigPoint = msgPoint.multiply(G1.normPrivateKeyToScalar(privateKey));
if (message instanceof G2.ProjectivePoint) return sigPoint;
return Signature.toRawBytes(sigPoint);
}
// Checks if pairing of public key & hash is equal to pairing of generator & signature.
// e(P, H(m)) == e(G, S)
function verify(signature: G2Hex, message: G2Hex, publicKey: G1Hex): boolean {
function verify(
signature: G2Hex,
message: G2Hex,
publicKey: G1Hex,
htfOpts?: htf.htfBasicOpts
): boolean {
const P = normP1(publicKey);
const Hm = normP2Hash(message);
const G = G1.Point.BASE;
const Hm = normP2Hash(message, htfOpts);
const G = G1.ProjectivePoint.BASE;
const S = normP2(signature);
// Instead of doing 2 exponentiations, we use property of billinear maps
// and do one exp after multiplying 2 points.
const ePHm = pairing(P.negate(), Hm, false);
const eGS = pairing(G, S, false);
const exp = Fp12.finalExponentiate(Fp12.mul(eGS, ePHm));
return Fp12.equals(exp, Fp12.ONE);
return Fp12.eql(exp, Fp12.ONE);
}
// Adds a bunch of public key points together.
@@ -335,11 +304,9 @@ export function bls<Fp2, Fp6, Fp12>(
function aggregatePublicKeys(publicKeys: G1[]): G1;
function aggregatePublicKeys(publicKeys: G1Hex[]): Uint8Array | G1 {
if (!publicKeys.length) throw new Error('Expected non-empty array');
const agg = publicKeys
.map(normP1)
.reduce((sum, p) => sum.add(G1.ProjectivePoint.fromAffine(p)), G1.ProjectivePoint.ZERO);
const aggAffine = agg.toAffine();
if (publicKeys[0] instanceof G1.Point) {
const agg = publicKeys.map(normP1).reduce((sum, p) => sum.add(p), G1.ProjectivePoint.ZERO);
const aggAffine = agg; //.toAffine();
if (publicKeys[0] instanceof G1.ProjectivePoint) {
aggAffine.assertValidity();
return aggAffine;
}
@@ -352,25 +319,31 @@ export function bls<Fp2, Fp6, Fp12>(
function aggregateSignatures(signatures: G2[]): G2;
function aggregateSignatures(signatures: G2Hex[]): Uint8Array | G2 {
if (!signatures.length) throw new Error('Expected non-empty array');
const agg = signatures
.map(normP2)
.reduce((sum, s) => sum.add(G2.ProjectivePoint.fromAffine(s)), G2.ProjectivePoint.ZERO);
const aggAffine = agg.toAffine();
if (signatures[0] instanceof G2.Point) {
const agg = signatures.map(normP2).reduce((sum, s) => sum.add(s), G2.ProjectivePoint.ZERO);
const aggAffine = agg; //.toAffine();
if (signatures[0] instanceof G2.ProjectivePoint) {
aggAffine.assertValidity();
return aggAffine;
}
return Signature.encode(aggAffine);
return Signature.toRawBytes(aggAffine);
}
// https://ethresear.ch/t/fast-verification-of-multiple-bls-signatures/5407
// e(G, S) = e(G, SUM(n)(Si)) = MUL(n)(e(G, Si))
function verifyBatch(signature: G2Hex, messages: G2Hex[], publicKeys: G1Hex[]): boolean {
function verifyBatch(
signature: G2Hex,
messages: G2Hex[],
publicKeys: G1Hex[],
htfOpts?: htf.htfBasicOpts
): boolean {
// @ts-ignore
// console.log('verifyBatch', bytesToHex(signature as any), messages, publicKeys.map(bytesToHex));
if (!messages.length) throw new Error('Expected non-empty messages array');
if (publicKeys.length !== messages.length)
throw new Error('Pubkey count should equal msg count');
const sig = normP2(signature);
const nMessages = messages.map(normP2Hash);
const nMessages = messages.map((i) => normP2Hash(i, htfOpts));
const nPublicKeys = publicKeys.map(normP1);
try {
const paired = [];
@@ -378,42 +351,48 @@ export function bls<Fp2, Fp6, Fp12>(
const groupPublicKey = nMessages.reduce(
(groupPublicKey, subMessage, i) =>
subMessage === message ? groupPublicKey.add(nPublicKeys[i]) : groupPublicKey,
G1.Point.ZERO
G1.ProjectivePoint.ZERO
);
// const msg = message instanceof PointG2 ? message : await PointG2.hashToCurve(message);
// Possible to batch pairing for same msg with different groupPublicKey here
paired.push(pairing(groupPublicKey, message, false));
}
paired.push(pairing(G1.Point.BASE.negate(), sig, false));
paired.push(pairing(G1.ProjectivePoint.BASE.negate(), sig, false));
const product = paired.reduce((a, b) => Fp12.mul(a, b), Fp12.ONE);
const exp = Fp12.finalExponentiate(product);
return Fp12.equals(exp, Fp12.ONE);
return Fp12.eql(exp, Fp12.ONE);
} catch {
return false;
}
}
// Pre-compute points. Refer to README.
G1.Point.BASE._setWindowSize(4);
G1.ProjectivePoint.BASE._setWindowSize(4);
return {
CURVE,
getPublicKey,
sign,
verify,
verifyBatch,
aggregatePublicKeys,
aggregateSignatures,
millerLoop,
pairing,
G1,
G2,
Signature,
fields: {
Fr,
Fp,
Fp2,
Fp6,
Fp12,
G1,
G2,
Signature,
millerLoop,
calcPairingPrecomputes,
pairing,
getPublicKey,
sign,
verify,
aggregatePublicKeys,
aggregateSignatures,
verifyBatch,
},
params: {
x: CURVE.params.x,
r: CURVE.params.r,
G1b: CURVE.G1.b,
G2b: CURVE.G2.b,
},
utils,
};
}

View File

@@ -1,22 +1,41 @@
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
// Abelian group utilities
import { IField, validateField, nLength } from './modular.js';
import { validateObject } from './utils.js';
const _0n = BigInt(0);
const _1n = BigInt(1);
export type AffinePoint<T> = {
x: T;
y: T;
} & { z?: never; t?: never };
export interface Group<T extends Group<T>> {
double(): T;
negate(): T;
add(other: T): T;
subtract(other: T): T;
equals(other: T): boolean;
multiply(scalar: number | bigint): T;
multiply(scalar: bigint): T;
}
export type GroupConstructor<T> = {
BASE: T;
ZERO: T;
};
// Not big, but pretty complex and it is easy to break stuff. To avoid too much copy paste
export type Mapper<T> = (i: T[]) => T[];
// Elliptic curve multiplication of Point by scalar. Fragile.
// Scalars should always be less than curve order: this should be checked inside of a curve itself.
// Creates precomputation tables for fast multiplication:
// - private scalar is split by fixed size windows of W bits
// - every window point is collected from window's table & added to accumulator
// - since windows are different, same point inside tables won't be accessed more than once per calc
// - each multiplication is 'Math.ceil(CURVE_ORDER / 𝑊) + 1' point additions (fixed for any scalar)
// - +1 window is neccessary for wNAF
// - wNAF reduces table size: 2x less memory + 2x faster generation, but 10% slower multiplication
// TODO: Research returning 2d JS array of windows, instead of a single window. This would allow
// windows to be in different memory locations
export function wNAF<T extends Group<T>>(c: GroupConstructor<T>, bits: number) {
const constTimeNegate = (condition: boolean, item: T): T => {
const neg = item.negate();
@@ -44,8 +63,12 @@ export function wNAF<T extends Group<T>>(c: GroupConstructor<T>, bits: number) {
/**
* Creates a wNAF precomputation window. Used for caching.
* Default window size is set by `utils.precompute()` and is equal to 8.
* Which means we are caching 65536 points: 256 points for every bit from 0 to 256.
* @returns 65K precomputed points, depending on W
* Number of precomputed points depends on the curve size:
* 2^(𝑊1) * (Math.ceil(𝑛 / 𝑊) + 1), where:
* - 𝑊 is the window size
* - 𝑛 is the bitlength of the curve order.
* For a 256-bit curve and window size 8, the number of precomputed points is 128 * 33 = 4224.
* @returns precomputed point tables flattened to a single array
*/
precomputeWindow(elm: T, W: number): Group<T>[] {
const { windows, windowSize } = opts(W);
@@ -66,14 +89,14 @@ export function wNAF<T extends Group<T>>(c: GroupConstructor<T>, bits: number) {
},
/**
* Implements w-ary non-adjacent form for calculating ec multiplication.
* Implements ec multiplication using precomputed tables and w-ary non-adjacent form.
* @param W window size
* @param affinePoint optional 2d point to save cached precompute windows on it.
* @param n bits
* @param precomputes precomputed tables
* @param n scalar (we don't check here, but should be less than curve order)
* @returns real and fake (for const-time) points
*/
wNAF(W: number, precomputes: T[], n: bigint): { p: T; f: T } {
// TODO: maybe check that scalar is less than group order? wNAF will fail otherwise
// TODO: maybe check that scalar is less than group order? wNAF behavious is undefined otherwise
// But need to carefully remove other checks before wNAF. ORDER == bits here
const { windows, windowSize } = opts(W);
@@ -125,5 +148,56 @@ export function wNAF<T extends Group<T>>(c: GroupConstructor<T>, bits: number) {
// which makes it less const-time: around 1 bigint multiply.
return { p, f };
},
wNAFCached(P: T, precomputesMap: Map<T, T[]>, n: bigint, transform: Mapper<T>): { p: T; f: T } {
// @ts-ignore
const W: number = P._WINDOW_SIZE || 1;
// Calculate precomputes on a first run, reuse them after
let comp = precomputesMap.get(P);
if (!comp) {
comp = this.precomputeWindow(P, W) as T[];
if (W !== 1) {
precomputesMap.set(P, transform(comp));
}
}
return this.wNAF(W, comp, n);
},
};
}
// Generic BasicCurve interface: works even for polynomial fields (BLS): P, n, h would be ok.
// Though generator can be different (Fp2 / Fp6 for BLS).
export type BasicCurve<T> = {
Fp: IField<T>; // Field over which we'll do calculations (Fp)
n: bigint; // Curve order, total count of valid points in the field
nBitLength?: number; // bit length of curve order
nByteLength?: number; // byte length of curve order
h: bigint; // cofactor. we can assign default=1, but users will just ignore it w/o validation
hEff?: bigint; // Number to multiply to clear cofactor
Gx: T; // base point X coordinate
Gy: T; // base point Y coordinate
allowInfinityPoint?: boolean; // bls12-381 requires it. ZERO point is valid, but invalid pubkey
};
export function validateBasic<FP, T>(curve: BasicCurve<FP> & T) {
validateField(curve.Fp);
validateObject(
curve,
{
n: 'bigint',
h: 'bigint',
Gx: 'field',
Gy: 'field',
},
{
nBitLength: 'isSafeInteger',
nByteLength: 'isSafeInteger',
}
);
// Set defaults
return Object.freeze({
...nLength(curve.n, curve.nBitLength),
...curve,
...{ p: curve.Fp.ORDER },
} as const);
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,58 +1,36 @@
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
import { CHash, concatBytes } from './utils.js';
import * as mod from './modular.js';
import type { Group, GroupConstructor, AffinePoint } from './curve.js';
import { mod, IField } from './modular.js';
import { bytesToNumberBE, CHash, concatBytes, utf8ToBytes, validateObject } from './utils.js';
export type htfOpts = {
// DST: a domain separation tag
// defined in section 2.2.5
DST: string;
// p: the characteristic of F
// where F is a finite field of characteristic p and order q = p^m
/**
* * `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 = {
DST: UnicodeOrBytes;
p: bigint;
// m: the extension degree of F, m >= 1
// where F is a finite field of characteristic p and order q = p^m
m: number;
// k: the target security level for the suite in bits
// defined in section 5.1
k: number;
// option to use a message that has already been processed by
// expand_message_xmd
expand: boolean;
// 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
expand: 'xmd' | 'xof';
hash: CHash;
};
export function validateHTFOpts(opts: htfOpts) {
if (typeof opts.DST !== 'string') throw new Error('Invalid htf/DST');
if (typeof opts.p !== 'bigint') throw new Error('Invalid htf/p');
if (typeof opts.m !== 'number') throw new Error('Invalid htf/m');
if (typeof opts.k !== 'number') throw new Error('Invalid htf/k');
if (typeof opts.expand !== 'boolean') throw new Error('Invalid htf/expand');
if (typeof opts.hash !== 'function' || !Number.isSafeInteger(opts.hash.outputLen))
throw new Error('Invalid htf/hash function');
function validateDST(dst: UnicodeOrBytes): Uint8Array {
if (dst instanceof Uint8Array) return dst;
if (typeof dst === 'string') return utf8ToBytes(dst);
throw new Error('DST must be Uint8Array or string');
}
// UTF8 to ui8a
// TODO: looks broken, ASCII only, why not TextEncoder/TextDecoder? it is in hashes anyway
export function stringToBytes(str: string) {
const bytes = new Uint8Array(str.length);
for (let i = 0; i < str.length; i++) bytes[i] = str.charCodeAt(i);
return bytes;
}
// Octet Stream to Integer. "spec" implementation of os2ip is 2.5x slower vs bytesToNumberBE.
const os2ip = bytesToNumberBE;
// Octet Stream to Integer (bytesToNumberBE)
function os2ip(bytes: Uint8Array): bigint {
let result = 0n;
for (let i = 0; i < bytes.length; i++) {
result <<= 8n;
result += BigInt(bytes[i]);
}
return result;
}
// Integer to Octet Stream
// Integer to Octet Stream (numberToBytesBE)
function i2osp(value: number, length: number): Uint8Array {
if (value < 0 || value >= 1 << (8 * length)) {
throw new Error(`bad I2OSP call: value=${value} length=${length}`);
@@ -73,6 +51,13 @@ function strxor(a: Uint8Array, b: Uint8Array): Uint8Array {
return arr;
}
function isBytes(item: unknown): void {
if (!(item instanceof Uint8Array)) throw new Error('Uint8Array expected');
}
function isNum(item: unknown): void {
if (!Number.isSafeInteger(item)) throw new Error('number expected');
}
// Produces a uniformly random byte string using a cryptographic hash function H that outputs b bits
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-5.4.1
export function expand_message_xmd(
@@ -81,15 +66,17 @@ export function expand_message_xmd(
lenInBytes: number,
H: CHash
): Uint8Array {
isBytes(msg);
isBytes(DST);
isNum(lenInBytes);
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-16#section-5.3.3
if (DST.length > 255) DST = H(concatBytes(stringToBytes('H2C-OVERSIZE-DST-'), DST));
const b_in_bytes = H.outputLen;
const r_in_bytes = H.blockLen;
if (DST.length > 255) DST = H(concatBytes(utf8ToBytes('H2C-OVERSIZE-DST-'), DST));
const { outputLen: b_in_bytes, blockLen: r_in_bytes } = H;
const ell = Math.ceil(lenInBytes / b_in_bytes);
if (ell > 255) throw new Error('Invalid xmd length');
const DST_prime = concatBytes(DST, i2osp(DST.length, 1));
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_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));
@@ -101,38 +88,83 @@ export function expand_message_xmd(
return pseudo_random_bytes.slice(0, lenInBytes);
}
// 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
// Inputs:
// msg - a byte string containing the message to hash.
// count - the number of elements of F to output.
// Outputs:
// [u_0, ..., u_(count - 1)], a list of field elements.
export function hash_to_field(msg: Uint8Array, count: number, options: htfOpts): bigint[][] {
// if options is provided but incomplete, fill any missing fields with the
// value in hftDefaults (ie hash to G2).
const log2p = options.p.toString(2).length;
const L = Math.ceil((log2p + options.k) / 8); // section 5.1 of ietf draft link above
const len_in_bytes = count * options.m * L;
const DST = stringToBytes(options.DST);
let pseudo_random_bytes = msg;
if (options.expand) {
pseudo_random_bytes = expand_message_xmd(msg, DST, len_in_bytes, options.hash);
export function expand_message_xof(
msg: Uint8Array,
DST: Uint8Array,
lenInBytes: number,
k: number,
H: CHash
): Uint8Array {
isBytes(msg);
isBytes(DST);
isNum(lenInBytes);
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-16#section-5.3.3
// DST = H('H2C-OVERSIZE-DST-' || a_very_long_DST, Math.ceil((lenInBytes * k) / 8));
if (DST.length > 255) {
const dkLen = Math.ceil((2 * k) / 8);
DST = H.create({ dkLen }).update(utf8ToBytes('H2C-OVERSIZE-DST-')).update(DST).digest();
}
if (lenInBytes > 65535 || DST.length > 255)
throw new Error('expand_message_xof: invalid lenInBytes');
return (
H.create({ dkLen: lenInBytes })
.update(msg)
.update(i2osp(lenInBytes, 2))
// 2. DST_prime = DST || I2OSP(len(DST), 1)
.update(DST)
.update(i2osp(DST.length, 1))
.digest()
);
}
/**
* Hashes arbitrary-length byte strings to a list of one or more elements of a finite field F
* https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-5.3
* @param msg a byte string containing the message to hash
* @param count the number of elements of F to output
* @param options `{DST: string, p: bigint, m: number, k: number, expand: 'xmd' | 'xof', hash: H}`, see above
* @returns [u_0, ..., u_(count - 1)], a list of field elements.
*/
export function hash_to_field(msg: Uint8Array, count: number, options: Opts): bigint[][] {
validateObject(options, {
DST: 'string',
p: 'bigint',
m: 'isSafeInteger',
k: 'isSafeInteger',
hash: 'hash',
});
const { p, k, m, hash, expand, DST: _DST } = options;
isBytes(msg);
isNum(count);
const DST = validateDST(_DST);
const log2p = p.toString(2).length;
const L = Math.ceil((log2p + k) / 8); // section 5.1 of ietf draft link above
const len_in_bytes = count * m * L;
let prb; // pseudo_random_bytes
if (expand === 'xmd') {
prb = expand_message_xmd(msg, DST, len_in_bytes, hash);
} else if (expand === 'xof') {
prb = expand_message_xof(msg, DST, len_in_bytes, k, hash);
} else if (expand === '_internal_pass') {
// for internal tests only
prb = msg;
} else {
throw new Error('expand must be "xmd" or "xof"');
}
const u = new Array(count);
for (let i = 0; i < count; i++) {
const e = new Array(options.m);
for (let j = 0; j < options.m; j++) {
const elm_offset = L * (j + i * options.m);
const tv = pseudo_random_bytes.subarray(elm_offset, elm_offset + L);
e[j] = mod.mod(os2ip(tv), options.p);
const e = new Array(m);
for (let j = 0; j < m; j++) {
const elm_offset = L * (j + i * m);
const tv = prb.subarray(elm_offset, elm_offset + L);
e[j] = mod(os2ip(tv), p);
}
u[i] = e;
}
return u;
}
export function isogenyMap<T, F extends mod.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
const COEFF = map.map((i) => Array.from(i).reverse());
return (x: T, y: T) => {
@@ -144,3 +176,48 @@ export function isogenyMap<T, F extends mod.Field<T>>(field: F, map: [T[], T[],
return { x, y };
};
}
export interface H2CPoint<T> extends Group<H2CPoint<T>> {
add(rhs: H2CPoint<T>): H2CPoint<T>;
toAffine(iz?: bigint): AffinePoint<T>;
clearCofactor(): H2CPoint<T>;
assertValidity(): void;
}
export interface H2CPointConstructor<T> extends GroupConstructor<H2CPoint<T>> {
fromAffine(ap: AffinePoint<T>): H2CPoint<T>;
}
export type MapToCurve<T> = (scalar: bigint[]) => AffinePoint<T>;
// Separated from initialization opts, so users won't accidentally change per-curve parameters
// (changing DST is ok!)
export type htfBasicOpts = { DST: UnicodeOrBytes };
export function createHasher<T>(
Point: H2CPointConstructor<T>,
mapToCurve: MapToCurve<T>,
def: Opts & { encodeDST?: UnicodeOrBytes }
) {
if (typeof mapToCurve !== 'function') throw new Error('mapToCurve() must be defined');
return {
// Encodes byte string to elliptic curve
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-16#section-3
hashToCurve(msg: Uint8Array, options?: htfBasicOpts) {
const u = hash_to_field(msg, 2, { ...def, DST: def.DST, ...options } as Opts);
const u0 = Point.fromAffine(mapToCurve(u[0]));
const u1 = Point.fromAffine(mapToCurve(u[1]));
const P = u0.add(u1).clearCofactor();
P.assertValidity();
return P;
},
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-16#section-3
encodeToCurve(msg: Uint8Array, options?: htfBasicOpts) {
const u = hash_to_field(msg, 1, { ...def, DST: def.encodeDST, ...options } as Opts);
const P = Point.fromAffine(mapToCurve(u[0])).clearCofactor();
P.assertValidity();
return P;
},
};
}

View File

@@ -1,10 +1,18 @@
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
import * as utils from './utils.js';
// Utilities for modular arithmetics and finite fields
import {
bitMask,
numberToBytesBE,
numberToBytesLE,
bytesToNumberBE,
bytesToNumberLE,
ensureBytes,
validateObject,
} from './utils.js';
// prettier-ignore
const _0n = BigInt(0), _1n = BigInt(1), _2n = BigInt(2), _3n = BigInt(3);
// prettier-ignore
const _4n = BigInt(4), _5n = BigInt(5), _7n = BigInt(7), _8n = BigInt(8);
const _4n = BigInt(4), _5n = BigInt(5), _8n = BigInt(8);
// prettier-ignore
const _9n = BigInt(9), _16n = BigInt(16);
@@ -14,10 +22,10 @@ export function mod(a: bigint, b: bigint): bigint {
return result >= _0n ? result : b + result;
}
/**
* Efficiently exponentiate num to power and do modular division.
* Efficiently raise num to power and do modular division.
* Unsafe in some contexts: uses ladder, so can expose bigint bits.
* @example
* powMod(2n, 6n, 11n) // 64n % 11n == 9n
* pow(2n, 6n, 11n) // 64n % 11n == 9n
*/
// TODO: use field version && remove
export function pow(num: bigint, power: bigint, modulo: bigint): bigint {
@@ -33,7 +41,6 @@ export function pow(num: bigint, power: bigint, modulo: bigint): bigint {
}
// Does x ^ (2 ^ power) mod p. pow2(30, 4) == 30 ^ (2 ^ 4)
// TODO: Fp version?
export function pow2(x: bigint, power: bigint, modulo: bigint): bigint {
let res = x;
while (power-- > _0n) {
@@ -48,12 +55,14 @@ export function invert(number: bigint, modulo: bigint): bigint {
if (number === _0n || modulo <= _0n) {
throw new Error(`invert: expected positive integers, got n=${number} mod=${modulo}`);
}
// Eucledian GCD https://brilliant.org/wiki/extended-euclidean-algorithm/
// Euclidean GCD https://brilliant.org/wiki/extended-euclidean-algorithm/
// Fermat's little theorem "CT-like" version inv(n) = n^(m-2) mod m is 30x slower.
let a = mod(number, modulo);
let b = modulo;
// prettier-ignore
let x = _0n, y = _1n, u = _1n, v = _0n;
while (a !== _0n) {
// JIT applies optimization if those two lines follow each other
const q = b / a;
const r = b % a;
const m = x - u * q;
@@ -66,26 +75,68 @@ export function invert(number: bigint, modulo: bigint): bigint {
return mod(x, modulo);
}
/**
* Calculates Legendre symbol (a | p), which denotes the value of a^((p-1)/2) (mod p).
* * (a | p) ≡ 1 if a is a square (mod p)
* * (a | p) ≡ -1 if a is not a square (mod p)
* * (a | p) ≡ 0 if a ≡ 0 (mod p)
*/
export function legendre(num: bigint, fieldPrime: bigint): bigint {
return pow(num, (fieldPrime - _1n) / _2n, fieldPrime);
// Tonelli-Shanks algorithm
// Paper 1: https://eprint.iacr.org/2012/685.pdf (page 12)
// Paper 2: Square Roots from 1; 24, 51, 10 to Dan Shanks
export function tonelliShanks(P: bigint) {
// Legendre constant: used to calculate Legendre symbol (a | p),
// which denotes the value of a^((p-1)/2) (mod p).
// (a | p) ≡ 1 if a is a square (mod p)
// (a | p) ≡ -1 if a is not a square (mod p)
// (a | p) ≡ 0 if a ≡ 0 (mod p)
const legendreC = (P - _1n) / _2n;
let Q: bigint, S: number, Z: bigint;
// Step 1: By factoring out powers of 2 from p - 1,
// find q and s such that p - 1 = q*(2^s) with q odd
for (Q = P - _1n, S = 0; Q % _2n === _0n; Q /= _2n, S++);
// Step 2: Select a non-square z such that (z | p) ≡ -1 and set c ≡ zq
for (Z = _2n; Z < P && pow(Z, legendreC, P) !== P - _1n; Z++);
// Fast-path
if (S === 1) {
const p1div4 = (P + _1n) / _4n;
return function tonelliFast<T>(Fp: IField<T>, n: T) {
const root = Fp.pow(n, p1div4);
if (!Fp.eql(Fp.sqr(root), n)) throw new Error('Cannot find square root');
return root;
};
}
// Slow-path
const Q1div2 = (Q + _1n) / _2n;
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
if (Fp.pow(n, legendreC) === Fp.neg(Fp.ONE)) throw new Error('Cannot find square root');
let r = S;
// TODO: will fail at Fp2/etc
let g = Fp.pow(Fp.mul(Fp.ONE, Z), Q); // will update both x and b
let x = Fp.pow(n, Q1div2); // first guess at the square root
let b = Fp.pow(n, Q); // first guess at the fudge factor
while (!Fp.eql(b, Fp.ONE)) {
if (Fp.eql(b, Fp.ZERO)) return Fp.ZERO; // https://en.wikipedia.org/wiki/Tonelli%E2%80%93Shanks_algorithm (4. If t = 0, return r = 0)
// Find m such b^(2^m)==1
let m = 1;
for (let t2 = Fp.sqr(b); m < r; m++) {
if (Fp.eql(t2, Fp.ONE)) break;
t2 = Fp.sqr(t2); // t2 *= t2
}
// NOTE: r-m-1 can be bigger than 32, need to convert to bigint before shift, otherwise there will be overflow
const ge = Fp.pow(g, _1n << BigInt(r - m - 1)); // ge = 2^(r-m-1)
g = Fp.sqr(ge); // g = ge * ge
x = Fp.mul(x, ge); // x *= ge
b = Fp.mul(b, g); // b *= g
r = m;
}
return x;
};
}
/**
* Calculates square root of a number in a finite field.
* √a mod P
*/
// TODO: rewrite as generic Fp function && remove bls versions
export function sqrt(number: bigint, modulo: bigint): bigint {
// prettier-ignore
const n = number;
const P = modulo;
const p1div4 = (P + _1n) / _4n;
export function FpSqrt(P: bigint) {
// NOTE: different algorithms can give different roots, it is up to user to decide which one they want.
// For example there is FpSqrtOdd/FpSqrtEven to choice root based on oddness (used for hash-to-curve).
// P ≡ 3 (mod 4)
// √n = n^((P+1)/4)
@@ -94,59 +145,61 @@ export function sqrt(number: bigint, modulo: bigint): bigint {
// const ORDER =
// 0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaabn;
// const NUM = 72057594037927816n;
// TODO: fix sqrtMod in secp256k1
const root = pow(n, p1div4, P);
if (mod(root * root, modulo) !== number) throw new Error('Cannot find square root');
const p1div4 = (P + _1n) / _4n;
return function sqrt3mod4<T>(Fp: IField<T>, n: T) {
const root = Fp.pow(n, p1div4);
// Throw if root**2 != n
if (!Fp.eql(Fp.sqr(root), n)) throw new Error('Cannot find square root');
return root;
};
}
// P ≡ 5 (mod 8)
// Atkin algorithm for q ≡ 5 (mod 8), https://eprint.iacr.org/2012/685.pdf (page 10)
if (P % _8n === _5n) {
const n2 = mod(n * _2n, P);
const v = pow(n2, (P - _5n) / _8n, P);
const nv = mod(n * v, P);
const i = mod(_2n * nv * v, P);
const r = mod(nv * (i - _1n), P);
return r;
const c1 = (P - _5n) / _8n;
return function sqrt5mod8<T>(Fp: IField<T>, n: T) {
const n2 = Fp.mul(n, _2n);
const v = Fp.pow(n2, c1);
const nv = Fp.mul(n, v);
const i = Fp.mul(Fp.mul(nv, _2n), v);
const root = Fp.mul(nv, Fp.sub(i, Fp.ONE));
if (!Fp.eql(Fp.sqr(root), n)) throw new Error('Cannot find square root');
return root;
};
}
// P ≡ 9 (mod 16)
if (P % _16n === _9n) {
// NOTE: tonelli is too slow for bls-Fp2 calculations even on start
// Means we cannot use sqrt for constants at all!
//
// const c1 = Fp.sqrt(Fp.negate(Fp.ONE)); // 1. c1 = sqrt(-1) in F, i.e., (c1^2) == -1 in F
// const c2 = Fp.sqrt(c1); // 2. c2 = sqrt(c1) in F, i.e., (c2^2) == c1 in F
// const c3 = Fp.sqrt(Fp.negate(c1)); // 3. c3 = sqrt(-c1) in F, i.e., (c3^2) == -c1 in F
// const c4 = (P + _7n) / _16n; // 4. c4 = (q + 7) / 16 # Integer arithmetic
// sqrt = (x) => {
// let tv1 = Fp.pow(x, c4); // 1. tv1 = x^c4
// let tv2 = Fp.mul(c1, tv1); // 2. tv2 = c1 * tv1
// const tv3 = Fp.mul(c2, tv1); // 3. tv3 = c2 * tv1
// let tv4 = Fp.mul(c3, tv1); // 4. tv4 = c3 * tv1
// const e1 = Fp.equals(Fp.square(tv2), x); // 5. e1 = (tv2^2) == x
// const e2 = Fp.equals(Fp.square(tv3), x); // 6. e2 = (tv3^2) == x
// tv1 = Fp.cmov(tv1, tv2, e1); // 7. tv1 = CMOV(tv1, tv2, e1) # Select tv2 if (tv2^2) == x
// tv2 = Fp.cmov(tv4, tv3, e2); // 8. tv2 = CMOV(tv4, tv3, e2) # Select tv3 if (tv3^2) == x
// const e3 = Fp.equals(Fp.square(tv2), x); // 9. e3 = (tv2^2) == x
// return Fp.cmov(tv1, tv2, e3); // 10. z = CMOV(tv1, tv2, e3) # Select the sqrt from tv1 and tv2
// }
}
// Other cases: Tonelli-Shanks algorithm
if (legendre(n, P) !== _1n) throw new Error('Cannot find square root');
let q: bigint, s: number, z: bigint;
for (q = P - _1n, s = 0; q % _2n === _0n; q /= _2n, s++);
if (s === 1) return pow(n, p1div4, P);
for (z = _2n; z < P && legendre(z, P) !== P - _1n; z++);
let c = pow(z, q, P);
let r = pow(n, (q + _1n) / _2n, P);
let t = pow(n, q, P);
let t2 = _0n;
while (mod(t - _1n, P) !== _0n) {
t2 = mod(t * t, P);
let i;
for (i = 1; i < s; i++) {
if (mod(t2 - _1n, P) === _0n) break;
t2 = mod(t2 * t2, P);
}
let b = pow(c, BigInt(1 << (s - i - 1)), P);
r = mod(r * b, P);
c = mod(b * b, P);
t = mod(t * c, P);
s = i;
}
return r;
return tonelliShanks(P);
}
// Little-endian check for first LE bit (last BE bit);
export const isNegativeLE = (num: bigint, modulo: bigint) => (mod(num, modulo) & _1n) === _1n;
// Currently completly inconsistent naming:
// - readable: add, mul, sqr, sqrt, inv, div, pow, eq, sub
// - unreadable mess: addition, multiply, square, squareRoot, inversion, divide, power, equals, subtract
// Field is not always over prime, Fp2 for example has ORDER(q)=p^m
export interface Field<T> {
export interface IField<T> {
ORDER: bigint;
BYTES: number;
BITS: number;
@@ -156,13 +209,13 @@ export interface Field<T> {
// 1-arg
create: (num: T) => T;
isValid: (num: T) => boolean;
isZero: (num: T) => boolean;
negate(num: T): T;
invert(num: T): T;
is0: (num: T) => boolean;
neg(num: T): T;
inv(num: T): T;
sqrt(num: T): T;
square(num: T): T;
sqr(num: T): T;
// 2-args
equals(lhs: T, rhs: T): boolean;
eql(lhs: T, rhs: T): boolean;
add(lhs: T, rhs: T): T;
sub(lhs: T, rhs: T): T;
mul(lhs: T, rhs: T | bigint): T;
@@ -172,12 +225,13 @@ export interface Field<T> {
addN(lhs: T, rhs: T): T;
subN(lhs: T, rhs: T): T;
mulN(lhs: T, rhs: T | bigint): T;
squareN(num: T): T;
sqrN(num: T): T;
// Optional
// Should be same as sgn0 function in https://datatracker.ietf.org/doc/draft-irtf-cfrg-hash-to-curve/
// NOTE: sgn0 is 'negative in LE', which is same as odd. And negative in LE is kinda strange definition anyway.
isOdd?(num: T): boolean; // Odd instead of even since we have it for Fp2
legendre?(num: T): T;
// legendre?(num: T): T;
pow(lhs: T, power: bigint): T;
invertBatch: (lst: T[]) => T[];
toBytes(num: T): Uint8Array;
@@ -187,27 +241,26 @@ export interface Field<T> {
}
// prettier-ignore
const FIELD_FIELDS = [
'create', 'isValid', 'isZero', 'negate', 'invert', 'sqrt', 'square',
'equals', 'add', 'sub', 'mul', 'pow', 'div',
'addN', 'subN', 'mulN', 'squareN'
'create', 'isValid', 'is0', 'neg', 'inv', 'sqrt', 'sqr',
'eql', 'add', 'sub', 'mul', 'pow', 'div',
'addN', 'subN', 'mulN', 'sqrN'
] as const;
export function validateField<T>(field: Field<T>) {
for (const i of ['ORDER', 'MASK'] as const) {
if (typeof field[i] !== 'bigint')
throw new Error(`Invalid field param ${i}=${field[i]} (${typeof field[i]})`);
}
for (const i of ['BYTES', 'BITS'] as const) {
if (typeof field[i] !== 'number')
throw new Error(`Invalid field param ${i}=${field[i]} (${typeof field[i]})`);
}
for (const i of FIELD_FIELDS) {
if (typeof field[i] !== 'function')
throw new Error(`Invalid field param ${i}=${field[i]} (${typeof field[i]})`);
}
export function validateField<T>(field: IField<T>) {
const initial = {
ORDER: 'bigint',
MASK: 'bigint',
BYTES: 'isSafeInteger',
BITS: 'isSafeInteger',
} as Record<string, string>;
const opts = FIELD_FIELDS.reduce((map, val: string) => {
map[val] = 'function';
return map;
}, initial);
return validateObject(field, opts);
}
// Generic field functions
export function FpPow<T>(f: Field<T>, num: T, power: bigint): T {
export function FpPow<T>(f: IField<T>, num: T, power: bigint): T {
// Should have same speed as pow for bigints
// TODO: benchmark!
if (power < _0n) throw new Error('Expected power > 0');
@@ -217,68 +270,95 @@ export function FpPow<T>(f: Field<T>, num: T, power: bigint): T {
let d = num;
while (power > _0n) {
if (power & _1n) p = f.mul(p, d);
d = f.square(d);
power >>= 1n;
d = f.sqr(d);
power >>= _1n;
}
return p;
}
export function FpInvertBatch<T>(f: Field<T>, nums: T[]): T[] {
// 0 is non-invertible: non-batched version will throw on 0
export function FpInvertBatch<T>(f: IField<T>, nums: T[]): T[] {
const tmp = new Array(nums.length);
// Walk from first to last, multiply them by each other MOD p
const lastMultiplied = nums.reduce((acc, num, i) => {
if (f.isZero(num)) return acc;
if (f.is0(num)) return acc;
tmp[i] = acc;
return f.mul(acc, num);
}, f.ONE);
// Invert last element
const inverted = f.invert(lastMultiplied);
const inverted = f.inv(lastMultiplied);
// Walk from last to first, multiply them by inverted each other MOD p
nums.reduceRight((acc, num, i) => {
if (f.isZero(num)) return acc;
if (f.is0(num)) return acc;
tmp[i] = f.mul(acc, tmp[i]);
return f.mul(acc, num);
}, inverted);
return tmp;
}
export function FpDiv<T>(f: Field<T>, lhs: T, rhs: T | bigint): T {
return f.mul(lhs, typeof rhs === 'bigint' ? invert(rhs, f.ORDER) : f.invert(rhs));
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));
}
// NOTE: very fragile, always bench. Major performance points:
// - NonNormalized ops
// - Object.freeze
// - same shape of object (don't add/remove keys)
export function Fp(
// This function returns True whenever the value x is a square in the field F.
export function FpIsSquare<T>(f: IField<T>) {
const legendreConst = (f.ORDER - _1n) / _2n; // Integer arithmetic
return (x: T): boolean => {
const p = f.pow(x, legendreConst);
return f.eql(p, f.ZERO) || f.eql(p, f.ONE);
};
}
// CURVE.n lengths
export function nLength(n: bigint, nBitLength?: number) {
// Bit size, byte size of CURVE.n
const _nBitLength = nBitLength !== undefined ? nBitLength : n.toString(2).length;
const nByteLength = Math.ceil(_nBitLength / 8);
return { nBitLength: _nBitLength, nByteLength };
}
type FpField = IField<bigint> & Required<Pick<IField<bigint>, 'isOdd'>>;
/**
* Initializes a galois field over prime. Non-primes are not supported for now.
* Do not init in loop: slow. Very fragile: always run a benchmark on change.
* Major performance gains:
* a) non-normalized operations like mulN instead of mul
* b) `Object.freeze`
* c) Same object shape: never add or remove keys
* @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,
bitLen?: number,
isLE = false,
redef: Partial<Field<bigint>> = {}
): Readonly<Field<bigint>> {
redef: Partial<IField<bigint>> = {}
): Readonly<FpField> {
if (ORDER <= _0n) throw new Error(`Expected Fp ORDER > 0, got ${ORDER}`);
const { nBitLength: BITS, nByteLength: BYTES } = utils.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');
const sqrtP = (num: bigint) => sqrt(num, ORDER);
const f: Field<bigint> = Object.freeze({
const sqrtP = FpSqrt(ORDER);
const f: Readonly<FpField> = Object.freeze({
ORDER,
BITS,
BYTES,
MASK: utils.bitMask(BITS),
MASK: bitMask(BITS),
ZERO: _0n,
ONE: _1n,
create: (num) => mod(num, ORDER),
isValid: (num) => {
if (typeof num !== 'bigint')
throw new Error(`Invalid field element: expected bigint, got ${typeof num}`);
return _0n <= num && num < ORDER;
return _0n <= num && num < ORDER; // 0 is valid element, but it's not invertible
},
isZero: (num) => num === _0n,
is0: (num) => num === _0n,
isOdd: (num) => (num & _1n) === _1n,
negate: (num) => mod(-num, ORDER),
equals: (lhs, rhs) => lhs === rhs,
neg: (num) => mod(-num, ORDER),
eql: (lhs, rhs) => lhs === rhs,
square: (num) => mod(num * num, ORDER),
sqr: (num) => mod(num * num, ORDER),
add: (lhs, rhs) => mod(lhs + rhs, ORDER),
sub: (lhs, rhs) => mod(lhs - rhs, ORDER),
mul: (lhs, rhs) => mod(lhs * rhs, ORDER),
@@ -286,106 +366,60 @@ export function Fp(
div: (lhs, rhs) => mod(lhs * invert(rhs, ORDER), ORDER),
// Same as above, but doesn't normalize
squareN: (num) => num * num,
sqrN: (num) => num * num,
addN: (lhs, rhs) => lhs + rhs,
subN: (lhs, rhs) => lhs - rhs,
mulN: (lhs, rhs) => lhs * rhs,
invert: (num) => invert(num, ORDER),
sqrt: redef.sqrt || sqrtP,
inv: (num) => invert(num, ORDER),
sqrt: redef.sqrt || ((n) => sqrtP(f, n)),
invertBatch: (lst) => FpInvertBatch(f, lst),
// TODO: do we really need constant cmov?
// We don't have const-time bigints anyway, so probably will be not very useful
cmov: (a, b, c) => (c ? b : a),
toBytes: (num) =>
isLE ? utils.numberToBytesLE(num, BYTES) : utils.numberToBytesBE(num, BYTES),
toBytes: (num) => (isLE ? numberToBytesLE(num, BYTES) : numberToBytesBE(num, BYTES)),
fromBytes: (bytes) => {
if (bytes.length !== BYTES)
throw new Error(`Fp.fromBytes: expected ${BYTES}, got ${bytes.length}`);
return isLE ? utils.bytesToNumberLE(bytes) : utils.bytesToNumberBE(bytes);
return isLE ? bytesToNumberLE(bytes) : bytesToNumberBE(bytes);
},
} as Field<bigint>);
} as FpField);
return Object.freeze(f);
}
// TODO: re-use in bls/generic sqrt for field/etc?
// Something like sqrtUnsafe which always returns value, but sqrt throws exception if non-square
// From draft-irtf-cfrg-hash-to-curve-16
export function FpSqrt<T>(Fp: Field<T>) {
// NOTE: it requires another sqrt for constant precomputes, but no need for roots of unity,
// probably we can simply bls code using it
const q = Fp.ORDER;
const squareConst = (q - _1n) / _2n;
// is_square(x) := { True, if x^((q - 1) / 2) is 0 or 1 in F;
// { False, otherwise.
let isSquare: (x: T) => boolean = (x) => {
const p = Fp.pow(x, squareConst);
return Fp.equals(p, Fp.ZERO) || Fp.equals(p, Fp.ONE);
};
// Constant-time Tonelli-Shanks algorithm
let l = _0n;
for (let o = q - _1n; o % _2n === _0n; o /= _2n) l += _1n;
const c1 = l; // 1. c1, the largest integer such that 2^c1 divides q - 1.
const c2 = (q - _1n) / _2n ** c1; // 2. c2 = (q - 1) / (2^c1) # Integer arithmetic
const c3 = (c2 - _1n) / _2n; // 3. c3 = (c2 - 1) / 2 # Integer arithmetic
// 4. c4, a non-square value in F
// 5. c5 = c4^c2 in F
let c4 = Fp.ONE;
while (isSquare(c4)) c4 = Fp.add(c4, Fp.ONE);
const c5 = Fp.pow(c4, c2);
let sqrt: (x: T) => T = (x) => {
let z = Fp.pow(x, c3); // 1. z = x^c3
let t = Fp.square(z); // 2. t = z * z
t = Fp.mul(t, x); // 3. t = t * x
z = Fp.mul(z, x); // 4. z = z * x
let b = t; // 5. b = t
let c = c5; // 6. c = c5
// 7. for i in (c1, c1 - 1, ..., 2):
for (let i = c1; i > 1; i--) {
// 8. for j in (1, 2, ..., i - 2):
// 9. b = b * b
for (let j = _1n; j < i - _1n; i++) b = Fp.square(b);
const e = Fp.equals(b, Fp.ONE); // 10. e = b == 1
const zt = Fp.mul(z, c); // 11. zt = z * c
z = Fp.cmov(zt, z, e); // 12. z = CMOV(zt, z, e)
c = Fp.square(c); // 13. c = c * c
let tt = Fp.mul(t, c); // 14. tt = t * c
t = Fp.cmov(tt, t, e); // 15. t = CMOV(tt, t, e)
b = t; // 16. b = t
}
return z; // 17. return z
};
if (q % _4n === _3n) {
const c1 = (q + _1n) / _4n; // 1. c1 = (q + 1) / 4 # Integer arithmetic
sqrt = (x) => Fp.pow(x, c1);
} else if (q % _8n === _5n) {
const c1 = Fp.sqrt(Fp.negate(Fp.ONE)); // 1. c1 = sqrt(-1) in F, i.e., (c1^2) == -1 in F
const c2 = (q + _3n) / _8n; // 2. c2 = (q + 3) / 8 # Integer arithmetic
sqrt = (x) => {
let tv1 = Fp.pow(x, c2); // 1. tv1 = x^c2
let tv2 = Fp.mul(tv1, c1); // 2. tv2 = tv1 * c1
let e = Fp.equals(Fp.square(tv1), x); // 3. e = (tv1^2) == x
return Fp.cmov(tv2, tv1, e); // 4. z = CMOV(tv2, tv1, e)
};
} else if (Fp.ORDER % _16n === _9n) {
const c1 = Fp.sqrt(Fp.negate(Fp.ONE)); // 1. c1 = sqrt(-1) in F, i.e., (c1^2) == -1 in F
const c2 = Fp.sqrt(c1); // 2. c2 = sqrt(c1) in F, i.e., (c2^2) == c1 in F
const c3 = Fp.sqrt(Fp.negate(c1)); // 3. c3 = sqrt(-c1) in F, i.e., (c3^2) == -c1 in F
const c4 = (Fp.ORDER + _7n) / _16n; // 4. c4 = (q + 7) / 16 # Integer arithmetic
sqrt = (x) => {
let tv1 = Fp.pow(x, c4); // 1. tv1 = x^c4
let tv2 = Fp.mul(c1, tv1); // 2. tv2 = c1 * tv1
const tv3 = Fp.mul(c2, tv1); // 3. tv3 = c2 * tv1
let tv4 = Fp.mul(c3, tv1); // 4. tv4 = c3 * tv1
const e1 = Fp.equals(Fp.square(tv2), x); // 5. e1 = (tv2^2) == x
const e2 = Fp.equals(Fp.square(tv3), x); // 6. e2 = (tv3^2) == x
tv1 = Fp.cmov(tv1, tv2, e1); // 7. tv1 = CMOV(tv1, tv2, e1) # Select tv2 if (tv2^2) == x
tv2 = Fp.cmov(tv4, tv3, e2); // 8. tv2 = CMOV(tv4, tv3, e2) # Select tv3 if (tv3^2) == x
const e3 = Fp.equals(Fp.square(tv2), x); // 9. e3 = (tv2^2) == x
return Fp.cmov(tv1, tv2, e3); // 10. z = CMOV(tv1, tv2, e3) # Select the sqrt from tv1 and tv2
};
}
return { sqrt, isSquare };
export function FpSqrtOdd<T>(Fp: IField<T>, elm: T) {
if (!Fp.isOdd) throw new Error(`Field doesn't have isOdd`);
const root = Fp.sqrt(elm);
return Fp.isOdd(root) ? root : Fp.neg(root);
}
export function FpSqrtEven<T>(Fp: IField<T>, elm: T) {
if (!Fp.isOdd) throw new Error(`Field doesn't have isOdd`);
const root = Fp.sqrt(elm);
return Fp.isOdd(root) ? Fp.neg(root) : root;
}
/**
* FIPS 186 B.4.1-compliant "constant-time" private key generation utility.
* Can take (n+8) or more bytes of uniform input e.g. from CSPRNG or KDF
* and convert them into private scalar, with the modulo bias being negligible.
* Needs at least 40 bytes of input for 32-byte private key.
* https://research.kudelskisecurity.com/2020/07/28/the-definitive-guide-to-modulo-bias-and-how-to-avoid-it/
* @param hash hash output from SHA3 or a similar function
* @param groupOrder size of subgroup - (e.g. curveFn.CURVE.n)
* @param isLE interpret hash bytes as LE num
* @returns valid private scalar
*/
export function hashToPrivateScalar(
hash: string | Uint8Array,
groupOrder: bigint,
isLE = false
): bigint {
hash = ensureBytes('privateHash', hash);
const hashLen = hash.length;
const minLen = nLength(groupOrder).nByteLength + 8;
if (minLen < 24 || hashLen < minLen || hashLen > 1024)
throw new Error(`hashToPrivateScalar: expected ${minLen}-1024 bytes of input, got ${hashLen}`);
const num = isLE ? bytesToNumberLE(hash) : bytesToNumberBE(hash);
return mod(num, groupOrder - _1n) + _1n;
}

View File

@@ -1,57 +1,48 @@
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
import * as mod from './modular.js';
import {
ensureBytes,
numberToBytesLE,
bytesToNumberLE,
// nLength,
} from './utils.js';
import { mod, pow } from './modular.js';
import { bytesToNumberLE, ensureBytes, numberToBytesLE, validateObject } from './utils.js';
const _0n = BigInt(0);
const _1n = BigInt(1);
type Hex = string | Uint8Array;
export type CurveType = {
// Field over which we'll do calculations. Verify with:
P: bigint;
P: bigint; // finite field prime
nByteLength: number;
adjustScalarBytes?: (bytes: Uint8Array) => Uint8Array;
domain?: (data: Uint8Array, ctx: Uint8Array, phflag: boolean) => Uint8Array;
a24: bigint; // Related to d, but cannot be derived from it
a: bigint;
montgomeryBits: number;
powPminus2?: (x: bigint) => bigint;
xyToU?: (x: bigint, y: bigint) => bigint;
Gu: string;
Gu: bigint;
randomBytes?: (bytesLength?: number) => Uint8Array;
};
export type CurveFn = {
scalarMult: (scalar: Hex, u: Hex) => Uint8Array;
scalarMultBase: (scalar: Hex) => Uint8Array;
getSharedSecret: (privateKeyA: Hex, publicKeyB: Hex) => Uint8Array;
getPublicKey: (privateKey: Hex) => Uint8Array;
Gu: string;
utils: { randomPrivateKey: () => Uint8Array };
GuBytes: Uint8Array;
};
function validateOpts(curve: CurveType) {
for (const i of ['a24'] as const) {
if (typeof curve[i] !== 'bigint')
throw new Error(`Invalid curve param ${i}=${curve[i]} (${typeof curve[i]})`);
}
for (const i of ['montgomeryBits', 'nByteLength'] as const) {
if (curve[i] === undefined) continue; // Optional
if (!Number.isSafeInteger(curve[i]))
throw new Error(`Invalid curve param ${i}=${curve[i]} (${typeof curve[i]})`);
}
for (const fn of ['adjustScalarBytes', 'domain', 'powPminus2'] as const) {
if (curve[fn] === undefined) continue; // Optional
if (typeof curve[fn] !== 'function') throw new Error(`Invalid ${fn} function`);
}
for (const i of ['Gu'] as const) {
if (curve[i] === undefined) continue; // Optional
if (typeof curve[i] !== 'string')
throw new Error(`Invalid curve param ${i}=${curve[i]} (${typeof curve[i]})`);
validateObject(
curve,
{
a: 'bigint',
},
{
montgomeryBits: 'isSafeInteger',
nByteLength: 'isSafeInteger',
adjustScalarBytes: 'function',
domain: 'function',
powPminus2: 'function',
Gu: 'bigint',
}
);
// Set defaults
// ...nLength(curve.n, curve.nBitLength),
return Object.freeze({ ...curve } as const);
}
@@ -60,34 +51,14 @@ function validateOpts(curve: CurveType) {
export function montgomery(curveDef: CurveType): CurveFn {
const CURVE = validateOpts(curveDef);
const { P } = CURVE;
const modP = (a: bigint) => mod.mod(a, P);
const modP = (n: bigint) => mod(n, P);
const montgomeryBits = CURVE.montgomeryBits;
const montgomeryBytes = Math.ceil(montgomeryBits / 8);
const fieldLen = CURVE.nByteLength;
const adjustScalarBytes = CURVE.adjustScalarBytes || ((bytes: Uint8Array) => bytes);
const powPminus2 = CURVE.powPminus2 || ((x: bigint) => mod.pow(x, P - BigInt(2), P));
const powPminus2 = CURVE.powPminus2 || ((x: bigint) => pow(x, P - BigInt(2), P));
/**
* Checks for num to be in range:
* For strict == true: `0 < num < max`.
* For strict == false: `0 <= num < max`.
* Converts non-float safe numbers to bigints.
*/
function normalizeScalar(num: number | bigint, max: bigint, strict = true): bigint {
if (!max) throw new TypeError('Specify max value');
if (typeof num === 'number' && Number.isSafeInteger(num)) num = BigInt(num);
if (typeof num === 'bigint' && num < max) {
if (strict) {
if (_0n < num) return num;
} else {
if (_0n <= num) return num;
}
}
throw new TypeError('Expected valid scalar: 0 < scalar < max');
}
// cswap from RFC7748
// NOTE: cswap is not from RFC7748!
// cswap from RFC7748. But it is not from RFC7748!
/*
cswap(swap, x_2, x_3):
dummy = mask(swap) AND (x_2 XOR x_3)
@@ -104,7 +75,15 @@ export function montgomery(curveDef: CurveType): CurveFn {
return [x_2, x_3];
}
// Accepts 0 as well
function assertFieldElement(n: bigint): bigint {
if (typeof n === 'bigint' && _0n <= n && n < P) return n;
throw new Error('Expected valid scalar 0 < scalar < CURVE.P');
}
// 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
@@ -112,13 +91,10 @@ export function montgomery(curveDef: CurveType): CurveFn {
* @returns new Point on Montgomery curve
*/
function montgomeryLadder(pointU: bigint, scalar: bigint): bigint {
const { P } = CURVE;
const u = normalizeScalar(pointU, P);
const u = assertFieldElement(pointU);
// Section 5: Implementations MUST accept non-canonical values and process them as
// if they had been reduced modulo the field prime.
const k = normalizeScalar(scalar, P);
// The constant a24 is (486662 - 2) / 4 = 121665 for curve25519/X25519
const a24 = CURVE.a24;
const k = assertFieldElement(scalar);
const x_1 = u;
let x_2 = _1n;
let z_2 = _0n;
@@ -172,28 +148,21 @@ export function montgomery(curveDef: CurveType): CurveFn {
}
function decodeUCoordinate(uEnc: Hex): bigint {
const u = ensureBytes(uEnc, montgomeryBytes);
// Section 5: When receiving such an array, implementations of X25519
// MUST mask the most significant bit in the final byte.
// This is very ugly way, but it works because fieldLen-1 is outside of bounds for X448, so this becomes NOOP
// fieldLen - scalaryBytes = 1 for X448 and = 0 for X25519
u[fieldLen - 1] &= 127; // 0b0111_1111
const u = ensureBytes('u coordinate', uEnc, montgomeryBytes);
// u[fieldLen-1] crashes QuickJS (TypeError: out-of-bound numeric index)
if (fieldLen === montgomeryBytes) u[fieldLen - 1] &= 127; // 0b0111_1111
return bytesToNumberLE(u);
}
function decodeScalar(n: Hex): bigint {
const bytes = ensureBytes(n);
const bytes = ensureBytes('scalar', n);
if (bytes.length !== montgomeryBytes && bytes.length !== fieldLen)
throw new Error(`Expected ${montgomeryBytes} or ${fieldLen} bytes, got ${bytes.length}`);
return bytesToNumberLE(adjustScalarBytes(bytes));
}
/**
* Computes shared secret between private key "scalar" and public key's "u" (x) coordinate.
* We can get 'y' coordinate from 'u',
* but Point.fromHex also wants 'x' coordinate oddity flag,
* and we cannot get 'x' without knowing 'v'.
* Need to add generic conversion between twisted edwards and complimentary curve for JubJub.
*/
function scalarMult(scalar: Hex, u: Hex): Uint8Array {
const pointU = decodeUCoordinate(u);
const _scalar = decodeScalar(scalar);
@@ -203,14 +172,10 @@ export function montgomery(curveDef: CurveType): CurveFn {
if (pu === _0n) throw new Error('Invalid private or public key received');
return encodeUCoordinate(pu);
}
/**
* Computes public key from private.
* Executes scalar multiplication of curve's base point by scalar.
* @param scalar private key
* @returns new public key
*/
// Computes public key from private. By doing scalar multiplication of base point.
const GuBytes = encodeUCoordinate(CURVE.Gu);
function scalarMultBase(scalar: Hex): Uint8Array {
return scalarMult(scalar, CURVE.Gu);
return scalarMult(scalar, GuBytes);
}
return {
@@ -218,6 +183,7 @@ export function montgomery(curveDef: CurveType): CurveFn {
scalarMultBase,
getSharedSecret: (privateKey: Hex, publicKey: Hex) => scalarMult(privateKey, publicKey),
getPublicKey: (privateKey: Hex): Uint8Array => scalarMultBase(privateKey),
Gu: CURVE.Gu,
utils: { randomPrivateKey: () => CURVE.randomBytes!(CURVE.nByteLength) },
GuBytes: GuBytes,
};
}

119
src/abstract/poseidon.ts Normal file
View File

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

View File

@@ -1,70 +1,32 @@
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
import * as mod from './modular.js';
// 100 lines of code in the file are duplicated from noble-hashes (utils).
// This is OK: `abstract` directory does not use noble-hashes.
// User may opt-in into using different hashing library. This way, noble-hashes
// won't be included into their bundle.
const _0n = BigInt(0);
const _1n = BigInt(1);
const _2n = BigInt(2);
// 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 | number;
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
export type CHash = {
(message: Uint8Array | string): Uint8Array;
blockLen: number;
outputLen: number;
create(): any;
create(opts?: { dkLen?: number }): any; // For shake
};
// NOTE: these are generic, even if curve is on some polynominal field (bls), it will still have P/n/h
// But generator can be different (Fp2/Fp6 for bls?)
export type BasicCurve<T> = {
// Field over which we'll do calculations (Fp)
Fp: mod.Field<T>;
// Curve order, total count of valid points in the field
n: bigint;
// Bit/byte length of curve order
nBitLength?: number;
nByteLength?: number;
// Cofactor
// NOTE: we can assign default value of 1, but then users will just ignore it, without validating with spec
// Has not use for now, but nice to have in API
h: bigint;
hEff?: bigint; // Number to multiply to clear cofactor
// Base point (x, y) aka generator point
Gx: T;
Gy: T;
// Wrap private key by curve order (% CURVE.n instead of throwing error)
wrapPrivateKey?: boolean;
// Point at infinity is perfectly valid point, but not valid public key.
// Disabled by default because of compatibility reasons with @noble/secp256k1
allowInfinityPoint?: boolean;
};
export function validateOpts<FP, T>(curve: BasicCurve<FP> & T) {
mod.validateField(curve.Fp);
for (const i of ['n', 'h'] as const) {
if (typeof curve[i] !== 'bigint')
throw new Error(`Invalid curve param ${i}=${curve[i]} (${typeof curve[i]})`);
}
if (!curve.Fp.isValid(curve.Gx)) throw new Error('Invalid generator X coordinate Fp element');
if (!curve.Fp.isValid(curve.Gy)) throw new Error('Invalid generator Y coordinate Fp element');
for (const i of ['nBitLength', 'nByteLength'] as const) {
if (curve[i] === undefined) continue; // Optional
if (!Number.isSafeInteger(curve[i]))
throw new Error(`Invalid curve param ${i}=${curve[i]} (${typeof curve[i]})`);
}
// Set defaults
return Object.freeze({ ...nLength(curve.n, curve.nBitLength), ...curve } as const);
}
export type FHash = (message: Uint8Array | string) => Uint8Array;
const hexes = Array.from({ length: 256 }, (v, i) => i.toString(16).padStart(2, '0'));
export function bytesToHex(uint8a: Uint8Array): string {
if (!(uint8a instanceof Uint8Array)) throw new Error('Expected Uint8Array');
/**
* @example bytesToHex(Uint8Array.from([0xca, 0xfe, 0x01, 0x23])) // 'cafe0123'
*/
export function bytesToHex(bytes: Uint8Array): string {
if (!u8a(bytes)) throw new Error('Uint8Array expected');
// pre-caching improves the speed 6x
let hex = '';
for (let i = 0; i < uint8a.length; i++) {
hex += hexes[uint8a[i]];
for (let i = 0; i < bytes.length; i++) {
hex += hexes[bytes[i]];
}
return hex;
}
@@ -75,20 +37,19 @@ export function numberToHexUnpadded(num: number | bigint): string {
}
export function hexToNumber(hex: string): bigint {
if (typeof hex !== 'string') {
throw new TypeError('hexToNumber: expected string, got ' + typeof hex);
}
if (typeof hex !== 'string') throw new Error('hex string expected, got ' + typeof hex);
// Big Endian
return BigInt(`0x${hex}`);
return BigInt(hex === '' ? '0' : `0x${hex}`);
}
// Caching slows it down 2-3x
/**
* @example hexToBytes('cafe0123') // Uint8Array.from([0xca, 0xfe, 0x01, 0x23])
*/
export function hexToBytes(hex: string): Uint8Array {
if (typeof hex !== 'string') {
throw new TypeError('hexToBytes: expected string, got ' + typeof hex);
}
if (hex.length % 2) throw new Error('hexToBytes: received invalid unpadded hex ' + hex.length);
const array = new Uint8Array(hex.length / 2);
if (typeof hex !== 'string') throw new Error('hex string expected, got ' + typeof hex);
const len = hex.length;
if (len % 2) throw new Error('padded hex string expected, got unpadded hex of length ' + len);
const array = new Uint8Array(len / 2);
for (let i = 0; i < array.length; i++) {
const j = i * 2;
const hexByte = hex.slice(j, j + 2);
@@ -99,66 +60,68 @@ export function hexToBytes(hex: string): Uint8Array {
return array;
}
// Big Endian
// BE: Big Endian, LE: Little Endian
export function bytesToNumberBE(bytes: Uint8Array): bigint {
return hexToNumber(bytesToHex(bytes));
}
export function bytesToNumberLE(uint8a: Uint8Array): bigint {
if (!(uint8a instanceof Uint8Array)) throw new Error('Expected Uint8Array');
return BigInt('0x' + bytesToHex(Uint8Array.from(uint8a).reverse()));
export function bytesToNumberLE(bytes: Uint8Array): bigint {
if (!u8a(bytes)) throw new Error('Uint8Array expected');
return hexToNumber(bytesToHex(Uint8Array.from(bytes).reverse()));
}
export const numberToBytesBE = (n: bigint, len: number) =>
hexToBytes(n.toString(16).padStart(len * 2, '0'));
export const numberToBytesLE = (n: bigint, len: number) => numberToBytesBE(n, len).reverse();
export function ensureBytes(hex: Hex, expectedLength?: number): Uint8Array {
// Uint8Array.from() instead of hash.slice() because node.js Buffer
// is instance of Uint8Array, and its slice() creates **mutable** copy
const bytes = hex instanceof Uint8Array ? Uint8Array.from(hex) : hexToBytes(hex);
if (typeof expectedLength === 'number' && bytes.length !== expectedLength)
throw new Error(`Expected ${expectedLength} bytes`);
return bytes;
export function numberToBytesBE(n: number | bigint, len: number): Uint8Array {
return hexToBytes(n.toString(16).padStart(len * 2, '0'));
}
// Copies several Uint8Arrays into one.
export function concatBytes(...arrays: Uint8Array[]): Uint8Array {
if (!arrays.every((b) => b instanceof Uint8Array)) throw new Error('Uint8Array list expected');
if (arrays.length === 1) return arrays[0];
const length = arrays.reduce((a, arr) => a + arr.length, 0);
const result = new Uint8Array(length);
for (let i = 0, pad = 0; i < arrays.length; i++) {
const arr = arrays[i];
result.set(arr, pad);
pad += arr.length;
}
return result;
export function numberToBytesLE(n: number | bigint, len: number): Uint8Array {
return numberToBytesBE(n, len).reverse();
}
// CURVE.n lengths
export function nLength(n: bigint, nBitLength?: number) {
// Bit size, byte size of CURVE.n
const _nBitLength = nBitLength !== undefined ? nBitLength : n.toString(2).length;
const nByteLength = Math.ceil(_nBitLength / 8);
return { nBitLength: _nBitLength, nByteLength };
// Unpadded, rarely used
export function numberToVarBytesBE(n: number | bigint): Uint8Array {
return hexToBytes(numberToHexUnpadded(n));
}
/**
* Can take (n+8) or more bytes of uniform input e.g. from CSPRNG or KDF
* and convert them into private scalar, with the modulo bias being neglible.
* As per FIPS 186 B.4.1.
* https://research.kudelskisecurity.com/2020/07/28/the-definitive-guide-to-modulo-bias-and-how-to-avoid-it/
* @param hash hash output from sha512, or a similar function
* @returns valid private scalar
* 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 hashToPrivateScalar(hash: Hex, CURVE_ORDER: bigint, isLE = false): bigint {
hash = ensureBytes(hash);
const orderLen = nLength(CURVE_ORDER).nByteLength;
const minLen = orderLen + 8;
if (orderLen < 16 || hash.length < minLen || hash.length > 1024)
throw new Error('Expected valid bytes of private key as per FIPS 186');
const num = isLE ? bytesToNumberLE(hash) : bytesToNumberBE(hash);
return mod.mod(num, CURVE_ORDER - _1n) + _1n;
export function ensureBytes(title: string, hex: Hex, expectedLength?: number): Uint8Array {
let res: Uint8Array;
if (typeof hex === 'string') {
try {
res = hexToBytes(hex);
} catch (e) {
throw new Error(`${title} must be valid hex string, got "${hex}". Cause: ${e}`);
}
} else if (u8a(hex)) {
// Uint8Array.from() instead of hash.slice() because node.js Buffer
// is instance of Uint8Array, and its slice() creates **mutable** copy
res = Uint8Array.from(hex);
} else {
throw new Error(`${title} must be hex string or Uint8Array`);
}
const len = res.length;
if (typeof expectedLength === 'number' && len !== expectedLength)
throw new Error(`${title} expected ${expectedLength} bytes, got ${len}`);
return res;
}
/**
* Copies several Uint8Arrays into one.
*/
export function concatBytes(...arrays: Uint8Array[]): Uint8Array {
const r = new Uint8Array(arrays.reduce((sum, a) => sum + a.length, 0));
let pad = 0; // walk through each item, ensure they have proper type
arrays.forEach((a) => {
if (!u8a(a)) throw new Error('Uint8Array expected');
r.set(a, pad);
pad += a.length;
});
return r;
}
export function equalBytes(b1: Uint8Array, b2: Uint8Array) {
@@ -168,20 +131,157 @@ export function equalBytes(b1: Uint8Array, b2: Uint8Array) {
return true;
}
// Global symbols in both browsers and Node.js since v11
// See https://github.com/microsoft/TypeScript/issues/31535
declare const TextEncoder: any;
/**
* @example utf8ToBytes('abc') // new Uint8Array([97, 98, 99])
*/
export function utf8ToBytes(str: string): Uint8Array {
if (typeof str !== 'string') throw new Error(`utf8ToBytes expected string, got ${typeof str}`);
return new Uint8Array(new TextEncoder().encode(str)); // https://bugzil.la/1681809
}
// Bit operations
// Amount of bits inside bigint (Same as n.toString(2).length)
/**
* Calculates amount of bits in a bigint.
* Same as `n.toString(2).length`
*/
export function bitLen(n: bigint) {
let len;
for (len = 0; n > 0n; n >>= _1n, len += 1);
for (len = 0; n > _0n; n >>= _1n, len += 1);
return len;
}
// Gets single bit at position. NOTE: first bit position is 0 (same as arrays)
// Same as !!+Array.from(n.toString(2)).reverse()[pos]
export const bitGet = (n: bigint, pos: number) => (n >> BigInt(pos)) & 1n;
// Sets single bit at position
export const bitSet = (n: bigint, pos: number, value: boolean) =>
n | ((value ? _1n : _0n) << BigInt(pos));
// Return mask for N bits (Same as BigInt(`0b${Array(i).fill('1').join('')}`))
// Not using ** operator with bigints for old engines.
/**
* Gets single bit at position.
* NOTE: first bit position is 0 (same as arrays)
* Same as `!!+Array.from(n.toString(2)).reverse()[pos]`
*/
export function bitGet(n: bigint, pos: number) {
return (n >> BigInt(pos)) & _1n;
}
/**
* Sets single bit at position.
*/
export const bitSet = (n: bigint, pos: number, value: boolean) => {
return n | ((value ? _1n : _0n) << BigInt(pos));
};
/**
* Calculate mask for N bits. Not using ** operator with bigints because of old engines.
* Same as BigInt(`0b${Array(i).fill('1').join('')}`)
*/
export const bitMask = (n: number) => (_2n << BigInt(n - 1)) - _1n;
// DRBG
const u8n = (data?: any) => new Uint8Array(data); // creates Uint8Array
const u8fr = (arr: any) => Uint8Array.from(arr); // another shortcut
type Pred<T> = (v: Uint8Array) => T | undefined;
/**
* Minimal HMAC-DRBG from NIST 800-90 for RFC6979 sigs.
* @returns function that will call DRBG until 2nd arg returns something meaningful
* @example
* const drbg = createHmacDRBG<Key>(32, 32, hmac);
* drbg(seed, bytesToKey); // bytesToKey must return Key or undefined
*/
export function createHmacDrbg<T>(
hashLen: number,
qByteLen: number,
hmacFn: (key: Uint8Array, ...messages: Uint8Array[]) => Uint8Array
): (seed: Uint8Array, predicate: Pred<T>) => T {
if (typeof hashLen !== 'number' || hashLen < 2) throw new Error('hashLen must be a number');
if (typeof qByteLen !== 'number' || qByteLen < 2) throw new Error('qByteLen must be a number');
if (typeof hmacFn !== 'function') throw new Error('hmacFn must be a function');
// Step B, Step C: set hashLen to 8*ceil(hlen/8)
let v = u8n(hashLen); // Minimal non-full-spec HMAC-DRBG from NIST 800-90 for RFC6979 sigs.
let k = u8n(hashLen); // Steps B and C of RFC6979 3.2: set hashLen, in our case always same
let i = 0; // Iterations counter, will throw when over 1000
const reset = () => {
v.fill(1);
k.fill(0);
i = 0;
};
const h = (...b: Uint8Array[]) => hmacFn(k, v, ...b); // hmac(k)(v, ...values)
const reseed = (seed = u8n()) => {
// HMAC-DRBG reseed() function. Steps D-G
k = h(u8fr([0x00]), seed); // k = hmac(k || v || 0x00 || seed)
v = h(); // v = hmac(k || v)
if (seed.length === 0) return;
k = h(u8fr([0x01]), seed); // k = hmac(k || v || 0x01 || seed)
v = h(); // v = hmac(k || v)
};
const gen = () => {
// HMAC-DRBG generate() function
if (i++ >= 1000) throw new Error('drbg: tried 1000 values');
let len = 0;
const out: Uint8Array[] = [];
while (len < qByteLen) {
v = h();
const sl = v.slice();
out.push(sl);
len += v.length;
}
return concatBytes(...out);
};
const genUntil = (seed: Uint8Array, pred: Pred<T>): T => {
reset();
reseed(seed); // Steps D-G
let res: T | undefined = undefined; // Step H: grind until k is in [1..n-1]
while (!(res = pred(gen()))) reseed();
reset();
return res;
};
return genUntil;
}
// Validating curves and fields
const validatorFns = {
bigint: (val: any) => typeof val === 'bigint',
function: (val: any) => typeof val === 'function',
boolean: (val: any) => typeof val === 'boolean',
string: (val: any) => typeof val === 'string',
isSafeInteger: (val: any) => Number.isSafeInteger(val),
array: (val: any) => Array.isArray(val),
field: (val: any, object: any) => (object as any).Fp.isValid(val),
hash: (val: any) => typeof val === 'function' && Number.isSafeInteger(val.outputLen),
} as const;
type Validator = keyof typeof validatorFns;
type ValMap<T extends Record<string, any>> = { [K in keyof T]?: Validator };
// type Record<K extends string | number | symbol, T> = { [P in K]: T; }
export function validateObject<T extends Record<string, any>>(
object: T,
validators: ValMap<T>,
optValidators: ValMap<T> = {}
) {
const checkField = (fieldName: keyof T, type: Validator, isOptional: boolean) => {
const checkVal = validatorFns[type];
if (typeof checkVal !== 'function')
throw new Error(`Invalid validator "${type}", expected function`);
const val = object[fieldName as keyof typeof object];
if (isOptional && val === undefined) return;
if (!checkVal(val, object)) {
throw new Error(
`Invalid param ${String(fieldName)}=${val} (${typeof val}), expected ${type}`
);
}
};
for (const [fieldName, type] of Object.entries(validators)) checkField(fieldName, type!, false);
for (const [fieldName, type] of Object.entries(optValidators)) checkField(fieldName, type!, true);
return object;
}
// validate type tests
// const o: { a: number; b: number; c: number } = { a: 1, b: 5, c: 6 };
// const z0 = validateObject(o, { a: 'isSafeInteger' }, { c: 'bigint' }); // Ok!
// // Should fail type-check
// const z1 = validateObject(o, { a: 'tmp' }, { c: 'zz' });
// const z2 = validateObject(o, { a: 'isSafeInteger' }, { c: 'zz' });
// const z3 = validateObject(o, { test: 'boolean', z: 'bug' });
// const z4 = validateObject(o, { a: 'boolean', z: 'bug' });

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,8 +1,8 @@
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
import { weierstrass } from './abstract/weierstrass.js';
import { sha256 } from '@noble/hashes/sha256';
import { weierstrass } from './abstract/weierstrass.js';
import { getHash } from './_shortw_utils.js';
import { Fp } from './abstract/modular.js';
import { Field } from './abstract/modular.js';
/**
* bn254 pairing-friendly curve.
* Previously known as alt_bn_128, when it had 128-bit security.
@@ -12,7 +12,7 @@ import { Fp } from './abstract/modular.js';
export const bn254 = weierstrass({
a: BigInt(0),
b: BigInt(3),
Fp: Fp(BigInt('0x30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47')),
Fp: Field(BigInt('0x30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47')),
n: BigInt('0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001'),
Gx: BigInt(1),
Gy: BigInt(2),

View File

@@ -1,17 +1,19 @@
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
import { sha512 } from '@noble/hashes/sha512';
import { concatBytes, randomBytes, utf8ToBytes } from '@noble/hashes/utils';
import { twistedEdwards, ExtendedPointType } from './abstract/edwards.js';
import { ExtPointType, twistedEdwards } from './abstract/edwards.js';
import { montgomery } from './abstract/montgomery.js';
import { mod, pow2, isNegativeLE, Fp as Field } from './abstract/modular.js';
import { Field, FpSqrtEven, isNegativeLE, mod, pow2 } from './abstract/modular.js';
import {
ensureBytes,
equalBytes,
bytesToHex,
bytesToNumberLE,
numberToBytesLE,
ensureBytes,
equalBytes,
Hex,
numberToBytesLE,
} from './abstract/utils.js';
import { createHasher, htfBasicOpts, expand_message_xmd } from './abstract/hash-to-curve.js';
import { AffinePoint } from './abstract/curve.js';
/**
* ed25519 Twisted Edwards curve with following addons:
@@ -32,6 +34,7 @@ const ED25519_SQRT_M1 = BigInt(
const _0n = BigInt(0), _1n = BigInt(1), _2n = BigInt(2), _5n = BigInt(5);
// prettier-ignore
const _10n = BigInt(10), _20n = BigInt(20), _40n = BigInt(40), _80n = BigInt(80);
function ed25519_pow_2_252_3(x: bigint) {
const P = ED25519_P;
const x2 = (x * x) % P;
@@ -49,6 +52,7 @@ function ed25519_pow_2_252_3(x: bigint) {
// ^ To pow to (p+3)/8, multiply it by x.
return { pow_p_5_8, b2 };
}
function adjustScalarBytes(bytes: Uint8Array): Uint8Array {
// Section 5: For X25519, in order to decode 32 random bytes as an integer scalar,
// set the three least significant bits of the first byte
@@ -59,6 +63,7 @@ function adjustScalarBytes(bytes: Uint8Array): Uint8Array {
bytes[31] |= 64; // 0b0100_0000
return bytes;
}
// sqrt(u/v)
function uvRatio(u: bigint, v: bigint): { isValid: boolean; value: bigint } {
const P = ED25519_P;
@@ -91,18 +96,18 @@ export const ED25519_TORSION_SUBGROUP = [
'c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac03fa',
];
const Fp = Field(ED25519_P);
const Fp = Field(ED25519_P, undefined, true);
const ED25519_DEF = {
const ed25519Defaults = {
// Param: a
a: BigInt(-1),
// Equal to -121665/121666 over finite field.
a: BigInt(-1), // Fp.create(-1) is proper; our way still works and is faster
// d is equal to -121665/121666 over finite field.
// Negative number is P - number, and division is invert(number, P)
d: BigInt('37095705934669439343138083508754565189542113879843219016388785533085940283555'),
// Finite field 𝔽p over which we'll do calculations; 2n ** 255n - 19n
// Finite field 𝔽p over which we'll do calculations; 2n**255n - 19n
Fp,
// Subgroup order: how many points ed25519 has
// 2n ** 252n + 27742317777372353535851937790883648493n;
// Subgroup order: how many points curve has
// 2n**252n + 27742317777372353535851937790883648493n;
n: BigInt('7237005577332262213973186563042994240857116359379907606001950938285454250989'),
// Cofactor
h: BigInt(8),
@@ -116,22 +121,10 @@ const ED25519_DEF = {
// Ratio of u to v. Allows us to combine inversion and square root. Uses algo from RFC8032 5.1.3.
// Constant-time, u/√v
uvRatio,
htfDefaults: {
DST: 'edwards25519_XMD:SHA-512_ELL2_RO_',
p: Fp.ORDER,
m: 1,
k: 128,
expand: true,
hash: sha512,
},
mapToCurve: (scalars: bigint[]): { x: bigint; y: bigint } => {
throw new Error('Not supported yet');
// const { x, y } = calcElligatorRistrettoMap(scalars[0]).toAffine();
// return { x, y };
},
} as const;
export const ed25519 = twistedEdwards(ED25519_DEF);
export const ed25519 = twistedEdwards(ed25519Defaults);
function ed25519_domain(data: Uint8Array, ctx: Uint8Array, phflag: boolean) {
if (ctx.length > 255) throw new Error('Context is too big');
return concatBytes(
@@ -141,19 +134,21 @@ function ed25519_domain(data: Uint8Array, ctx: Uint8Array, phflag: boolean) {
data
);
}
export const ed25519ctx = twistedEdwards({ ...ED25519_DEF, domain: ed25519_domain });
export const ed25519ctx = twistedEdwards({ ...ed25519Defaults, domain: ed25519_domain });
export const ed25519ph = twistedEdwards({
...ED25519_DEF,
...ed25519Defaults,
domain: ed25519_domain,
preHash: sha512,
prehash: sha512,
});
export const x25519 = montgomery({
export const x25519 = /* @__PURE__ */ (() =>
montgomery({
P: ED25519_P,
a24: BigInt('121665'),
a: BigInt(486662),
montgomeryBits: 255, // n is 253 bits
nByteLength: 32,
Gu: '0900000000000000000000000000000000000000000000000000000000000000',
Gu: BigInt(9),
powPminus2: (x: bigint): bigint => {
const P = ED25519_P;
// x^(p-2) aka x^(2^255-21)
@@ -161,15 +156,134 @@ export const x25519 = montgomery({
return mod(pow2(pow_p_5_8, BigInt(3), P) * b2, P);
},
adjustScalarBytes,
});
randomBytes,
}))();
/**
* Converts ed25519 public key to x25519 public key. Uses formula:
* * `(u, v) = ((1+y)/(1-y), sqrt(-486664)*u/x)`
* * `(x, y) = (sqrt(-486664)*u/v, (u-1)/(u+1))`
* @example
* const 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)
// NOTE: very important part is usage of FpSqrtEven for ELL2_C1_EDWARDS, since
// SageMath returns different root first and everything falls apart
const ELL2_C1 = (Fp.ORDER + BigInt(3)) / BigInt(8); // 1. c1 = (q + 3) / 8 # Integer arithmetic
const ELL2_C2 = Fp.pow(_2n, ELL2_C1); // 2. c2 = 2^c1
const ELL2_C3 = Fp.sqrt(Fp.neg(Fp.ONE)); // 3. c3 = sqrt(-1)
const ELL2_C4 = (Fp.ORDER - BigInt(5)) / BigInt(8); // 4. c4 = (q - 5) / 8 # Integer arithmetic
const ELL2_J = BigInt(486662);
// prettier-ignore
function map_to_curve_elligator2_curve25519(u: bigint) {
let tv1 = Fp.sqr(u); // 1. tv1 = u^2
tv1 = Fp.mul(tv1, _2n); // 2. tv1 = 2 * tv1
let xd = Fp.add(tv1, Fp.ONE); // 3. xd = tv1 + 1 # Nonzero: -1 is square (mod p), tv1 is not
let x1n = Fp.neg(ELL2_J); // 4. x1n = -J # x1 = x1n / xd = -J / (1 + 2 * u^2)
let tv2 = Fp.sqr(xd); // 5. tv2 = xd^2
let gxd = Fp.mul(tv2, xd); // 6. gxd = tv2 * xd # gxd = xd^3
let gx1 = Fp.mul(tv1, ELL2_J); // 7. gx1 = J * tv1 # x1n + J * xd
gx1 = Fp.mul(gx1, x1n); // 8. gx1 = gx1 * x1n # x1n^2 + J * x1n * xd
gx1 = Fp.add(gx1, tv2); // 9. gx1 = gx1 + tv2 # x1n^2 + J * x1n * xd + xd^2
gx1 = Fp.mul(gx1, x1n); // 10. gx1 = gx1 * x1n # x1n^3 + J * x1n^2 * xd + x1n * xd^2
let tv3 = Fp.sqr(gxd); // 11. tv3 = gxd^2
tv2 = Fp.sqr(tv3); // 12. tv2 = tv3^2 # gxd^4
tv3 = Fp.mul(tv3, gxd); // 13. tv3 = tv3 * gxd # gxd^3
tv3 = Fp.mul(tv3, gx1); // 14. tv3 = tv3 * gx1 # gx1 * gxd^3
tv2 = Fp.mul(tv2, tv3); // 15. tv2 = tv2 * tv3 # gx1 * gxd^7
let y11 = Fp.pow(tv2, ELL2_C4); // 16. y11 = tv2^c4 # (gx1 * gxd^7)^((p - 5) / 8)
y11 = Fp.mul(y11, tv3); // 17. y11 = y11 * tv3 # gx1*gxd^3*(gx1*gxd^7)^((p-5)/8)
let y12 = Fp.mul(y11, ELL2_C3); // 18. y12 = y11 * c3
tv2 = Fp.sqr(y11); // 19. tv2 = y11^2
tv2 = Fp.mul(tv2, gxd); // 20. tv2 = tv2 * gxd
let e1 = Fp.eql(tv2, gx1); // 21. e1 = tv2 == gx1
let y1 = Fp.cmov(y12, y11, e1); // 22. y1 = CMOV(y12, y11, e1) # If g(x1) is square, this is its sqrt
let x2n = Fp.mul(x1n, tv1); // 23. x2n = x1n * tv1 # x2 = x2n / xd = 2 * u^2 * x1n / xd
let y21 = Fp.mul(y11, u); // 24. y21 = y11 * u
y21 = Fp.mul(y21, ELL2_C2); // 25. y21 = y21 * c2
let y22 = Fp.mul(y21, ELL2_C3); // 26. y22 = y21 * c3
let gx2 = Fp.mul(gx1, tv1); // 27. gx2 = gx1 * tv1 # g(x2) = gx2 / gxd = 2 * u^2 * g(x1)
tv2 = Fp.sqr(y21); // 28. tv2 = y21^2
tv2 = Fp.mul(tv2, gxd); // 29. tv2 = tv2 * gxd
let e2 = Fp.eql(tv2, gx2); // 30. e2 = tv2 == gx2
let y2 = Fp.cmov(y22, y21, e2); // 31. y2 = CMOV(y22, y21, e2) # If g(x2) is square, this is its sqrt
tv2 = Fp.sqr(y1); // 32. tv2 = y1^2
tv2 = Fp.mul(tv2, gxd); // 33. tv2 = tv2 * gxd
let e3 = Fp.eql(tv2, gx1); // 34. e3 = tv2 == gx1
let xn = Fp.cmov(x2n, x1n, e3); // 35. xn = CMOV(x2n, x1n, e3) # If e3, x = x1, else x = x2
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
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)
}
const ELL2_C1_EDWARDS = FpSqrtEven(Fp, Fp.neg(BigInt(486664))); // sgn0(c1) MUST equal 0
function map_to_curve_elligator2_edwards25519(u: bigint) {
const { xMn, xMd, yMn, yMd } = map_to_curve_elligator2_curve25519(u); // 1. (xMn, xMd, yMn, yMd) =
// map_to_curve_elligator2_curve25519(u)
let xn = Fp.mul(xMn, yMd); // 2. xn = xMn * yMd
xn = Fp.mul(xn, ELL2_C1_EDWARDS); // 3. xn = xn * c1
let xd = Fp.mul(xMd, yMn); // 4. xd = xMd * yMn # xn / xd = c1 * xM / yM
let yn = Fp.sub(xMn, xMd); // 5. yn = xMn - xMd
let yd = Fp.add(xMn, xMd); // 6. yd = xMn + xMd # (n / d - 1) / (n / d + 1) = (n - d) / (n + d)
let tv1 = Fp.mul(xd, yd); // 7. tv1 = xd * yd
let e = Fp.eql(tv1, Fp.ZERO); // 8. e = tv1 == 0
xn = Fp.cmov(xn, Fp.ZERO, e); // 9. xn = CMOV(xn, 0, e)
xd = Fp.cmov(xd, Fp.ONE, e); // 10. xd = CMOV(xd, 1, e)
yn = Fp.cmov(yn, Fp.ONE, e); // 11. yn = CMOV(yn, 1, e)
yd = Fp.cmov(yd, Fp.ONE, e); // 12. yd = CMOV(yd, 1, e)
const inv = Fp.invertBatch([xd, yd]); // batch division
return { x: Fp.mul(xn, inv[0]), y: Fp.mul(yn, inv[1]) }; // 13. return (xn, xd, yn, yd)
}
const htf = /* @__PURE__ */ (() =>
createHasher(
ed25519.ExtendedPoint,
(scalars: bigint[]) => map_to_curve_elligator2_edwards25519(scalars[0]),
{
DST: 'edwards25519_XMD:SHA-512_ELL2_RO_',
encodeDST: 'edwards25519_XMD:SHA-512_ELL2_NU_',
p: Fp.ORDER,
m: 1,
k: 128,
expand: 'xmd',
hash: sha512,
}
))();
export const hashToCurve = /* @__PURE__ */ (() => htf.hashToCurve)();
export const encodeToCurve = /* @__PURE__ */ (() => htf.encodeToCurve)();
function assertRstPoint(other: unknown) {
if (!(other instanceof RistrettoPoint)) throw new TypeError('RistrettoPoint expected');
if (!(other instanceof RistPoint)) throw new Error('RistrettoPoint expected');
}
// √(-1) aka √(a) aka 2^((p-1)/4)
const SQRT_M1 = BigInt(
'19681161376707505956807079304988542015446066515923890162744021073123829784752'
);
const SQRT_M1 = ED25519_SQRT_M1;
// √(ad - 1)
const SQRT_AD_MINUS_ONE = BigInt(
'25063068953384623474111414158702152701244531502492656460079210482610430750235'
@@ -191,16 +305,16 @@ const invertSqrt = (number: bigint) => uvRatio(_1n, number);
const MAX_255B = BigInt('0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff');
const bytes255ToNumberLE = (bytes: Uint8Array) =>
ed25519.utils.mod(bytesToNumberLE(bytes) & MAX_255B);
ed25519.CURVE.Fp.create(bytesToNumberLE(bytes) & MAX_255B);
type ExtendedPoint = ExtendedPointType;
type ExtendedPoint = ExtPointType;
// Computes Elligator map for Ristretto
// https://ristretto.group/formulas/elligator.html
function calcElligatorRistrettoMap(r0: bigint): ExtendedPoint {
const { d } = ed25519.CURVE;
const P = ed25519.CURVE.Fp.ORDER;
const { mod } = ed25519.utils;
const mod = ed25519.CURVE.Fp.create;
const r = mod(SQRT_M1 * r0 * r0); // 1
const Ns = mod((r + _1n) * ONE_MINUS_D_SQ); // 2
let c = BigInt(-1); // 3
@@ -226,27 +340,31 @@ function calcElligatorRistrettoMap(r0: bigint): ExtendedPoint {
* but it should work in its own namespace: do not combine those two.
* https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-ristretto255-decaf448
*/
export class RistrettoPoint {
static BASE = new RistrettoPoint(ed25519.ExtendedPoint.BASE);
static ZERO = new RistrettoPoint(ed25519.ExtendedPoint.ZERO);
class RistPoint {
static BASE: RistPoint;
static ZERO: RistPoint;
// Private property to discourage combining ExtendedPoint + RistrettoPoint
// Always use Ristretto encoding/decoding instead.
constructor(private readonly ep: ExtendedPoint) {}
static fromAffine(ap: AffinePoint<bigint>) {
return new RistPoint(ed25519.ExtendedPoint.fromAffine(ap));
}
/**
* Takes uniform output of 64-bit hash function like sha512 and converts it to `RistrettoPoint`.
* Takes uniform output of 64-byte hash function like sha512 and converts it to `RistrettoPoint`.
* The hash-to-group operation applies Elligator twice and adds the results.
* **Note:** this is one-way map, there is no conversion from point to hash.
* https://ristretto.group/formulas/elligator.html
* @param hex 64-bit output of a hash function
* @param hex 64-byte output of a hash function
*/
static hashToCurve(hex: Hex): RistrettoPoint {
hex = ensureBytes(hex, 64);
static hashToCurve(hex: Hex): RistPoint {
hex = ensureBytes('ristrettoHash', hex, 64);
const r1 = bytes255ToNumberLE(hex.slice(0, 32));
const R1 = calcElligatorRistrettoMap(r1);
const r2 = bytes255ToNumberLE(hex.slice(32, 64));
const R2 = calcElligatorRistrettoMap(r2);
return new RistrettoPoint(R1.add(R2));
return new RistPoint(R1.add(R2));
}
/**
@@ -254,11 +372,11 @@ export class RistrettoPoint {
* https://ristretto.group/formulas/decoding.html
* @param hex Ristretto-encoded 32 bytes. Not every 32-byte string is valid ristretto encoding
*/
static fromHex(hex: Hex): RistrettoPoint {
hex = ensureBytes(hex, 32);
static fromHex(hex: Hex): RistPoint {
hex = ensureBytes('ristrettoHex', hex, 32);
const { a, d } = ed25519.CURVE;
const P = ed25519.CURVE.Fp.ORDER;
const { mod } = ed25519.utils;
const mod = ed25519.CURVE.Fp.create;
const emsg = 'RistrettoPoint.fromHex: the hex is not valid encoding of RistrettoPoint';
const s = bytes255ToNumberLE(hex);
// 1. Check that s_bytes is the canonical encoding of a field element, or else abort.
@@ -278,7 +396,7 @@ export class RistrettoPoint {
const y = mod(u1 * Dy); // 11
const t = mod(x * y); // 12
if (!isValid || isNegativeLE(t, P) || y === _0n) throw new Error(emsg);
return new RistrettoPoint(new ed25519.ExtendedPoint(x, y, _1n, t));
return new RistPoint(new ed25519.ExtendedPoint(x, y, _1n, t));
}
/**
@@ -286,9 +404,9 @@ export class RistrettoPoint {
* https://ristretto.group/formulas/encoding.html
*/
toRawBytes(): Uint8Array {
let { x, y, z, t } = this.ep;
let { ex: x, ey: y, ez: z, et: t } = this.ep;
const P = ed25519.CURVE.Fp.ORDER;
const { mod } = ed25519.utils;
const mod = ed25519.CURVE.Fp.create;
const u1 = mod(mod(z + y) * mod(z - y)); // 1
const u2 = mod(x * y); // 2
// Square root always exists
@@ -322,32 +440,47 @@ export class RistrettoPoint {
}
// Compare one point to another.
equals(other: RistrettoPoint): boolean {
equals(other: RistPoint): boolean {
assertRstPoint(other);
const a = this.ep;
const b = other.ep;
const { mod } = ed25519.utils;
const { ex: X1, ey: Y1 } = this.ep;
const { ex: X2, ey: Y2 } = other.ep;
const mod = ed25519.CURVE.Fp.create;
// (x1 * y2 == y1 * x2) | (y1 * y2 == x1 * x2)
const one = mod(a.x * b.y) === mod(a.y * b.x);
const two = mod(a.y * b.y) === mod(a.x * b.x);
const one = mod(X1 * Y2) === mod(Y1 * X2);
const two = mod(Y1 * Y2) === mod(X1 * X2);
return one || two;
}
add(other: RistrettoPoint): RistrettoPoint {
add(other: RistPoint): RistPoint {
assertRstPoint(other);
return new RistrettoPoint(this.ep.add(other.ep));
return new RistPoint(this.ep.add(other.ep));
}
subtract(other: RistrettoPoint): RistrettoPoint {
subtract(other: RistPoint): RistPoint {
assertRstPoint(other);
return new RistrettoPoint(this.ep.subtract(other.ep));
return new RistPoint(this.ep.subtract(other.ep));
}
multiply(scalar: number | bigint): RistrettoPoint {
return new RistrettoPoint(this.ep.multiply(scalar));
multiply(scalar: bigint): RistPoint {
return new RistPoint(this.ep.multiply(scalar));
}
multiplyUnsafe(scalar: number | bigint): RistrettoPoint {
return new RistrettoPoint(this.ep.multiplyUnsafe(scalar));
multiplyUnsafe(scalar: bigint): RistPoint {
return new RistPoint(this.ep.multiplyUnsafe(scalar));
}
}
export const RistrettoPoint = /* @__PURE__ */ (() => {
if (!RistPoint.BASE) RistPoint.BASE = new RistPoint(ed25519.ExtendedPoint.BASE);
if (!RistPoint.ZERO) RistPoint.ZERO = new RistPoint(ed25519.ExtendedPoint.ZERO);
return RistPoint;
})();
// https://datatracker.ietf.org/doc/draft-irtf-cfrg-hash-to-curve/14/
// Appendix B. Hashing to ristretto255
export const hash_to_ristretto255 = (msg: Uint8Array, options: 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;
};

View File

@@ -2,8 +2,9 @@
import { shake256 } from '@noble/hashes/sha3';
import { concatBytes, randomBytes, utf8ToBytes, wrapConstructor } from '@noble/hashes/utils';
import { twistedEdwards } from './abstract/edwards.js';
import { mod, pow2, Fp } from './abstract/modular.js';
import { mod, pow2, Field } from './abstract/modular.js';
import { montgomery } from './abstract/montgomery.js';
import { createHasher } from './abstract/hash-to-curve.js';
/**
* Edwards448 (not Ed448-Goldilocks) curve with following addons:
@@ -52,6 +53,9 @@ function adjustScalarBytes(bytes: Uint8Array): Uint8Array {
return bytes;
}
const Fp = Field(ed448P, 456, true);
const _4n = BigInt(4);
const ED448_DEF = {
// Param: a
a: BigInt(1),
@@ -59,9 +63,10 @@ const ED448_DEF = {
d: BigInt(
'726838724295606890549323807888004534353641360687318060281490199180612328166730772686396383698676545930088884461843637361053498018326358'
),
// Finite field 𝔽p over which we'll do calculations; 2n ** 448n - 2n ** 224n - 1n
Fp: Fp(ed448P, 456),
// Subgroup order: how many points ed448 has; 2n**446n - 13818066809895115352007386748515426880336692474882178609894547503885n
// Finite field 𝔽p over which we'll do calculations; 2n**448n - 2n**224n - 1n
Fp,
// Subgroup order: how many points curve has;
// 2n**446n - 13818066809895115352007386748515426880336692474882178609894547503885n
n: BigInt(
'181709681073901722637330951972001133588410340171829515070372549795146003961539585716195755291692375963310293709091662304773755859649779'
),
@@ -115,14 +120,15 @@ const ED448_DEF = {
export const ed448 = twistedEdwards(ED448_DEF);
// NOTE: there is no ed448ctx, since ed448 supports ctx by default
export const ed448ph = twistedEdwards({ ...ED448_DEF, preHash: shake256_64 });
export const ed448ph = twistedEdwards({ ...ED448_DEF, prehash: shake256_64 });
export const x448 = montgomery({
a24: BigInt(39081),
export const x448 = /* @__PURE__ */ (() =>
montgomery({
a: BigInt(156326),
montgomeryBits: 448,
nByteLength: 57,
P: ed448P,
Gu: '0500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000',
Gu: BigInt(5),
powPminus2: (x: bigint): bigint => {
const P = ed448P;
const Pminus3div4 = ed448_pow_Pminus3div4(x);
@@ -130,18 +136,112 @@ export const x448 = montgomery({
return mod(Pminus3 * x, P); // Pminus3 * x = Pminus2
},
adjustScalarBytes,
// The 4-isogeny maps between the Montgomery curve and this Edwards
// 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)/
// (u^5 - 2*u^2*v^2 - 2*u^3 - 2*v^2 + u))
// xyToU: (p: PointType) => {
// const P = ed448P;
// const { x, y } = p;
// if (x === _0n) throw new Error(`Point with x=0 doesn't have mapping`);
// const invX = invert(x * x, P); // x^2
// const u = mod(y * y * invX, P); // (y^2/x^2)
// return numberToBytesLE(u, 56);
// },
});
randomBytes,
}))();
/**
* Converts edwards448 public key to x448 public key. Uses formula:
* * `(u, v) = ((y-1)/(y+1), sqrt(156324)*u/x)`
* * `(x, y) = (sqrt(156324)*u/v, (1+u)/(1-u))`
* @example
* const aPub = ed448.getPublicKey(utils.randomPrivateKey());
* x448.getSharedSecret(edwardsToMontgomery(aPub), edwardsToMontgomery(someonesPub))
*/
export function edwardsToMontgomeryPub(edwardsPub: string | Uint8Array): Uint8Array {
const { y } = ed448.ExtendedPoint.fromHex(edwardsPub);
const _1n = BigInt(1);
return Fp.toBytes(Fp.create((y - _1n) * Fp.inv(y + _1n)));
}
export const edwardsToMontgomery = edwardsToMontgomeryPub; // deprecated
// Hash To Curve Elligator2 Map
const ELL2_C1 = (Fp.ORDER - BigInt(3)) / BigInt(4); // 1. c1 = (q - 3) / 4 # Integer arithmetic
const ELL2_J = BigInt(156326);
function map_to_curve_elligator2_curve448(u: bigint) {
let tv1 = Fp.sqr(u); // 1. tv1 = u^2
let e1 = Fp.eql(tv1, Fp.ONE); // 2. e1 = tv1 == 1
tv1 = Fp.cmov(tv1, Fp.ZERO, e1); // 3. tv1 = CMOV(tv1, 0, e1) # If Z * u^2 == -1, set tv1 = 0
let xd = Fp.sub(Fp.ONE, tv1); // 4. xd = 1 - tv1
let x1n = Fp.neg(ELL2_J); // 5. x1n = -J
let tv2 = Fp.sqr(xd); // 6. tv2 = xd^2
let gxd = Fp.mul(tv2, xd); // 7. gxd = tv2 * xd # gxd = xd^3
let gx1 = Fp.mul(tv1, Fp.neg(ELL2_J)); // 8. gx1 = -J * tv1 # x1n + J * xd
gx1 = Fp.mul(gx1, x1n); // 9. gx1 = gx1 * x1n # x1n^2 + J * x1n * xd
gx1 = Fp.add(gx1, tv2); // 10. gx1 = gx1 + tv2 # x1n^2 + J * x1n * xd + xd^2
gx1 = Fp.mul(gx1, x1n); // 11. gx1 = gx1 * x1n # x1n^3 + J * x1n^2 * xd + x1n * xd^2
let tv3 = Fp.sqr(gxd); // 12. tv3 = gxd^2
tv2 = Fp.mul(gx1, gxd); // 13. tv2 = gx1 * gxd # gx1 * gxd
tv3 = Fp.mul(tv3, tv2); // 14. tv3 = tv3 * tv2 # gx1 * gxd^3
let y1 = Fp.pow(tv3, ELL2_C1); // 15. y1 = tv3^c1 # (gx1 * gxd^3)^((p - 3) / 4)
y1 = Fp.mul(y1, tv2); // 16. y1 = y1 * tv2 # gx1 * gxd * (gx1 * gxd^3)^((p - 3) / 4)
let x2n = Fp.mul(x1n, Fp.neg(tv1)); // 17. x2n = -tv1 * x1n # x2 = x2n / xd = -1 * u^2 * x1n / xd
let y2 = Fp.mul(y1, u); // 18. y2 = y1 * u
y2 = Fp.cmov(y2, Fp.ZERO, e1); // 19. y2 = CMOV(y2, 0, e1)
tv2 = Fp.sqr(y1); // 20. tv2 = y1^2
tv2 = Fp.mul(tv2, gxd); // 21. tv2 = tv2 * gxd
let e2 = Fp.eql(tv2, gx1); // 22. e2 = tv2 == gx1
let xn = Fp.cmov(x2n, x1n, e2); // 23. xn = CMOV(x2n, x1n, e2) # If e2, x = x1, else x = x2
let y = Fp.cmov(y2, y1, e2); // 24. y = CMOV(y2, y1, e2) # If e2, y = y1, else y = y2
let e3 = Fp.isOdd(y); // 25. e3 = sgn0(y) == 1 # Fix sign of y
y = Fp.cmov(y, Fp.neg(y), e2 !== e3); // 26. y = CMOV(y, -y, e2 XOR e3)
return { xn, xd, yn: y, yd: Fp.ONE }; // 27. return (xn, xd, y, 1)
}
function map_to_curve_elligator2_edwards448(u: bigint) {
let { xn, xd, yn, yd } = map_to_curve_elligator2_curve448(u); // 1. (xn, xd, yn, yd) = map_to_curve_elligator2_curve448(u)
let xn2 = Fp.sqr(xn); // 2. xn2 = xn^2
let xd2 = Fp.sqr(xd); // 3. xd2 = xd^2
let xd4 = Fp.sqr(xd2); // 4. xd4 = xd2^2
let yn2 = Fp.sqr(yn); // 5. yn2 = yn^2
let yd2 = Fp.sqr(yd); // 6. yd2 = yd^2
let xEn = Fp.sub(xn2, xd2); // 7. xEn = xn2 - xd2
let tv2 = Fp.sub(xEn, xd2); // 8. tv2 = xEn - xd2
xEn = Fp.mul(xEn, xd2); // 9. xEn = xEn * xd2
xEn = Fp.mul(xEn, yd); // 10. xEn = xEn * yd
xEn = Fp.mul(xEn, yn); // 11. xEn = xEn * yn
xEn = Fp.mul(xEn, _4n); // 12. xEn = xEn * 4
tv2 = Fp.mul(tv2, xn2); // 13. tv2 = tv2 * xn2
tv2 = Fp.mul(tv2, yd2); // 14. tv2 = tv2 * yd2
let tv3 = Fp.mul(yn2, _4n); // 15. tv3 = 4 * yn2
let tv1 = Fp.add(tv3, yd2); // 16. tv1 = tv3 + yd2
tv1 = Fp.mul(tv1, xd4); // 17. tv1 = tv1 * xd4
let xEd = Fp.add(tv1, tv2); // 18. xEd = tv1 + tv2
tv2 = Fp.mul(tv2, xn); // 19. tv2 = tv2 * xn
let tv4 = Fp.mul(xn, xd4); // 20. tv4 = xn * xd4
let yEn = Fp.sub(tv3, yd2); // 21. yEn = tv3 - yd2
yEn = Fp.mul(yEn, tv4); // 22. yEn = yEn * tv4
yEn = Fp.sub(yEn, tv2); // 23. yEn = yEn - tv2
tv1 = Fp.add(xn2, xd2); // 24. tv1 = xn2 + xd2
tv1 = Fp.mul(tv1, xd2); // 25. tv1 = tv1 * xd2
tv1 = Fp.mul(tv1, xd); // 26. tv1 = tv1 * xd
tv1 = Fp.mul(tv1, yn2); // 27. tv1 = tv1 * yn2
tv1 = Fp.mul(tv1, BigInt(-2)); // 28. tv1 = -2 * tv1
let yEd = Fp.add(tv2, tv1); // 29. yEd = tv2 + tv1
tv4 = Fp.mul(tv4, yd2); // 30. tv4 = tv4 * yd2
yEd = Fp.add(yEd, tv4); // 31. yEd = yEd + tv4
tv1 = Fp.mul(xEd, yEd); // 32. tv1 = xEd * yEd
let e = Fp.eql(tv1, Fp.ZERO); // 33. e = tv1 == 0
xEn = Fp.cmov(xEn, Fp.ZERO, e); // 34. xEn = CMOV(xEn, 0, e)
xEd = Fp.cmov(xEd, Fp.ONE, e); // 35. xEd = CMOV(xEd, 1, e)
yEn = Fp.cmov(yEn, Fp.ONE, e); // 36. yEn = CMOV(yEn, 1, e)
yEd = Fp.cmov(yEd, Fp.ONE, e); // 37. yEd = CMOV(yEd, 1, e)
const inv = Fp.invertBatch([xEd, yEd]); // batch division
return { x: Fp.mul(xEn, inv[0]), y: Fp.mul(yEn, inv[1]) }; // 38. return (xEn, xEd, yEn, yEd)
}
const htf = /* @__PURE__ */ (() =>
createHasher(
ed448.ExtendedPoint,
(scalars: bigint[]) => map_to_curve_elligator2_edwards448(scalars[0]),
{
DST: 'edwards448_XOF:SHAKE256_ELL2_RO_',
encodeDST: 'edwards448_XOF:SHAKE256_ELL2_NU_',
p: Fp.ORDER,
m: 1,
k: 224,
expand: 'xof',
hash: shake256,
}
))();
export const hashToCurve = /* @__PURE__ */ (() => htf.hashToCurve)();
export const encodeToCurve = /* @__PURE__ */ (() => htf.encodeToCurve)();

View File

@@ -1,13 +1,14 @@
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
import { sha256 } from '@noble/hashes/sha256';
import { sha512 } from '@noble/hashes/sha512';
import { concatBytes, randomBytes, utf8ToBytes } from '@noble/hashes/utils';
import { twistedEdwards } from './abstract/edwards.js';
import { blake2s } from '@noble/hashes/blake2s';
import { Fp } from './abstract/modular.js';
import { Field } from './abstract/modular.js';
/**
* jubjub Twisted Edwards curve.
* https://neuromancer.sk/std/other/JubJub
* jubjub does not use EdDSA, so `hash`/sha512 params are passed because interface expects them.
*/
export const jubjub = twistedEdwards({
@@ -15,16 +16,16 @@ export const jubjub = twistedEdwards({
a: BigInt('0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000000'),
d: BigInt('0x2a9318e74bfa2b48f5fd9207e6bd7fd4292d7f6d37579d2601065fd6d6343eb1'),
// Finite field 𝔽p over which we'll do calculations
Fp: Fp(BigInt('0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001')),
// Subgroup order: how many points ed25519 has
// 2n ** 252n + 27742317777372353535851937790883648493n;
// Same value as bls12-381 Fr (not Fp)
Fp: Field(BigInt('0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001')),
// Subgroup order: how many points curve has
n: BigInt('0xe7db4ea6533afa906673b0101343b00a6682093ccc81082d0970e5ed6f72cb7'),
// Cofactor
h: BigInt(8),
// Base point (x, y) aka generator point
Gx: BigInt('0x11dafe5d23e1218086a365b99fbf3d3be72f6afd7d1f72623e6b071492d1122b'),
Gy: BigInt('0x1d523cf1ddab1a1793132e78c866c0c33e26ba5cc220fed7cc3f870e59d292aa'),
hash: sha256,
hash: sha512,
randomBytes,
} as const);
@@ -38,7 +39,7 @@ export function groupHash(tag: Uint8Array, personalization: Uint8Array) {
h.update(GH_FIRST_BLOCK);
h.update(tag);
// NOTE: returns ExtendedPoint, in case it will be multiplied later
let p = jubjub.ExtendedPoint.fromAffine(jubjub.Point.fromHex(h.digest()));
let p = jubjub.ExtendedPoint.fromHex(h.digest());
// NOTE: cannot replace with isSmallOrder, returns Point*8
p = p.multiply(jubjub.CURVE.h);
if (p.equals(jubjub.ExtendedPoint.ZERO)) throw new Error('Point has small order');

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; 2n**224n - 2n**96n + 1n
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,48 @@
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
import { createCurve } from './_shortw_utils.js';
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 { 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
// Field over which we'll do calculations; 2n**224n * (2n**32n-1n) + 2n**192n + 2n**96n-1n
const Fp = Field(BigInt('0xffffffff00000001000000000000000000000000ffffffffffffffffffffffff'));
const CURVE_A = Fp.create(BigInt('-3'));
const CURVE_B = BigInt('0x5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b');
const mapSWU = mapToCurveSimpleSWU(Fp, {
A: CURVE_A,
B: CURVE_B,
Z: Fp.create(BigInt('-10')),
});
export const P256 = createCurve(
{
// Params: a, b
a: CURVE_A,
// prettier-ignore
export const p256 = createCurve({
a: CURVE_A, // Equation params: a, 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
n: BigInt('0xffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551'),
// Base point (x, y) aka generator point
// Base (generator) point (x, y)
Gx: BigInt('0x6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296'),
Gy: BigInt('0x4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5'),
h: BigInt(1),
lowS: false,
mapToCurve: (scalars: bigint[]) => mapSWU(scalars[0]),
htfDefaults: {
} as const, sha256);
export const secp256r1 = p256;
const mapSWU = /* @__PURE__ */ (() =>
mapToCurveSimpleSWU(Fp, {
A: CURVE_A,
B: CURVE_B,
Z: Fp.create(BigInt('-10')),
}))();
const htf = /* @__PURE__ */ (() =>
createHasher(secp256r1.ProjectivePoint, (scalars: bigint[]) => mapSWU(scalars[0]), {
DST: 'P256_XMD:SHA-256_SSWU_RO_',
encodeDST: 'P256_XMD:SHA-256_SSWU_NU_',
p: Fp.ORDER,
m: 1,
k: 128,
expand: true,
expand: 'xmd',
hash: sha256,
},
} as const,
sha256
);
export const secp256r1 = P256;
}))();
export const hashToCurve = /* @__PURE__ */ (() => htf.hashToCurve)();
export const encodeToCurve = /* @__PURE__ */ (() => htf.encodeToCurve)();

View File

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

View File

@@ -1,61 +1,68 @@
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
import { createCurve } from './_shortw_utils.js';
import { sha512 } from '@noble/hashes/sha512';
import { bytesToHex, PrivKey } from './abstract/utils.js';
import { Fp as Field } from './abstract/modular.js';
import { Field } from './abstract/modular.js';
import { mapToCurveSimpleSWU } from './abstract/weierstrass.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.
// 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
const P = BigInt('0x1ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff');
const Fp = Field(P);
const CURVE_A = Fp.create(BigInt('-3'));
// prettier-ignore
const CURVE_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,
const CURVE = {
a: Fp.create(BigInt('-3')),
b: BigInt(
'0x0051953eb9618e1c9a1f929a21a0b68540eea2da725b99b315f3b8b489918ef109e156193951ec7e937b1652c0bd3bb1bf073573df883d2c34f1ef451fd46b503f00'
),
Fp,
// Curve order, total count of valid points in the field
n: BigInt('0x01fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa51868783bf2f966b7fcc0148f709a5d03bb5c9b8899c47aebb6fb71e91386409'),
// Base point (x, y) aka generator point
Gx: BigInt('0x00c6858e06b70404e9cd9e3ecb662395b4429c648139053fb521f828af606b4d3dbaa14b5e77efe75928fe1dc127a2ffa8de3348b3c1856a429bf97e7e31c2e5bd66'),
Gy: BigInt('0x011839296a789a3bc0045c8a5fb42c7d1bd998f54449579b446817afbd17273e662c97ee72995ef42640c550b9013fad0761353c7086a272c24088be94769fd16650'),
n: BigInt(
'0x01fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa51868783bf2f966b7fcc0148f709a5d03bb5c9b8899c47aebb6fb71e91386409'
),
Gx: BigInt(
'0x00c6858e06b70404e9cd9e3ecb662395b4429c648139053fb521f828af606b4d3dbaa14b5e77efe75928fe1dc127a2ffa8de3348b3c1856a429bf97e7e31c2e5bd66'
),
Gy: BigInt(
'0x011839296a789a3bc0045c8a5fb42c7d1bd998f54449579b446817afbd17273e662c97ee72995ef42640c550b9013fad0761353c7086a272c24088be94769fd16650'
),
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,
// P521 keys could be 130, 131, 132 bytes - which doesn't play nicely.
// We ensure all keys are 132 bytes.
// Does not replace validation; invalid keys would still be rejected.
normalizePrivateKey(key: PrivKey) {
if (typeof key === 'bigint') return key;
if (key instanceof Uint8Array) key = bytesToHex(key);
if (typeof key !== 'string' || !([130, 131, 132].includes(key.length))) {
throw new Error('Invalid key');
}
return key.padStart(66 * 2, '0');
},
mapToCurve: (scalars: bigint[]) => mapSWU(scalars[0]),
htfDefaults: {
allowedPrivateKeyLengths: [130, 131, 132] // P521 keys are variable-length. Normalize to 132b
} as const, sha512);
export const secp521r1 = p521;
const mapSWU = /* @__PURE__ */ (() =>
mapToCurveSimpleSWU(Fp, {
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_',
encodeDST: 'P521_XMD:SHA-512_SSWU_NU_',
p: Fp.ORDER,
m: 1,
k: 256,
expand: true,
expand: 'xmd',
hash: sha512,
},
} as const, sha512);
export const secp521r1 = P521;
}))();
export const hashToCurve = /* @__PURE__ */ (() => htf.hashToCurve)();
export const encodeToCurve = /* @__PURE__ */ (() => htf.encodeToCurve)();

View File

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

View File

@@ -1,28 +1,12 @@
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
import { sha256 } from '@noble/hashes/sha256';
import { Fp as Field, mod, pow2 } from './abstract/modular.js';
import { createCurve } from './_shortw_utils.js';
import { PointType, mapToCurveSimpleSWU } from './abstract/weierstrass.js';
import {
ensureBytes,
concatBytes,
Hex,
hexToBytes,
bytesToNumberBE,
PrivKey,
} from './abstract/utils.js';
import { randomBytes } from '@noble/hashes/utils';
import { isogenyMap } from './abstract/hash-to-curve.js';
/**
* secp256k1 belongs to Koblitz curves: it has
* efficiently computable Frobenius endomorphism.
* Endomorphism improves efficiency:
* Uses 2x less RAM, speeds up precomputation by 2x and ECDH / sign key recovery by 20%.
* Should always be used for Projective's double-and-add multiplication.
* For affines cached multiplication, it trades off 1/2 init time & 1/3 ram for 20% perf hit.
* https://gist.github.com/paulmillr/eb670806793e84df628a7c434a873066
*/
import { Field, mod, pow2 } from './abstract/modular.js';
import { ProjPointType as PointType, mapToCurveSimpleSWU } from './abstract/weierstrass.js';
import type { Hex, PrivKey } from './abstract/utils.js';
import { bytesToNumberBE, concatBytes, ensureBytes, numberToBytesBE } from './abstract/utils.js';
import { createHasher, isogenyMap } from './abstract/hash-to-curve.js';
import { createCurve } from './_shortw_utils.js';
const secp256k1P = BigInt('0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f');
const secp256k1N = BigInt('0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141');
@@ -31,10 +15,7 @@ const _2n = BigInt(2);
const divNearest = (a: bigint, b: bigint) => (a + b / _2n) / b;
/**
* Allows to compute square root √y 2x faster.
* To calculate √y, we need to exponentiate it to a very big number:
* `y² = x³ + ax + b; y = y² ^ (p+1)/4`
* We are unwrapping the loop and multiplying it bit-by-bit.
* √n = n^((p+1)/4) for fields p = 3 mod 4. We unwrap the loop and multiply bit-by-bit.
* (P+1n/4n).toString(2) would produce bits [223x 1, 0, 22x 1, 4x 0, 11, 00]
*/
function sqrtMod(y: bigint): bigint {
@@ -56,13 +37,185 @@ function sqrtMod(y: bigint): bigint {
const b223 = (pow2(b220, _3n, P) * b3) % P;
const t1 = (pow2(b223, _23n, P) * b22) % P;
const t2 = (pow2(t1, _6n, P) * b2) % P;
return pow2(t2, _2n, P);
const root = pow2(t2, _2n, P);
if (!Fp.eql(Fp.sqr(root), y)) throw new Error('Cannot find square root');
return root;
}
const Fp = Field(secp256k1P, undefined, undefined, { sqrt: sqrtMod });
type Fp = bigint;
const isoMap = isogenyMap(
export const secp256k1 = createCurve(
{
a: BigInt(0), // equation params: a, b
b: BigInt(7), // Seem to be rigid: bitcointalk.org/index.php?topic=289795.msg3183975#msg3183975
Fp, // Field's prime: 2n**256n - 2n**32n - 2n**9n - 2n**8n - 2n**7n - 2n**6n - 2n**4n - 1n
n: secp256k1N, // Curve order, total count of valid points in the field
// Base point (x, y) aka generator point
Gx: BigInt('55066263022277343669578718895168534326250603453777594175500187360389116729240'),
Gy: BigInt('32670510020758816978083085130507043184471273380659243275938904335757337482424'),
h: BigInt(1), // Cofactor
lowS: true, // Allow only low-S signatures by default in sign() and verify()
/**
* secp256k1 belongs to Koblitz curves: it has efficiently computable endomorphism.
* Endomorphism uses 2x less RAM, speeds up precomputation by 2x and ECDH / key recovery by 20%.
* For precomputed wNAF it trades off 1/2 init time & 1/3 ram for 20% perf hit.
* Explanation: https://gist.github.com/paulmillr/eb670806793e84df628a7c434a873066
*/
endo: {
beta: BigInt('0x7ae96a2b657c07106e64479eac3434e99cf0497512f58995c1396c28719501ee'),
splitScalar: (k: bigint) => {
const n = secp256k1N;
const a1 = BigInt('0x3086d221a7d46bcde86c90e49284eb15');
const b1 = -_1n * BigInt('0xe4437ed6010e88286f547fa90abfe4c3');
const a2 = BigInt('0x114ca50f7a8e2f3f657c1108d9d44cfd8');
const b2 = a1;
const POW_2_128 = BigInt('0x100000000000000000000000000000000'); // (2n**128n).toString(16)
const c1 = divNearest(b2 * k, n);
const c2 = divNearest(-b1 * k, n);
let k1 = mod(k - c1 * a1 - c2 * a2, n);
let k2 = mod(-c1 * b1 - c2 * b2, n);
const k1neg = k1 > POW_2_128;
const k2neg = k2 > POW_2_128;
if (k1neg) k1 = n - k1;
if (k2neg) k2 = n - k2;
if (k1 > POW_2_128 || k2 > POW_2_128) {
throw new Error('splitScalar: Endomorphism failed, k=' + k);
}
return { k1neg, k1, k2neg, k2 };
},
},
},
sha256
);
// Schnorr signatures are superior to ECDSA from above. Below is Schnorr-specific BIP0340 code.
// https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki
const _0n = BigInt(0);
const fe = (x: bigint) => typeof x === 'bigint' && _0n < x && x < secp256k1P;
const ge = (x: bigint) => typeof x === 'bigint' && _0n < x && x < secp256k1N;
/** An object mapping tags to their tagged hash prefix of [SHA256(tag) | SHA256(tag)] */
const TAGGED_HASH_PREFIXES: { [tag: string]: Uint8Array } = {};
function taggedHash(tag: string, ...messages: Uint8Array[]): Uint8Array {
let tagP = TAGGED_HASH_PREFIXES[tag];
if (tagP === undefined) {
const tagH = sha256(Uint8Array.from(tag, (c) => c.charCodeAt(0)));
tagP = concatBytes(tagH, tagH);
TAGGED_HASH_PREFIXES[tag] = tagP;
}
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 numTo32b = (n: bigint) => numberToBytesBE(n, 32);
const modP = (x: bigint) => mod(x, secp256k1P);
const modN = (x: bigint) => mod(x, secp256k1N);
const Point = secp256k1.ProjectivePoint;
const GmulAdd = (Q: PointType<bigint>, a: bigint, b: bigint) =>
Point.BASE.multiplyAndAddUnsafe(Q, a, b);
// Calculate point, scalar and bytes
function schnorrGetExtPubKey(priv: PrivKey) {
let d_ = secp256k1.utils.normPrivateKeyToScalar(priv); // same method executed in fromPrivateKey
let p = Point.fromPrivateKey(d_); // P = d'⋅G; 0 < d' < n check is done inside
const scalar = p.hasEvenY() ? d_ : modN(-d_);
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> {
if (!fe(x)) throw new Error('bad x: need 0 < x < p'); // Fail if x ≥ p.
const xx = modP(x * x);
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.
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.
p.assertValidity();
return p;
}
/**
* Create tagged hash, convert it to bigint, reduce modulo-n.
*/
function challenge(...args: Uint8Array[]): bigint {
return modN(bytesToNumberBE(taggedHash('BIP0340/challenge', ...args)));
}
/**
* Schnorr public key is just `x` coordinate of Point as per BIP340.
*/
function schnorrGetPublicKey(privateKey: Hex): Uint8Array {
return schnorrGetExtPubKey(privateKey).bytes; // d'=int(sk). Fail if d'=0 or d'≥n. Ret bytes(d'⋅G)
}
/**
* Creates Schnorr signature as per BIP340. Verifies itself before returning anything.
* auxRand is optional and is not the sole source of k generation: bad CSPRNG won't be dangerous.
*/
function schnorrSign(
message: Hex,
privateKey: PrivKey,
auxRand: Hex = randomBytes(32)
): Uint8Array {
const m = ensureBytes('message', message);
const { bytes: px, scalar: d } = schnorrGetExtPubKey(privateKey); // checks for isWithinCurveOrder
const a = ensureBytes('auxRand', auxRand, 32); // Auxiliary random data a: a 32-byte array
const t = numTo32b(d ^ bytesToNumberBE(taggedHash('BIP0340/aux', a))); // Let t be the byte-wise xor of bytes(d) and hash/aux(a)
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
if (k_ === _0n) throw new Error('sign failed: k is zero'); // Fail if k' = 0.
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 sig = new Uint8Array(64); // Let sig = bytes(R) || bytes((k + ed) mod n).
sig.set(rx, 0);
sig.set(numTo32b(modN(k + e * d)), 32);
// If Verify(bytes(P), m, sig) (see below) returns failure, abort
if (!schnorrVerify(sig, m, px)) throw new Error('sign: Invalid signature produced');
return sig;
}
/**
* Verifies Schnorr signature.
* Will swallow errors & return false except for initial type validation of arguments.
*/
function schnorrVerify(signature: Hex, message: Hex, publicKey: Hex): boolean {
const sig = ensureBytes('signature', signature, 64);
const m = ensureBytes('message', message);
const pub = ensureBytes('publicKey', publicKey, 32);
try {
const P = lift_x(bytesToNumberBE(pub)); // P = lift_x(int(pk)); fail if that fails
const r = bytesToNumberBE(sig.subarray(0, 32)); // Let r = int(sig[0:32]); fail if r ≥ p.
if (!fe(r)) return false;
const s = bytesToNumberBE(sig.subarray(32, 64)); // Let s = int(sig[32:64]); fail if s ≥ n.
if (!ge(s)) return false;
const e = challenge(numTo32b(r), pointToBytes(P), m); // int(challenge(bytes(r)||bytes(P)||m))%n
const R = GmulAdd(P, s, modN(-e)); // R = s⋅G - e⋅P
if (!R || !R.hasEvenY() || R.toAffine().x !== r) return false; // -eP == (n-e)P
return true; // Fail if is_infinite(R) / not has_even_y(R) / x(R) ≠ r.
} catch (error) {
return false;
}
}
export const schnorr = /* @__PURE__ */ (() => ({
getPublicKey: schnorrGetPublicKey,
sign: schnorrSign,
verify: schnorrVerify,
utils: {
randomPrivateKey: secp256k1.utils.randomPrivateKey,
lift_x,
pointToBytes,
numberToBytesBE,
bytesToNumberBE,
taggedHash,
mod,
},
}))();
const isoMap = /* @__PURE__ */ (() =>
isogenyMap(
Fp,
[
// xNum
@@ -92,226 +245,30 @@ const isoMap = isogenyMap(
'0x6484aa716545ca2cf3a70c3fa8fe337e0a3d21162f0d6299a7bf8192bfd2a76f',
'0x0000000000000000000000000000000000000000000000000000000000000001', // LAST 1
],
].map((i) => i.map((j) => BigInt(j))) as [Fp[], Fp[], Fp[], Fp[]]
);
const mapSWU = mapToCurveSimpleSWU(Fp, {
].map((i) => i.map((j) => BigInt(j))) as [bigint[], bigint[], bigint[], bigint[]]
))();
const mapSWU = /* @__PURE__ */ (() =>
mapToCurveSimpleSWU(Fp, {
A: BigInt('0x3f8731abdd661adca08a5558f0f5d272e953d363cb6f0e5d405447c01a444533'),
B: BigInt('1771'),
Z: Fp.create(BigInt('-11')),
});
export const secp256k1 = createCurve(
{
// Params: a, b
// Seem to be rigid https://bitcointalk.org/index.php?topic=289795.msg3183975#msg3183975
a: BigInt(0),
b: BigInt(7),
// Field over which we'll do calculations;
// 2n**256n - 2n**32n - 2n**9n - 2n**8n - 2n**7n - 2n**6n - 2n**4n - 1n
Fp,
// Curve order, total count of valid points in the field
n: secp256k1N,
// Base point (x, y) aka generator point
Gx: BigInt('55066263022277343669578718895168534326250603453777594175500187360389116729240'),
Gy: BigInt('32670510020758816978083085130507043184471273380659243275938904335757337482424'),
h: BigInt(1),
// Alllow only low-S signatures by default in sign() and verify()
lowS: true,
endo: {
// Params taken from https://gist.github.com/paulmillr/eb670806793e84df628a7c434a873066
beta: BigInt('0x7ae96a2b657c07106e64479eac3434e99cf0497512f58995c1396c28719501ee'),
splitScalar: (k: bigint) => {
const n = secp256k1N;
const a1 = BigInt('0x3086d221a7d46bcde86c90e49284eb15');
const b1 = -_1n * BigInt('0xe4437ed6010e88286f547fa90abfe4c3');
const a2 = BigInt('0x114ca50f7a8e2f3f657c1108d9d44cfd8');
const b2 = a1;
const POW_2_128 = BigInt('0x100000000000000000000000000000000');
const c1 = divNearest(b2 * k, n);
const c2 = divNearest(-b1 * k, n);
let k1 = mod(k - c1 * a1 - c2 * a2, n);
let k2 = mod(-c1 * b1 - c2 * b2, n);
const k1neg = k1 > POW_2_128;
const k2neg = k2 > POW_2_128;
if (k1neg) k1 = n - k1;
if (k2neg) k2 = n - k2;
if (k1 > POW_2_128 || k2 > POW_2_128) {
throw new Error('splitScalar: Endomorphism failed, k=' + k);
}
return { k1neg, k1, k2neg, k2 };
},
},
mapToCurve: (scalars: bigint[]) => {
}))();
const htf = /* @__PURE__ */ (() =>
createHasher(
secp256k1.ProjectivePoint,
(scalars: bigint[]) => {
const { x, y } = mapSWU(Fp.create(scalars[0]));
return isoMap(x, y);
},
htfDefaults: {
{
DST: 'secp256k1_XMD:SHA-256_SSWU_RO_',
encodeDST: 'secp256k1_XMD:SHA-256_SSWU_NU_',
p: Fp.ORDER,
m: 1,
k: 128,
expand: true,
expand: 'xmd',
hash: sha256,
},
},
sha256
);
// Schnorr
const _0n = BigInt(0);
const numTo32b = secp256k1.utils._bigintToBytes;
const numTo32bStr = secp256k1.utils._bigintToString;
const normalizePrivateKey = secp256k1.utils._normalizePrivateKey;
// TODO: export?
function normalizePublicKey(publicKey: Hex | PointType<bigint>): PointType<bigint> {
if (publicKey instanceof secp256k1.Point) {
publicKey.assertValidity();
return publicKey;
} else {
const bytes = ensureBytes(publicKey);
// Schnorr is 32 bytes
if (bytes.length === 32) {
const x = bytesToNumberBE(bytes);
if (!isValidFieldElement(x)) throw new Error('Point is not on curve');
const y2 = secp256k1.utils._weierstrassEquation(x); // y² = x³ + ax + b
let y = sqrtMod(y2); // y = y² ^ (p+1)/4
const isYOdd = (y & _1n) === _1n;
// Schnorr
if (isYOdd) y = secp256k1.CURVE.Fp.negate(y);
const point = new secp256k1.Point(x, y);
point.assertValidity();
return point;
}
// Do we need that in schnorr at all?
return secp256k1.Point.fromHex(publicKey);
}
}
const isWithinCurveOrder = secp256k1.utils._isWithinCurveOrder;
const isValidFieldElement = secp256k1.utils._isValidFieldElement;
const TAGS = {
challenge: 'BIP0340/challenge',
aux: 'BIP0340/aux',
nonce: 'BIP0340/nonce',
} as const;
/** An object mapping tags to their tagged hash prefix of [SHA256(tag) | SHA256(tag)] */
const TAGGED_HASH_PREFIXES: { [tag: string]: Uint8Array } = {};
export function taggedHash(tag: string, ...messages: Uint8Array[]): Uint8Array {
let tagP = TAGGED_HASH_PREFIXES[tag];
if (tagP === undefined) {
const tagH = sha256(Uint8Array.from(tag, (c) => c.charCodeAt(0)));
tagP = concatBytes(tagH, tagH);
TAGGED_HASH_PREFIXES[tag] = tagP;
}
return sha256(concatBytes(tagP, ...messages));
}
const toRawX = (point: PointType<bigint>) => point.toRawBytes(true).slice(1);
// Schnorr signatures are superior to ECDSA from above.
// Below is Schnorr-specific code as per BIP0340.
function schnorrChallengeFinalize(ch: Uint8Array): bigint {
return mod(bytesToNumberBE(ch), secp256k1.CURVE.n);
}
// Do we need this at all for Schnorr?
class SchnorrSignature {
constructor(readonly r: bigint, readonly s: bigint) {
this.assertValidity();
}
static fromHex(hex: Hex) {
const bytes = ensureBytes(hex);
if (bytes.length !== 64)
throw new TypeError(`SchnorrSignature.fromHex: expected 64 bytes, not ${bytes.length}`);
const r = bytesToNumberBE(bytes.subarray(0, 32));
const s = bytesToNumberBE(bytes.subarray(32, 64));
return new SchnorrSignature(r, s);
}
assertValidity() {
const { r, s } = this;
if (!isValidFieldElement(r) || !isWithinCurveOrder(s)) throw new Error('Invalid signature');
}
toHex(): string {
return numTo32bStr(this.r) + numTo32bStr(this.s);
}
toRawBytes(): Uint8Array {
return hexToBytes(this.toHex());
}
}
function schnorrGetScalar(priv: bigint) {
const point = secp256k1.Point.fromPrivateKey(priv);
const scalar = point.hasEvenY() ? priv : secp256k1.CURVE.n - priv;
return { point, scalar, x: toRawX(point) };
}
/**
* Synchronously creates Schnorr signature. Improved security: verifies itself before
* producing an output.
* @param msg message (not message hash)
* @param privateKey private key
* @param auxRand random bytes that would be added to k. Bad RNG won't break it.
*/
function schnorrSign(
message: Hex,
privateKey: PrivKey,
auxRand: Hex = randomBytes(32)
): Uint8Array {
if (message == null) throw new TypeError(`sign: Expected valid message, not "${message}"`);
const m = ensureBytes(message);
// checks for isWithinCurveOrder
const { x: px, scalar: d } = schnorrGetScalar(normalizePrivateKey(privateKey));
const rand = ensureBytes(auxRand);
if (rand.length !== 32) throw new TypeError('sign: Expected 32 bytes of aux randomness');
const tag = taggedHash;
const t0h = tag(TAGS.aux, rand);
const t = numTo32b(d ^ bytesToNumberBE(t0h));
const k0h = tag(TAGS.nonce, t, px, m);
const k0 = mod(bytesToNumberBE(k0h), secp256k1.CURVE.n);
if (k0 === _0n) throw new Error('sign: Creation of signature failed. k is zero');
const { point: R, x: rx, scalar: k } = schnorrGetScalar(k0);
const e = schnorrChallengeFinalize(tag(TAGS.challenge, rx, px, m));
const sig = new SchnorrSignature(R.x, mod(k + e * d, secp256k1.CURVE.n)).toRawBytes();
if (!schnorrVerify(sig, m, px)) throw new Error('sign: Invalid signature produced');
return sig;
}
/**
* Verifies Schnorr signature synchronously.
*/
function schnorrVerify(signature: Hex, message: Hex, publicKey: Hex): boolean {
try {
const raw = signature instanceof SchnorrSignature;
const sig: SchnorrSignature = raw ? signature : SchnorrSignature.fromHex(signature);
if (raw) sig.assertValidity(); // just in case
const { r, s } = sig;
const m = ensureBytes(message);
const P = normalizePublicKey(publicKey);
const e = schnorrChallengeFinalize(taggedHash(TAGS.challenge, numTo32b(r), toRawX(P), m));
// Finalize
// R = s⋅G - e⋅P
// -eP == (n-e)P
const R = secp256k1.Point.BASE.multiplyAndAddUnsafe(
P,
normalizePrivateKey(s),
mod(-e, secp256k1.CURVE.n)
);
if (!R || !R.hasEvenY() || R.x !== r) return false;
return true;
} catch (error) {
return false;
}
}
export const schnorr = {
Signature: SchnorrSignature,
// Schnorr's pubkey is just `x` of Point (BIP340)
getPublicKey: (privateKey: PrivKey): Uint8Array =>
toRawX(secp256k1.Point.fromPrivateKey(privateKey)),
sign: schnorrSign,
verify: schnorrVerify,
};
))();
export const hashToCurve = /* @__PURE__ */ (() => htf.hashToCurve)();
export const encodeToCurve = /* @__PURE__ */ (() => htf.encodeToCurve)();

View File

@@ -1,262 +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, ProjectivePointType } from './abstract/weierstrass.js';
import * as cutils from './abstract/utils.js';
import { Fp } from './abstract/modular.js';
import { getHash } from './_shortw_utils.js';
type ProjectivePoint = ProjectivePointType<bigint>;
// Stark-friendly elliptic curve
// https://docs.starkware.co/starkex/stark-curve.html
const CURVE_N = BigInt(
'3618502788666131213697322783095070105526743751716087489154079457884512865583'
);
const nBitLength = 252;
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),
truncateHash: (hash: Uint8Array, truncateOnly = false): bigint => {
// TODO: cleanup, ugly code
// Fix truncation
if (!truncateOnly) {
let hashS = bytesToNumber0x(hash).toString(16);
if (hashS.length === 63) {
hashS += '0';
hash = hexToBytes0x(hashS);
}
}
// Truncate zero bytes on left (compat with elliptic)
while (hash[0] === 0) hash = hash.subarray(1);
const byteLength = hash.length;
const delta = byteLength * 8 - nBitLength; // size of curve.n (252 bits)
let h = hash.length ? bytesToNumber0x(hash) : 0n;
if (delta > 0) h = h >> BigInt(delta);
if (!truncateOnly && h >= CURVE_N) h -= CURVE_N;
return h;
},
});
// Custom Starknet type conversion functions that can handle 0x and unpadded hex
function hexToBytes0x(hex: string): Uint8Array {
if (typeof hex !== 'string') {
throw new TypeError('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 TypeError('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(32 * 2, '0');
}
function getPublicKey0x(privKey: Hex, isCompressed?: boolean) {
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, Point, ProjectivePoint, Signature } = starkCurve;
export const utils = starkCurve.utils;
export {
CURVE,
Point,
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 bytesToNumber0x(sha256(cutils.concatBytes(key, hexToBytes0x(indexHex))));
}
export function grindKey(seed: Hex) {
const _seed = ensureBytes0x(seed);
const sha256mask = 2n ** 256n;
const limit = sha256mask - starkCurve.utils.mod(sha256mask, starkCurve.CURVE.n);
for (let i = 0; ; i++) {
const key = hashKeyWithIndex(_seed, i);
// key should be in [0, limit)
if (key < limit) return starkCurve.utils.mod(key, starkCurve.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(bytesToNumber0x(sha256(layer)));
const applicationNum = int31(bytesToNumber0x(sha256(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 Point(
2089986280348253421170679821480865132823066470938446095505822317253594081284n,
1713931329540660377023406109199410414810705867260802078187082345529207694986n
),
new Point(
996781205833008774514500082376783249102396023663454813447423147977397232763n,
1668503676786377725805489344771023921079126552019160156920634619255970485781n
),
new Point(
2251563274489750535117886426533222435294046428347329203627021249169616184184n,
1798716007562728905295480679789526322175868328062420237419143593021674992973n
),
new Point(
2138414695194151160943305727036575959195309218611738193261179310511854807447n,
113410276730064486255102093846540133784865286929052426931474106396135072156n
),
new Point(
2379962749567351885752724891227938183011949129833673362440656643086021394946n,
776496453633298175483985398648758586525933812536653089401905292063708816422n
),
];
// for (const p of PEDERSEN_POINTS) p._setWindowSize(8);
const PEDERSEN_POINTS = PEDERSEN_POINTS_AFFINE.map(ProjectivePoint.fromAffine);
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();
}
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.x === point.x) 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.toAffine().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 = 2n ** 250n - 1n;
export const keccak = (data: Uint8Array) => bytesToNumber0x(keccak_256(data)) & MASK_250;

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,34 +1,328 @@
import { deepStrictEqual, throws } from 'assert';
import { should } from 'micro-should';
import { should, describe } from 'micro-should';
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 '../esm/abstract/utils.js';
// Generic tests for all curves in package
import { secp192r1 } from '../lib/esm/p192.js';
import { secp224r1 } from '../lib/esm/p224.js';
import { secp256r1 } from '../lib/esm/p256.js';
import { secp384r1 } from '../lib/esm/p384.js';
import { secp521r1 } from '../lib/esm/p521.js';
import { secp256k1 } from '../lib/esm/secp256k1.js';
import { ed25519, ed25519ctx, ed25519ph } from '../lib/esm/ed25519.js';
import { ed448, ed448ph } from '../lib/esm/ed448.js';
import { starkCurve } from '../lib/esm/stark.js';
import { pallas, vesta } from '../lib/esm/pasta.js';
import { bn254 } from '../lib/esm/bn.js';
import { jubjub } from '../lib/esm/jubjub.js';
import { secp192r1, secp224r1 } from './_more-curves.helpers.js';
import { secp256r1 } from '../esm/p256.js';
import { secp384r1 } from '../esm/p384.js';
import { secp521r1 } from '../esm/p521.js';
import { secp256k1 } from '../esm/secp256k1.js';
import { ed25519, ed25519ctx, ed25519ph, x25519 } from '../esm/ed25519.js';
import { ed448, ed448ph } from '../esm/ed448.js';
import { pallas, vesta } from '../esm/pasta.js';
import { bn254 } from '../esm/bn254.js';
import { jubjub } from '../esm/jubjub.js';
import { bls12_381 } from '../esm/bls12-381.js';
import { default as wyche_curves } from './wycheproof/ec_prime_order_curves_test.json' assert { type: 'json' };
import { createCurve } from '../esm/_shortw_utils.js';
import { Field } from '../esm/abstract/modular.js';
import { sha256 } from '@noble/hashes/sha256';
// Fields tests
const FIELDS = {
secp192r1: { Fp: [secp192r1.CURVE.Fp] },
secp224r1: { Fp: [secp224r1.CURVE.Fp] },
secp256r1: { Fp: [secp256r1.CURVE.Fp] },
secp521r1: { Fp: [secp521r1.CURVE.Fp] },
secp256k1: { Fp: [secp256k1.CURVE.Fp] },
jubjub: { Fp: [jubjub.CURVE.Fp] },
ed25519: { Fp: [ed25519.CURVE.Fp] },
ed448: { Fp: [ed448.CURVE.Fp] },
bn254: { Fp: [bn254.CURVE.Fp] },
pallas: { Fp: [pallas.CURVE.Fp] },
vesta: { Fp: [vesta.CURVE.Fp] },
bls12: {
Fp: [bls12_381.fields.Fp],
Fp2: [
bls12_381.fields.Fp2,
fc.array(fc.bigInt(1n, bls12_381.fields.Fp.ORDER - 1n), {
minLength: 2,
maxLength: 2,
}),
(Fp2, num) => Fp2.fromBigTuple([num[0], num[1]]),
],
// Fp6: [bls12_381.fields.Fp6],
Fp12: [
bls12_381.fields.Fp12,
fc.array(fc.bigInt(1n, bls12_381.fields.Fp.ORDER - 1n), {
minLength: 12,
maxLength: 12,
}),
(Fp12, num) => Fp12.fromBigTwelve(num),
],
},
};
for (const c in FIELDS) {
const curve = FIELDS[c];
for (const f in curve) {
const Fp = curve[f][0];
const name = `${c}/${f}:`;
const FC_BIGINT = curve[f][1] ? curve[f][1] : fc.bigInt(1n, Fp.ORDER - 1n);
const create = curve[f][2] ? curve[f][2].bind(null, Fp) : (num) => Fp.create(num);
describe(name, () => {
should('equality', () => {
fc.assert(
fc.property(FC_BIGINT, (num) => {
const a = create(num);
const b = create(num);
deepStrictEqual(Fp.eql(a, b), true);
deepStrictEqual(Fp.eql(b, a), true);
})
);
});
should('non-equality', () => {
fc.assert(
fc.property(FC_BIGINT, FC_BIGINT, (num1, num2) => {
const a = create(num1);
const b = create(num2);
deepStrictEqual(Fp.eql(a, b), num1 === num2);
deepStrictEqual(Fp.eql(b, a), num1 === num2);
})
);
});
should('add/subtract/commutativity', () => {
fc.assert(
fc.property(FC_BIGINT, FC_BIGINT, (num1, num2) => {
const a = create(num1);
const b = create(num2);
deepStrictEqual(Fp.add(a, b), Fp.add(b, a));
})
);
});
should('add/subtract/associativity', () => {
fc.assert(
fc.property(FC_BIGINT, FC_BIGINT, FC_BIGINT, (num1, num2, num3) => {
const a = create(num1);
const b = create(num2);
const c = create(num3);
deepStrictEqual(Fp.add(a, Fp.add(b, c)), Fp.add(Fp.add(a, b), c));
})
);
});
should('add/subtract/x+0=x', () => {
fc.assert(
fc.property(FC_BIGINT, (num) => {
const a = create(num);
deepStrictEqual(Fp.add(a, Fp.ZERO), a);
})
);
});
should('add/subtract/x-0=x', () => {
fc.assert(
fc.property(FC_BIGINT, (num) => {
const a = create(num);
deepStrictEqual(Fp.sub(a, Fp.ZERO), a);
deepStrictEqual(Fp.sub(a, a), Fp.ZERO);
})
);
});
should('add/subtract/negate equality', () => {
fc.assert(
fc.property(FC_BIGINT, (num1) => {
const a = create(num1);
const b = create(num1);
deepStrictEqual(Fp.sub(Fp.ZERO, a), Fp.neg(a));
deepStrictEqual(Fp.sub(a, b), Fp.add(a, Fp.neg(b)));
deepStrictEqual(Fp.sub(a, b), Fp.add(a, Fp.mul(b, Fp.create(-1n))));
})
);
});
should('add/subtract/negate', () => {
fc.assert(
fc.property(FC_BIGINT, (num) => {
const a = create(num);
deepStrictEqual(Fp.neg(a), Fp.sub(Fp.ZERO, a));
deepStrictEqual(Fp.neg(a), Fp.mul(a, Fp.create(-1n)));
})
);
});
should('negate(0)', () => {
deepStrictEqual(Fp.neg(Fp.ZERO), Fp.ZERO);
});
should('multiply/commutativity', () => {
fc.assert(
fc.property(FC_BIGINT, FC_BIGINT, (num1, num2) => {
const a = create(num1);
const b = create(num2);
deepStrictEqual(Fp.mul(a, b), Fp.mul(b, a));
})
);
});
should('multiply/associativity', () => {
fc.assert(
fc.property(FC_BIGINT, FC_BIGINT, FC_BIGINT, (num1, num2, num3) => {
const a = create(num1);
const b = create(num2);
const c = create(num3);
deepStrictEqual(Fp.mul(a, Fp.mul(b, c)), Fp.mul(Fp.mul(a, b), c));
})
);
});
should('multiply/distributivity', () => {
fc.assert(
fc.property(FC_BIGINT, FC_BIGINT, FC_BIGINT, (num1, num2, num3) => {
const a = create(num1);
const b = create(num2);
const c = create(num3);
deepStrictEqual(Fp.mul(a, Fp.add(b, c)), Fp.add(Fp.mul(b, a), Fp.mul(c, a)));
})
);
});
should('multiply/add equality', () => {
fc.assert(
fc.property(FC_BIGINT, (num) => {
const a = create(num);
deepStrictEqual(Fp.mul(a, 0n), Fp.ZERO);
deepStrictEqual(Fp.mul(a, Fp.ZERO), Fp.ZERO);
deepStrictEqual(Fp.mul(a, 1n), a);
deepStrictEqual(Fp.mul(a, Fp.ONE), a);
deepStrictEqual(Fp.mul(a, 2n), Fp.add(a, a));
deepStrictEqual(Fp.mul(a, 3n), Fp.add(Fp.add(a, a), a));
deepStrictEqual(Fp.mul(a, 4n), Fp.add(Fp.add(Fp.add(a, a), a), a));
})
);
});
should('multiply/square equality', () => {
fc.assert(
fc.property(FC_BIGINT, (num) => {
const a = create(num);
deepStrictEqual(Fp.sqr(a), Fp.mul(a, a));
})
);
});
should('multiply/pow equality', () => {
fc.assert(
fc.property(FC_BIGINT, (num) => {
const a = create(num);
deepStrictEqual(Fp.pow(a, 0n), Fp.ONE);
deepStrictEqual(Fp.pow(a, 1n), a);
deepStrictEqual(Fp.pow(a, 2n), Fp.mul(a, a));
deepStrictEqual(Fp.pow(a, 3n), Fp.mul(Fp.mul(a, a), a));
})
);
});
should('square(0)', () => {
deepStrictEqual(Fp.sqr(Fp.ZERO), Fp.ZERO);
deepStrictEqual(Fp.mul(Fp.ZERO, Fp.ZERO), Fp.ZERO);
});
should('square(1)', () => {
deepStrictEqual(Fp.sqr(Fp.ONE), Fp.ONE);
deepStrictEqual(Fp.mul(Fp.ONE, Fp.ONE), Fp.ONE);
});
should('square(-1)', () => {
const minus1 = Fp.neg(Fp.ONE);
deepStrictEqual(Fp.sqr(minus1), Fp.ONE);
deepStrictEqual(Fp.mul(minus1, minus1), Fp.ONE);
});
const isSquare = mod.FpIsSquare(Fp);
// Not implemented
if (Fp !== bls12_381.fields.Fp12) {
should('multiply/sqrt', () => {
fc.assert(
fc.property(FC_BIGINT, (num) => {
const a = create(num);
let root;
try {
root = Fp.sqrt(a);
} catch (e) {
deepStrictEqual(isSquare(a), false);
return;
}
deepStrictEqual(isSquare(a), true);
deepStrictEqual(Fp.eql(Fp.sqr(root), a), true, 'sqrt(a)^2 == a');
deepStrictEqual(Fp.eql(Fp.sqr(Fp.neg(root)), a), true, '(-sqrt(a))^2 == a');
// Returns odd/even element
deepStrictEqual(Fp.isOdd(mod.FpSqrtOdd(Fp, a)), true);
deepStrictEqual(Fp.isOdd(mod.FpSqrtEven(Fp, a)), false);
deepStrictEqual(Fp.eql(Fp.sqr(mod.FpSqrtOdd(Fp, a)), a), true);
deepStrictEqual(Fp.eql(Fp.sqr(mod.FpSqrtEven(Fp, a)), a), true);
})
);
});
should('sqrt(0)', () => {
deepStrictEqual(Fp.sqrt(Fp.ZERO), Fp.ZERO);
const sqrt1 = Fp.sqrt(Fp.ONE);
deepStrictEqual(
Fp.eql(sqrt1, Fp.ONE) || Fp.eql(sqrt1, Fp.neg(Fp.ONE)),
true,
'sqrt(1) = 1 or -1'
);
});
}
should('div/division by one equality', () => {
fc.assert(
fc.property(FC_BIGINT, (num) => {
const a = create(num);
if (Fp.eql(a, Fp.ZERO)) return; // No division by zero
deepStrictEqual(Fp.div(a, Fp.ONE), a);
deepStrictEqual(Fp.div(a, a), Fp.ONE);
// FpDiv tests
deepStrictEqual(mod.FpDiv(Fp, a, Fp.ONE), a);
deepStrictEqual(mod.FpDiv(Fp, a, a), Fp.ONE);
})
);
});
should('zero division equality', () => {
fc.assert(
fc.property(FC_BIGINT, (num) => {
const a = create(num);
deepStrictEqual(Fp.div(Fp.ZERO, a), Fp.ZERO);
deepStrictEqual(mod.FpDiv(Fp, Fp.ZERO, a), Fp.ZERO);
})
);
});
should('div/division distributivity', () => {
fc.assert(
fc.property(FC_BIGINT, FC_BIGINT, FC_BIGINT, (num1, num2, num3) => {
const a = create(num1);
const b = create(num2);
const c = create(num3);
deepStrictEqual(Fp.div(Fp.add(a, b), c), Fp.add(Fp.div(a, c), Fp.div(b, c)));
deepStrictEqual(
mod.FpDiv(Fp, Fp.add(a, b), c),
Fp.add(mod.FpDiv(Fp, a, c), mod.FpDiv(Fp, b, c))
);
})
);
});
should('div/division and multiplication equality', () => {
fc.assert(
fc.property(FC_BIGINT, FC_BIGINT, (num1, num2) => {
const a = create(num1);
const b = create(num2);
deepStrictEqual(Fp.div(a, b), Fp.mul(a, Fp.inv(b)));
})
);
});
});
}
}
// Group tests
// prettier-ignore
const CURVES = {
secp192r1, secp224r1, secp256r1, secp384r1, secp521r1,
secp256k1,
ed25519, ed25519ctx, ed25519ph,
ed448, ed448ph,
starkCurve,
pallas, vesta,
bn254,
jubjub,
};
const NUM_RUNS = 5;
const getXY = (p) => ({ x: p.x, y: p.y });
function equal(a, b, comment) {
@@ -61,9 +355,12 @@ for (const name in CURVES) {
if (!p) continue;
const G = [p.ZERO, p.BASE];
for (let i = 2; i < 10; i++) G.push(G[1].multiply(i));
for (let i = 2n; i < 10n; i++) G.push(G[1].multiply(i));
const title = `${name}/${pointName}`;
describe(title, () => {
describe('basic group laws', () => {
// Here we check basic group laws, to verify that points works as group
should(`${name}/${pointName}/Basic group laws (zero)`, () => {
should('zero', () => {
equal(G[0].double(), G[0], '(0*G).double() = 0');
equal(G[0].add(G[0]), G[0], '0*G + 0*G = 0');
equal(G[0].subtract(G[0]), G[0], '0*G - 0*G = 0');
@@ -71,37 +368,37 @@ for (const name in CURVES) {
for (let i = 0; i < G.length; i++) {
const p = G[i];
equal(p, p.add(G[0]), `${i}*G + 0 = ${i}*G`);
equal(G[0].multiply(i + 1), G[0], `${i + 1}*0 = 0`);
equal(G[0].multiply(BigInt(i + 1)), G[0], `${i + 1}*0 = 0`);
}
});
should(`${name}/${pointName}/Basic group laws (one)`, () => {
should('one', () => {
equal(G[1].double(), G[2], '(1*G).double() = 2*G');
equal(G[1].subtract(G[1]), G[0], '1*G - 1*G = 0');
equal(G[1].add(G[1]), G[2], '1*G + 1*G = 2*G');
});
should(`${name}/${pointName}/Basic group laws (sanity tests)`, () => {
equal(G[2].double(), G[4], `(2*G).double() = 4*G`);
equal(G[2].add(G[2]), G[4], `2*G + 2*G = 4*G`);
equal(G[7].add(G[3].negate()), G[4], `7*G - 3*G = 4*G`);
should('sanity tests', () => {
equal(G[2].double(), G[4], '(2*G).double() = 4*G');
equal(G[2].add(G[2]), G[4], '2*G + 2*G = 4*G');
equal(G[7].add(G[3].negate()), G[4], '7*G - 3*G = 4*G');
});
should(`${name}/${pointName}/Basic group laws (addition commutativity)`, () => {
equal(G[4].add(G[3]), G[3].add(G[4]), `4*G + 3*G = 3*G + 4*G`);
equal(G[4].add(G[3]), G[3].add(G[2]).add(G[2]), `4*G + 3*G = 3*G + 2*G + 2*G`);
should('add commutativity', () => {
equal(G[4].add(G[3]), G[3].add(G[4]), '4*G + 3*G = 3*G + 4*G');
equal(G[4].add(G[3]), G[3].add(G[2]).add(G[2]), '4*G + 3*G = 3*G + 2*G + 2*G');
});
should(`${name}/${pointName}/Basic group laws (double)`, () => {
should('double', () => {
equal(G[3].double(), G[6], '(3*G).double() = 6*G');
});
should(`${name}/${pointName}/Basic group laws (multiply)`, () => {
equal(G[2].multiply(3), G[6], '(2*G).multiply(3) = 6*G');
should('multiply', () => {
equal(G[2].multiply(3n), G[6], '(2*G).multiply(3) = 6*G');
});
should(`${name}/${pointName}/Basic group laws (same point addition)`, () => {
equal(G[3].add(G[3]), G[6], `3*G + 3*G = 6*G`);
should('add same-point', () => {
equal(G[3].add(G[3]), G[6], '3*G + 3*G = 6*G');
});
should(`${name}/${pointName}/Basic group laws (same point (negative) addition)`, () => {
should('add same-point negative', () => {
equal(G[3].add(G[3].negate()), G[0], '3*G + (- 3*G) = 0*G');
equal(G[3].subtract(G[3]), G[0], '3*G - 3*G = 0*G');
});
should(`${name}/${pointName}/Basic group laws (curve order)`, () => {
should('mul by curve order', () => {
equal(G[1].multiply(CURVE_ORDER - 1n).add(G[1]), G[0], '(N-1)*G + G = 0');
equal(G[1].multiply(CURVE_ORDER - 1n).add(G[2]), G[1], '(N-1)*G + 2*G = 1*G');
equal(G[1].multiply(CURVE_ORDER - 2n).add(G[2]), G[0], '(N-2)*G + 2*G = 0');
@@ -109,7 +406,7 @@ for (const name in CURVES) {
const carry = CURVE_ORDER % 2n === 1n ? G[1] : G[0];
equal(G[1].multiply(half).double().add(carry), G[0], '((N/2) * G).double() = 0');
});
should(`${name}/${pointName}/Basic group laws (inversion)`, () => {
should('inversion', () => {
const a = 1234n;
const b = 5678n;
const c = a * b;
@@ -117,7 +414,7 @@ for (const name in CURVES) {
const inv = mod.invert(b, CURVE_ORDER);
equal(G[1].multiply(c).multiply(inv), G[1].multiply(a), 'c*G * (1/b)*G = a*G');
});
should(`${name}/${pointName}/Basic group laws (multiply, rand)`, () =>
should('multiply, rand', () =>
fc.assert(
fc.property(FC_BIGINT, FC_BIGINT, (a, b) => {
const c = mod.mod(a + b, CURVE_ORDER);
@@ -125,45 +422,58 @@ for (const name in CURVES) {
const pA = G[1].multiply(a);
const pB = G[1].multiply(b);
const pC = G[1].multiply(c);
equal(pA.add(pB), pB.add(pA), `pA + pB = pB + pA`);
equal(pA.add(pB), pC, `pA + pB = pC`);
equal(pA.add(pB), pB.add(pA), 'pA + pB = pB + pA');
equal(pA.add(pB), pC, 'pA + pB = pC');
}),
{ numRuns: NUM_RUNS }
)
);
should(`${name}/${pointName}/Basic group laws (multiply2, rand)`, () =>
should('multiply2, rand', () =>
fc.assert(
fc.property(FC_BIGINT, FC_BIGINT, (a, b) => {
const c = mod.mod(a * b, CURVE_ORDER);
const pA = G[1].multiply(a);
const pB = G[1].multiply(b);
equal(pA.multiply(b), pB.multiply(a), `b*pA = a*pB`);
equal(pA.multiply(b), G[1].multiply(c), `b*pA = c*G`);
equal(pA.multiply(b), pB.multiply(a), 'b*pA = a*pB');
equal(pA.multiply(b), G[1].multiply(c), 'b*pA = c*G');
}),
{ numRuns: NUM_RUNS }
)
);
});
for (const op of ['add', 'subtract']) {
should(`${name}/${pointName}/${op} type check`, () => {
describe(op, () => {
should('type check', () => {
throws(() => G[1][op](0), '0');
throws(() => G[1][op](0n), '0n');
G[1][op](G[2]);
throws(() => G[1][op](CURVE_ORDER), 'CURVE_ORDER');
throws(() => G[1][op](-123n), '-123n');
throws(() => G[1][op](123), '123');
throws(() => G[1][op](123.456), '123.456');
throws(() => G[1][op](true), 'true');
throws(() => G[1][op](false), 'false');
throws(() => G[1][op](null), 'null');
throws(() => G[1][op](undefined), 'undefined');
throws(() => G[1][op]('1'), "'1'");
throws(() => G[1][op]({ x: 1n, y: 1n, z: 1n, t: 1n }), '{ x: 1n, y: 1n, z: 1n, t: 1n }');
throws(() => G[1][op]({ x: 1n, y: 1n }), '{ x: 1n, y: 1n }');
throws(() => G[1][op]({ x: 1n, y: 1n, z: 1n }), '{ x: 1n, y: 1n, z: 1n }');
throws(
() => G[1][op]({ x: 1n, y: 1n, z: 1n, t: 1n }),
'{ x: 1n, y: 1n, z: 1n, t: 1n }'
);
throws(() => G[1][op](new Uint8Array([])), 'ui8a([])');
throws(() => G[1][op](new Uint8Array([0])), 'ui8a([0])');
throws(() => G[1][op](new Uint8Array([1])), 'ui8a([1])');
throws(() => G[1][op](new Uint8Array(4096).fill(1)), 'ui8a(4096*[1])');
if (G[1].toAffine) throws(() => G[1][op](C.Point.BASE), `Point ${op} ${pointName}`);
// if (G[1].toAffine) throws(() => G[1][op](C.Point.BASE), `Point ${op} ${pointName}`);
throws(() => G[1][op](o.BASE), `${op}/other curve point`);
});
});
}
should(`${name}/${pointName}/equals type check`, () => {
should('equals type check', () => {
throws(() => G[1].equals(0), '0');
throws(() => G[1].equals(0n), '0n');
deepStrictEqual(G[1].equals(G[2]), false, '1*G != 2*G');
@@ -178,13 +488,14 @@ for (const name in CURVES) {
throws(() => G[1].equals(new Uint8Array([0])), 'ui8a([0])');
throws(() => G[1].equals(new Uint8Array([1])), 'ui8a([1])');
throws(() => G[1].equals(new Uint8Array(4096).fill(1)), 'ui8a(4096*[1])');
if (G[1].toAffine) throws(() => G[1].equals(C.Point.BASE), `Point.equals(${pointName})`);
// if (G[1].toAffine) throws(() => G[1].equals(C.Point.BASE), 'Point.equals(${pointName})');
throws(() => G[1].equals(o.BASE), 'other curve point');
});
for (const op of ['multiply', 'multiplyUnsafe']) {
if (!p.BASE[op]) continue;
should(`${name}/${pointName}/${op} type check`, () => {
describe(op, () => {
should('type check', () => {
if (op !== 'multiplyUnsafe') {
throws(() => G[1][op](0), '0');
throws(() => G[1][op](0n), '0n');
@@ -203,75 +514,174 @@ for (const name in CURVES) {
throws(() => G[1][op](new Uint8Array(4096).fill(1)), 'ui8a(4096*[1])');
throws(() => G[1][op](o.BASE), 'other curve point');
});
});
}
// Complex point (Extended/Jacobian/Projective?)
if (p.BASE.toAffine) {
should(`${name}/${pointName}/toAffine()`, () => {
equal(p.ZERO.toAffine(), C.Point.ZERO, `0 = 0`);
equal(p.BASE.toAffine(), C.Point.BASE, `1 = 1`);
});
}
if (p.fromAffine) {
should(`${name}/${pointName}/fromAffine()`, () => {
equal(p.ZERO, p.fromAffine(C.Point.ZERO), `0 = 0`);
equal(p.BASE, p.fromAffine(C.Point.BASE), `1 = 1`);
});
}
// if (p.BASE.toAffine && C.Point) {
// should('toAffine()', () => {
// equal(p.ZERO.toAffine(), C.Point.ZERO, '0 = 0');
// equal(p.BASE.toAffine(), C.Point.BASE, '1 = 1');
// });
// }
// if (p.fromAffine && C.Point) {
// should('fromAffine()', () => {
// equal(p.ZERO, p.fromAffine(C.Point.ZERO), '0 = 0');
// equal(p.BASE, p.fromAffine(C.Point.BASE), '1 = 1');
// });
// }
// toHex/fromHex (if available)
if (p.fromHex && p.BASE.toHex) {
should(`${name}/${pointName}/fromHex(toHex()) roundtrip`, () => {
should('fromHex(toHex()) roundtrip', () => {
fc.assert(
fc.property(FC_BIGINT, (x) => {
const hex = p.BASE.multiply(x).toHex();
const point = p.BASE.multiply(x);
const hex = point.toHex();
const bytes = point.toRawBytes();
deepStrictEqual(p.fromHex(hex).toHex(), hex);
deepStrictEqual(p.fromHex(bytes).toHex(), hex);
})
);
});
should('fromHex(toHex(compressed=true)) roundtrip', () => {
fc.assert(
fc.property(FC_BIGINT, (x) => {
const point = p.BASE.multiply(x);
const hex = point.toHex(true);
const bytes = point.toRawBytes(true);
deepStrictEqual(p.fromHex(hex).toHex(true), hex);
deepStrictEqual(p.fromHex(bytes).toHex(true), hex);
})
);
});
}
});
}
describe(name, () => {
if (['bn254', 'pallas', 'vesta'].includes(name)) return;
// Generic complex things (getPublicKey/sign/verify/getSharedSecret)
should(`${name}/getPublicKey type check`, () => {
should('.getPublicKey() type check', () => {
throws(() => C.getPublicKey(0), '0');
throws(() => C.getPublicKey(0n), '0n');
throws(() => C.getPublicKey(false), 'false');
throws(() => C.getPublicKey(-123n), '-123n');
throws(() => C.getPublicKey(123), '123');
throws(() => C.getPublicKey(123.456), '123.456');
throws(() => C.getPublicKey(true), 'true');
throws(() => C.getPublicKey(false), 'false');
throws(() => C.getPublicKey(null), 'null');
throws(() => C.getPublicKey(undefined), 'undefined');
throws(() => C.getPublicKey(''), "''");
// NOTE: passes because of disabled hex padding checks for starknet, maybe enable?
//throws(() => C.getPublicKey('1'), "'1'");
// throws(() => C.getPublicKey('1'), "'1'");
throws(() => C.getPublicKey('key'), "'key'");
throws(() => C.getPublicKey({}));
throws(() => C.getPublicKey(new Uint8Array([])));
throws(() => C.getPublicKey(new Uint8Array([0])));
throws(() => C.getPublicKey(new Uint8Array([1])));
throws(() => C.getPublicKey(new Uint8Array(4096).fill(1)));
throws(() => C.getPublicKey(Array(32).fill(1)));
});
should(`${name}.verify()/should verify random signatures`, () =>
should('.verify() should verify random signatures', () =>
fc.assert(
fc.property(fc.hexaString({ minLength: 64, maxLength: 64 }), (msg) => {
const priv = C.utils.randomPrivateKey();
const pub = C.getPublicKey(priv);
const sig = C.sign(msg, priv);
deepStrictEqual(C.verify(sig, msg, pub), true);
deepStrictEqual(
C.verify(sig, msg, pub),
true,
`priv=${toHex(priv)},pub=${toHex(pub)},msg=${msg}`
);
}),
{ numRuns: NUM_RUNS }
)
);
should(`${name}.sign()/edge cases`, () => {
should('.verify() should verify empty signatures', () => {
const msg = new Uint8Array([]);
const priv = C.utils.randomPrivateKey();
const pub = C.getPublicKey(priv);
const sig = C.sign(msg, priv);
deepStrictEqual(
C.verify(sig, msg, pub),
true,
'priv=${toHex(priv)},pub=${toHex(pub)},msg=${msg}'
);
});
should('.sign() edge cases', () => {
throws(() => C.sign());
throws(() => C.sign(''));
throws(() => C.sign('', ''));
throws(() => C.sign(new Uint8Array(), new Uint8Array()));
});
should(`${name}.verify()/should not verify signature with wrong hash`, () => {
const MSG = '01'.repeat(32);
const PRIV_KEY = 0x2n;
const WRONG_MSG = '11'.repeat(32);
const signature = C.sign(MSG, PRIV_KEY);
const publicKey = C.getPublicKey(PRIV_KEY);
deepStrictEqual(C.verify(signature, WRONG_MSG, publicKey), false);
describe('verify()', () => {
const msg = '01'.repeat(32);
should('true for proper signatures', () => {
const priv = C.utils.randomPrivateKey();
const sig = C.sign(msg, priv);
const pub = C.getPublicKey(priv);
deepStrictEqual(C.verify(sig, msg, pub), true);
});
should('false for wrong messages', () => {
const priv = C.utils.randomPrivateKey();
const sig = C.sign(msg, priv);
const pub = C.getPublicKey(priv);
deepStrictEqual(C.verify(sig, '11'.repeat(32), pub), false);
});
should('false for wrong keys', () => {
const priv = C.utils.randomPrivateKey();
const sig = C.sign(msg, priv);
deepStrictEqual(C.verify(sig, msg, C.getPublicKey(C.utils.randomPrivateKey())), false);
});
});
if (C.Signature) {
should('Signature serialization roundtrip', () =>
fc.assert(
fc.property(fc.hexaString({ minLength: 64, maxLength: 64 }), (msg) => {
const priv = C.utils.randomPrivateKey();
const sig = C.sign(msg, priv);
const sigRS = (sig) => ({ s: sig.s, r: sig.r });
// Compact
deepStrictEqual(sigRS(C.Signature.fromCompact(sig.toCompactHex())), sigRS(sig));
deepStrictEqual(sigRS(C.Signature.fromCompact(sig.toCompactRawBytes())), sigRS(sig));
// DER
deepStrictEqual(sigRS(C.Signature.fromDER(sig.toDERHex())), sigRS(sig));
deepStrictEqual(sigRS(C.Signature.fromDER(sig.toDERRawBytes())), sigRS(sig));
}),
{ numRuns: NUM_RUNS }
)
);
should('Signature.addRecoveryBit/Signature.recoveryPublicKey', () =>
fc.assert(
fc.property(fc.hexaString({ minLength: 64, maxLength: 64 }), (msg) => {
const priv = C.utils.randomPrivateKey();
const pub = C.getPublicKey(priv);
const sig = C.sign(msg, priv);
deepStrictEqual(sig.recoverPublicKey(msg).toRawBytes(), pub);
const sig2 = C.Signature.fromCompact(sig.toCompactHex());
throws(() => sig2.recoverPublicKey(msg));
const sig3 = sig2.addRecoveryBit(sig.recovery);
deepStrictEqual(sig3.recoverPublicKey(msg).toRawBytes(), pub);
}),
{ numRuns: NUM_RUNS }
)
);
should('Signature.normalizeS', () =>
fc.assert(
fc.property(fc.hexaString({ minLength: 64, maxLength: 64 }), (msg) => {
const priv = C.utils.randomPrivateKey();
const pub = C.getPublicKey(priv);
const sig = C.sign(msg, priv);
const sig2 = sig.normalizeS();
deepStrictEqual(sig2.hasHighS(), false);
}),
{ numRuns: NUM_RUNS }
)
);
}
// NOTE: fails for ed, because of empty message. Since we convert it to scalar,
// need to check what other implementations do. Empty message != new Uint8Array([0]), but what scalar should be in that case?
// should(`${name}/should not verify signature with wrong message`, () => {
// should('should not verify signature with wrong message', () => {
// fc.assert(
// fc.property(
// fc.array(fc.integer({ min: 0x00, max: 0xff })),
@@ -293,7 +703,7 @@ for (const name in CURVES) {
// });
if (C.getSharedSecret) {
should(`${name}/getSharedSecret() should be commutative`, () => {
should('getSharedSecret() should be commutative', () => {
for (let i = 0; i < NUM_RUNS; i++) {
const asec = C.utils.randomPrivateKey();
const apub = C.getPublicKey(asec);
@@ -308,7 +718,82 @@ for (const name in CURVES) {
}
});
}
});
}
should('secp224k1 sqrt bug', () => {
const { Fp } = secp224r1.CURVE;
const sqrtMinus1 = Fp.sqrt(-1n);
// Verified against sage
deepStrictEqual(
sqrtMinus1,
23621584063597419797792593680131996961517196803742576047493035507225n
);
deepStrictEqual(
Fp.neg(sqrtMinus1),
3338362603553219996874421406887633712040719456283732096017030791656n
);
deepStrictEqual(Fp.sqr(sqrtMinus1), Fp.create(-1n));
});
should('bigInt private keys', () => {
// Doesn't support bigints anymore
throws(() => ed25519.sign('', 123n));
throws(() => ed25519.getPublicKey(123n));
throws(() => x25519.getPublicKey(123n));
// Weierstrass still supports
secp256k1.getPublicKey(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.
import url from 'url';
if (import.meta.url === url.pathToFileURL(process.argv[1]).href) {

File diff suppressed because it is too large Load Diff

362
test/ed25519-addons.test.js Normal file
View File

@@ -0,0 +1,362 @@
import { sha512 } from '@noble/hashes/sha512';
import { bytesToHex as hex, hexToBytes } from '@noble/hashes/utils';
import { deepStrictEqual, throws } from 'assert';
import { describe, should } from 'micro-should';
import { bytesToNumberLE, numberToBytesLE } from '../esm/abstract/utils.js';
import { default as x25519vectors } from './wycheproof/x25519_test.json' assert { type: 'json' };
import {
ed25519,
ed25519ctx,
ed25519ph,
edwardsToMontgomeryPub,
edwardsToMontgomeryPriv,
RistrettoPoint,
x25519,
} from '../esm/ed25519.js';
const VECTORS_RFC8032_CTX = [
{
secretKey: '0305334e381af78f141cb666f6199f57bc3495335a256a95bd2a55bf546663f6',
publicKey: 'dfc9425e4f968f7f0c29f0259cf5f9aed6851c2bb4ad8bfb860cfee0ab248292',
message: 'f726936d19c800494e3fdaff20b276a8',
context: '666f6f',
signature:
'55a4cc2f70a54e04288c5f4cd1e45a7b' +
'b520b36292911876cada7323198dd87a' +
'8b36950b95130022907a7fb7c4e9b2d5' +
'f6cca685a587b4b21f4b888e4e7edb0d',
},
{
secretKey: '0305334e381af78f141cb666f6199f57bc3495335a256a95bd2a55bf546663f6',
publicKey: 'dfc9425e4f968f7f0c29f0259cf5f9aed6851c2bb4ad8bfb860cfee0ab248292',
message: 'f726936d19c800494e3fdaff20b276a8',
context: '626172',
signature:
'fc60d5872fc46b3aa69f8b5b4351d580' +
'8f92bcc044606db097abab6dbcb1aee3' +
'216c48e8b3b66431b5b186d1d28f8ee1' +
'5a5ca2df6668346291c2043d4eb3e90d',
},
{
secretKey: '0305334e381af78f141cb666f6199f57bc3495335a256a95bd2a55bf546663f6',
publicKey: 'dfc9425e4f968f7f0c29f0259cf5f9aed6851c2bb4ad8bfb860cfee0ab248292',
message: '508e9e6882b979fea900f62adceaca35',
context: '666f6f',
signature:
'8b70c1cc8310e1de20ac53ce28ae6e72' +
'07f33c3295e03bb5c0732a1d20dc6490' +
'8922a8b052cf99b7c4fe107a5abb5b2c' +
'4085ae75890d02df26269d8945f84b0b',
},
{
secretKey: 'ab9c2853ce297ddab85c993b3ae14bcad39b2c682beabc27d6d4eb20711d6560',
publicKey: '0f1d1274943b91415889152e893d80e93275a1fc0b65fd71b4b0dda10ad7d772',
message: 'f726936d19c800494e3fdaff20b276a8',
context: '666f6f',
signature:
'21655b5f1aa965996b3f97b3c849eafb' +
'a922a0a62992f73b3d1b73106a84ad85' +
'e9b86a7b6005ea868337ff2d20a7f5fb' +
'd4cd10b0be49a68da2b2e0dc0ad8960f',
},
];
describe('RFC8032ctx', () => {
for (let i = 0; i < VECTORS_RFC8032_CTX.length; i++) {
const v = VECTORS_RFC8032_CTX[i];
should(`${i}`, () => {
deepStrictEqual(hex(ed25519ctx.getPublicKey(v.secretKey)), v.publicKey);
deepStrictEqual(
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
);
});
}
});
const VECTORS_RFC8032_PH = [
{
secretKey: '833fe62409237b9d62ec77587520911e9a759cec1d19755b7da901b96dca3d42',
publicKey: 'ec172b93ad5e563bf4932c70e1245034c35467ef2efd4d64ebf819683467e2bf',
message: '616263',
signature:
'98a70222f0b8121aa9d30f813d683f80' +
'9e462b469c7ff87639499bb94e6dae41' +
'31f85042463c2a355a2003d062adf5aa' +
'a10b8c61e636062aaad11c2a26083406',
},
];
describe('RFC8032ph', () => {
for (let i = 0; i < VECTORS_RFC8032_PH.length; i++) {
const v = VECTORS_RFC8032_PH[i];
should(`${i}`, () => {
deepStrictEqual(hex(ed25519ph.getPublicKey(v.secretKey)), v.publicKey);
deepStrictEqual(hex(ed25519ph.sign(v.message, v.secretKey)), v.signature);
deepStrictEqual(ed25519ph.verify(v.signature, v.message, v.publicKey), true);
});
}
});
// x25519
describe('RFC7748 X25519 ECDH', () => {
const rfc7748Mul = [
{
scalar: 'a546e36bf0527c9d3b16154b82465edd62144c0ac1fc5a18506a2244ba449ac4',
u: 'e6db6867583030db3594c1a424b15f7c726624ec26b3353b10a903a6d0ab1c4c',
outputU: 'c3da55379de9c6908e94ea4df28d084f32eccf03491c71f754b4075577a28552',
},
{
scalar: '4b66e9d4d1b4673c5ad22691957d6af5c11b6421e0ea01d42ca4169e7918ba0d',
u: 'e5210f12786811d3f4b7959d0538ae2c31dbe7106fc03c3efc4cd549c715a493',
outputU: '95cbde9476e8907d7aade45cb4b873f88b595a68799fa152e6f8f7647aac7957',
},
];
for (let i = 0; i < rfc7748Mul.length; i++) {
const v = rfc7748Mul[i];
should(`scalarMult (${i})`, () => {
deepStrictEqual(hex(x25519.scalarMult(v.scalar, v.u)), v.outputU);
});
}
const rfc7748Iter = [
{ scalar: '422c8e7a6227d7bca1350b3e2bb7279f7897b87bb6854b783c60e80311ae3079', iters: 1 },
{ scalar: '684cf59ba83309552800ef566f2f4d3c1c3887c49360e3875f2eb94d99532c51', iters: 1000 },
// { scalar: '7c3911e0ab2586fd864497297e575e6f3bc601c0883c30df5f4dd2d24f665424', iters: 1000000 },
];
for (let i = 0; i < rfc7748Iter.length; i++) {
const { scalar, iters } = rfc7748Iter[i];
should(`scalarMult iteration x${iters}`, () => {
let k = x25519.GuBytes;
for (let i = 0, u = k; i < iters; i++) [k, u] = [x25519.scalarMult(k, u), k];
deepStrictEqual(hex(k), scalar);
});
}
should('getSharedKey', () => {
const alicePrivate = '77076d0a7318a57d3c16c17251b26645df4c2f87ebc0992ab177fba51db92c2a';
const alicePublic = '8520f0098930a754748b7ddcb43ef75a0dbf3a0d26381af4eba4a98eaa9b4e6a';
const bobPrivate = '5dab087e624a8a4b79e17f8b83800ee66f3bb1292618b6fd1c2f8b27ff88e0eb';
const bobPublic = 'de9edb7d7b7dc1b4d35b61c2ece435373f8343c85b78674dadfc7e146f882b4f';
const shared = '4a5d9d5ba4ce2de1728e3bf480350f25e07e21c947d19e3376f09b3c1e161742';
deepStrictEqual(alicePublic, hex(x25519.getPublicKey(alicePrivate)));
deepStrictEqual(bobPublic, hex(x25519.getPublicKey(bobPrivate)));
deepStrictEqual(hex(x25519.scalarMult(alicePrivate, bobPublic)), shared);
deepStrictEqual(hex(x25519.scalarMult(bobPrivate, alicePublic)), shared);
});
should('X25519/getSharedSecret() should be commutative', () => {
for (let i = 0; i < 512; i++) {
const asec = x25519.utils.randomPrivateKey();
const apub = x25519.getPublicKey(asec);
const bsec = x25519.utils.randomPrivateKey();
const bpub = x25519.getPublicKey(bsec);
try {
deepStrictEqual(x25519.getSharedSecret(asec, bpub), x25519.getSharedSecret(bsec, apub));
} catch (error) {
console.error('not commutative', { asec, apub, bsec, bpub });
throw error;
}
}
});
should('edwardsToMontgomery should produce correct output', () => {
const edSecret = hexToBytes('77076d0a7318a57d3c16c17251b26645df4c2f87ebc0992ab177fba51db92c2a');
const edPublic = ed25519.getPublicKey(edSecret);
const xPrivate = edwardsToMontgomeryPriv(edSecret);
deepStrictEqual(
hex(xPrivate),
'a8cd44eb8e93319c0570bc11005c0e0189d34ff02f6c17773411ad191293c94f'
);
const xPublic = edwardsToMontgomeryPub(edPublic);
deepStrictEqual(
hex(xPublic),
'ed7749b4d989f6957f3bfde6c56767e988e21c9f8784d91d610011cd553f9b06'
);
});
should('edwardsToMontgomery should produce correct keyPair', () => {
const edSecret = ed25519.utils.randomPrivateKey();
const edPublic = ed25519.getPublicKey(edSecret);
const xSecret = edwardsToMontgomeryPriv(edSecret);
const expectedXPublic = x25519.getPublicKey(xSecret);
const xPublic = edwardsToMontgomeryPub(edPublic);
deepStrictEqual(xPublic, expectedXPublic);
});
should('ECDH through edwardsToMontgomery should be commutative', () => {
const edSecret1 = ed25519.utils.randomPrivateKey();
const edPublic1 = ed25519.getPublicKey(edSecret1);
const edSecret2 = ed25519.utils.randomPrivateKey();
const edPublic2 = ed25519.getPublicKey(edSecret2);
deepStrictEqual(
x25519.getSharedSecret(edwardsToMontgomeryPriv(edSecret1), edwardsToMontgomeryPub(edPublic2)),
x25519.getSharedSecret(edwardsToMontgomeryPriv(edSecret2), edwardsToMontgomeryPub(edPublic1))
);
});
should('base point', () => {
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];
should('wycheproof', () => {
for (let i = 0; i < group.tests.length; i++) {
const v = group.tests[i];
const comment = `(${i}, ${v.result}) ${v.comment}`;
if (v.result === 'valid' || v.result === 'acceptable') {
try {
const shared = hex(x25519.scalarMult(v.private, v.public));
deepStrictEqual(shared, v.shared, comment);
} 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;
throw e;
}
} else if (v.result === 'invalid') {
let failed = false;
try {
x25519.scalarMult(v.private, v.public);
} catch (error) {
failed = true;
}
deepStrictEqual(failed, true, comment);
} else throw new Error('unknown test result');
}
});
});
function utf8ToBytes(str) {
if (typeof str !== 'string') {
throw new Error(`utf8ToBytes expected string, got ${typeof str}`);
}
return new TextEncoder().encode(str);
}
describe('ristretto255', () => {
should('follow the byte encodings of small multiples', () => {
const encodingsOfSmallMultiples = [
// This is the identity point
'0000000000000000000000000000000000000000000000000000000000000000',
// This is the basepoint
'e2f2ae0a6abc4e71a884a961c500515f58e30b6aa582dd8db6a65945e08d2d76',
// These are small multiples of the basepoint
'6a493210f7499cd17fecb510ae0cea23a110e8d5b901f8acadd3095c73a3b919',
'94741f5d5d52755ece4f23f044ee27d5d1ea1e2bd196b462166b16152a9d0259',
'da80862773358b466ffadfe0b3293ab3d9fd53c5ea6c955358f568322daf6a57',
'e882b131016b52c1d3337080187cf768423efccbb517bb495ab812c4160ff44e',
'f64746d3c92b13050ed8d80236a7f0007c3b3f962f5ba793d19a601ebb1df403',
'44f53520926ec81fbd5a387845beb7df85a96a24ece18738bdcfa6a7822a176d',
'903293d8f2287ebe10e2374dc1a53e0bc887e592699f02d077d5263cdd55601c',
'02622ace8f7303a31cafc63f8fc48fdc16e1c8c8d234b2f0d6685282a9076031',
'20706fd788b2720a1ed2a5dad4952b01f413bcf0e7564de8cdc816689e2db95f',
'bce83f8ba5dd2fa572864c24ba1810f9522bc6004afe95877ac73241cafdab42',
'e4549ee16b9aa03099ca208c67adafcafa4c3f3e4e5303de6026e3ca8ff84460',
'aa52e000df2e16f55fb1032fc33bc42742dad6bd5a8fc0be0167436c5948501f',
'46376b80f409b29dc2b5f6f0c52591990896e5716f41477cd30085ab7f10301e',
'e0c418f7c8d9c4cdd7395b93ea124f3ad99021bb681dfc3302a9d99a2e53e64e',
];
let B = RistrettoPoint.BASE;
let P = RistrettoPoint.ZERO;
for (const encoded of encodingsOfSmallMultiples) {
deepStrictEqual(P.toHex(), encoded);
deepStrictEqual(RistrettoPoint.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.
'00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff',
'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f',
'f3ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f',
'edffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f',
// These are all bad because they're negative field elements.
'0100000000000000000000000000000000000000000000000000000000000000',
'01ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f',
'ed57ffd8c914fb201471d1c3d245ce3c746fcbe63a3679d51b6a516ebebe0e20',
'c34c4e1826e5d403b78e246e88aa051c36ccf0aafebffe137d148a2bf9104562',
'c940e5a4404157cfb1628b108db051a8d439e1a421394ec4ebccb9ec92a8ac78',
'47cfc5497c53dc8e61c91d17fd626ffb1c49e2bca94eed052281b510b1117a24',
'f1c6165d33367351b0da8f6e4511010c68174a03b6581212c71c0e1d026c3c72',
'87260f7a2f12495118360f02c26a470f450dadf34a413d21042b43b9d93e1309',
// These are all bad because they give a nonsquare x².
'26948d35ca62e643e26a83177332e6b6afeb9d08e4268b650f1f5bbd8d81d371',
'4eac077a713c57b4f4397629a4145982c661f48044dd3f96427d40b147d9742f',
'de6a7b00deadc788eb6b6c8d20c0ae96c2f2019078fa604fee5b87d6e989ad7b',
'bcab477be20861e01e4a0e295284146a510150d9817763caf1a6f4b422d67042',
'2a292df7e32cababbd9de088d1d1abec9fc0440f637ed2fba145094dc14bea08',
'f4a9e534fc0d216c44b218fa0c42d99635a0127ee2e53c712f70609649fdff22',
'8268436f8c4126196cf64b3c7ddbda90746a378625f9813dd9b8457077256731',
'2810e5cbc2cc4d4eece54f61c6f69758e289aa7ab440b3cbeaa21995c2f4232b',
// These are all bad because they give a negative xy value.
'3eb858e78f5a7254d8c9731174a94f76755fd3941c0ac93735c07ba14579630e',
'a45fdc55c76448c049a1ab33f17023edfb2be3581e9c7aade8a6125215e04220',
'd483fe813c6ba647ebbfd3ec41adca1c6130c2beeee9d9bf065c8d151c5f396e',
'8a2e1d30050198c65a54483123960ccc38aef6848e1ec8f5f780e8523769ba32',
'32888462f8b486c68ad7dd9610be5192bbeaf3b443951ac1a8118419d9fa097b',
'227142501b9d4355ccba290404bde41575b037693cef1f438c47f8fbf35d1165',
'5c37cc491da847cfeb9281d407efc41e15144c876e0170b499a96a22ed31e01e',
'445425117cb8c90edcbc7c1cc0e74f747f2c1efa5630a967c64f287792a48a4b',
// This is s = -1, which causes y = 0.
'ecffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f',
];
for (const badBytes of badEncodings) {
const b = hexToBytes(badBytes);
throws(() => RistrettoPoint.fromHex(b), badBytes);
}
});
should('create right points from uniform hash', () => {
const labels = [
'Ristretto is traditionally a short shot of espresso coffee',
'made with the normal amount of ground coffee but extracted with',
'about half the amount of water in the same amount of time',
'by using a finer grind.',
'This produces a concentrated shot of coffee per volume.',
'Just pulling a normal shot short will produce a weaker shot',
'and is not a Ristretto as some believe.',
];
const encodedHashToPoints = [
'3066f82a1a747d45120d1740f14358531a8f04bbffe6a819f86dfe50f44a0a46',
'f26e5b6f7d362d2d2a94c5d0e7602cb4773c95a2e5c31a64f133189fa76ed61b',
'006ccd2a9e6867e6a2c5cea83d3302cc9de128dd2a9a57dd8ee7b9d7ffe02826',
'f8f0c87cf237953c5890aec3998169005dae3eca1fbb04548c635953c817f92a',
'ae81e7dedf20a497e10c304a765c1767a42d6e06029758d2d7e8ef7cc4c41179',
'e2705652ff9f5e44d3e841bf1c251cf7dddb77d140870d1ab2ed64f1a9ce8628',
'80bd07262511cdde4863f8a7434cef696750681cb9510eea557088f76d9e5065',
];
for (let i = 0; i < labels.length; i++) {
const hash = sha512(utf8ToBytes(labels[i]));
const point = RistrettoPoint.hashToCurve(hash);
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.
import url from 'url';
if (import.meta.url === url.pathToFileURL(process.argv[1]).href) {
should.run();
}

2
test/ed25519.helpers.js Normal file
View File

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

View File

@@ -1,38 +1,35 @@
import { deepStrictEqual, throws } from 'assert';
import { should } from 'micro-should';
import * as fc from 'fast-check';
import { ed25519, ed25519ctx, ed25519ph, x25519, RistrettoPoint } from '../lib/esm/ed25519.js';
import { deepStrictEqual, strictEqual, throws } from 'assert';
import { readFileSync } from 'fs';
import { bytesToHex, concatBytes, hexToBytes, utf8ToBytes, randomBytes } from '@noble/hashes/utils';
import * as fc from 'fast-check';
import { describe, should } from 'micro-should';
import { ed25519 as ed, ED25519_TORSION_SUBGROUP, numberToBytesLE } from './ed25519.helpers.js';
// Old vectors allow to test sign() because they include private key
import { default as ed25519vectors_OLD } from './ed25519/ed25519_test_OLD.json' assert { type: 'json' };
import { default as ed25519vectors } from './wycheproof/ed25519_test.json' assert { type: 'json' };
import { default as zip215 } from './ed25519/zip215.json' assert { type: 'json' };
import { hexToBytes, bytesToHex, randomBytes } from '@noble/hashes/utils';
import { numberToBytesLE } from '../lib/esm/abstract/utils.js';
import { sha512 } from '@noble/hashes/sha512';
import { default as ed25519vectors } from './wycheproof/eddsa_test.json' assert { type: 'json' };
import { default as x25519vectors } from './wycheproof/x25519_test.json' assert { type: 'json' };
import { default as edgeCases } from './ed25519/edge-cases.json' assert { type: 'json' };
const ed = ed25519;
const hex = bytesToHex;
// Any changes to the file will need to be aware of the fact
// the file is shared between noble-curves and noble-ed25519.
function to32Bytes(numOrStr) {
describe('ed25519', () => {
const hex = bytesToHex;
const Point = ed.ExtendedPoint;
function to32Bytes(numOrStr) {
let hex = typeof numOrStr === 'string' ? numOrStr : numOrStr.toString(16);
return hexToBytes(hex.padStart(64, '0'));
}
function utf8ToBytes(str) {
if (typeof str !== 'string') {
throw new TypeError(`utf8ToBytes expected string, got ${typeof str}`);
}
return new TextEncoder().encode(str);
}
ed.utils.precompute(8);
ed.utils.precompute(8);
should('ed25519/should not accept >32byte private keys', () => {
should('not accept >32byte private keys', () => {
const invalidPriv =
100000000000000000000000000000000000009000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000090000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800073278156000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000n;
throws(() => ed.getPublicKey(invalidPriv));
});
should('ed25519/should verify recent signature', () => {
});
should('verify recent signature', () => {
fc.assert(
fc.property(
fc.hexaString({ minLength: 2, maxLength: 32 }),
@@ -47,8 +44,8 @@ should('ed25519/should verify recent signature', () => {
),
{ numRuns: 5 }
);
});
should('ed25519/should not verify signature with wrong message', () => {
});
should('not verify signature with wrong message', () => {
fc.assert(
fc.property(
fc.array(fc.integer({ min: 0x00, max: 0xff })),
@@ -68,101 +65,84 @@ should('ed25519/should not verify signature with wrong message', () => {
),
{ numRuns: 5 }
);
});
const privKey = to32Bytes('a665a45920422f9d417e4867ef');
const msg = hexToBytes('874f9960c5d2b7a9b5fad383e1ba44719ebb743a');
const wrongMsg = hexToBytes('589d8c7f1da0a24bc07b7381ad48b1cfc211af1c');
should('ed25519/basic methods/should sign and verify', () => {
});
const privKey = to32Bytes('a665a45920422f9d417e4867ef');
const wrongPriv = to32Bytes('a675a45920422f9d417e4867ef');
const msg = hexToBytes('874f9960c5d2b7a9b5fad383e1ba44719ebb743a');
const wrongMsg = hexToBytes('589d8c7f1da0a24bc07b7381ad48b1cfc211af1c');
describe('basic methods', () => {
should('sign and verify', () => {
const publicKey = ed.getPublicKey(privKey);
const signature = ed.sign(msg, privKey);
deepStrictEqual(ed.verify(signature, msg, publicKey), true);
});
should('ed25519/basic methods/should not verify signature with wrong public key', () => {
const publicKey = ed.getPublicKey(12);
const signature = ed.sign(msg, privKey);
deepStrictEqual(ed.verify(signature, msg, publicKey), false);
});
should('ed25519/basic methods/should not verify signature with wrong hash', () => {
const publicKey = ed.getPublicKey(privKey);
const signature = ed.sign(msg, privKey);
deepStrictEqual(ed.verify(signature, wrongMsg, publicKey), false);
});
should('ed25519/sync methods/should sign and verify', () => {
});
});
describe('sync methods', () => {
should('sign and verify', () => {
const publicKey = ed.getPublicKey(privKey);
const signature = ed.sign(msg, privKey);
deepStrictEqual(ed.verify(signature, msg, publicKey), true);
});
should('ed25519/sync methods/should not verify signature with wrong public key', () => {
const publicKey = ed.getPublicKey(12);
});
should('not verify signature with wrong public key', () => {
const publicKey = ed.getPublicKey(wrongPriv);
const signature = ed.sign(msg, privKey);
deepStrictEqual(ed.verify(signature, msg, publicKey), false);
});
should('ed25519/sync methods/should not verify signature with wrong hash', () => {
});
should('not verify signature with wrong hash', () => {
const publicKey = ed.getPublicKey(privKey);
const signature = ed.sign(msg, privKey);
deepStrictEqual(ed.verify(signature, wrongMsg, publicKey), false);
});
// https://xmr.llcoins.net/addresstests.html
should(
'ed25519/BASE_POINT.multiply()/should create right publicKey without SHA-512 hashing TEST 1',
() => {
});
});
describe('BASE_POINT.multiply()', () => {
// https://xmr.llcoins.net/addresstests.html
should('create right publicKey without SHA-512 hashing TEST 1', () => {
const publicKey =
ed.Point.BASE.multiply(0x90af56259a4b6bfbc4337980d5d75fbe3c074630368ff3804d33028e5dbfa77n);
Point.BASE.multiply(0x90af56259a4b6bfbc4337980d5d75fbe3c074630368ff3804d33028e5dbfa77n);
deepStrictEqual(
publicKey.toHex(),
'0f3b913371411b27e646b537e888f685bf929ea7aab93c950ed84433f064480d'
);
}
);
should(
'ed25519/BASE_POINT.multiply()/should create right publicKey without SHA-512 hashing TEST 2',
() => {
});
should('create right publicKey without SHA-512 hashing TEST 2', () => {
const publicKey =
ed.Point.BASE.multiply(0x364e8711a60780382a5d57b061c126f039940f28a9e91fe039d4d3094d8b88n);
Point.BASE.multiply(0x364e8711a60780382a5d57b061c126f039940f28a9e91fe039d4d3094d8b88n);
deepStrictEqual(
publicKey.toHex(),
'ad545340b58610f0cd62f17d55af1ab11ecde9c084d5476865ddb4dbda015349'
);
}
);
should(
'ed25519/BASE_POINT.multiply()/should create right publicKey without SHA-512 hashing TEST 3',
() => {
});
should('create right publicKey without SHA-512 hashing TEST 3', () => {
const publicKey =
ed.Point.BASE.multiply(0xb9bf90ff3abec042752cac3a07a62f0c16cfb9d32a3fc2305d676ec2d86e941n);
Point.BASE.multiply(0xb9bf90ff3abec042752cac3a07a62f0c16cfb9d32a3fc2305d676ec2d86e941n);
deepStrictEqual(
publicKey.toHex(),
'e097c4415fe85724d522b2e449e8fd78dd40d20097bdc9ae36fe8ec6fe12cb8c'
);
}
);
should(
'ed25519/BASE_POINT.multiply()/should create right publicKey without SHA-512 hashing TEST 4',
() => {
});
should('create right publicKey without SHA-512 hashing TEST 4', () => {
const publicKey =
ed.Point.BASE.multiply(0x69d896f02d79524c9878e080308180e2859d07f9f54454e0800e8db0847a46en);
Point.BASE.multiply(0x69d896f02d79524c9878e080308180e2859d07f9f54454e0800e8db0847a46en);
deepStrictEqual(
publicKey.toHex(),
'f12cb7c43b59971395926f278ce7c2eaded9444fbce62ca717564cb508a0db1d'
);
}
);
should('ed25519/BASE_POINT.multiply()/should throw Point#multiply on TEST 5', () => {
});
should('throw Point#multiply on TEST 5', () => {
for (const num of [0n, 0, -1n, -1, 1.1]) {
throws(() => ed.Point.BASE.multiply(num));
throws(() => Point.BASE.multiply(num));
}
});
});
});
// https://ed25519.cr.yp.to/python/sign.py
// https://ed25519.cr.yp.to/python/sign.input
const data = readFileSync('./test/ed25519/vectors.txt', 'utf-8');
const vectors = data
// https://ed25519.cr.yp.to/python/sign.py
// https://ed25519.cr.yp.to/python/sign.input
const data = readFileSync('./test/ed25519/vectors.txt', 'utf-8');
const vectors = data
.trim()
.split('\n')
.map((line) => line.split(':'));
should('ed25519 official vectors/should match 1024 official vectors', () => {
should('ed25519 official vectors/should match 1024 official vectors', () => {
for (let i = 0; i < vectors.length; i++) {
const vector = vectors[i];
// Extract.
@@ -174,17 +154,17 @@ should('ed25519 official vectors/should match 1024 official vectors', () => {
// Calculate
const pub = ed.getPublicKey(to32Bytes(priv));
deepStrictEqual(hex(pub), expectedPub);
deepStrictEqual(pub, ed.Point.fromHex(pub).toRawBytes());
deepStrictEqual(pub, Point.fromHex(pub).toRawBytes());
const signature = hex(ed.sign(msg, priv));
// console.log('vector', i);
// expect(pub).toBe(expectedPub);
deepStrictEqual(signature, expectedSignature);
}
});
});
// https://tools.ietf.org/html/rfc8032#section-7
should('rfc8032 vectors/should create right signature for 0x9d and empty string', () => {
// https://tools.ietf.org/html/rfc8032#section-7
should('rfc8032 vectors/should create right signature for 0x9d and empty string', () => {
const privateKey = '9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60';
const publicKey = ed.getPublicKey(privateKey);
const message = '';
@@ -197,8 +177,8 @@ should('rfc8032 vectors/should create right signature for 0x9d and empty string'
hex(signature),
'e5564300c360ac729086e2cc806e828a84877f1eb8e5d974d873e065224901555fb8821590a33bacc61e39701cf9b46bd25bf5f0595bbe24655141438e7a100b'
);
});
should('rfc8032 vectors/should create right signature for 0x4c and 72', () => {
});
should('rfc8032 vectors/should create right signature for 0x4c and 72', () => {
const privateKey = '4ccd089b28ff96da9db6c346ec114e0f5b8a319f35aba624da8cf6ed4fb8a6fb';
const publicKey = ed.getPublicKey(privateKey);
const message = '72';
@@ -211,8 +191,8 @@ should('rfc8032 vectors/should create right signature for 0x4c and 72', () => {
hex(signature),
'92a009a9f0d4cab8720e820b5f642540a2b27b5416503f8fb3762223ebdb69da085ac1e43e15996e458f3613d0f11d8c387b2eaeb4302aeeb00d291612bb0c00'
);
});
should('rfc8032 vectors/should create right signature for 0x00 and 5a', () => {
});
should('rfc8032 vectors/should create right signature for 0x00 and 5a', () => {
const privateKey = '002fdd1f7641793ab064bb7aa848f762e7ec6e332ffc26eeacda141ae33b1783';
const publicKey = ed.getPublicKey(privateKey);
const message =
@@ -226,8 +206,8 @@ should('rfc8032 vectors/should create right signature for 0x00 and 5a', () => {
hex(signature),
'0df3aa0d0999ad3dc580378f52d152700d5b3b057f56a66f92112e441e1cb9123c66f18712c87efe22d2573777296241216904d7cdd7d5ea433928bd2872fa0c'
);
});
should('rfc8032 vectors/should create right signature for 0xf5 and long msg', () => {
});
should('rfc8032 vectors/should create right signature for 0xf5 and long msg', () => {
const privateKey = 'f5e5767cf153319517630f226876b86c8160cc583bc013744c6bf255f5cc0ee5';
const publicKey = ed.getPublicKey(privateKey);
const message =
@@ -241,165 +221,67 @@ should('rfc8032 vectors/should create right signature for 0xf5 and long msg', ()
hex(signature),
'0aab4c900501b3e24d7cdf4663326a3a87df5e4843b2cbdb67cbf6e460fec350aa5371b1508f9f4528ecea23c436d94b5e8fcd4f681e30a6ac00a9704a188a03'
);
});
});
// const PRIVATE_KEY = 0xa665a45920422f9d417e4867efn;
// const MESSAGE = ripemd160(new Uint8Array([97, 98, 99, 100, 101, 102, 103]));
// prettier-ignore
// const MESSAGE = new Uint8Array([
// 135, 79, 153, 96, 197, 210, 183, 169, 181, 250, 211, 131, 225, 186, 68, 113, 158, 187, 116, 58,
// ]);
// const WRONG_MESSAGE = ripemd160(new Uint8Array([98, 99, 100, 101, 102, 103]));
// prettier-ignore
// const WRONG_MESSAGE = new Uint8Array([
// 88, 157, 140, 127, 29, 160, 162, 75, 192, 123, 115, 129, 173, 72, 177, 207, 194, 17, 175, 28,
// ]);
// // it("should verify just signed message", async () => {
// // await fc.assert(fc.asyncProperty(
// // fc.hexa(),
// // fc.bigInt(2n, ristretto25519.PRIME_ORDER),
// // async (message, privateKey) => {
// // const publicKey = await ristretto25519.getPublicKey(privateKey);
// // const signature = await ristretto25519.sign(message, privateKey);
// // expect(publicKey.length).toBe(32);
// // expect(signature.length).toBe(64);
// // expect(await ristretto25519.verify(signature, message, publicKey)).toBe(true);
// // }),
// // { numRuns: 1 }
// // );
// // });
// // it("should not verify sign with wrong message", async () => {
// // await fc.assert(fc.asyncProperty(
// // fc.array(fc.integer(0x00, 0xff)),
// // fc.array(fc.integer(0x00, 0xff)),
// // fc.bigInt(2n, ristretto25519.PRIME_ORDER),
// // async (bytes, wrongBytes, privateKey) => {
// // const message = new Uint8Array(bytes);
// // const wrongMessage = new Uint8Array(wrongBytes);
// // const publicKey = await ristretto25519.getPublicKey(privateKey);
// // const signature = await ristretto25519.sign(message, privateKey);
// // expect(await ristretto25519.verify(signature, wrongMessage, publicKey)).toBe(
// // bytes.toString() === wrongBytes.toString()
// // );
// // }),
// // { numRuns: 1 }
// // );
// // });
// // it("should sign and verify", async () => {
// // const publicKey = await ristretto25519.getPublicKey(PRIVATE_KEY);
// // const signature = await ristretto25519.sign(MESSAGE, PRIVATE_KEY);
// // expect(await ristretto25519.verify(signature, MESSAGE, publicKey)).toBe(true);
// // });
// // it("should not verify signature with wrong public key", async () => {
// // const publicKey = await ristretto25519.getPublicKey(12);
// // const signature = await ristretto25519.sign(MESSAGE, PRIVATE_KEY);
// // expect(await ristretto25519.verify(signature, MESSAGE, publicKey)).toBe(false);
// // });
// // it("should not verify signature with wrong hash", async () => {
// // const publicKey = await ristretto25519.getPublicKey(PRIVATE_KEY);
// // const signature = await ristretto25519.sign(MESSAGE, PRIVATE_KEY);
// // expect(await ristretto25519.verify(signature, WRONG_MESSAGE, publicKey)).toBe(false);
// // });
should('ristretto255/should follow the byte encodings of small multiples', () => {
const encodingsOfSmallMultiples = [
// This is the identity point
'0000000000000000000000000000000000000000000000000000000000000000',
// This is the basepoint
'e2f2ae0a6abc4e71a884a961c500515f58e30b6aa582dd8db6a65945e08d2d76',
// These are small multiples of the basepoint
'6a493210f7499cd17fecb510ae0cea23a110e8d5b901f8acadd3095c73a3b919',
'94741f5d5d52755ece4f23f044ee27d5d1ea1e2bd196b462166b16152a9d0259',
'da80862773358b466ffadfe0b3293ab3d9fd53c5ea6c955358f568322daf6a57',
'e882b131016b52c1d3337080187cf768423efccbb517bb495ab812c4160ff44e',
'f64746d3c92b13050ed8d80236a7f0007c3b3f962f5ba793d19a601ebb1df403',
'44f53520926ec81fbd5a387845beb7df85a96a24ece18738bdcfa6a7822a176d',
'903293d8f2287ebe10e2374dc1a53e0bc887e592699f02d077d5263cdd55601c',
'02622ace8f7303a31cafc63f8fc48fdc16e1c8c8d234b2f0d6685282a9076031',
'20706fd788b2720a1ed2a5dad4952b01f413bcf0e7564de8cdc816689e2db95f',
'bce83f8ba5dd2fa572864c24ba1810f9522bc6004afe95877ac73241cafdab42',
'e4549ee16b9aa03099ca208c67adafcafa4c3f3e4e5303de6026e3ca8ff84460',
'aa52e000df2e16f55fb1032fc33bc42742dad6bd5a8fc0be0167436c5948501f',
'46376b80f409b29dc2b5f6f0c52591990896e5716f41477cd30085ab7f10301e',
'e0c418f7c8d9c4cdd7395b93ea124f3ad99021bb681dfc3302a9d99a2e53e64e',
];
let B = RistrettoPoint.BASE;
let P = RistrettoPoint.ZERO;
for (const encoded of encodingsOfSmallMultiples) {
deepStrictEqual(P.toHex(), encoded);
deepStrictEqual(RistrettoPoint.fromHex(encoded).toHex(), encoded);
P = P.add(B);
}
});
should('ristretto255/should not convert bad bytes encoding', () => {
const badEncodings = [
// These are all bad because they're non-canonical field encodings.
'00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff',
'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f',
'f3ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f',
'edffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f',
// These are all bad because they're negative field elements.
'0100000000000000000000000000000000000000000000000000000000000000',
'01ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f',
'ed57ffd8c914fb201471d1c3d245ce3c746fcbe63a3679d51b6a516ebebe0e20',
'c34c4e1826e5d403b78e246e88aa051c36ccf0aafebffe137d148a2bf9104562',
'c940e5a4404157cfb1628b108db051a8d439e1a421394ec4ebccb9ec92a8ac78',
'47cfc5497c53dc8e61c91d17fd626ffb1c49e2bca94eed052281b510b1117a24',
'f1c6165d33367351b0da8f6e4511010c68174a03b6581212c71c0e1d026c3c72',
'87260f7a2f12495118360f02c26a470f450dadf34a413d21042b43b9d93e1309',
// These are all bad because they give a nonsquare x².
'26948d35ca62e643e26a83177332e6b6afeb9d08e4268b650f1f5bbd8d81d371',
'4eac077a713c57b4f4397629a4145982c661f48044dd3f96427d40b147d9742f',
'de6a7b00deadc788eb6b6c8d20c0ae96c2f2019078fa604fee5b87d6e989ad7b',
'bcab477be20861e01e4a0e295284146a510150d9817763caf1a6f4b422d67042',
'2a292df7e32cababbd9de088d1d1abec9fc0440f637ed2fba145094dc14bea08',
'f4a9e534fc0d216c44b218fa0c42d99635a0127ee2e53c712f70609649fdff22',
'8268436f8c4126196cf64b3c7ddbda90746a378625f9813dd9b8457077256731',
'2810e5cbc2cc4d4eece54f61c6f69758e289aa7ab440b3cbeaa21995c2f4232b',
// These are all bad because they give a negative xy value.
'3eb858e78f5a7254d8c9731174a94f76755fd3941c0ac93735c07ba14579630e',
'a45fdc55c76448c049a1ab33f17023edfb2be3581e9c7aade8a6125215e04220',
'd483fe813c6ba647ebbfd3ec41adca1c6130c2beeee9d9bf065c8d151c5f396e',
'8a2e1d30050198c65a54483123960ccc38aef6848e1ec8f5f780e8523769ba32',
'32888462f8b486c68ad7dd9610be5192bbeaf3b443951ac1a8118419d9fa097b',
'227142501b9d4355ccba290404bde41575b037693cef1f438c47f8fbf35d1165',
'5c37cc491da847cfeb9281d407efc41e15144c876e0170b499a96a22ed31e01e',
'445425117cb8c90edcbc7c1cc0e74f747f2c1efa5630a967c64f287792a48a4b',
// This is s = -1, which causes y = 0.
'ecffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f',
];
for (const badBytes of badEncodings) {
const b = hexToBytes(badBytes);
throws(() => RistrettoPoint.fromHex(b), badBytes);
}
});
should('ristretto255/should create right points from uniform hash', async () => {
const labels = [
'Ristretto is traditionally a short shot of espresso coffee',
'made with the normal amount of ground coffee but extracted with',
'about half the amount of water in the same amount of time',
'by using a finer grind.',
'This produces a concentrated shot of coffee per volume.',
'Just pulling a normal shot short will produce a weaker shot',
'and is not a Ristretto as some believe.',
];
const encodedHashToPoints = [
'3066f82a1a747d45120d1740f14358531a8f04bbffe6a819f86dfe50f44a0a46',
'f26e5b6f7d362d2d2a94c5d0e7602cb4773c95a2e5c31a64f133189fa76ed61b',
'006ccd2a9e6867e6a2c5cea83d3302cc9de128dd2a9a57dd8ee7b9d7ffe02826',
'f8f0c87cf237953c5890aec3998169005dae3eca1fbb04548c635953c817f92a',
'ae81e7dedf20a497e10c304a765c1767a42d6e06029758d2d7e8ef7cc4c41179',
'e2705652ff9f5e44d3e841bf1c251cf7dddb77d140870d1ab2ed64f1a9ce8628',
'80bd07262511cdde4863f8a7434cef696750681cb9510eea557088f76d9e5065',
];
// const PRIVATE_KEY = 0xa665a45920422f9d417e4867efn;
// const MESSAGE = ripemd160(new Uint8Array([97, 98, 99, 100, 101, 102, 103]));
// prettier-ignore
// const MESSAGE = new Uint8Array([
// 135, 79, 153, 96, 197, 210, 183, 169, 181, 250, 211, 131, 225, 186, 68, 113, 158, 187, 116, 58,
// ]);
// const WRONG_MESSAGE = ripemd160(new Uint8Array([98, 99, 100, 101, 102, 103]));
// prettier-ignore
// const WRONG_MESSAGE = new Uint8Array([
// 88, 157, 140, 127, 29, 160, 162, 75, 192, 123, 115, 129, 173, 72, 177, 207, 194, 17, 175, 28,
// ]);
// // it("should verify just signed message", async () => {
// // await fc.assert(fc.asyncProperty(
// // fc.hexa(),
// // fc.bigInt(2n, ristretto25519.PRIME_ORDER),
// // async (message, privateKey) => {
// // const publicKey = await ristretto25519.getPublicKey(privateKey);
// // const signature = await ristretto25519.sign(message, privateKey);
// // expect(publicKey.length).toBe(32);
// // expect(signature.length).toBe(64);
// // expect(await ristretto25519.verify(signature, message, publicKey)).toBe(true);
// // }),
// // { numRuns: 1 }
// // );
// // });
// // it("should not verify sign with wrong message", async () => {
// // await fc.assert(fc.asyncProperty(
// // fc.array(fc.integer(0x00, 0xff)),
// // fc.array(fc.integer(0x00, 0xff)),
// // fc.bigInt(2n, ristretto25519.PRIME_ORDER),
// // async (bytes, wrongBytes, privateKey) => {
// // const message = new Uint8Array(bytes);
// // const wrongMessage = new Uint8Array(wrongBytes);
// // const publicKey = await ristretto25519.getPublicKey(privateKey);
// // const signature = await ristretto25519.sign(message, privateKey);
// // expect(await ristretto25519.verify(signature, wrongMessage, publicKey)).toBe(
// // bytes.toString() === wrongBytes.toString()
// // );
// // }),
// // { numRuns: 1 }
// // );
// // });
// // it("should sign and verify", async () => {
// // const publicKey = await ristretto25519.getPublicKey(PRIVATE_KEY);
// // const signature = await ristretto25519.sign(MESSAGE, PRIVATE_KEY);
// // expect(await ristretto25519.verify(signature, MESSAGE, publicKey)).toBe(true);
// // });
// // it("should not verify signature with wrong public key", async () => {
// // const publicKey = await ristretto25519.getPublicKey(12);
// // const signature = await ristretto25519.sign(MESSAGE, PRIVATE_KEY);
// // expect(await ristretto25519.verify(signature, MESSAGE, publicKey)).toBe(false);
// // });
// // it("should not verify signature with wrong hash", async () => {
// // const publicKey = await ristretto25519.getPublicKey(PRIVATE_KEY);
// // const signature = await ristretto25519.sign(MESSAGE, PRIVATE_KEY);
// // expect(await ristretto25519.verify(signature, WRONG_MESSAGE, publicKey)).toBe(false);
// // });
for (let i = 0; i < labels.length; i++) {
const hash = sha512(utf8ToBytes(labels[i]));
const point = RistrettoPoint.hashToCurve(hash);
deepStrictEqual(point.toHex(), encodedHashToPoints[i]);
}
});
should('input immutability: sign/verify are immutable', () => {
should('input immutability: sign/verify are immutable', () => {
const privateKey = ed.utils.randomPrivateKey();
const publicKey = ed.getPublicKey(privateKey);
@@ -417,11 +299,12 @@ should('input immutability: sign/verify are immutable', () => {
if (!ed.verify(signatureCopy, payload, publicKey))
throw new Error('Copied signature verification failed');
}
});
});
// https://zips.z.cash/zip-0215
// Vectors from https://gist.github.com/hdevalence/93ed42d17ecab8e42138b213812c8cc7
should('ZIP-215 compliance tests/should pass all of them', () => {
// https://zips.z.cash/zip-0215
// Vectors from https://gist.github.com/hdevalence/93ed42d17ecab8e42138b213812c8cc7
describe('ZIP215', () => {
should('pass all compliance tests', () => {
const str = utf8ToBytes('Zcash');
for (let v of zip215) {
let noble = false;
@@ -430,114 +313,42 @@ should('ZIP-215 compliance tests/should pass all of them', () => {
} catch (e) {
noble = false;
}
deepStrictEqual(noble, v.valid_zip215);
}
});
should('ZIP-215 compliance tests/disallows sig.s >= CURVE.n', () => {
const sig = new ed.Signature(ed.Point.BASE, 1n);
sig.s = ed.CURVE.n + 1n;
throws(() => ed.verify(sig, 'deadbeef', ed.Point.BASE));
});
const rfc7748Mul = [
{
scalar: 'a546e36bf0527c9d3b16154b82465edd62144c0ac1fc5a18506a2244ba449ac4',
u: 'e6db6867583030db3594c1a424b15f7c726624ec26b3353b10a903a6d0ab1c4c',
outputU: 'c3da55379de9c6908e94ea4df28d084f32eccf03491c71f754b4075577a28552',
},
{
scalar: '4b66e9d4d1b4673c5ad22691957d6af5c11b6421e0ea01d42ca4169e7918ba0d',
u: 'e5210f12786811d3f4b7959d0538ae2c31dbe7106fc03c3efc4cd549c715a493',
outputU: '95cbde9476e8907d7aade45cb4b873f88b595a68799fa152e6f8f7647aac7957',
},
];
for (let i = 0; i < rfc7748Mul.length; i++) {
const v = rfc7748Mul[i];
should(`RFC7748: scalarMult (${i})`, () => {
deepStrictEqual(hex(x25519.scalarMult(v.scalar, v.u)), v.outputU);
});
}
const rfc7748Iter = [
{ scalar: '422c8e7a6227d7bca1350b3e2bb7279f7897b87bb6854b783c60e80311ae3079', iters: 1 },
{ scalar: '684cf59ba83309552800ef566f2f4d3c1c3887c49360e3875f2eb94d99532c51', iters: 1000 },
// { scalar: '7c3911e0ab2586fd864497297e575e6f3bc601c0883c30df5f4dd2d24f665424', iters: 1000000 },
];
for (let i = 0; i < rfc7748Iter.length; i++) {
const { scalar, iters } = rfc7748Iter[i];
should(`RFC7748: scalarMult iteration (${i})`, () => {
let k = x25519.Gu;
for (let i = 0, u = k; i < iters; i++) [k, u] = [x25519.scalarMult(k, u), k];
deepStrictEqual(hex(k), scalar);
});
}
should('RFC7748 getSharedKey', () => {
const alicePrivate = '77076d0a7318a57d3c16c17251b26645df4c2f87ebc0992ab177fba51db92c2a';
const alicePublic = '8520f0098930a754748b7ddcb43ef75a0dbf3a0d26381af4eba4a98eaa9b4e6a';
const bobPrivate = '5dab087e624a8a4b79e17f8b83800ee66f3bb1292618b6fd1c2f8b27ff88e0eb';
const bobPublic = 'de9edb7d7b7dc1b4d35b61c2ece435373f8343c85b78674dadfc7e146f882b4f';
const shared = '4a5d9d5ba4ce2de1728e3bf480350f25e07e21c947d19e3376f09b3c1e161742';
deepStrictEqual(alicePublic, hex(x25519.getPublicKey(alicePrivate)));
deepStrictEqual(bobPublic, hex(x25519.getPublicKey(bobPrivate)));
deepStrictEqual(hex(x25519.scalarMult(alicePrivate, bobPublic)), shared);
deepStrictEqual(hex(x25519.scalarMult(bobPrivate, alicePublic)), shared);
});
// should('X25519/getSharedSecret() should be commutative', () => {
// for (let i = 0; i < 512; i++) {
// const asec = ed.utils.randomPrivateKey();
// const apub = ed.getPublicKey(asec);
// const bsec = ed.utils.randomPrivateKey();
// const bpub = ed.getPublicKey(bsec);
// try {
// deepStrictEqual(ed.getSharedSecret(asec, bpub), ed.getSharedSecret(bsec, apub));
// } catch (error) {
// console.error('not commutative', { asec, apub, bsec, bpub });
// throw error;
// }
// }
// });
// should('X25519: should convert base point to montgomery using fromPoint', () => {
// deepStrictEqual(
// hex(ed.montgomeryCurve.UfromPoint(ed.Point.BASE)),
// ed.montgomeryCurve.BASE_POINT_U
// );
// });
{
const group = x25519vectors.testGroups[0];
should(`Wycheproof/X25519`, () => {
for (let i = 0; i < group.tests.length; i++) {
const v = group.tests[i];
const comment = `(${i}, ${v.result}) ${v.comment}`;
if (v.result === 'valid' || v.result === 'acceptable') {
try {
const shared = hex(x25519.scalarMult(v.private, v.public));
deepStrictEqual(shared, v.shared, comment);
} 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;
throw e;
}
} else if (v.result === 'invalid') {
let failed = false;
try {
x25519.scalarMult(v.private, v.public);
} catch (error) {
failed = true;
}
deepStrictEqual(failed, true, comment);
} else throw new Error('unknown test result');
deepStrictEqual(noble, v.valid_zip215, JSON.stringify(v));
}
});
}
should('disallow sig.s >= CURVE.n', () => {
// sig.R = BASE, sig.s = N+1
const sig =
'5866666666666666666666666666666666666666666666666666666666666666eed3f55c1a631258d69cf7a2def9de1400000000000000000000000000000010';
deepStrictEqual(ed.verify(sig, 'deadbeef', Point.BASE), false);
});
});
should(`Wycheproof/ED25519`, () => {
for (let g = 0; g < ed25519vectors.testGroups.length; g++) {
const group = ed25519vectors.testGroups[g];
// should('X25519/getSharedSecret() should be commutative', () => {
// for (let i = 0; i < 512; i++) {
// const asec = ed.utils.randomPrivateKey();
// const apub = ed.getPublicKey(asec);
// const bsec = ed.utils.randomPrivateKey();
// const bpub = ed.getPublicKey(bsec);
// try {
// deepStrictEqual(ed.getSharedSecret(asec, bpub), ed.getSharedSecret(bsec, apub));
// } catch (error) {
// console.error('not commutative', { asec, apub, bsec, bpub });
// throw error;
// }
// }
// });
// should('X25519: should convert base point to montgomery using fromPoint', () => {
// deepStrictEqual(
// hex(ed.montgomeryCurve.UfromPoint(Point.BASE)),
// ed.montgomeryCurve.BASE_POINT_U
// );
// });
should(`wycheproof/ED25519 (OLD)`, () => {
for (let g = 0; g < ed25519vectors_OLD.testGroups.length; g++) {
const group = ed25519vectors_OLD.testGroups[g];
const key = group.key;
deepStrictEqual(hex(ed.getPublicKey(key.sk)), key.pk, `(${g}, public)`);
for (let i = 0; i < group.tests.length; i++) {
@@ -557,97 +368,83 @@ should(`Wycheproof/ED25519`, () => {
} else throw new Error('unknown test result');
}
}
});
});
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 signature = ed.sign(message, to32Bytes(1n));
const publicKey = ed.getPublicKey(to32Bytes(1n)); // <- was 1n
deepStrictEqual(ed.verify(signature, message, publicKey), true);
});
const VECTORS_RFC8032_CTX = [
{
secretKey: '0305334e381af78f141cb666f6199f57bc3495335a256a95bd2a55bf546663f6',
publicKey: 'dfc9425e4f968f7f0c29f0259cf5f9aed6851c2bb4ad8bfb860cfee0ab248292',
message: 'f726936d19c800494e3fdaff20b276a8',
context: '666f6f',
signature:
'55a4cc2f70a54e04288c5f4cd1e45a7b' +
'b520b36292911876cada7323198dd87a' +
'8b36950b95130022907a7fb7c4e9b2d5' +
'f6cca685a587b4b21f4b888e4e7edb0d',
},
{
secretKey: '0305334e381af78f141cb666f6199f57bc3495335a256a95bd2a55bf546663f6',
publicKey: 'dfc9425e4f968f7f0c29f0259cf5f9aed6851c2bb4ad8bfb860cfee0ab248292',
message: 'f726936d19c800494e3fdaff20b276a8',
context: '626172',
signature:
'fc60d5872fc46b3aa69f8b5b4351d580' +
'8f92bcc044606db097abab6dbcb1aee3' +
'216c48e8b3b66431b5b186d1d28f8ee1' +
'5a5ca2df6668346291c2043d4eb3e90d',
},
{
secretKey: '0305334e381af78f141cb666f6199f57bc3495335a256a95bd2a55bf546663f6',
publicKey: 'dfc9425e4f968f7f0c29f0259cf5f9aed6851c2bb4ad8bfb860cfee0ab248292',
message: '508e9e6882b979fea900f62adceaca35',
context: '666f6f',
signature:
'8b70c1cc8310e1de20ac53ce28ae6e72' +
'07f33c3295e03bb5c0732a1d20dc6490' +
'8922a8b052cf99b7c4fe107a5abb5b2c' +
'4085ae75890d02df26269d8945f84b0b',
},
{
secretKey: 'ab9c2853ce297ddab85c993b3ae14bcad39b2c682beabc27d6d4eb20711d6560',
publicKey: '0f1d1274943b91415889152e893d80e93275a1fc0b65fd71b4b0dda10ad7d772',
message: 'f726936d19c800494e3fdaff20b276a8',
context: '666f6f',
signature:
'21655b5f1aa965996b3f97b3c849eafb' +
'a922a0a62992f73b3d1b73106a84ad85' +
'e9b86a7b6005ea868337ff2d20a7f5fb' +
'd4cd10b0be49a68da2b2e0dc0ad8960f',
},
];
for (let i = 0; i < VECTORS_RFC8032_CTX.length; i++) {
const v = VECTORS_RFC8032_CTX[i];
should(`RFC8032ctx/${i}`, () => {
deepStrictEqual(hex(ed25519ctx.getPublicKey(v.secretKey)), v.publicKey);
deepStrictEqual(hex(ed25519ctx.sign(v.message, v.secretKey, v.context)), v.signature);
deepStrictEqual(ed25519ctx.verify(v.signature, v.message, v.publicKey, v.context), true);
});
}
const VECTORS_RFC8032_PH = [
{
secretKey: '833fe62409237b9d62ec77587520911e9a759cec1d19755b7da901b96dca3d42',
publicKey: 'ec172b93ad5e563bf4932c70e1245034c35467ef2efd4d64ebf819683467e2bf',
message: '616263',
signature:
'98a70222f0b8121aa9d30f813d683f80' +
'9e462b469c7ff87639499bb94e6dae41' +
'31f85042463c2a355a2003d062adf5aa' +
'a10b8c61e636062aaad11c2a26083406',
},
];
for (let i = 0; i < VECTORS_RFC8032_PH.length; i++) {
const v = VECTORS_RFC8032_PH[i];
should(`RFC8032ph/${i}`, () => {
deepStrictEqual(hex(ed25519ph.getPublicKey(v.secretKey)), v.publicKey);
deepStrictEqual(hex(ed25519ph.sign(v.message, v.secretKey)), v.signature);
deepStrictEqual(ed25519ph.verify(v.signature, v.message, v.publicKey), true);
should('isTorsionFree()', () => {
const orig = ed.utils.getExtendedPublicKey(ed.utils.randomPrivateKey()).point;
for (const hex of ED25519_TORSION_SUBGROUP.slice(1)) {
const dirty = orig.add(Point.fromHex(hex));
const cleared = dirty.clearCofactor();
strictEqual(orig.isTorsionFree(), true, `orig must be torsionFree: ${hex}`);
strictEqual(dirty.isTorsionFree(), false, `dirty must not be torsionFree: ${hex}`);
strictEqual(cleared.isTorsionFree(), true, `cleared must be torsionFree: ${hex}`);
}
});
}
should('X25519 base point', () => {
const { y } = ed25519.Point.BASE;
const u = ed25519.utils.mod((y + 1n) * ed25519.utils.invert(1n - y, ed25519.CURVE.P));
deepStrictEqual(hex(numberToBytesLE(u, 32)), x25519.Gu);
should('have strict SUF-CMA and SBS properties', () => {
// https://eprint.iacr.org/2020/1244
const list = [0, 1, 6, 7, 8, 9, 10, 11].map((i) => edgeCases[i]);
for (let v of list) {
const result = ed.verify(v.signature, v.message, v.pub_key, { zip215: false });
strictEqual(result, false, `zip215: false must not validate: ${v.signature}`);
}
});
should('not verify when sig.s >= CURVE.n', () => {
const privateKey = 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 point = Point.fromAffine({ x: t, y: t });
throws(() => point.assertValidity());
// Otherwise (without assertValidity):
// const point2 = point.double();
// point2.toAffine(); // crash!
});
});
// ESM is broken.

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

View File

@@ -1,18 +1,22 @@
import { deepStrictEqual, throws } from 'assert';
import { should } from 'micro-should';
import { describe, should } from 'micro-should';
import * as fc from 'fast-check';
import { ed448, ed448ph, x448 } from '../lib/esm/ed448.js';
import { hexToBytes, bytesToHex, randomBytes } from '@noble/hashes/utils';
import { numberToBytesLE } from '../lib/esm/abstract/utils.js';
import { ed448, ed448ph, x448 } from '../esm/ed448.js';
import { bytesToHex, concatBytes, hexToBytes, randomBytes } from '@noble/hashes/utils';
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 x448vectors } from './wycheproof/x448_test.json' assert { type: 'json' };
const ed = ed448;
const hex = bytesToHex;
ed.utils.precompute(4);
describe('ed448', () => {
const ed = ed448;
const hex = bytesToHex;
ed.utils.precompute(4);
const Point = ed.ExtendedPoint;
should(`Basic`, () => {
const G1 = ed.Point.BASE;
should(`Basic`, () => {
const G1 = Point.BASE.toAffine();
deepStrictEqual(
G1.x,
224580040295924300187604334099896036246789641632564134246125461686950415467406032909029192869357953282578032075146446173674602635247710n
@@ -21,7 +25,7 @@ should(`Basic`, () => {
G1.y,
298819210078481492676017930443930673437544040154080242095928241372331506189835876003536878655418784733982303233503462500531545062832660n
);
const G2 = ed.Point.BASE.multiply(2n);
const G2 = Point.BASE.multiply(2n).toAffine();
deepStrictEqual(
G2.x,
484559149530404593699549205258669689569094240458212040187660132787056912146709081364401144455726350866276831544947397859048262938744149n
@@ -30,7 +34,7 @@ should(`Basic`, () => {
G2.y,
494088759867433727674302672526735089350544552303727723746126484473087719117037293890093462157703888342865036477787453078312060500281069n
);
const G3 = ed.Point.BASE.multiply(3n);
const G3 = Point.BASE.multiply(3n).toAffine();
deepStrictEqual(
G3.x,
23839778817283171003887799738662344287085130522697782688245073320169861206004018274567429238677677920280078599146891901463786155880335n
@@ -39,18 +43,18 @@ should(`Basic`, () => {
G3.y,
636046652612779686502873775776967954190574036985351036782021535703553242737829645273154208057988851307101009474686328623630835377952508n
);
});
});
should('Basic/decompress', () => {
const G1 = ed.Point.BASE;
const G2 = ed.Point.BASE.multiply(2n);
const G3 = ed.Point.BASE.multiply(3n);
should('Basic/decompress', () => {
const G1 = Point.BASE;
const G2 = Point.BASE.multiply(2n);
const G3 = Point.BASE.multiply(3n);
const points = [G1, G2, G3];
const getXY = (p) => ({ x: p.x, y: p.y });
for (const p of points) deepStrictEqual(getXY(ed.Point.fromHex(p.toHex())), getXY(p));
});
const getXY = (p) => p.toAffine();
for (const p of points) deepStrictEqual(getXY(Point.fromHex(p.toHex())), getXY(p));
});
const VECTORS_RFC8032 = [
const VECTORS_RFC8032 = [
{
secretKey:
'6c82a562cb808d10d632be89c8513ebf' +
@@ -312,29 +316,31 @@ const VECTORS_RFC8032 = [
'3603ce30d8bb761785dc30dbc320869e' +
'1a00',
},
];
];
for (let i = 0; i < VECTORS_RFC8032.length; i++) {
describe('RFC8032', () => {
for (let i = 0; i < VECTORS_RFC8032.length; i++) {
const v = VECTORS_RFC8032[i];
should(`RFC8032/${i}`, () => {
should(`${i}`, () => {
deepStrictEqual(hex(ed.getPublicKey(v.secretKey)), v.publicKey);
deepStrictEqual(hex(ed.sign(v.message, v.secretKey)), v.signature);
deepStrictEqual(ed.verify(v.signature, v.message, v.publicKey), true);
});
}
}
});
should('ed448/should not accept >57byte private keys', async () => {
should('not accept >57byte private keys', () => {
const invalidPriv =
100000000000000000000000000000000000009000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000090000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800073278156000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000n;
throws(() => ed.getPublicKey(invalidPriv));
});
});
function to57Bytes(numOrStr) {
function to57Bytes(numOrStr) {
let hex = typeof numOrStr === 'string' ? numOrStr : numOrStr.toString(16);
return hexToBytes(hex.padStart(114, '0'));
}
}
should('ed448/should verify recent signature', () => {
should('verify recent signature', () => {
fc.assert(
fc.property(
fc.hexaString({ minLength: 2, maxLength: 57 }),
@@ -349,8 +355,8 @@ should('ed448/should verify recent signature', () => {
),
{ numRuns: 5 }
);
});
should('ed448/should not verify signature with wrong message', () => {
});
should('not verify signature with wrong message', () => {
fc.assert(
fc.property(
fc.array(fc.integer({ min: 0x00, max: 0xff })),
@@ -370,49 +376,52 @@ should('ed448/should not verify signature with wrong message', () => {
),
{ numRuns: 5 }
);
});
const privKey = to57Bytes('a665a45920422f9d417e4867ef');
const msg = hexToBytes('874f9960c5d2b7a9b5fad383e1ba44719ebb743a');
const wrongMsg = hexToBytes('589d8c7f1da0a24bc07b7381ad48b1cfc211af1c');
should('ed25519/basic methods/should sign and verify', () => {
});
const privKey = to57Bytes('a665a45920422f9d417e4867ef');
const msg = hexToBytes('874f9960c5d2b7a9b5fad383e1ba44719ebb743a');
const wrongMsg = hexToBytes('589d8c7f1da0a24bc07b7381ad48b1cfc211af1c');
describe('basic methods', () => {
should('sign and verify', () => {
const publicKey = ed.getPublicKey(privKey);
const signature = ed.sign(msg, privKey);
deepStrictEqual(ed.verify(signature, msg, publicKey), true);
});
should('ed25519/basic methods/should not verify signature with wrong public key', () => {
const publicKey = ed.getPublicKey(12);
});
should('not verify signature with wrong public key', () => {
const publicKey = ed.getPublicKey(ed.utils.randomPrivateKey());
const signature = ed.sign(msg, privKey);
deepStrictEqual(ed.verify(signature, msg, publicKey), false);
});
should('ed25519/basic methods/should not verify signature with wrong hash', () => {
});
should('not verify signature with wrong hash', () => {
const publicKey = ed.getPublicKey(privKey);
const signature = ed.sign(msg, privKey);
deepStrictEqual(ed.verify(signature, wrongMsg, publicKey), false);
});
should('ed25519/sync methods/should sign and verify', () => {
});
});
describe('sync methods', () => {
should('sign and verify', () => {
const publicKey = ed.getPublicKey(privKey);
const signature = ed.sign(msg, privKey);
deepStrictEqual(ed.verify(signature, msg, publicKey), true);
});
should('ed25519/sync methods/should not verify signature with wrong public key', async () => {
const publicKey = ed.getPublicKey(12);
});
should('not verify signature with wrong public key', () => {
const publicKey = ed.getPublicKey(ed.utils.randomPrivateKey());
const signature = ed.sign(msg, privKey);
deepStrictEqual(ed.verify(signature, msg, publicKey), false);
});
should('ed25519/sync methods/should not verify signature with wrong hash', async () => {
});
should('not verify signature with wrong hash', () => {
const publicKey = ed.getPublicKey(privKey);
const signature = ed.sign(msg, privKey);
deepStrictEqual(ed.verify(signature, wrongMsg, publicKey), false);
});
});
});
should('ed25519/BASE_POINT.multiply()/should throw Point#multiply on TEST 5', () => {
should('BASE_POINT.multiply() throws in Point#multiply on TEST 5', () => {
for (const num of [0n, 0, -1n, -1, 1.1]) {
throws(() => ed.Point.BASE.multiply(num));
throws(() => ed.ExtendedPoint.BASE.multiply(num));
}
});
});
should('input immutability: sign/verify are immutable', () => {
should('input immutability: sign/verify are immutable', () => {
const privateKey = ed.utils.randomPrivateKey();
const publicKey = ed.getPublicKey(privateKey);
@@ -430,16 +439,16 @@ should('input immutability: sign/verify are immutable', () => {
if (!ed.verify(signatureCopy, payload, publicKey))
throw new Error('Copied signature verification failed');
}
});
});
{
for (let g = 0; g < ed448vectors.testGroups.length; g++) {
const group = ed448vectors.testGroups[g];
describe('wycheproof (OLD)', () => {
for (let g = 0; g < ed448vectorsOld.testGroups.length; g++) {
const group = ed448vectorsOld.testGroups[g];
const key = group.key;
should(`Wycheproof/ED448(${g}, public)`, () => {
should(`ED448(${g}, public)`, () => {
deepStrictEqual(hex(ed.getPublicKey(key.sk)), key.pk);
});
should(`Wycheproof/ED448`, () => {
should(`ED448`, () => {
for (let i = 0; i < group.tests.length; i++) {
const v = group.tests[i];
const index = `${g}/${i} ${v.comment}`;
@@ -458,92 +467,22 @@ should('input immutability: sign/verify are immutable', () => {
}
});
}
}
// 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(`RFC7748: 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);
});
{
const group = x448vectors.testGroups[0];
should(`Wycheproof/X448`, () => {
describe('wycheproof', () => {
for (let g = 0; g < ed448vectors.testGroups.length; g++) {
const group = ed448vectors.testGroups[g];
const key = group.publicKey;
should(`ED448`, () => {
for (let i = 0; i < group.tests.length; 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') {
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;
}
deepStrictEqual(ed.verify(v.sig, v.msg, key.pk), true, index);
} else if (v.result === 'invalid') {
let failed = false;
try {
x448.scalarMult(v.private, v.public);
failed = !ed.verify(v.sig, v.msg, key.pk);
} catch (error) {
failed = true;
}
@@ -551,31 +490,32 @@ should('RFC7748 getSharedKey', () => {
} else throw new Error('unknown test result');
}
});
}
}
});
// should('X448: should convert base point to montgomery using fromPoint', () => {
// deepStrictEqual(
// hex(ed.montgomeryCurve.UfromPoint(Point.BASE)),
// ed.montgomeryCurve.BASE_POINT_U
// );
// });
// should('X448: should convert base point to montgomery using fromPoint', () => {
// deepStrictEqual(
// hex(ed.montgomeryCurve.UfromPoint(ed.Point.BASE)),
// ed.montgomeryCurve.BASE_POINT_U
// );
// });
// should('X448/getSharedSecret() should be commutative', async () => {
// for (let i = 0; i < 512; i++) {
// const asec = ed.utils.randomPrivateKey();
// const apub = ed.getPublicKey(asec);
// const bsec = ed.utils.randomPrivateKey();
// const bpub = ed.getPublicKey(bsec);
// try {
// deepStrictEqual(ed.getSharedSecret(asec, bpub), ed.getSharedSecret(bsec, apub));
// } catch (error) {
// console.error('not commutative', { asec, apub, bsec, bpub });
// throw error;
// }
// }
// });
// should('X448/getSharedSecret() should be commutative', async () => {
// for (let i = 0; i < 512; i++) {
// const asec = ed.utils.randomPrivateKey();
// const apub = ed.getPublicKey(asec);
// const bsec = ed.utils.randomPrivateKey();
// const bpub = ed.getPublicKey(bsec);
// try {
// deepStrictEqual(ed.getSharedSecret(asec, bpub), ed.getSharedSecret(bsec, apub));
// } catch (error) {
// console.error('not commutative', { asec, apub, bsec, bpub });
// throw error;
// }
// }
// });
const VECTORS_RFC8032_CTX = [
describe('ed448ctx', () => {
const VECTORS_RFC8032_CTX = [
{
secretKey:
'c4eab05d357007c632f3dbb48489924d552b08fe0c353a0d4a1f00acda2c463afbea67c5e8d2877c5e3bc397a659949ef8021e954e0a12274e',
@@ -593,18 +533,22 @@ const VECTORS_RFC8032_CTX = [
'5428407e85dcbc98a49155c13764e66c' +
'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];
should(`RFC8032ctx/${i}`, () => {
should(`${i}`, () => {
deepStrictEqual(hex(ed.getPublicKey(v.secretKey)), v.publicKey);
deepStrictEqual(hex(ed.sign(v.message, v.secretKey, v.context)), v.signature);
deepStrictEqual(ed.verify(v.signature, v.message, v.publicKey, v.context), true);
deepStrictEqual(hex(ed.sign(v.message, v.secretKey, { context: v.context })), v.signature);
deepStrictEqual(
ed.verify(v.signature, v.message, v.publicKey, { context: v.context }),
true
);
});
}
});
}
const VECTORS_RFC8032_PH = [
describe('ed448ph', () => {
const VECTORS_RFC8032_PH = [
{
secretKey:
'833fe62409237b9d62ec77587520911e9a759cec1d19755b7da901b96dca3d42ef7822e0d5104127dc05d6dbefde69e3ab2cec7c867c6e2c49',
@@ -638,23 +582,156 @@ const VECTORS_RFC8032_PH = [
'4f8d0704a608c54a6b62d97beb511d13' +
'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];
should(`RFC8032ph/${i}`, () => {
should(`${i}`, () => {
deepStrictEqual(hex(ed448ph.getPublicKey(v.secretKey)), v.publicKey);
deepStrictEqual(hex(ed448ph.sign(v.message, v.secretKey, v.context)), v.signature);
deepStrictEqual(ed448ph.verify(v.signature, v.message, v.publicKey, v.context), true);
deepStrictEqual(
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('X448 base point', () => {
const { x, y } = ed448.Point.BASE;
const { P } = ed448.CURVE;
const invX = ed448.utils.invert(x * x, P); // x²
const u = ed448.utils.mod(y * y * invX, P); // (y²/x²)
deepStrictEqual(hex(numberToBytesLE(u, 56)), x448.Gu);
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);
});
}
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 { Fp } = ed448.CURVE;
// const invX = Fp.invert(x * x); // x²
const u = Fp.div(Fp.create(y * y), Fp.create(x * x)); // (y²/x²)
// const u = Fp.create(y * y * invX);
deepStrictEqual(numberToBytesLE(u, 56), x448.GuBytes);
});
});
});
// ESM is broken.

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

@@ -1,19 +1,27 @@
import { deepStrictEqual } from 'assert';
import { should } from 'micro-should';
import { describe, should } from 'micro-should';
import { bytesToHex } from '@noble/hashes/utils';
// Generic tests for all curves in package
import { sha256 } from '@noble/hashes/sha256';
import { sha512 } from '@noble/hashes/sha512';
import { secp256r1 } from '../lib/esm/p256.js';
import { secp384r1 } from '../lib/esm/p384.js';
import { secp521r1 } from '../lib/esm/p521.js';
import { secp256k1 } from '../lib/esm/secp256k1.js';
import { bls12_381 } from '../lib/esm/bls12-381.js';
import { stringToBytes, expand_message_xmd } from '../lib/esm/abstract/hash-to-curve.js';
import { shake128, shake256 } from '@noble/hashes/sha3';
import * as secp256r1 from '../esm/p256.js';
import * as secp384r1 from '../esm/p384.js';
import * as secp521r1 from '../esm/p521.js';
import * as ed25519 from '../esm/ed25519.js';
import * as ed448 from '../esm/ed448.js';
import * as secp256k1 from '../esm/secp256k1.js';
import { bls12_381 } from '../esm/bls12-381.js';
import { expand_message_xmd, expand_message_xof } from '../esm/abstract/hash-to-curve.js';
import { utf8ToBytes } from '../esm/abstract/utils.js';
// 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_256 } from './hash-to-curve/expand_message_xmd_SHA256_256.json' assert { type: 'json' };
import { default as xmd_sha512_38 } from './hash-to-curve/expand_message_xmd_SHA512_38.json' assert { type: 'json' };
// XOF
import { default as xof_shake128_36 } from './hash-to-curve/expand_message_xof_SHAKE128_36.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' };
// P256
import { default as p256_ro } from './hash-to-curve/P256_XMD:SHA-256_SSWU_RO_.json' assert { type: 'json' };
import { default as p256_nu } from './hash-to-curve/P256_XMD:SHA-256_SSWU_NU_.json' assert { type: 'json' };
@@ -40,23 +48,51 @@ import { default as ed448_ro } from './hash-to-curve/edwards448_XOF:SHAKE256_ELL
import { default as ed448_nu } from './hash-to-curve/edwards448_XOF:SHAKE256_ELL2_NU_.json' assert { type: 'json' };
function testExpandXMD(hash, vectors) {
describe(`${vectors.hash}/${vectors.DST.length}`, () => {
for (let i = 0; i < vectors.tests.length; i++) {
const t = vectors.tests[i];
should(`expand_message_xmd/${vectors.hash}/${vectors.DST.length}/${i}`, () => {
should(`${vectors.hash}/${vectors.DST.length}/${i}`, () => {
const p = expand_message_xmd(
stringToBytes(t.msg),
stringToBytes(vectors.DST),
t.len_in_bytes,
utf8ToBytes(t.msg),
utf8ToBytes(vectors.DST),
Number.parseInt(t.len_in_bytes),
hash
);
deepStrictEqual(bytesToHex(p), t.uniform_bytes);
});
}
});
}
testExpandXMD(sha256, xmd_sha256_38);
testExpandXMD(sha256, xmd_sha256_256);
testExpandXMD(sha512, xmd_sha512_38);
describe('expand_message_xmd', () => {
testExpandXMD(sha256, xmd_sha256_38);
testExpandXMD(sha256, xmd_sha256_256);
testExpandXMD(sha512, xmd_sha512_38);
});
function testExpandXOF(hash, vectors) {
describe(`${vectors.hash}/${vectors.DST.length}`, () => {
for (let i = 0; i < vectors.tests.length; i++) {
const t = vectors.tests[i];
should(`${i}`, () => {
const p = expand_message_xof(
utf8ToBytes(t.msg),
utf8ToBytes(vectors.DST),
Number.parseInt(t.len_in_bytes),
vectors.k,
hash
);
deepStrictEqual(bytesToHex(p), t.uniform_bytes);
});
}
});
}
describe('expand_message_xof', () => {
testExpandXOF(shake128, xof_shake128_36);
testExpandXOF(shake128, xof_shake128_256);
testExpandXOF(shake256, xof_shake256_36);
});
function stringToFp(s) {
// bls-G2 support
@@ -68,37 +104,44 @@ function stringToFp(s) {
}
function testCurve(curve, ro, nu) {
describe(`${ro.curve}/${ro.ciphersuite}`, () => {
for (let i = 0; i < ro.vectors.length; i++) {
const t = ro.vectors[i];
should(`${ro.curve}/${ro.ciphersuite}(${i})`, () => {
const p = curve.Point.hashToCurve(stringToBytes(t.msg), {
should(`(${i})`, () => {
const p = curve
.hashToCurve(utf8ToBytes(t.msg), {
DST: ro.dst,
});
})
.toAffine();
deepStrictEqual(p.x, stringToFp(t.P.x), 'Px');
deepStrictEqual(p.y, stringToFp(t.P.y), 'Py');
});
}
});
describe(`${nu.curve}/${nu.ciphersuite}`, () => {
for (let i = 0; i < nu.vectors.length; i++) {
const t = nu.vectors[i];
should(`${nu.curve}/${nu.ciphersuite}(${i})`, () => {
const p = curve.Point.encodeToCurve(stringToBytes(t.msg), {
should(`(${i})`, () => {
const p = curve
.encodeToCurve(utf8ToBytes(t.msg), {
DST: nu.dst,
});
})
.toAffine();
deepStrictEqual(p.x, stringToFp(t.P.x), 'Px');
deepStrictEqual(p.y, stringToFp(t.P.y), 'Py');
});
}
});
}
testCurve(secp256r1, p256_ro, p256_nu);
testCurve(secp384r1, p384_ro, p384_nu);
testCurve(secp521r1, p521_ro, p521_nu);
// TODO: remove same tests from bls12
testCurve(bls12_381.G1, g1_ro, g1_nu);
testCurve(bls12_381.G2, g2_ro, g2_nu);
testCurve(secp256k1, secp256k1_ro, secp256k1_nu);
//testCurve(ed25519, ed25519_ro, ed25519_nu);
//testCurve(ed448, ed448_ro, ed448_nu);
testCurve(ed25519, ed25519_ro, ed25519_nu);
testCurve(ed448, ed448_ro, ed448_nu);
// ESM is broken.
import url from 'url';

View File

@@ -5,10 +5,12 @@ import './basic.test.js';
import './nist.test.js';
import './ed448.test.js';
import './ed25519.test.js';
import './ed25519-addons.test.js';
import './secp256k1.test.js';
import './stark/stark.test.js';
import './secp256k1-schnorr.test.js';
import './jubjub.test.js';
import './bls12-381.test.js';
import './hash-to-curve.test.js';
import './poseidon.test.js';
import './bls12-381.test.js';
should.run();

View File

@@ -1,15 +1,15 @@
import { jubjub, findGroupHash } from '../lib/esm/jubjub.js';
import { should } from 'micro-should';
import { jubjub, findGroupHash } from '../esm/jubjub.js';
import { describe, should } from 'micro-should';
import { deepStrictEqual, throws } from 'assert';
import { hexToBytes, bytesToHex } from '@noble/hashes/utils';
const Point = jubjub.ExtendedPoint;
const G_SPEND = new jubjub.ExtendedPoint(
const G_SPEND = new Point(
0x055f1f24f0f0512287e51c3c5a0a6903fc0baf8711de9eafd7c0e66f69d8d2dbn,
0x566178b2505fdd52132a5007d80a04652842e78ffb376897588f406278214ed7n,
0x0141fafa1f11088a3b2007c14d652375888f3b37838ba6bdffae096741ceddfen,
0x12eada93c0b7d595f5f04f5ebfb4b7d033ef2884136475cab5e41ce17db5be9cn
);
const G_PROOF = new jubjub.ExtendedPoint(
const G_PROOF = new Point(
0x0174d54ce9fad258a2f8a86a1deabf15c7a2b51106b0fbcd9d29020f78936f71n,
0x16871d6d877dcd222e4ec3bccb3f37cb1865a2d37dd3a5dcbc032a69b62b4445n,
0x57a3cd31e496d82bd4aa78bd5ecd751cfb76d54a5d3f4560866379f9fc11c9b3n,
@@ -18,10 +18,11 @@ const G_PROOF = new jubjub.ExtendedPoint(
const getXY = (p) => ({ x: p.x, y: p.y });
should('toHex/fromHex', () => {
describe('jubjub', () => {
should('toHex/fromHex', () => {
// More than field
throws(() =>
jubjub.Point.fromHex(
Point.fromHex(
new Uint8Array([
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
@@ -30,14 +31,14 @@ should('toHex/fromHex', () => {
);
// Multiplicative generator (sqrt == null), not on curve.
throws(() =>
jubjub.Point.fromHex(
Point.fromHex(
new Uint8Array([
7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0,
7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0,
])
)
);
const tmp = jubjub.Point.fromHex(
const tmp = Point.fromHex(
new Uint8Array([
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0,
@@ -46,25 +47,32 @@ should('toHex/fromHex', () => {
deepStrictEqual(tmp.x, 0x8d51ccce760304d0ec030002760300000001000000000000n);
deepStrictEqual(tmp.y, 0n);
const S = G_SPEND.toAffine().toRawBytes();
const S2 = G_SPEND.double().toAffine().toRawBytes();
const P = G_PROOF.toAffine().toRawBytes();
const P2 = G_PROOF.double().toAffine().toRawBytes();
const S_exp = jubjub.Point.fromHex(S);
const S2_exp = jubjub.Point.fromHex(S2);
const P_exp = jubjub.Point.fromHex(P);
const P2_exp = jubjub.Point.fromHex(P2);
const S = G_SPEND.toRawBytes();
const S2 = G_SPEND.double().toRawBytes();
const P = G_PROOF.toRawBytes();
const P2 = G_PROOF.double().toRawBytes();
const S_exp = Point.fromHex(S);
const S2_exp = Point.fromHex(S2);
const P_exp = Point.fromHex(P);
const P2_exp = Point.fromHex(P2);
deepStrictEqual(getXY(G_SPEND.toAffine()), getXY(S_exp));
deepStrictEqual(getXY(G_SPEND.double().toAffine()), getXY(S2_exp));
deepStrictEqual(getXY(G_PROOF.toAffine()), getXY(P_exp));
deepStrictEqual(getXY(G_PROOF.double().toAffine()), getXY(P2_exp));
});
});
should('Find generators', () => {
const spend = findGroupHash(new Uint8Array(), new Uint8Array([90, 99, 97, 115, 104, 95, 71, 95]));
const proof = findGroupHash(new Uint8Array(), new Uint8Array([90, 99, 97, 115, 104, 95, 72, 95]));
should('Find generators', () => {
const spend = findGroupHash(
new Uint8Array(),
new Uint8Array([90, 99, 97, 115, 104, 95, 71, 95])
);
const proof = findGroupHash(
new Uint8Array(),
new Uint8Array([90, 99, 97, 115, 104, 95, 72, 95])
);
deepStrictEqual(getXY(spend.toAffine()), getXY(G_SPEND.toAffine()));
deepStrictEqual(getXY(proof.toAffine()), getXY(G_PROOF.toAffine()));
});
});
// ESM is broken.

View File

@@ -1,29 +1,87 @@
import { deepStrictEqual, throws } from 'assert';
import { should } from 'micro-should';
import { secp192r1, P192 } from '../lib/esm/p192.js';
import { secp224r1, P224 } from '../lib/esm/p224.js';
import { secp256r1, P256 } from '../lib/esm/p256.js';
import { secp384r1, P384 } from '../lib/esm/p384.js';
import { secp521r1, P521 } from '../lib/esm/p521.js';
import { secp256k1 } from '../lib/esm/secp256k1.js';
import { hexToBytes, bytesToHex } from '../lib/esm/abstract/utils.js';
import { describe, should } from 'micro-should';
import { secp192r1, secp224r1, p192, p224 } from './_more-curves.helpers.js';
import { DER } from '../esm/abstract/weierstrass.js';
import { secp256r1, p256 } from '../esm/p256.js';
import { secp384r1, p384 } from '../esm/p384.js';
import { secp521r1, p521 } from '../esm/p521.js';
import { secp256k1 } from '../esm/secp256k1.js';
import { hexToBytes, bytesToHex } from '../esm/abstract/utils.js';
import { default as ecdsa } from './wycheproof/ecdsa_test.json' assert { type: 'json' };
import { default as ecdh } from './wycheproof/ecdh_test.json' assert { type: 'json' };
import { default as rfc6979 } from './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_secp256r1_test } from './wycheproof/ecdh_secp256r1_test.json' assert { type: 'json' };
import { default as ecdh_secp256k1_test } from './wycheproof/ecdh_secp256k1_test.json' assert { type: 'json' };
import { default as ecdh_secp384r1_test } from './wycheproof/ecdh_secp384r1_test.json' assert { type: 'json' };
import { default as ecdh_secp521r1_test } from './wycheproof/ecdh_secp521r1_test.json' assert { type: 'json' };
// Tests with custom hashes
import { default as secp224r1_sha224_test } from './wycheproof/ecdsa_secp224r1_sha224_test.json' assert { type: 'json' };
import { default as secp224r1_sha256_test } from './wycheproof/ecdsa_secp224r1_sha256_test.json' assert { type: 'json' };
import { default as secp224r1_sha3_224_test } from './wycheproof/ecdsa_secp224r1_sha3_224_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_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_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_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_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_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_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_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_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, shake128, shake256 } from '@noble/hashes/sha3';
import { sha512, sha384 } from '@noble/hashes/sha512';
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;
// prettier-ignore
const NIST = {
secp192r1, P192,
secp224r1, P224,
secp256r1, P256,
secp384r1, P384,
secp521r1, P521,
secp192r1, P192: p192,
secp224r1, P224: p224,
secp256r1, P256: p256,
secp384r1, P384: p384,
secp521r1, P521: p521,
secp256k1,
};
should('Curve Fields', () => {
describe('NIST curves', () => {});
should('fields', () => {
const vectors = {
secp192r1: 0xfffffffffffffffffffffffffffffffeffffffffffffffffn,
secp224r1: 0xffffffffffffffffffffffffffffffff000000000000000000000001n,
@@ -37,59 +95,18 @@ should('Curve Fields', () => {
for (const n in vectors) deepStrictEqual(NIST[n].CURVE.Fp.ORDER, vectors[n]);
});
should('wychenproof ECDSA vectors', () => {
for (const group of ecdsa.testGroups) {
// Tested in secp256k1.test.js
if (group.key.curve === 'secp256k1') continue;
let CURVE = NIST[group.key.curve];
if (!CURVE) continue;
if (group.key.curve === 'secp224r1' && group.sha !== 'SHA-224') {
if (group.sha === 'SHA-256') CURVE = CURVE.create(sha256);
}
const pubKey = CURVE.Point.fromHex(group.key.uncompressed);
deepStrictEqual(pubKey.x, BigInt(`0x${group.key.wx}`));
deepStrictEqual(pubKey.y, BigInt(`0x${group.key.wy}`));
for (const test of group.tests) {
if (['Hash weaker than DL-group'].includes(test.comment)) {
continue;
}
const m = CURVE.CURVE.hash(hexToBytes(test.msg));
if (test.result === 'valid' || test.result === 'acceptable') {
try {
CURVE.Signature.fromDER(test.sig);
} catch (e) {
// Some test has invalid signature which we don't accept
if (e.message.includes('Invalid signature: incorrect length')) continue;
throw e;
}
const verified = CURVE.verify(test.sig, m, pubKey);
deepStrictEqual(verified, true, 'valid');
} else if (test.result === 'invalid') {
let failed = false;
try {
failed = !CURVE.verify(test.sig, m, pubKey);
} catch (error) {
failed = true;
}
deepStrictEqual(failed, true, 'invalid');
} else throw new Error('unknown test result');
}
}
});
should('wychenproof ECDH vectors', () => {
describe('wycheproof ECDH', () => {
for (const group of ecdh.testGroups) {
// // Tested in secp256k1.test.js
// if (group.key.curve === 'secp256k1') continue;
// We don't have SHA-224
const CURVE = NIST[group.curve];
if (!CURVE) continue;
should(group.curve, () => {
for (const test of group.tests) {
if (test.result === 'valid' || test.result === 'acceptable') {
try {
const pub = CURVE.Point.fromHex(test.public);
const pub = CURVE.ProjectivePoint.fromHex(test.public);
} catch (e) {
if (e.message.includes('Point.fromHex: received invalid point.')) continue;
// Our strict validation filter doesn't let weird-length DER vectors
if (e.message.startsWith('Point of length')) continue;
throw e;
}
const shared = CURVE.getSharedSecret(test.private, test.public);
@@ -104,52 +121,47 @@ should('wychenproof ECDH vectors', () => {
deepStrictEqual(failed, true, 'invalid');
} else throw new Error('unknown test result');
}
});
}
});
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_secp256k1_test } from './wycheproof/ecdh_secp256k1_test.json' assert { type: 'json' };
import { default as ecdh_secp384r1_test } from './wycheproof/ecdh_secp384r1_test.json' assert { type: 'json' };
import { default as ecdh_secp521r1_test } from './wycheproof/ecdh_secp521r1_test.json' assert { type: 'json' };
// More per curve tests
const WYCHEPROOF_ECDH = {
P224: {
curve: P224,
// More per curve tests
const WYCHEPROOF_ECDH = {
p224: {
curve: p224,
tests: [ecdh_secp224r1_test],
},
P256: {
curve: P256,
p256: {
curve: p256,
tests: [ecdh_secp256r1_test],
},
secp256k1: {
curve: secp256k1,
tests: [ecdh_secp256k1_test],
},
P384: {
curve: P384,
p384: {
curve: p384,
tests: [ecdh_secp384r1_test],
},
P521: {
curve: P521,
p521: {
curve: p521,
tests: [ecdh_secp521r1_test],
},
};
};
for (const name in WYCHEPROOF_ECDH) {
for (const name in WYCHEPROOF_ECDH) {
const { curve, tests } = WYCHEPROOF_ECDH[name];
for (let i = 0; i < tests.length; i++) {
const test = tests[i];
for (let j = 0; j < test.testGroups.length; j++) {
const group = test.testGroups[j];
should(`Wycheproof/ECDH ${name} (${i}/${j})`, () => {
should(`additional ${name} (${i}/${j})`, () => {
for (const test of group.tests) {
if (test.result === 'valid' || test.result === 'acceptable') {
try {
const pub = curve.Point.fromHex(test.public);
const pub = curve.ProjectivePoint.fromHex(test.public);
} catch (e) {
if (e.message.includes('Point.fromHex: received invalid point.')) continue;
// 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);
@@ -167,41 +179,12 @@ for (const name in WYCHEPROOF_ECDH) {
});
}
}
}
// Tests with custom hashes
import { default as secp224r1_sha224_test } from './wycheproof/ecdsa_secp224r1_sha224_test.json' assert { type: 'json' };
import { default as secp224r1_sha256_test } from './wycheproof/ecdsa_secp224r1_sha256_test.json' assert { type: 'json' };
import { default as secp224r1_sha3_224_test } from './wycheproof/ecdsa_secp224r1_sha3_224_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_sha512_test } from './wycheproof/ecdsa_secp224r1_sha512_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_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 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_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 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_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 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 { sha3_224, sha3_256, sha3_384, sha3_512 } from '@noble/hashes/sha3';
import { sha512, sha384 } from '@noble/hashes/sha512';
import { sha224, sha256 } from '@noble/hashes/sha256';
}
});
const WYCHEPROOF_ECDSA = {
P224: {
curve: P224,
p224: {
curve: p224,
hashes: {
sha224: {
hash: sha224,
@@ -227,15 +210,18 @@ const WYCHEPROOF_ECDSA = {
hash: sha512,
tests: [secp224r1_sha512_test],
},
shake128: {
hash: shake128_224,
tests: [secp224r1_shake128_test],
},
},
},
secp256k1: {
curve: secp256k1,
hashes: {
// TODO: debug why fails, can be bug
sha256: {
hash: sha256,
tests: [secp256k1_sha256_test],
tests: [secp256k1_sha256_test, secp256k1_sha256_bitcoin_test],
},
sha3_256: {
hash: sha3_256,
@@ -249,10 +235,18 @@ const WYCHEPROOF_ECDSA = {
hash: sha512,
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: {
sha256: {
hash: sha256,
@@ -270,10 +264,14 @@ const WYCHEPROOF_ECDSA = {
hash: sha512,
tests: [secp256r1_sha512_test],
},
shake128: {
hash: shake128_256,
tests: [secp256r1_shake128_test],
},
},
P384: {
curve: P384,
},
p384: {
curve: p384,
hashes: {
sha384: {
hash: sha384,
@@ -291,10 +289,14 @@ const WYCHEPROOF_ECDSA = {
hash: sha512,
tests: [secp384r1_sha512_test],
},
shake256: {
hash: shake256_384,
tests: [secp384r1_shake256_test],
},
},
P521: {
curve: P521,
},
p521: {
curve: p521,
hashes: {
sha3_512: {
hash: sha3_512,
@@ -304,36 +306,42 @@ const WYCHEPROOF_ECDSA = {
hash: sha512,
tests: [secp521r1_sha512_test],
},
shake256: {
hash: shake256_512,
tests: [secp521r1_shake256_test],
},
},
},
};
function runWycheproof(name, CURVE, group, index) {
const pubKey = CURVE.Point.fromHex(group.key.uncompressed);
deepStrictEqual(pubKey.x, BigInt(`0x${group.key.wx}`));
deepStrictEqual(pubKey.y, BigInt(`0x${group.key.wy}`));
const key = group.publicKey;
const pubKey = CURVE.ProjectivePoint.fromHex(key.uncompressed);
deepStrictEqual(pubKey.x, BigInt(`0x${key.wx}`));
deepStrictEqual(pubKey.y, BigInt(`0x${key.wy}`));
const pubR = pubKey.toRawBytes();
for (const test of group.tests) {
const m = CURVE.CURVE.hash(hexToBytes(test.msg));
const { sig } = test;
if (test.result === 'valid' || test.result === 'acceptable') {
try {
CURVE.Signature.fromDER(test.sig);
CURVE.Signature.fromDER(sig);
} catch (e) {
// Some tests has invalid signature which we don't accept
if (e.message.includes('Invalid signature: incorrect length')) continue;
throw e;
}
const verified = CURVE.verify(test.sig, m, pubKey);
const verified = CURVE.verify(sig, m, pubR);
if (name === 'secp256k1') {
// lowS: true for secp256k1
deepStrictEqual(verified, !CURVE.Signature.fromDER(test.sig).hasHighS(), `${index}: valid`);
deepStrictEqual(verified, !CURVE.Signature.fromDER(sig).hasHighS(), `${index}: valid`);
} else {
deepStrictEqual(verified, true, `${index}: valid`);
}
} else if (test.result === 'invalid') {
let failed = false;
try {
failed = !CURVE.verify(test.sig, m, pubKey);
failed = !CURVE.verify(sig, m, pubR);
} catch (error) {
failed = true;
}
@@ -342,12 +350,60 @@ function runWycheproof(name, CURVE, group, index) {
}
}
for (const name in WYCHEPROOF_ECDSA) {
describe('wycheproof ECDSA', () => {
should('generic', () => {
for (const group of ecdsa.testGroups) {
// Tested in secp256k1.test.js
let CURVE = NIST[group.key.curve];
if (!CURVE) continue;
if (group.key.curve === 'secp224r1' && group.sha !== 'SHA-224') {
if (group.sha === 'SHA-256') CURVE = CURVE.create(sha256);
}
const pubKey = CURVE.ProjectivePoint.fromHex(group.key.uncompressed);
deepStrictEqual(pubKey.x, BigInt(`0x${group.key.wx}`));
deepStrictEqual(pubKey.y, BigInt(`0x${group.key.wy}`));
for (const test of group.tests) {
if (['Hash weaker than DL-group'].includes(test.comment)) {
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));
if (test.result === 'valid' || test.result === 'acceptable') {
try {
CURVE.Signature.fromDER(test.sig);
} catch (e) {
// Some test has invalid signature which we don't accept
if (e.message.includes('Invalid signature: incorrect length')) continue;
throw e;
}
const verified = CURVE.verify(test.sig, m, pubKey.toHex());
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') {
let failed = false;
try {
failed = !CURVE.verify(test.sig, m, pubKey.toHex());
} catch (error) {
failed = true;
}
deepStrictEqual(failed, true, 'invalid');
} else throw new Error('unknown test result');
}
}
});
for (const name in WYCHEPROOF_ECDSA) {
const { curve, hashes } = WYCHEPROOF_ECDSA[name];
describe(name, () => {
for (const hName in hashes) {
const { hash, tests } = hashes[hName];
const CURVE = curve.create(hash);
should(`Wycheproof/WYCHEPROOF_ECDSA ${name}/${hName}`, () => {
should(`${name}/${hName}`, () => {
for (let i = 0; i < tests.length; i++) {
const groups = tests[i].testGroups;
for (let j = 0; j < groups.length; j++) {
@@ -357,15 +413,18 @@ for (const name in WYCHEPROOF_ECDSA) {
}
});
}
}
});
}
});
const hexToBigint = (hex) => BigInt(`0x${hex}`);
should('RFC6979', () => {
describe('RFC6979', () => {
for (const v of rfc6979) {
should(v.curve, () => {
const curve = NIST[v.curve];
deepStrictEqual(curve.CURVE.n, hexToBigint(v.q));
const pubKey = curve.getPublicKey(v.private);
const pubPoint = curve.Point.fromHex(pubKey);
const pubPoint = curve.ProjectivePoint.fromHex(pubKey);
deepStrictEqual(pubPoint.x, hexToBigint(v.Ux));
deepStrictEqual(pubPoint.y, hexToBigint(v.Uy));
for (const c of v.cases) {
@@ -376,6 +435,44 @@ should('RFC6979', () => {
deepStrictEqual(curve.verify(sigObj.toDERRawBytes(), h, pubKey), true, 'verify(1)');
deepStrictEqual(curve.verify(sigObj, h, pubKey), true, 'verify(2)');
}
});
}
});
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));
}
});

377
test/poseidon.test.js Normal file
View File

@@ -0,0 +1,377 @@
import { deepStrictEqual, throws } from 'assert';
import { should, describe } from 'micro-should';
import * as poseidon from '../esm/abstract/poseidon.js';
import * as stark from './_poseidon.helpers.js';
import * as mod from '../esm/abstract/modular.js';
import { default as pvectors } from './vectors/poseidon.json' assert { type: 'json' };
const { st1, st2, st3, st4 } = pvectors;
describe('Stark', () => {
should('poseidonMdsMatrixUnsafe', () => {
const matrix = [
[
2778560475384578201077246683568670693743746494974613838537993780462451025202n,
1175299404131241652930097281601393692628174430208909163156444576599667748918n,
459930634481240293374476654621049426021644833445120509139335338093973616187n,
],
[
2699370377471722242958186781613316939129713429759631049128040020458992590651n,
1488831960940040807419416081499284128899207850625157044437836107358246188803n,
3405112981980800875534081635548548562399171531483475155039499736396630179833n,
],
[
1860070716810022053527433635909648527418980081585070357136946388030401399342n,
2606527819893847364468965441606872534271438365089422719512470850627617054272n,
2715867691630559973784374069384091521307896505826088878858115800121387149186n,
],
];
deepStrictEqual(stark._poseidonMDS(stark.Fp251, 'HadesMDS', 3, 0), matrix);
});
should('HadesPermutation', () => {
deepStrictEqual(
stark.poseidonSmall([
4379311784651118086770398084575492314150568148003994287303975907890254409956n,
5329163686893598957822497554130545759427567507701132391649270915797304266381n,
1081797873147645298856697595691862435558345225505029083672323747888463248125n,
]),
[
1342232677189718451682683203787286758407058155581807117466384919996430343159n,
380853961496438693334706417244065195303131974442781224856980145160981376662n,
1919212703304954644851339421413808305076993030243665926017858381407659820613n,
]
);
});
should('HadesPermutation (custom)', () => {
const h = stark.poseidonCreate({
Fp: stark.Fp251,
rate: 2,
capacity: 1,
roundsFull: 8,
roundsPartial: 83,
});
deepStrictEqual(
h([
4379311784651118086770398084575492314150568148003994287303975907890254409956n,
5329163686893598957822497554130545759427567507701132391649270915797304266381n,
1081797873147645298856697595691862435558345225505029083672323747888463248125n,
]),
[
2864461397224564530993577865807718592436235694918699912757414692654057505365n,
1576206983934669422583425346343473837630736957734769961428118554039862202613n,
1607006208879950753054674913136990521997740361932184292107790666308092455675n,
]
);
});
should('HadesPermutation (custom, Fp253)', () => {
const h = stark.poseidonCreate({
Fp: stark.Fp253,
rate: 2,
capacity: 1,
roundsFull: 8,
roundsPartial: 83,
});
deepStrictEqual(
h([
4379311784651118086770398084575492314150568148003994287303975907890254409956n,
5329163686893598957822497554130545759427567507701132391649270915797304266381n,
1081797873147645298856697595691862435558345225505029083672323747888463248125n,
]),
[
11142411210283675631592374649001218595612035205233832049083369488791454026844n,
98304838055259883374145304326851527594402230455144399354815642835291000581n,
8643534790068701259242695637167859384191499281344739826454631748110172472997n,
]
);
});
should('PoseidonHash', () => {
deepStrictEqual(
stark.poseidonHash(
4379311784651118086770398084575492314150568148003994287303975907890254409956n,
5329163686893598957822497554130545759427567507701132391649270915797304266381n
),
2457757238178986673695038558497063891521456354791980183317105434323761563347n
);
});
should('PoseidonHash (custom)', () => {
const h = stark.poseidonCreate({
Fp: stark.Fp251,
rate: 2,
capacity: 1,
roundsFull: 8,
roundsPartial: 83,
});
deepStrictEqual(
stark.poseidonHash(
4379311784651118086770398084575492314150568148003994287303975907890254409956n,
5329163686893598957822497554130545759427567507701132391649270915797304266381n,
h
),
654164301216498483748450956182386165976155551413834652546305861430119544536n
);
});
should('PoseidonHash (custom, Fp253)', () => {
const h = stark.poseidonCreate({
Fp: stark.Fp253,
rate: 2,
capacity: 1,
roundsFull: 8,
roundsPartial: 83,
});
deepStrictEqual(
stark.poseidonHash(
4379311784651118086770398084575492314150568148003994287303975907890254409956n,
5329163686893598957822497554130545759427567507701132391649270915797304266381n,
h
),
9557424461253897982213839283192966960594440725760392861778010931094267239786n
);
});
});
// Official vectors: https://extgit.iaik.tugraz.at/krypto/hadeshash/-/blob/master/code/test_vectors.txt
should('poseidonperm_x5_255_3', () => {
const Fp = mod.Field(
BigInt('0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001')
);
const mds = [
[
0x3d955d6c02fe4d7cb500e12f2b55eff668a7b4386bd27413766713c93f2acfcdn,
0x3798866f4e6058035dcf8addb2cf1771fac234bcc8fc05d6676e77e797f224bfn,
0x2c51456a7bf2467eac813649f3f25ea896eac27c5da020dae54a6e640278fda2n,
],
[
0x20088ca07bbcd7490a0218ebc0ecb31d0ea34840e2dc2d33a1a5adfecff83b43n,
0x1d04ba0915e7807c968ea4b1cb2d610c7f9a16b4033f02ebacbb948c86a988c3n,
0x5387ccd5729d7acbd09d96714d1d18bbd0eeaefb2ddee3d2ef573c9c7f953307n,
],
[
0x1e208f585a72558534281562cad89659b428ec61433293a8d7f0f0e38a6726acn,
0x0455ebf862f0b60f69698e97d36e8aafd4d107cae2b61be1858b23a3363642e0n,
0x569e2c206119e89455852059f707370e2c1fc9721f6c50991cedbbf782daef54n,
],
];
const t = 3;
const roundConstants = poseidon.splitConstants(st1.map(BigInt), t);
const poseidon_x5_255_3 = poseidon.poseidon({
Fp,
t,
roundsFull: 8,
roundsPartial: 57,
mds,
roundConstants,
});
deepStrictEqual(
poseidon_x5_255_3([
0x0000000000000000000000000000000000000000000000000000000000000000n,
0x0000000000000000000000000000000000000000000000000000000000000001n,
0x0000000000000000000000000000000000000000000000000000000000000002n,
]),
[
0x28ce19420fc246a05553ad1e8c98f5c9d67166be2c18e9e4cb4b4e317dd2a78an,
0x51f3e312c95343a896cfd8945ea82ba956c1118ce9b9859b6ea56637b4b1ddc4n,
0x3b2b69139b235626a0bfb56c9527ae66a7bf486ad8c11c14d1da0c69bbe0f79an,
]
);
});
should('poseidonperm_x5_255_5', () => {
const Fp = mod.Field(0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001n);
const t = 5;
const mds = [
[
0x354423b163d1078b0dd645be56316e34a9b98e52dcf9f469be44b108be46c107n,
0x44778737e8bc1154aca1cd92054a1e5b83808403705f7d54da88bbd1920e1053n,
0x5872eefb5ab6b2946556524168a2aebb69afd513a2fff91e50167b1f6e4055e0n,
0x43dff85b25129835819bc8c95819f1a34136f6114e900cd3656e1b9e0e13f86an,
0x07803d2ffe72940596803f244ac090a9cf2d3616546520bc360c7eed0b81cbf8n,
],
[
0x45d6bc4b818e2b9a53e0e2c0a08f70c34167fd8128e05ac800651ddfee0932d1n,
0x08317abbb9e5046b22dfb79e64c8184855107c1d95dddd2b63ca10dddea9ff1an,
0x1bb80eba77c5dcffafb55ccba4ae39ac8f94a054f2a0ee3006b362f709d5e470n,
0x038e75bdcf8be7fd3a1e844c4de7333531bbd5a8d2c3779627df88e7480e7c5cn,
0x2dd797a699e620ea6b31b91ba3fad4a82f40cffb3e8a30c0b7a546ff69a9002bn,
],
[
0x4b906f9ee339b196e958e3541b555b4b53e540a113b2f1cabba627be16eb5608n,
0x605f0c707b82ef287f46431f9241fe4acf0b7ddb151803cbcf1e7bbd27c3e974n,
0x100c514bf38f6ff10df1c83bb428397789cfff7bb0b1280f52343861e8c8737en,
0x2d40ce8af8a252f5611701c3d6b1e517161d0549ef27f443570c81fcdfe3706bn,
0x3e6418bdf0313f59afc5f40b4450e56881110ea9a0532e8092efb06a12a8b0f1n,
],
[
0x71788bf7f6c0cebae5627c5629d012d5fba52428d1f25cdaa0a7434e70e014d0n,
0x55cc73296f7e7d26d10b9339721d7983ca06145675255025ab00b34342557db7n,
0x0f043b29be2def73a6c6ec92168ea4b47bc9f434a5e6b5d48677670a7ca4d285n,
0x62ccc9cdfed859a610f103d74ea04dec0f6874a9b36f3b4e9b47fd73368d45b4n,
0x55fb349dd6200b34eaba53a67e74f47d08e473da139dc47e44df50a26423d2d1n,
],
[
0x45bfbe5ed2f4a01c13b15f20bba00ff577b1154a81b3f318a6aff86369a66735n,
0x6a008906685587af05dce9ad2c65ea1d42b1ec32609597bd00c01f58443329efn,
0x004feebd0dbdb9b71176a1d43c9eb495e16419382cdf7864e4bce7b37440cd58n,
0x09f080180ce23a5aef3a07e60b28ffeb2cf1771aefbc565c2a3059b39ed82f43n,
0x2f7126ddc54648ab6d02493dbe9907f29f4ef3967ad8cd609f0d9467e1694607n,
],
];
const roundConstants = poseidon.splitConstants(st2.map(BigInt), t);
const poseidon_x5_255_5 = poseidon.poseidon({
Fp,
t,
roundsFull: 8,
roundsPartial: 60,
mds,
roundConstants,
});
deepStrictEqual(
poseidon_x5_255_5([
0x0000000000000000000000000000000000000000000000000000000000000000n,
0x0000000000000000000000000000000000000000000000000000000000000001n,
0x0000000000000000000000000000000000000000000000000000000000000002n,
0x0000000000000000000000000000000000000000000000000000000000000003n,
0x0000000000000000000000000000000000000000000000000000000000000004n,
]),
[
0x2a918b9c9f9bd7bb509331c81e297b5707f6fc7393dcee1b13901a0b22202e18n,
0x65ebf8671739eeb11fb217f2d5c5bf4a0c3f210e3f3cd3b08b5db75675d797f7n,
0x2cc176fc26bc70737a696a9dfd1b636ce360ee76926d182390cdb7459cf585cen,
0x4dc4e29d283afd2a491fe6aef122b9a968e74eff05341f3cc23fda1781dcb566n,
0x03ff622da276830b9451b88b85e6184fd6ae15c8ab3ee25a5667be8592cce3b1n,
]
);
});
should('poseidonperm_x5_254_3', () => {
const Fp = mod.Field(0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001n);
const t = 3;
const mds = [
[
0x109b7f411ba0e4c9b2b70caf5c36a7b194be7c11ad24378bfedb68592ba8118bn,
0x16ed41e13bb9c0c66ae119424fddbcbc9314dc9fdbdeea55d6c64543dc4903e0n,
0x2b90bba00fca0589f617e7dcbfe82e0df706ab640ceb247b791a93b74e36736dn,
],
[
0x2969f27eed31a480b9c36c764379dbca2cc8fdd1415c3dded62940bcde0bd771n,
0x2e2419f9ec02ec394c9871c832963dc1b89d743c8c7b964029b2311687b1fe23n,
0x101071f0032379b697315876690f053d148d4e109f5fb065c8aacc55a0f89bfan,
],
[
0x143021ec686a3f330d5f9e654638065ce6cd79e28c5b3753326244ee65a1b1a7n,
0x176cc029695ad02582a70eff08a6fd99d057e12e58e7d7b6b16cdfabc8ee2911n,
0x19a3fc0a56702bf417ba7fee3802593fa644470307043f7773279cd71d25d5e0n,
],
];
const roundConstants = poseidon.splitConstants(st3.map(BigInt), t);
const poseidon_x5_254_3 = poseidon.poseidon({
Fp,
t,
roundsFull: 8,
roundsPartial: 57,
mds,
roundConstants,
});
deepStrictEqual(
poseidon_x5_254_3([
0x0000000000000000000000000000000000000000000000000000000000000000n,
0x0000000000000000000000000000000000000000000000000000000000000001n,
0x0000000000000000000000000000000000000000000000000000000000000002n,
]),
[
0x115cc0f5e7d690413df64c6b9662e9cf2a3617f2743245519e19607a4417189an,
0x0fca49b798923ab0239de1c9e7a4a9a2210312b6a2f616d18b5a87f9b628ae29n,
0x0e7ae82e40091e63cbd4f16a6d16310b3729d4b6e138fcf54110e2867045a30cn,
]
);
});
should('poseidonperm_x5_254_5', () => {
const Fp = mod.Field(0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001n);
const t = 5;
const mds = [
[
0x251e7fdf99591080080b0af133b9e4369f22e57ace3cd7f64fc6fdbcf38d7da1n,
0x25fb50b65acf4fb047cbd3b1c17d97c7fe26ea9ca238d6e348550486e91c7765n,
0x293d617d7da72102355f39ebf62f91b06deb5325f367a4556ea1e31ed5767833n,
0x104d0295ab00c85e960111ac25da474366599e575a9b7edf6145f14ba6d3c1c4n,
0x0aaa35e2c84baf117dea3e336cd96a39792b3813954fe9bf3ed5b90f2f69c977n,
],
[
0x2a70b9f1d4bbccdbc03e17c1d1dcdb02052903dc6609ea6969f661b2eb74c839n,
0x281154651c921e746315a9934f1b8a1bba9f92ad8ef4b979115b8e2e991ccd7an,
0x28c2be2f8264f95f0b53c732134efa338ccd8fdb9ee2b45fb86a894f7db36c37n,
0x21888041e6febd546d427c890b1883bb9b626d8cb4dc18dcc4ec8fa75e530a13n,
0x14ddb5fada0171db80195b9592d8cf2be810930e3ea4574a350d65e2cbff4941n,
],
[
0x2f69a7198e1fbcc7dea43265306a37ed55b91bff652ad69aa4fa8478970d401dn,
0x001c1edd62645b73ad931ab80e37bbb267ba312b34140e716d6a3747594d3052n,
0x15b98ce93e47bc64ce2f2c96c69663c439c40c603049466fa7f9a4b228bfc32bn,
0x12c7e2adfa524e5958f65be2fbac809fcba8458b28e44d9265051de33163cf9cn,
0x2efc2b90d688134849018222e7b8922eaf67ce79816ef468531ec2de53bbd167n,
],
[
0x0c3f050a6bf5af151981e55e3e1a29a13c3ffa4550bd2514f1afd6c5f721f830n,
0x0dec54e6dbf75205fa75ba7992bd34f08b2efe2ecd424a73eda7784320a1a36en,
0x1c482a25a729f5df20225815034b196098364a11f4d988fb7cc75cf32d8136fan,
0x2625ce48a7b39a4252732624e4ab94360812ac2fc9a14a5fb8b607ae9fd8514an,
0x07f017a7ebd56dd086f7cd4fd710c509ed7ef8e300b9a8bb9fb9f28af710251fn,
],
[
0x2a20e3a4a0e57d92f97c9d6186c6c3ea7c5e55c20146259be2f78c2ccc2e3595n,
0x1049f8210566b51faafb1e9a5d63c0ee701673aed820d9c4403b01feb727a549n,
0x02ecac687ef5b4b568002bd9d1b96b4bef357a69e3e86b5561b9299b82d69c8en,
0x2d3a1aea2e6d44466808f88c9ba903d3bdcb6b58ba40441ed4ebcf11bbe1e37bn,
0x14074bb14c982c81c9ad171e4f35fe49b39c4a7a72dbb6d9c98d803bfed65e64n,
],
];
const roundConstants = poseidon.splitConstants(st4.map(BigInt), t);
const poseidon_x5_254_5 = poseidon.poseidon({
Fp,
t,
roundsFull: 8,
roundsPartial: 60,
mds,
roundConstants,
});
deepStrictEqual(
poseidon_x5_254_5([
0x0000000000000000000000000000000000000000000000000000000000000000n,
0x0000000000000000000000000000000000000000000000000000000000000001n,
0x0000000000000000000000000000000000000000000000000000000000000002n,
0x0000000000000000000000000000000000000000000000000000000000000003n,
0x0000000000000000000000000000000000000000000000000000000000000004n,
]),
[
0x299c867db6c1fdd79dcefa40e4510b9837e60ebb1ce0663dbaa525df65250465n,
0x1148aaef609aa338b27dafd89bb98862d8bb2b429aceac47d86206154ffe053dn,
0x24febb87fed7462e23f6665ff9a0111f4044c38ee1672c1ac6b0637d34f24907n,
0x0eb08f6d809668a981c186beaf6110060707059576406b248e5d9cf6e78b3d3en,
0x07748bc6877c9b82c8b98666ee9d0626ec7f5be4205f79ee8528ef1c4a376fc7n,
]
);
});
// Startadperm is unsupported, since it is non prime field
// ESM is broken.
import url from 'url';
if (import.meta.url === url.pathToFileURL(process.argv[1]).href) {
should.run();
}

View File

@@ -0,0 +1,34 @@
import { deepStrictEqual, throws } from 'assert';
import { readFileSync } from 'fs';
import { should, describe } from 'micro-should';
import { bytesToHex as hex } from '@noble/hashes/utils';
import { schnorr } from '../esm/secp256k1.js';
const schCsv = readFileSync('./test/vectors/secp256k1/schnorr.csv', 'utf-8');
describe('schnorr.sign()', () => {
// index,secret key,public key,aux_rand,message,signature,verification result,comment
const vectors = schCsv
.split('\n')
.map((line) => line.split(','))
.slice(1, -1);
for (let vec of vectors) {
const [index, sec, pub, rnd, msg, expSig, passes, comment] = vec;
should(`${comment || 'vector ' + index}`, () => {
if (sec) {
deepStrictEqual(hex(schnorr.getPublicKey(sec)), pub.toLowerCase());
const sig = schnorr.sign(msg, sec, rnd);
deepStrictEqual(hex(sig), expSig.toLowerCase());
deepStrictEqual(schnorr.verify(sig, msg, pub), true);
} else {
const passed = schnorr.verify(expSig, msg, pub);
deepStrictEqual(passed, passes === 'TRUE');
}
});
}
});
// ESM is broken.
import url from 'url';
if (import.meta.url === url.pathToFileURL(process.argv[1]).href) {
should.run();
}

11
test/secp256k1.helpers.js Normal file
View File

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

View File

@@ -1,22 +1,26 @@
import * as fc from 'fast-check';
import { secp256k1, schnorr } from '../lib/esm/secp256k1.js';
import { readFileSync } from 'fs';
import { default as ecdsa } from './vectors/ecdsa.json' assert { type: 'json' };
import { default as ecdh } from './vectors/ecdh.json' assert { type: 'json' };
import { default as privates } from './vectors/privates.json' assert { type: 'json' };
import { default as points } from './vectors/points.json' assert { type: 'json' };
import { default as wp } from './vectors/wychenproof.json' assert { type: 'json' };
import { should } from 'micro-should';
import { hexToBytes, bytesToHex as hex } from '@noble/hashes/utils';
import { deepStrictEqual, throws } from 'assert';
import { hexToBytes, bytesToHex } from '@noble/hashes/utils';
import * as fc from 'fast-check';
import { readFileSync } from 'fs';
import { should, describe } from 'micro-should';
// prettier-ignore
import {
secp, sigFromDER, sigToDER, selectHash, normVerifySig, mod, bytesToNumberBE, numberToBytesBE
} from './secp256k1.helpers.js';
const hex = bytesToHex;
const secp = secp256k1;
const privatesTxt = readFileSync('./test/vectors/privates-2.txt', 'utf-8');
const schCsv = readFileSync('./test/vectors/schnorr.csv', 'utf-8');
import { default as ecdsa } from './vectors/secp256k1/ecdsa.json' assert { type: 'json' };
import { default as ecdh } from './wycheproof/ecdh_secp256k1_test.json' assert { type: 'json' };
import { default as privates } from './vectors/secp256k1/privates.json' assert { type: 'json' };
import { default as points } from './vectors/secp256k1/points.json' assert { type: 'json' };
import { default as wp } from './wycheproof/ecdsa_secp256k1_sha256_test.json' assert { type: 'json' };
// Any changes to the file will need to be aware of the fact
// the file is shared between noble-curves and noble-secp256k1.
const Point = secp.ProjectivePoint;
const privatesTxt = readFileSync('./test/vectors/secp256k1/privates-2.txt', 'utf-8');
const FC_BIGINT = fc.bigInt(1n + 1n, secp.CURVE.n - 1n);
const P = secp.CURVE.Fp.ORDER;
// prettier-ignore
const INVALID_ITEMS = ['deadbeef', Math.pow(2, 53), [1], 'xyzxyzxyxyzxyzxyxyzxyzxyxyzxyzxyxyzxyzxyxyzxyzxyxyzxyzxyxyzxyzxy', secp.CURVE.n + 2n];
@@ -24,108 +28,110 @@ const toBEHex = (n) => n.toString(16).padStart(64, '0');
function hexToNumber(hex) {
if (typeof hex !== 'string') {
throw new TypeError('hexToNumber: expected string, got ' + typeof hex);
throw new Error('hexToNumber: expected string, got ' + typeof hex);
}
// Big Endian
return BigInt(`0x${hex}`);
}
should('secp256k1.getPublicKey()', () => {
describe('secp256k1', () => {
should('getPublicKey()', () => {
const data = privatesTxt
.split('\n')
.filter((line) => line)
.map((line) => line.split(':'));
for (let [priv, x, y] of data) {
const point = secp.Point.fromPrivateKey(BigInt(priv));
const point = Point.fromPrivateKey(BigInt(priv));
deepStrictEqual(toBEHex(point.x), x);
deepStrictEqual(toBEHex(point.y), y);
const point2 = secp.Point.fromHex(secp.getPublicKey(toBEHex(BigInt(priv))));
const point2 = Point.fromHex(secp.getPublicKey(toBEHex(BigInt(priv))));
deepStrictEqual(toBEHex(point2.x), x);
deepStrictEqual(toBEHex(point2.y), y);
const point3 = secp.Point.fromHex(secp.getPublicKey(hexToBytes(toBEHex(BigInt(priv)))));
const point3 = Point.fromHex(secp.getPublicKey(hexToBytes(toBEHex(BigInt(priv)))));
deepStrictEqual(toBEHex(point3.x), x);
deepStrictEqual(toBEHex(point3.y), y);
}
});
should('secp256k1.getPublicKey() rejects invalid keys', () => {
// for (const item of INVALID_ITEMS) {
// throws(() => secp.getPublicKey(item));
// }
});
should('secp256k1.precompute', () => {
});
should('getPublicKey() rejects invalid keys', () => {
for (const item of INVALID_ITEMS) {
throws(() => secp.getPublicKey(item));
}
});
should('precompute', () => {
secp.utils.precompute(4);
const data = privatesTxt
.split('\n')
.filter((line) => line)
.map((line) => line.split(':'));
for (let [priv, x, y] of data) {
const point = secp.Point.fromPrivateKey(BigInt(priv));
const point = Point.fromPrivateKey(BigInt(priv));
deepStrictEqual(toBEHex(point.x), x);
deepStrictEqual(toBEHex(point.y), y);
const point2 = secp.Point.fromHex(secp.getPublicKey(toBEHex(BigInt(priv))));
const point2 = Point.fromHex(secp.getPublicKey(toBEHex(BigInt(priv))));
deepStrictEqual(toBEHex(point2.x), x);
deepStrictEqual(toBEHex(point2.y), y);
const point3 = secp.Point.fromHex(secp.getPublicKey(hexToBytes(toBEHex(BigInt(priv)))));
const point3 = Point.fromHex(secp.getPublicKey(hexToBytes(toBEHex(BigInt(priv)))));
deepStrictEqual(toBEHex(point3.x), x);
deepStrictEqual(toBEHex(point3.y), y);
}
});
});
should('secp256k1.Point.isValidPoint()', () => {
describe('Point', () => {
should('fromHex() assertValidity', () => {
for (const vector of points.valid.isPoint) {
const { P, expected } = vector;
if (expected) {
secp.Point.fromHex(P);
Point.fromHex(P);
} else {
throws(() => secp.Point.fromHex(P));
throws(() => Point.fromHex(P));
}
}
});
});
should('secp256k1.Point.fromPrivateKey()', () => {
should('.fromPrivateKey()', () => {
for (const vector of points.valid.pointFromScalar) {
const { d, expected } = vector;
let p = secp.Point.fromPrivateKey(d);
let p = Point.fromPrivateKey(d);
deepStrictEqual(p.toHex(true), expected);
}
});
});
should('secp256k1.Point#toHex(compressed)', () => {
should('#toHex(compressed)', () => {
for (const vector of points.valid.pointCompress) {
const { P, compress, expected } = vector;
let p = secp.Point.fromHex(P);
let p = Point.fromHex(P);
deepStrictEqual(p.toHex(compress), expected);
}
});
});
should('secp256k1.Point#toHex() roundtrip (failed case)', () => {
should('#toHex() roundtrip (failed case)', () => {
const point1 =
secp.Point.fromPrivateKey(
Point.fromPrivateKey(
88572218780422190464634044548753414301110513745532121983949500266768436236425n
);
// const hex = point1.toHex(true);
// deepStrictEqual(secp.Point.fromHex(hex).toHex(true), hex);
});
// deepStrictEqual(Point.fromHex(hex).toHex(true), hex);
});
should('secp256k1.Point#toHex() roundtrip', () => {
should('#toHex() roundtrip', () => {
fc.assert(
fc.property(FC_BIGINT, (x) => {
const point1 = secp.Point.fromPrivateKey(x);
const point1 = Point.fromPrivateKey(x);
const hex = point1.toHex(true);
deepStrictEqual(secp.Point.fromHex(hex).toHex(true), hex);
deepStrictEqual(Point.fromHex(hex).toHex(true), hex);
})
);
});
});
should('secp256k1.Point#add(other)', () => {
should('#add(other)', () => {
for (const vector of points.valid.pointAdd) {
const { P, Q, expected } = vector;
let p = secp.Point.fromHex(P);
let q = secp.Point.fromHex(Q);
let p = Point.fromHex(P);
let q = Point.fromHex(Q);
if (expected) {
deepStrictEqual(p.add(q).toHex(true), expected);
} else {
@@ -134,14 +140,14 @@ should('secp256k1.Point#add(other)', () => {
}
}
}
});
});
should('secp256k1.Point#multiply(privateKey)', () => {
should('#multiply(privateKey)', () => {
for (const vector of points.valid.pointMultiply) {
const { P, d, expected } = vector;
const p = secp.Point.fromHex(P);
const p = Point.fromHex(P);
if (expected) {
deepStrictEqual(p.multiply(hexToNumber(d)).toHex(true), expected);
deepStrictEqual(p.multiply(hexToNumber(d)).toHex(true), expected, P);
} else {
throws(() => {
p.multiply(hexToNumber(d)).toHex(true);
@@ -153,47 +159,50 @@ should('secp256k1.Point#multiply(privateKey)', () => {
const { P, d } = vector;
if (hexToNumber(d) < secp.CURVE.n) {
throws(() => {
const p = secp.Point.fromHex(P);
const p = Point.fromHex(P);
p.multiply(hexToNumber(d)).toHex(true);
});
}
}
for (const num of [0n, 0, -1n, -1, 1.1]) {
throws(() => secp.Point.BASE.multiply(num));
throws(() => Point.BASE.multiply(num));
}
});
});
});
// multiply() should equal multiplyUnsafe()
// should('ProjectivePoint#multiplyUnsafe', () => {
// const p0 = new secp.ProjectivePoint(
// 55066263022277343669578718895168534326250603453777594175500187360389116729240n,
// 32670510020758816978083085130507043184471273380659243275938904335757337482424n,
// 1n
// );
// const z = 106011723082030650010038151861333186846790370053628296836951575624442507889495n;
// console.log(p0.multiply(z));
// console.log(secp.ProjectivePoint.normalizeZ([p0.multiplyUnsafe(z)])[0])
// });
should('secp256k1.Signature.fromCompactHex() roundtrip', () => {
// multiply() should equal multiplyUnsafe()
// should('ProjectivePoint#multiplyUnsafe', () => {
// const p0 = new secp.ProjectivePoint(
// 55066263022277343669578718895168534326250603453777594175500187360389116729240n,
// 32670510020758816978083085130507043184471273380659243275938904335757337482424n,
// 1n
// );
// const z = 106011723082030650010038151861333186846790370053628296836951575624442507889495n;
// console.log(p0.multiply(z));
// console.log(secp.ProjectivePoint.normalizeZ([p0.multiplyUnsafe(z)])[0])
// });
describe('Signature', () => {
should('.fromCompactHex() roundtrip', () => {
fc.assert(
fc.property(FC_BIGINT, FC_BIGINT, (r, s) => {
const sig = new secp.Signature(r, s);
deepStrictEqual(secp.Signature.fromCompact(sig.toCompactHex()), sig);
})
);
});
});
should('secp256k1.Signature.fromDERHex() roundtrip', () => {
should('.fromDERHex() roundtrip', () => {
fc.assert(
fc.property(FC_BIGINT, FC_BIGINT, (r, s) => {
const sig = new secp.Signature(r, s);
deepStrictEqual(secp.Signature.fromDER(sig.toDERHex()), sig);
deepStrictEqual(sigFromDER(sigToDER(sig)), sig);
})
);
});
});
});
should('secp256k1.sign()/should create deterministic signatures with RFC 6979', () => {
describe('sign()', () => {
should('create deterministic signatures with RFC 6979', () => {
for (const vector of ecdsa.valid) {
let usig = secp.sign(vector.m, vector.d);
let sig = usig.toCompactHex();
@@ -201,20 +210,20 @@ should('secp256k1.sign()/should create deterministic signatures with RFC 6979',
deepStrictEqual(sig.slice(0, 64), vsig.slice(0, 64));
deepStrictEqual(sig.slice(64, 128), vsig.slice(64, 128));
}
});
});
should('secp256k1.sign()/should not create invalid deterministic signatures with RFC 6979', () => {
should('not create invalid deterministic signatures with RFC 6979', () => {
for (const vector of ecdsa.invalid.sign) {
throws(() => secp.sign(vector.m, vector.d));
}
});
});
should('secp256k1.sign()/edge cases', () => {
should('edge cases', () => {
throws(() => secp.sign());
throws(() => secp.sign(''));
});
});
should('secp256k1.sign()/should create correct DER encoding against libsecp256k1', () => {
should('create correct DER encoding against libsecp256k1', () => {
const CASES = [
[
'd1a9dc8ed4e46a6a3e5e594615ca351d7d7ef44df1e4c94c1802f3592183794b',
@@ -229,15 +238,17 @@ should('secp256k1.sign()/should create correct DER encoding against libsecp256k1
'3045022100d18990bba7832bb283e3ecf8700b67beb39acc73f4200ed1c331247c46edccc602202e5c8bbfe47ae159512c583b30a3fa86575cddc62527a03de7756517ae4c6c73',
],
];
const privKey = hexToBytes('0101010101010101010101010101010101010101010101010101010101010101');
const privKey = hexToBytes(
'0101010101010101010101010101010101010101010101010101010101010101'
);
for (const [msg, exp] of CASES) {
const res = secp.sign(msg, privKey, { extraEntropy: undefined });
deepStrictEqual(res.toDERHex(), exp);
const rs = secp.Signature.fromDER(res.toDERHex()).toCompactHex();
deepStrictEqual(secp.Signature.fromCompact(rs).toDERHex(), exp);
deepStrictEqual(sigToDER(res), exp);
const rs = sigFromDER(sigToDER(res)).toCompactHex();
deepStrictEqual(sigToDER(secp.Signature.fromCompact(rs)), exp);
}
});
should('secp256k1.sign()/sign ecdsa extraData', () => {
});
should('handle {extraData} option', () => {
const ent1 = '0000000000000000000000000000000000000000000000000000000000000000';
const ent2 = '0000000000000000000000000000000000000000000000000000000000000001';
const ent3 = '6e723d3fd94ed5d2b6bdd4f123364b0f3ca52af829988a63f8afe91d29db1c33';
@@ -256,35 +267,37 @@ should('secp256k1.sign()/sign ecdsa extraData', () => {
deepStrictEqual(sign(ent4), e.extraEntropyN);
deepStrictEqual(sign(ent5), e.extraEntropyMax);
}
});
});
});
should('secp256k1.verify()/should verify signature', () => {
describe('verify()', () => {
should('verify signature', () => {
const MSG = '01'.repeat(32);
const PRIV_KEY = 0x2n;
const signature = secp.sign(MSG, PRIV_KEY);
const publicKey = secp.getPublicKey(PRIV_KEY);
deepStrictEqual(publicKey.length, 65);
deepStrictEqual(publicKey.length, 33);
deepStrictEqual(secp.verify(signature, MSG, publicKey), true);
});
should('secp256k1.verify()/should not verify signature with wrong public key', () => {
});
should(' not verify signature with wrong public key', () => {
const MSG = '01'.repeat(32);
const PRIV_KEY = 0x2n;
const WRONG_PRIV_KEY = 0x22n;
const PRIV_KEY = '01'.repeat(32);
const WRONG_PRIV_KEY = '02'.repeat(32);
const signature = secp.sign(MSG, PRIV_KEY);
const publicKey = secp.Point.fromPrivateKey(WRONG_PRIV_KEY).toHex();
deepStrictEqual(publicKey.length, 130);
const publicKey = Point.fromPrivateKey(WRONG_PRIV_KEY).toHex();
deepStrictEqual(publicKey.length, 66);
deepStrictEqual(secp.verify(signature, MSG, publicKey), false);
});
should('secp256k1.verify()/should not verify signature with wrong hash', () => {
});
should('not verify signature with wrong hash', () => {
const MSG = '01'.repeat(32);
const PRIV_KEY = 0x2n;
const WRONG_MSG = '11'.repeat(32);
const signature = secp.sign(MSG, PRIV_KEY);
const publicKey = secp.getPublicKey(PRIV_KEY);
deepStrictEqual(publicKey.length, 65);
deepStrictEqual(publicKey.length, 33);
deepStrictEqual(secp.verify(signature, WRONG_MSG, publicKey), false);
});
should('secp256k1.verify()/should verify random signatures', () =>
});
should('verify random signatures', () =>
fc.assert(
fc.property(FC_BIGINT, fc.hexaString({ minLength: 64, maxLength: 64 }), (privKey, msg) => {
const pub = secp.getPublicKey(privKey);
@@ -292,18 +305,19 @@ should('secp256k1.verify()/should verify random signatures', () =>
deepStrictEqual(secp.verify(sig, msg, pub), true);
})
)
);
should('secp256k1.verify()/should not verify signature with invalid r/s', () => {
);
should('not verify signature with invalid r/s', () => {
const msg = new Uint8Array([
0xbb, 0x5a, 0x52, 0xf4, 0x2f, 0x9c, 0x92, 0x61, 0xed, 0x43, 0x61, 0xf5, 0x94, 0x22, 0xa1, 0xe3,
0x00, 0x36, 0xe7, 0xc3, 0x2b, 0x27, 0x0c, 0x88, 0x07, 0xa4, 0x19, 0xfe, 0xca, 0x60, 0x50, 0x23,
0xbb, 0x5a, 0x52, 0xf4, 0x2f, 0x9c, 0x92, 0x61, 0xed, 0x43, 0x61, 0xf5, 0x94, 0x22, 0xa1,
0xe3, 0x00, 0x36, 0xe7, 0xc3, 0x2b, 0x27, 0x0c, 0x88, 0x07, 0xa4, 0x19, 0xfe, 0xca, 0x60,
0x50, 0x23,
]);
const x = 100260381870027870612475458630405506840396644859280795015145920502443964769584n;
const y = 41096923727651821103518389640356553930186852801619204169823347832429067794568n;
const r = 1n;
const s = 115792089237316195423570985008687907852837564279074904382605163141518162728904n;
const pub = new secp.Point(x, y);
const pub = new Point(x, y, 1n).toRawBytes();
const signature = new secp.Signature(2n, 2n);
signature.r = r;
signature.s = s;
@@ -311,98 +325,78 @@ should('secp256k1.verify()/should not verify signature with invalid r/s', () =>
const verified = secp.verify(signature, msg, pub);
// Verifies, but it shouldn't, because signature S > curve order
deepStrictEqual(verified, false);
});
should('secp256k1.verify()/should not verify msg = curve order', () => {
});
should('not verify msg = curve order', () => {
const msg = 'fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141';
const x = 55066263022277343669578718895168534326250603453777594175500187360389116729240n;
const y = 32670510020758816978083085130507043184471273380659243275938904335757337482424n;
const r = 104546003225722045112039007203142344920046999340768276760147352389092131869133n;
const s = 96900796730960181123786672629079577025401317267213807243199432755332205217369n;
const pub = new secp.Point(x, y);
const pub = new Point(x, y, 1n).toRawBytes();
const sig = new secp.Signature(r, s);
deepStrictEqual(secp.verify(sig, msg, pub), false);
});
should('secp256k1.verify()/should verify non-strict msg bb5a...', () => {
});
should('verify non-strict msg bb5a...', () => {
const msg = 'bb5a52f42f9c9261ed4361f59422a1e30036e7c32b270c8807a419feca605023';
const x = 3252872872578928810725465493269682203671229454553002637820453004368632726370n;
const y = 17482644437196207387910659778872952193236850502325156318830589868678978890912n;
const r = 432420386565659656852420866390673177323n;
const s = 115792089237316195423570985008687907852837564279074904382605163141518161494334n;
const pub = new secp.Point(x, y);
const pub = new Point(x, y, 1n).toRawBytes();
const sig = new secp.Signature(r, s);
deepStrictEqual(secp.verify(sig, msg, pub, { strict: false }), true);
});
should(
'secp256k1.verify()/should not verify invalid deterministic signatures with RFC 6979',
() => {
deepStrictEqual(secp.verify(sig, msg, pub, { lowS: false }), true);
});
should('not verify invalid deterministic signatures with RFC 6979', () => {
for (const vector of ecdsa.invalid.verify) {
const res = secp.verify(vector.signature, vector.m, vector.Q);
deepStrictEqual(res, false);
}
}
);
// index,secret key,public key,aux_rand,message,signature,verification result,comment
const vectors = schCsv
.split('\n')
.map((line) => line.split(','))
.slice(1, -1);
for (let vec of vectors) {
const [index, sec, pub, rnd, msg, expSig, passes, comment] = vec;
should(`sign with Schnorr scheme vector ${index}`, () => {
if (sec) {
deepStrictEqual(hex(schnorr.getPublicKey(sec)), pub.toLowerCase());
const sig = schnorr.sign(msg, sec, rnd);
deepStrictEqual(hex(sig), expSig.toLowerCase());
deepStrictEqual(schnorr.verify(sig, msg, pub), true);
} else {
const passed = schnorr.verify(expSig, msg, pub);
deepStrictEqual(passed, passes === 'TRUE');
}
});
}
should('secp256k1.recoverPublicKey()/should recover public key from recovery bit', () => {
});
describe('recoverPublicKey()', () => {
should('recover public key from recovery bit', () => {
const message = '00000000000000000000000000000000000000000000000000000000deadbeef';
const privateKey = 123456789n;
const publicKey = secp.Point.fromHex(secp.getPublicKey(privateKey)).toHex(false);
const publicKey = Point.fromHex(secp.getPublicKey(privateKey)).toHex(false);
const sig = secp.sign(message, privateKey);
const recoveredPubkey = sig.recoverPublicKey(message);
// const recoveredPubkey = secp.recoverPublicKey(message, signature, recovery);
deepStrictEqual(recoveredPubkey !== null, true);
deepStrictEqual(recoveredPubkey.toHex(), publicKey);
deepStrictEqual(recoveredPubkey.toHex(false), publicKey);
deepStrictEqual(secp.verify(sig, message, publicKey), true);
});
should('secp256k1.recoverPublicKey()/should not recover zero points', () => {
});
should('not recover zero points', () => {
const msgHash = '6b8d2c81b11b2d699528dde488dbdf2f94293d0d33c32e347f255fa4a6c1f0a9';
const sig =
'79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f817986b8d2c81b11b2d699528dde488dbdf2f94293d0d33c32e347f255fa4a6c1f0a9';
const recovery = 0;
throws(() => secp.recoverPublicKey(msgHash, sig, recovery));
});
should('secp256k1.recoverPublicKey()/should handle all-zeros msghash', () => {
});
should('handle all-zeros msghash', () => {
const privKey = secp.utils.randomPrivateKey();
const pub = secp.getPublicKey(privKey);
const zeros = '0000000000000000000000000000000000000000000000000000000000000000';
const sig = secp.sign(zeros, privKey, { recovered: true });
const sig = secp.sign(zeros, privKey);
const recoveredKey = sig.recoverPublicKey(zeros);
deepStrictEqual(recoveredKey.toRawBytes(), pub);
});
should('secp256k1.recoverPublicKey()/should handle RFC 6979 vectors', () => {
});
should('handle RFC 6979 vectors', () => {
for (const vector of ecdsa.valid) {
let usig = secp.sign(vector.m, vector.d);
let sig = usig.toDERHex();
let sig = sigToDER(usig);
const vpub = secp.getPublicKey(vector.d);
const recovered = usig.recoverPublicKey(vector.m);
deepStrictEqual(recovered.toHex(), hex(vpub));
}
});
});
});
// TODO: Real implementation.
function derToPub(der) {
describe('getSharedSecret()', () => {
// TODO: Real implementation.
function derToPub(der) {
return der.slice(46);
}
should('secp256k1.getSharedSecret()/should produce correct results', () => {
}
should('produce correct results', () => {
// TODO: Once der is there, run all tests.
for (const vector of ecdh.testGroups[0].tests.slice(0, 230)) {
if (vector.result === 'invalid' || vector.private.length !== 64) {
@@ -414,8 +408,8 @@ should('secp256k1.getSharedSecret()/should produce correct results', () => {
deepStrictEqual(hex(res.slice(1)), `${vector.shared}`);
}
}
});
should('secp256k1.getSharedSecret()/priv/pub order matters', () => {
});
should('priv/pub order matters', () => {
for (const vector of ecdh.testGroups[0].tests.slice(0, 100)) {
if (vector.result === 'valid') {
let priv = vector.private;
@@ -423,92 +417,110 @@ should('secp256k1.getSharedSecret()/priv/pub order matters', () => {
throws(() => secp.getSharedSecret(derToPub(vector.public), priv, true));
}
}
});
should('secp256k1.getSharedSecret()/rejects invalid keys', () => {
});
should('reject invalid keys', () => {
throws(() => secp.getSharedSecret('01', '02'));
});
});
});
should('secp256k1.utils.isValidPrivateKey()', () => {
should('utils.isValidPrivateKey()', () => {
for (const vector of privates.valid.isPrivate) {
const { d, expected } = vector;
deepStrictEqual(secp.utils.isValidPrivateKey(d), expected);
}
});
const normal = secp.utils._normalizePrivateKey;
const tweakUtils = {
});
should('have proper curve equation in assertValidity()', () => {
throws(() => {
const { Fp } = secp.CURVE;
let point = new Point(Fp.create(-2n), Fp.create(-1n), Fp.create(1n));
point.assertValidity();
});
});
describe('tweak utilities (legacy)', () => {
const normal = secp.utils.normPrivateKeyToScalar;
const tweakUtils = {
privateAdd: (privateKey, tweak) => {
const p = normal(privateKey);
const t = normal(tweak);
return secp.utils._bigintToBytes(secp.utils.mod(p + t, secp.CURVE.n));
return numberToBytesBE(mod(normal(privateKey) + normal(tweak), secp.CURVE.n), 32);
},
privateNegate: (privateKey) => {
const p = normal(privateKey);
return secp.utils._bigintToBytes(secp.CURVE.n - p);
return numberToBytesBE(mod(-normal(privateKey), secp.CURVE.n), 32);
},
pointAddScalar: (p, tweak, isCompressed) => {
const P = secp.Point.fromHex(p);
const t = normal(tweak);
const Q = secp.Point.BASE.multiplyAndAddUnsafe(P, t, 1n);
if (!Q) throw new Error('Tweaked point at infinity');
return Q.toRawBytes(isCompressed);
const tweaked = Point.fromHex(p).add(Point.fromPrivateKey(tweak));
if (tweaked.equals(Point.ZERO)) throw new Error('Tweaked point at infinity');
return tweaked.toRawBytes(isCompressed);
},
pointMultiply: (p, tweak, isCompressed) => {
const P = secp.Point.fromHex(p);
const h = typeof tweak === 'string' ? tweak : bytesToHex(tweak);
const t = BigInt(`0x${h}`);
return P.multiply(t).toRawBytes(isCompressed);
if (typeof tweak === 'string') tweak = hexToBytes(tweak);
const t = bytesToNumberBE(tweak);
return Point.fromHex(p).multiply(t).toRawBytes(isCompressed);
},
};
};
should('secp256k1.privateAdd()', () => {
should('privateAdd()', () => {
for (const vector of privates.valid.add) {
const { a, b, expected } = vector;
deepStrictEqual(bytesToHex(tweakUtils.privateAdd(a, b)), expected);
deepStrictEqual(hex(tweakUtils.privateAdd(a, b)), expected);
}
});
should('secp256k1.privateNegate()', () => {
});
should('privateNegate()', () => {
for (const vector of privates.valid.negate) {
const { a, expected } = vector;
deepStrictEqual(bytesToHex(tweakUtils.privateNegate(a)), expected);
deepStrictEqual(hex(tweakUtils.privateNegate(a)), expected);
}
});
should('secp256k1.pointAddScalar()', () => {
});
should('pointAddScalar()', () => {
for (const vector of points.valid.pointAddScalar) {
const { description, P, d, expected } = vector;
const compressed = !!expected && expected.length === 66; // compressed === 33 bytes
deepStrictEqual(bytesToHex(tweakUtils.pointAddScalar(P, d, compressed)), expected);
deepStrictEqual(hex(tweakUtils.pointAddScalar(P, d, compressed)), expected);
}
});
should('secp256k1.pointAddScalar() invalid', () => {
});
should('pointAddScalar() invalid', () => {
for (const vector of points.invalid.pointAddScalar) {
const { P, d, exception } = vector;
throws(() => tweakUtils.pointAddScalar(P, d));
}
});
should('secp256k1.pointMultiply()', () => {
});
should('pointMultiply()', () => {
for (const vector of points.valid.pointMultiply) {
const { P, d, expected } = vector;
deepStrictEqual(bytesToHex(tweakUtils.pointMultiply(P, d, true)), expected);
deepStrictEqual(hex(tweakUtils.pointMultiply(P, d, true)), expected);
}
});
should('secp256k1.pointMultiply() invalid', () => {
});
should('pointMultiply() invalid', () => {
for (const vector of points.invalid.pointMultiply) {
const { P, d, exception } = vector;
throws(() => tweakUtils.pointMultiply(P, d));
}
});
});
});
should('secp256k1.wychenproof vectors', () => {
should('wycheproof vectors', () => {
for (let group of wp.testGroups) {
const pubKey = secp.Point.fromHex(group.key.uncompressed);
// const pubKey = Point.fromHex().toRawBytes();
const key = group.publicKey;
const pubKey = key.uncompressed;
for (let test of group.tests) {
const m = secp.CURVE.hash(hexToBytes(test.msg));
const h = selectHash(secp);
const m = h(hexToBytes(test.msg));
if (test.result === 'valid' || test.result === 'acceptable') {
const verified = secp.verify(test.sig, m, pubKey);
if (secp.Signature.fromDER(test.sig).hasHighS()) {
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);
if (sig.hasHighS()) {
deepStrictEqual(verified, false);
} else {
deepStrictEqual(verified, true);
@@ -527,6 +539,7 @@ should('secp256k1.wychenproof vectors', () => {
}
}
}
});
});
// ESM is broken.

View File

@@ -1,200 +0,0 @@
import { deepStrictEqual, throws } from 'assert';
import { should } from 'micro-should';
import * as starknet from '../../lib/esm/stark.js';
import { default as issue2 } from './fixtures/issue2.json' assert { type: 'json' };
should('Basic elliptic sanity check', () => {
const g1 = starknet.Point.BASE;
deepStrictEqual(
g1.x.toString(16),
'1ef15c18599971b7beced415a40f0c7deacfd9b0d1819e03d723d8bc943cfca'
);
deepStrictEqual(
g1.y.toString(16),
'5668060aa49730b7be4801df46ec62de53ecd11abe43a32873000c36e8dc1f'
);
const g2 = g1.double();
deepStrictEqual(
g2.x.toString(16),
'759ca09377679ecd535a81e83039658bf40959283187c654c5416f439403cf5'
);
deepStrictEqual(
g2.y.toString(16),
'6f524a3400e7708d5c01a28598ad272e7455aa88778b19f93b562d7a9646c41'
);
const g3 = g2.add(g1);
deepStrictEqual(
g3.x.toString(16),
'411494b501a98abd8262b0da1351e17899a0c4ef23dd2f96fec5ba847310b20'
);
deepStrictEqual(
g3.y.toString(16),
'7e1b3ebac08924d2c26f409549191fcf94f3bf6f301ed3553e22dfb802f0686'
);
const g32 = g1.multiply(3);
deepStrictEqual(
g32.x.toString(16),
'411494b501a98abd8262b0da1351e17899a0c4ef23dd2f96fec5ba847310b20'
);
deepStrictEqual(
g32.y.toString(16),
'7e1b3ebac08924d2c26f409549191fcf94f3bf6f301ed3553e22dfb802f0686'
);
const minus1 = g1.multiply(starknet.CURVE.n - 1n);
deepStrictEqual(
minus1.x.toString(16),
'1ef15c18599971b7beced415a40f0c7deacfd9b0d1819e03d723d8bc943cfca'
);
deepStrictEqual(
minus1.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'
);
});
import * as bip32 from '@scure/bip32';
import * as bip39 from '@scure/bip39';
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.Point.BASE;
const half = starknet.CURVE.n / 2n;
const last = starknet.CURVE.n;
const vectors = [
1,
2,
3,
4,
5,
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, _WINDOW_SIZE: undefined });
for (const v of vectors) {
const uncompressed = v.toHex();
const compressed = v.toHex(true);
const exp = fixPoint(v);
deepStrictEqual(fixPoint(starknet.Point.fromHex(uncompressed)), exp);
deepStrictEqual(fixPoint(starknet.Point.fromHex(compressed)), exp);
deepStrictEqual(starknet.Point.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,5 +0,0 @@
import './basic.test.js';
import './stark.test.js';
import './property.test.js';

View File

@@ -1,51 +0,0 @@
import { deepStrictEqual, throws } from 'assert';
import { should } from 'micro-should';
import * as starknet from '../../lib/esm/stark.js';
import * as fc from 'fast-check';
const FC_BIGINT = fc.bigInt(1n + 1n, starknet.CURVE.n - 1n);
should('Point#toHex() roundtrip', () => {
fc.assert(
fc.property(FC_BIGINT, (x) => {
const point1 = starknet.Point.fromPrivateKey(x);
const hex = point1.toHex(true);
deepStrictEqual(starknet.Point.fromHex(hex).toHex(true), hex);
})
);
});
should('Signature.fromCompactHex() roundtrip', () => {
fc.assert(
fc.property(FC_BIGINT, FC_BIGINT, (r, s) => {
const sig = new starknet.Signature(r, s);
deepStrictEqual(starknet.Signature.fromCompact(sig.toCompactHex()), sig);
})
);
});
should('Signature.fromDERHex() roundtrip', () => {
fc.assert(
fc.property(FC_BIGINT, FC_BIGINT, (r, s) => {
const sig = new starknet.Signature(r, s);
deepStrictEqual(starknet.Signature.fromDER(sig.toDERHex()), sig);
})
);
});
should('verify()/should verify random signatures', () =>
fc.assert(
fc.asyncProperty(FC_BIGINT, fc.hexaString({ minLength: 64, maxLength: 64 }), (privNum, msg) => {
const privKey = privNum.toString(16).padStart(64, '0');
const pub = starknet.getPublicKey(privKey);
const sig = starknet.sign(msg, privKey);
deepStrictEqual(starknet.verify(sig, msg, pub), true);
})
)
);
// ESM is broken.
import url from 'url';
if (import.meta.url === url.pathToFileURL(process.argv[1]).href) {
should.run();
}

View File

@@ -1,286 +0,0 @@
import { deepStrictEqual, throws } from 'assert';
import { should } from 'micro-should';
import { hex, utf8 } from '@scure/base';
import * as bip32 from '@scure/bip32';
import * as bip39 from '@scure/bip39';
import * as starknet from '../../lib/esm/stark.js';
import { default as sigVec } from './fixtures/rfc6979_signature_test_vector.json' assert { type: 'json' };
import { default as precomputedKeys } from './fixtures/keys_precomputed.json' assert { type: 'json' };
should('Starknet keccak', () => {
const value = starknet.keccak(utf8.decode('hello'));
deepStrictEqual(value, 0x8aff950685c2ed4bc3174f3472287b56d9517b9c948127319a09a7a36deac8n);
deepStrictEqual(value < 2n ** 250n, true);
});
should('RFC6979', () => {
for (const msg of sigVec.messages) {
const { r, s } = starknet.sign(msg.hash, sigVec.private_key);
// const { r, s } = starknet.Signature.fromDER(sig);
deepStrictEqual(r.toString(10), msg.r);
deepStrictEqual(s.toString(10), msg.s);
}
});
should('Signatures', () => {
const vectors = [
{
// Message hash of length 61.
msg: 'c465dd6b1bbffdb05442eb17f5ca38ad1aa78a6f56bf4415bdee219114a47',
r: '5f496f6f210b5810b2711c74c15c05244dad43d18ecbbdbe6ed55584bc3b0a2',
s: '4e8657b153787f741a67c0666bad6426c3741b478c8eaa3155196fc571416f3',
},
{
// Message hash of length 61, with leading zeros.
msg: '00c465dd6b1bbffdb05442eb17f5ca38ad1aa78a6f56bf4415bdee219114a47',
r: '5f496f6f210b5810b2711c74c15c05244dad43d18ecbbdbe6ed55584bc3b0a2',
s: '4e8657b153787f741a67c0666bad6426c3741b478c8eaa3155196fc571416f3',
},
{
// Message hash of length 62.
msg: 'c465dd6b1bbffdb05442eb17f5ca38ad1aa78a6f56bf4415bdee219114a47a',
r: '233b88c4578f0807b4a7480c8076eca5cfefa29980dd8e2af3c46a253490e9c',
s: '28b055e825bc507349edfb944740a35c6f22d377443c34742c04e0d82278cf1',
},
{
// Message hash of length 63.
msg: '7465dd6b1bbffdb05442eb17f5ca38ad1aa78a6f56bf4415bdee219114a47a1',
r: 'b6bee8010f96a723f6de06b5fa06e820418712439c93850dd4e9bde43ddf',
s: '1a3d2bc954ed77e22986f507d68d18115fa543d1901f5b4620db98e2f6efd80',
},
];
const privateKey = '2dccce1da22003777062ee0870e9881b460a8b7eca276870f57c601f182136c';
const publicKey = starknet.getPublicKey(privateKey);
for (const v of vectors) {
const sig = starknet.sign(v.msg, privateKey);
const { r, s } = sig;
// const { r, s } = starknet.Signature.fromDER(sig);
deepStrictEqual(r.toString(16), v.r, 'r equality');
deepStrictEqual(s.toString(16), v.s, 's equality');
deepStrictEqual(starknet.verify(sig, v.msg, publicKey), true, 'verify');
}
});
should('Invalid signatures', () => {
/*
it('should not verify invalid signature inputs lengths', () => {
const ecOrder = starkwareCrypto.ec.n;
const {maxEcdsaVal} = starkwareCrypto;
const maxMsgHash = maxEcdsaVal.sub(oneBn);
const maxR = maxEcdsaVal.sub(oneBn);
const maxS = ecOrder.sub(oneBn).sub(oneBn);
const maxStarkKey = maxEcdsaVal.sub(oneBn);
// Test invalid message length.
expect(() =>
starkwareCrypto.verify(maxStarkKey, maxMsgHash.add(oneBn).toString(16), {
r: maxR,
s: maxS
})
).to.throw('Message not signable, invalid msgHash length.');
// Test invalid r length.
expect(() =>
starkwareCrypto.verify(maxStarkKey, maxMsgHash.toString(16), {
r: maxR.add(oneBn),
s: maxS
})
).to.throw('Message not signable, invalid r length.');
// Test invalid w length.
expect(() =>
starkwareCrypto.verify(maxStarkKey, maxMsgHash.toString(16), {
r: maxR,
s: maxS.add(oneBn)
})
).to.throw('Message not signable, invalid w length.');
// Test invalid s length.
expect(() =>
starkwareCrypto.verify(maxStarkKey, maxMsgHash.toString(16), {
r: maxR,
s: maxS.add(oneBn).add(oneBn)
})
).to.throw('Message not signable, invalid s length.');
});
it('should not verify invalid signatures', () => {
const privKey = generateRandomStarkPrivateKey();
const keyPair = starkwareCrypto.ec.keyFromPrivate(privKey, 'hex');
const keyPairPub = starkwareCrypto.ec.keyFromPublic(
keyPair.getPublic(),
'BN'
);
const msgHash = new BN(randomHexString(61));
const msgSignature = starkwareCrypto.sign(keyPair, msgHash);
// Test invalid public key.
const invalidKeyPairPub = starkwareCrypto.ec.keyFromPublic(
{x: keyPairPub.pub.getX().add(oneBn), y: keyPairPub.pub.getY()},
'BN'
);
expect(
starkwareCrypto.verify(
invalidKeyPairPub,
msgHash.toString(16),
msgSignature
)
).to.be.false;
// Test invalid message.
expect(
starkwareCrypto.verify(
keyPair,
msgHash.add(oneBn).toString(16),
msgSignature
)
).to.be.false;
expect(
starkwareCrypto.verify(
keyPairPub,
msgHash.add(oneBn).toString(16),
msgSignature
)
).to.be.false;
// Test invalid r.
msgSignature.r.iadd(oneBn);
expect(starkwareCrypto.verify(keyPair, msgHash.toString(16), msgSignature))
.to.be.false;
expect(
starkwareCrypto.verify(keyPairPub, msgHash.toString(16), msgSignature)
).to.be.false;
// Test invalid s.
msgSignature.r.isub(oneBn);
msgSignature.s.iadd(oneBn);
expect(starkwareCrypto.verify(keyPair, msgHash.toString(16), msgSignature))
.to.be.false;
expect(
starkwareCrypto.verify(keyPairPub, msgHash.toString(16), msgSignature)
).to.be.false;
});
});
*/
});
should('Pedersen', () => {
deepStrictEqual(
starknet.pedersen(
'0x3d937c035c878245caf64531a5756109c53068da139362728feb561405371cb',
'0x208a0a10250e382e1e4bbe2880906c2791bf6275695e02fbbc6aeff9cd8b31a'
),
'0x30e480bed5fe53fa909cc0f8c4d99b8f9f2c016be4c41e13a4848797979c662'
);
deepStrictEqual(
starknet.pedersen(
'0x58f580910a6ca59b28927c08fe6c43e2e303ca384badc365795fc645d479d45',
'0x78734f65a067be9bdb39de18434d71e79f7b6466a4b66bbd979ab9e7515fe0b'
),
'0x68cc0b76cddd1dd4ed2301ada9b7c872b23875d5ff837b3a87993e0d9996b87'
);
});
should('Hash chain', () => {
deepStrictEqual(starknet.hashChain([1, 2, 3]), starknet.pedersen(1, starknet.pedersen(2, 3)));
});
should('Key grinding', () => {
deepStrictEqual(
starknet.grindKey('86F3E7293141F20A8BAFF320E8EE4ACCB9D4A4BF2B4D295E8CEE784DB46E0519'),
'5c8c8683596c732541a59e03007b2d30dbbbb873556fe65b5fb63c16688f941'
);
// Loops more than once (verified manually)
deepStrictEqual(
starknet.grindKey('94F3E7293141F20A8BAFF320E8EE4ACCB9D4A4BF2B4D295E8CEE784DB46E0595'),
'33880b9aba464c1c01c9f8f5b4fc1134698f9b0a8d18505cab6cdd34d93dc02'
);
});
should('Private to stark key', () => {
deepStrictEqual(
starknet.getStarkKey('0x178047D3869489C055D7EA54C014FFB834A069C9595186ABE04EA4D1223A03F'),
'0x1895a6a77ae14e7987b9cb51329a5adfb17bd8e7c638f92d6892d76e51cebcf'
);
for (const [privKey, expectedPubKey] of Object.entries(precomputedKeys)) {
deepStrictEqual(starknet.getStarkKey(privKey), expectedPubKey);
}
});
should('Private stark key from eth signature', () => {
const ethSignature =
'0x21fbf0696d5e0aa2ef41a2b4ffb623bcaf070461d61cf7251c74161f82fec3a43' +
'70854bc0a34b3ab487c1bc021cd318c734c51ae29374f2beb0e6f2dd49b4bf41c';
deepStrictEqual(
starknet.ethSigToPrivate(ethSignature),
'766f11e90cd7c7b43085b56da35c781f8c067ac0d578eabdceebc4886435bda'
);
});
should('Key derivation', () => {
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 VECTORS = [
{
index: 0,
path: "m/2645'/579218131'/891216374'/1961790679'/2135936222'/0",
privateKey: '6cf0a8bf113352eb863157a45c5e5567abb34f8d32cddafd2c22aa803f4892c',
},
{
index: 7,
path: "m/2645'/579218131'/891216374'/1961790679'/2135936222'/7",
privateKey: '341751bdc42841da35ab74d13a1372c1f0250617e8a2ef96034d9f46e6847af',
},
{
index: 598,
path: "m/2645'/579218131'/891216374'/1961790679'/2135936222'/598",
privateKey: '41a4d591a868353d28b7947eb132aa4d00c4a022743689ffd20a3628d6ca28c',
},
];
const hd = bip32.HDKey.fromMasterSeed(bip39.mnemonicToSeedSync(mnemonic));
for (const { index, path, privateKey } of VECTORS) {
const realPath = starknet.getAccountPath(layer, application, ethAddress, index);
deepStrictEqual(realPath, path);
deepStrictEqual(starknet.grindKey(hd.derive(realPath).privateKey), privateKey);
}
});
// Verified against starknet.js
should('Starknet.js cross-tests', () => {
const privateKey = '0x019800ea6a9a73f94aee6a3d2edf018fc770443e90c7ba121e8303ec6b349279';
// NOTE: there is no compressed keys here, getPubKey returns stark-key (which is schnorr-like X coordinate)
// But it is not used in signing/verifying
deepStrictEqual(
starknet.getStarkKey(privateKey),
'0x33f45f07e1bd1a51b45fc24ec8c8c9908db9e42191be9e169bfcac0c0d99745'
);
const msgHash = '0x6d1706bd3d1ba7c517be2a2a335996f63d4738e2f182144d078a1dd9997062e';
const sig = starknet.sign(msgHash, privateKey);
const { r, s } = (sig);
deepStrictEqual(
r.toString(),
'1427981024487605678086498726488552139932400435436186597196374630267616399345'
);
deepStrictEqual(
s.toString(),
'1853664302719670721837677288395394946745467311923401353018029119631574115563'
);
const hashMsg2 = starknet.pedersen(
'0x33f45f07e1bd1a51b45fc24ec8c8c9908db9e42191be9e169bfcac0c0d99745',
'1'
);
deepStrictEqual(hashMsg2, '0x2b0d4d43acce8ff68416f667f92ec7eab2b96f1d2224abd4d9d4d1e7fa4bb00');
const pubKey =
'04033f45f07e1bd1a51b45fc24ec8c8c9908db9e42191be9e169bfcac0c0d997450319d0f53f6ca077c4fa5207819144a2a4165daef6ee47a7c1d06c0dcaa3e456';
const sig2 = new starknet.Signature(
558858382392827003930138586379728730695763862039474863361948210004201119180n,
2440689354481625417078677634625227600823892606910345662891037256374285369343n
);
deepStrictEqual(starknet.verify(sig2.toDERHex(), hashMsg2, pubKey), true);
});
// ESM is broken.
import url from 'url';
if (import.meta.url === url.pathToFileURL(process.argv[1]).href) {
should.run();
}

File diff suppressed because it is too large Load Diff

1080
test/vectors/poseidon.json Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,72 +1,107 @@
[
{
"message": "test data",
"d": "fee0a1f7afebf9d2a5a80c0c98a31c709681cce195cbcd06342b517970c0be1e",
"k0": "fcce1de7a9bcd6b2d3defade6afa1913fb9229e3b7ddf4749b55c4848b2a196e",
"k1": "727fbcb59eb48b1d7d46f95a04991fc512eb9dbf9105628e3aec87428df28fd8",
"k15": "398f0e2c9f79728f7b3d84d447ac3a86d8b2083c8f234a0ffa9c4043d68bd258"
"curve": "P192",
"q": "FFFFFFFFFFFFFFFFFFFFFFFF99DEF836146BC9B1B4D22831",
"private": "6FAB034934E4C0FC9AE67F5B5659A9D7D1FEFD187EE09FD4",
"Ux": "AC2C77F529F91689FEA0EA5EFEC7F210D8EEA0B9E047ED56",
"Uy": "3BC723E57670BD4887EBC732C523063D0A7C957BC97C1C43",
"cases": [
{
"k": "32B1B6D7D42A05CB449065727A84804FB1A3E34D8F261496",
"message": "sample",
"r": "4B0B8CE98A92866A2820E20AA6B75B56382E0F9BFD5ECB55",
"s": "CCDB006926EA9565CBADC840829D8C384E06DE1F1E381B85"
},
{
"message": "Everything should be made as simple as possible, but not simpler.",
"d": "0000000000000000000000000000000000000000000000000000000000000001",
"k0": "ec633bd56a5774a0940cb97e27a9e4e51dc94af737596a0c5cbb3d30332d92a5",
"k1": "df55b6d1b5c48184622b0ead41a0e02bfa5ac3ebdb4c34701454e80aabf36f56",
"k15": "def007a9a3c2f7c769c75da9d47f2af84075af95cadd1407393dc1e26086ef87"
"k": "5C4CE89CF56D9E7C77C8585339B006B97B5F0680B4306C6C",
"message": "test",
"r": "3A718BD8B4926C3B52EE6BBE67EF79B18CB6EB62B1AD97AE",
"s": "5662E6848A4A19B1F1AE2F72ACD4B8BBE50F1EAC65D9124F"
}
]
},
{
"message": "Satoshi Nakamoto",
"d": "0000000000000000000000000000000000000000000000000000000000000002",
"k0": "d3edc1b8224e953f6ee05c8bbf7ae228f461030e47caf97cde91430b4607405e",
"k1": "f86d8e43c09a6a83953f0ab6d0af59fb7446b4660119902e9967067596b58374",
"k15": "241d1f57d6cfd2f73b1ada7907b199951f95ef5ad362b13aed84009656e0254a"
"curve": "P224",
"q": "FFFFFFFFFFFFFFFFFFFFFFFFFFFF16A2E0B8F03E13DD29455C5C2A3D",
"private": "F220266E1105BFE3083E03EC7A3A654651F45E37167E88600BF257C1",
"Ux": "00CF08DA5AD719E42707FA431292DEA11244D64FC51610D94B130D6C",
"Uy": "EEAB6F3DEBE455E3DBF85416F7030CBD94F34F2D6F232C69F3C1385A",
"cases": [
{
"k": "C1D1F2F10881088301880506805FEB4825FE09ACB6816C36991AA06D",
"message": "sample",
"r": "1CDFE6662DDE1E4A1EC4CDEDF6A1F5A2FB7FBD9145C12113E6ABFD3E",
"s": "A6694FD7718A21053F225D3F46197CA699D45006C06F871808F43EBC"
},
{
"message": "Diffie Hellman",
"d": "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f",
"k0": "c378a41cb17dce12340788dd3503635f54f894c306d52f6e9bc4b8f18d27afcc",
"k1": "90756c96fef41152ac9abe08819c4e95f16da2af472880192c69a2b7bac29114",
"k15": "7b3f53300ab0ccd0f698f4d67db87c44cf3e9e513d9df61137256652b2e94e7c"
"k": "DF8B38D40DCA3E077D0AC520BF56B6D565134D9B5F2EAE0D34900524",
"message": "test",
"r": "C441CE8E261DED634E4CF84910E4C5D1D22C5CF3B732BB204DBEF019",
"s": "902F42847A63BDC5F6046ADA114953120F99442D76510150F372A3F4"
}
]
},
{
"message": "Japan",
"d": "8080808080808080808080808080808080808080808080808080808080808080",
"k0": "f471e61b51d2d8db78f3dae19d973616f57cdc54caaa81c269394b8c34edcf59",
"k1": "6819d85b9730acc876fdf59e162bf309e9f63dd35550edf20869d23c2f3e6d17",
"k15": "d8e8bae3ee330a198d1f5e00ad7c5f9ed7c24c357c0a004322abca5d9cd17847"
"curve": "P256",
"q": "FFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551",
"private": "C9AFA9D845BA75166B5C215767B1D6934E50C3DB36E89B127B8A622B120F6721",
"Ux": "60FED4BA255A9D31C961EB74C6356D68C049B8923B61FA6CE669622E60F29FB6",
"Uy": "7903FE1008B8BC99A41AE9E95628BC64F2F1B20C2D7E9F5177A3C294D4462299",
"cases": [
{
"k": "A6E3C57DD01ABE90086538398355DD4C3B17AA873382B0F24D6129493D8AAD60",
"message": "sample",
"r": "EFD48B2AACB6A8FD1140DD9CD45E81D69D2C877B56AAF991C34D0EA84EAF3716",
"s": "F7CB1C942D657C41D436C7A1B6E29F65F3E900DBB9AFF4064DC4AB2F843ACDA8"
},
{
"message": "Bitcoin",
"d": "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140",
"k0": "36c848ffb2cbecc5422c33a994955b807665317c1ce2a0f59c689321aaa631cc",
"k1": "4ed8de1ec952a4f5b3bd79d1ff96446bcd45cabb00fc6ca127183e14671bcb85",
"k15": "56b6f47babc1662c011d3b1f93aa51a6e9b5f6512e9f2e16821a238d450a31f8"
"k": "D16B6AE827F17175E040871A1C7EC3500192C4C92677336EC2537ACAEE0008E0",
"message": "test",
"r": "F1ABB023518351CD71D881567B1EA663ED3EFCF6C5132B354F28D3B0B7D38367",
"s": "019F4113742A2B14BD25926B49C649155F267E60D3814B4C0CC84250E46F0083"
}
]
},
{
"message": "i2FLPP8WEus5WPjpoHwheXOMSobUJVaZM1JPMQZq",
"d": "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140",
"k0": "6e9b434fcc6bbb081a0463c094356b47d62d7efae7da9c518ed7bac23f4e2ed6",
"k1": "ae5323ae338d6117ce8520a43b92eacd2ea1312ae514d53d8e34010154c593bb",
"k15": "3eaa1b61d1b8ab2f1ca71219c399f2b8b3defa624719f1e96fe3957628c2c4ea"
"curve": "P384",
"q": "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC7634D81F4372DDF581A0DB248B0A77AECEC196ACCC52973",
"private": "6B9D3DAD2E1B8C1C05B19875B6659F4DE23C3B667BF297BA9AA47740787137D896D5724E4C70A825F872C9EA60D2EDF5",
"Ux": "EC3A4E415B4E19A4568618029F427FA5DA9A8BC4AE92E02E06AAE5286B300C64DEF8F0EA9055866064A254515480BC13",
"Uy": "8015D9B72D7D57244EA8EF9AC0C621896708A59367F9DFB9F54CA84B3F1C9DB1288B231C3AE0D4FE7344FD2533264720",
"cases": [
{
"k": "94ED910D1A099DAD3254E9242AE85ABDE4BA15168EAF0CA87A555FD56D10FBCA2907E3E83BA95368623B8C4686915CF9",
"message": "sample",
"r": "94EDBB92A5ECB8AAD4736E56C691916B3F88140666CE9FA73D64C4EA95AD133C81A648152E44ACF96E36DD1E80FABE46",
"s": "99EF4AEB15F178CEA1FE40DB2603138F130E740A19624526203B6351D0A3A94FA329C145786E679E7B82C71A38628AC8"
},
{
"message": "lEE55EJNP7aLrMtjkeJKKux4Yg0E8E1SAJnWTCEh",
"d": "3881e5286abc580bb6139fe8e83d7c8271c6fe5e5c2d640c1f0ed0e1ee37edc9",
"k0": "5b606665a16da29cc1c5411d744ab554640479dd8abd3c04ff23bd6b302e7034",
"k1": "f8b25263152c042807c992eacd2ac2cc5790d1e9957c394f77ea368e3d9923bd",
"k15": "ea624578f7e7964ac1d84adb5b5087dd14f0ee78b49072aa19051cc15dab6f33"
"k": "015EE46A5BF88773ED9123A5AB0807962D193719503C527B031B4C2D225092ADA71F4A459BC0DA98ADB95837DB8312EA",
"message": "test",
"r": "8203B63D3C853E8D77227FB377BCF7B7B772E97892A80F36AB775D509D7A5FEB0542A7F0812998DA8F1DD3CA3CF023DB",
"s": "DDD0760448D42D8A43AF45AF836FCE4DE8BE06B485E9B61B827C2F13173923E06A739F040649A667BF3B828246BAA5A5"
}
]
},
{
"message": "2SaVPvhxkAPrayIVKcsoQO5DKA8Uv5X/esZFlf+y",
"d": "7259dff07922de7f9c4c5720d68c9745e230b32508c497dd24cb95ef18856631",
"k0": "3ab6c19ab5d3aea6aa0c6da37516b1d6e28e3985019b3adb388714e8f536686b",
"k1": "19af21b05004b0ce9cdca82458a371a9d2cf0dc35a813108c557b551c08eb52e",
"k15": "117a32665fca1b7137a91c4739ac5719fec0cf2e146f40f8e7c21b45a07ebc6a"
"curve": "P521",
"q": "1FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA51868783BF2F966B7FCC0148F709A5D03BB5C9B8899C47AEBB6FB71E91386409",
"private": "0FAD06DAA62BA3B25D2FB40133DA757205DE67F5BB0018FEE8C86E1B68C7E75CAA896EB32F1F47C70855836A6D16FCC1466F6D8FBEC67DB89EC0C08B0E996B83538",
"Ux": "1894550D0785932E00EAA23B694F213F8C3121F86DC97A04E5A7167DB4E5BCD371123D46E45DB6B5D5370A7F20FB633155D38FFA16D2BD761DCAC474B9A2F5023A4",
"Uy": "0493101C962CD4D2FDDF782285E64584139C2F91B47F87FF82354D6630F746A28A0DB25741B5B34A828008B22ACC23F924FAAFBD4D33F81EA66956DFEAA2BFDFCF5",
"cases": [
{
"k": "1DAE2EA071F8110DC26882D4D5EAE0621A3256FC8847FB9022E2B7D28E6F10198B1574FDD03A9053C08A1854A168AA5A57470EC97DD5CE090124EF52A2F7ECBFFD3",
"message": "sample",
"r": "0C328FAFCBD79DD77850370C46325D987CB525569FB63C5D3BC53950E6D4C5F174E25A1EE9017B5D450606ADD152B534931D7D4E8455CC91F9B15BF05EC36E377FA",
"s": "0617CCE7CF5064806C467F678D3B4080D6F1CC50AF26CA209417308281B68AF282623EAA63E5B5C0723D8B8C37FF0777B1A20F8CCB1DCCC43997F1EE0E44DA4A67A"
},
{
"message": "00A0OwO2THi7j5Z/jp0FmN6nn7N/DQd6eBnCS+/b",
"d": "0d6ea45d62b334777d6995052965c795a4f8506044b4fd7dc59c15656a28f7aa",
"k0": "79487de0c8799158294d94c0eb92ee4b567e4dc7ca18addc86e49d31ce1d2db6",
"k1": "9561d2401164a48a8f600882753b3105ebdd35e2358f4f808c4f549c91490009",
"k15": "b0d273634129ff4dbdf0df317d4062a1dbc58818f88878ffdb4ec511c77976c0"
"k": "16200813020EC986863BEDFC1B121F605C1215645018AEA1A7B215A564DE9EB1B38A67AA1128B80CE391C4FB71187654AAA3431027BFC7F395766CA988C964DC56D",
"message": "test",
"r": "13E99020ABF5CEE7525D16B69B229652AB6BDF2AFFCAEF38773B4B7D08725F10CDB93482FDCC54EDCEE91ECA4166B2A7C6265EF0CE2BD7051B7CEF945BABD47EE6D",
"s": "1FBD0013C674AA79CB39849527916CE301C66EA7CE8B80682786AD60F98F7E78A19CA69EFF5C57400E3B3A0AD66CE0978214D13BAF4E9AC60752F7B155E2DE4DCE3"
}
]
}
]

View File

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

View File

@@ -0,0 +1,72 @@
[
{
"message": "test data",
"d": "fee0a1f7afebf9d2a5a80c0c98a31c709681cce195cbcd06342b517970c0be1e",
"k0": "fcce1de7a9bcd6b2d3defade6afa1913fb9229e3b7ddf4749b55c4848b2a196e",
"k1": "727fbcb59eb48b1d7d46f95a04991fc512eb9dbf9105628e3aec87428df28fd8",
"k15": "398f0e2c9f79728f7b3d84d447ac3a86d8b2083c8f234a0ffa9c4043d68bd258"
},
{
"message": "Everything should be made as simple as possible, but not simpler.",
"d": "0000000000000000000000000000000000000000000000000000000000000001",
"k0": "ec633bd56a5774a0940cb97e27a9e4e51dc94af737596a0c5cbb3d30332d92a5",
"k1": "df55b6d1b5c48184622b0ead41a0e02bfa5ac3ebdb4c34701454e80aabf36f56",
"k15": "def007a9a3c2f7c769c75da9d47f2af84075af95cadd1407393dc1e26086ef87"
},
{
"message": "Satoshi Nakamoto",
"d": "0000000000000000000000000000000000000000000000000000000000000002",
"k0": "d3edc1b8224e953f6ee05c8bbf7ae228f461030e47caf97cde91430b4607405e",
"k1": "f86d8e43c09a6a83953f0ab6d0af59fb7446b4660119902e9967067596b58374",
"k15": "241d1f57d6cfd2f73b1ada7907b199951f95ef5ad362b13aed84009656e0254a"
},
{
"message": "Diffie Hellman",
"d": "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f",
"k0": "c378a41cb17dce12340788dd3503635f54f894c306d52f6e9bc4b8f18d27afcc",
"k1": "90756c96fef41152ac9abe08819c4e95f16da2af472880192c69a2b7bac29114",
"k15": "7b3f53300ab0ccd0f698f4d67db87c44cf3e9e513d9df61137256652b2e94e7c"
},
{
"message": "Japan",
"d": "8080808080808080808080808080808080808080808080808080808080808080",
"k0": "f471e61b51d2d8db78f3dae19d973616f57cdc54caaa81c269394b8c34edcf59",
"k1": "6819d85b9730acc876fdf59e162bf309e9f63dd35550edf20869d23c2f3e6d17",
"k15": "d8e8bae3ee330a198d1f5e00ad7c5f9ed7c24c357c0a004322abca5d9cd17847"
},
{
"message": "Bitcoin",
"d": "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140",
"k0": "36c848ffb2cbecc5422c33a994955b807665317c1ce2a0f59c689321aaa631cc",
"k1": "4ed8de1ec952a4f5b3bd79d1ff96446bcd45cabb00fc6ca127183e14671bcb85",
"k15": "56b6f47babc1662c011d3b1f93aa51a6e9b5f6512e9f2e16821a238d450a31f8"
},
{
"message": "i2FLPP8WEus5WPjpoHwheXOMSobUJVaZM1JPMQZq",
"d": "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140",
"k0": "6e9b434fcc6bbb081a0463c094356b47d62d7efae7da9c518ed7bac23f4e2ed6",
"k1": "ae5323ae338d6117ce8520a43b92eacd2ea1312ae514d53d8e34010154c593bb",
"k15": "3eaa1b61d1b8ab2f1ca71219c399f2b8b3defa624719f1e96fe3957628c2c4ea"
},
{
"message": "lEE55EJNP7aLrMtjkeJKKux4Yg0E8E1SAJnWTCEh",
"d": "3881e5286abc580bb6139fe8e83d7c8271c6fe5e5c2d640c1f0ed0e1ee37edc9",
"k0": "5b606665a16da29cc1c5411d744ab554640479dd8abd3c04ff23bd6b302e7034",
"k1": "f8b25263152c042807c992eacd2ac2cc5790d1e9957c394f77ea368e3d9923bd",
"k15": "ea624578f7e7964ac1d84adb5b5087dd14f0ee78b49072aa19051cc15dab6f33"
},
{
"message": "2SaVPvhxkAPrayIVKcsoQO5DKA8Uv5X/esZFlf+y",
"d": "7259dff07922de7f9c4c5720d68c9745e230b32508c497dd24cb95ef18856631",
"k0": "3ab6c19ab5d3aea6aa0c6da37516b1d6e28e3985019b3adb388714e8f536686b",
"k1": "19af21b05004b0ce9cdca82458a371a9d2cf0dc35a813108c557b551c08eb52e",
"k15": "117a32665fca1b7137a91c4739ac5719fec0cf2e146f40f8e7c21b45a07ebc6a"
},
{
"message": "00A0OwO2THi7j5Z/jp0FmN6nn7N/DQd6eBnCS+/b",
"d": "0d6ea45d62b334777d6995052965c795a4f8506044b4fd7dc59c15656a28f7aa",
"k0": "79487de0c8799158294d94c0eb92ee4b567e4dc7ca18addc86e49d31ce1d2db6",
"k1": "9561d2401164a48a8f600882753b3105ebdd35e2358f4f808c4f549c91490009",
"k15": "b0d273634129ff4dbdf0df317d4062a1dbc58818f88878ffdb4ec511c77976c0"
}
]

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,434 @@
{
"algorithm" : "EcCurveTest",
"schema" : "ec_curve_test_schema.json",
"generatorVersion" : "0.9rc5",
"numberOfTests" : 26,
"header" : [
"Test vectors of type EcCurveTest are for checking curve parameters."
],
"notes" : {
},
"testGroups" : [
{
"type" : "EcCurveTest",
"tests" : [
{
"tcId" : 1,
"comment" : "",
"flags" : [],
"name" : "secp224r1",
"oid" : "1.3.132.0.33",
"ref" : "ANSI X9.62",
"p" : "00ffffffffffffffffffffffffffffffff000000000000000000000001",
"n" : "00ffffffffffffffffffffffffffff16a2e0b8f03e13dd29455c5c2a3d",
"a" : "00fffffffffffffffffffffffffffffffefffffffffffffffffffffffe",
"b" : "00b4050a850c04b3abf54132565044b0b7d7bfd8ba270b39432355ffb4",
"gx" : "00b70e0cbd6bb4bf7f321390b94a03c1d356c21122343280d6115c1d21",
"gy" : "00bd376388b5f723fb4c22dfe6cd4375a05a07476444d5819985007e34",
"h" : 1,
"result" : "valid"
},
{
"tcId" : 2,
"comment" : "",
"flags" : [],
"name" : "secp256r1",
"oid" : "1.2.840.10045.3.1.7",
"ref" : "ANSI X9.62",
"p" : "00ffffffff00000001000000000000000000000000ffffffffffffffffffffffff",
"n" : "00ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551",
"a" : "00ffffffff00000001000000000000000000000000fffffffffffffffffffffffc",
"b" : "5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b",
"gx" : "6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296",
"gy" : "4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5",
"h" : 1,
"result" : "valid"
},
{
"tcId" : 3,
"comment" : "",
"flags" : [],
"name" : "secp384r1",
"oid" : "1.3.132.0.34",
"ref" : "ANSI X9.62",
"p" : "00fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffff0000000000000000ffffffff",
"n" : "00ffffffffffffffffffffffffffffffffffffffffffffffffc7634d81f4372ddf581a0db248b0a77aecec196accc52973",
"a" : "00fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffff0000000000000000fffffffc",
"b" : "00b3312fa7e23ee7e4988e056be3f82d19181d9c6efe8141120314088f5013875ac656398d8a2ed19d2a85c8edd3ec2aef",
"gx" : "00aa87ca22be8b05378eb1c71ef320ad746e1d3b628ba79b9859f741e082542a385502f25dbf55296c3a545e3872760ab7",
"gy" : "3617de4a96262c6f5d9e98bf9292dc29f8f41dbd289a147ce9da3113b5f0b8c00a60b1ce1d7e819d7a431d7c90ea0e5f",
"h" : 1,
"result" : "valid"
},
{
"tcId" : 4,
"comment" : "",
"flags" : [],
"name" : "secp521r1",
"oid" : "1.3.132.0.35",
"ref" : "ANSI X9.62",
"p" : "01ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
"n" : "01fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa51868783bf2f966b7fcc0148f709a5d03bb5c9b8899c47aebb6fb71e91386409",
"a" : "01fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc",
"b" : "51953eb9618e1c9a1f929a21a0b68540eea2da725b99b315f3b8b489918ef109e156193951ec7e937b1652c0bd3bb1bf073573df883d2c34f1ef451fd46b503f00",
"gx" : "00c6858e06b70404e9cd9e3ecb662395b4429c648139053fb521f828af606b4d3dbaa14b5e77efe75928fe1dc127a2ffa8de3348b3c1856a429bf97e7e31c2e5bd66",
"gy" : "011839296a789a3bc0045c8a5fb42c7d1bd998f54449579b446817afbd17273e662c97ee72995ef42640c550b9013fad0761353c7086a272c24088be94769fd16650",
"h" : 1,
"result" : "valid"
},
{
"tcId" : 5,
"comment" : "",
"flags" : [],
"name" : "secp256k1",
"oid" : "1.3.132.0.10",
"ref" : "https://www.secg.org/sec2-v2.pdf",
"p" : "00fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f",
"n" : "00fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141",
"a" : "00",
"b" : "07",
"gx" : "79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798",
"gy" : "483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8",
"h" : 1,
"result" : "valid"
},
{
"tcId" : 6,
"comment" : "",
"flags" : [],
"name" : "secp224k1",
"oid" : "1.3.132.0.32",
"ref" : "ANSI X9.62",
"p" : "00fffffffffffffffffffffffffffffffffffffffffffffffeffffe56d",
"n" : "010000000000000000000000000001dce8d2ec6184caf0a971769fb1f7",
"a" : "00",
"b" : "05",
"gx" : "00a1455b334df099df30fc28a169a467e9e47075a90f7e650eb6b7a45c",
"gy" : "7e089fed7fba344282cafbd6f7e319f7c0b0bd59e2ca4bdb556d61a5",
"h" : 1,
"result" : "valid"
},
{
"tcId" : 7,
"comment" : "",
"flags" : [],
"name" : "brainpoolP224r1",
"oid" : "1.3.36.3.3.2.8.1.1.5",
"ref" : "RFC 5639",
"p" : "00d7c134aa264366862a18302575d1d787b09f075797da89f57ec8c0ff",
"n" : "00d7c134aa264366862a18302575d0fb98d116bc4b6ddebca3a5a7939f",
"a" : "68a5e62ca9ce6c1c299803a6c1530b514e182ad8b0042a59cad29f43",
"b" : "2580f63ccfe44138870713b1a92369e33e2135d266dbb372386c400b",
"gx" : "0d9029ad2c7e5cf4340823b2a87dc68c9e4ce3174c1e6efdee12c07d",
"gy" : "58aa56f772c0726f24c6b89e4ecdac24354b9e99caa3f6d3761402cd",
"h" : 1,
"result" : "valid"
},
{
"tcId" : 8,
"comment" : "",
"flags" : [],
"name" : "brainpoolP256r1",
"oid" : "1.3.36.3.3.2.8.1.1.7",
"ref" : "RFC 5639",
"p" : "00a9fb57dba1eea9bc3e660a909d838d726e3bf623d52620282013481d1f6e5377",
"n" : "00a9fb57dba1eea9bc3e660a909d838d718c397aa3b561a6f7901e0e82974856a7",
"a" : "7d5a0975fc2c3057eef67530417affe7fb8055c126dc5c6ce94a4b44f330b5d9",
"b" : "26dc5c6ce94a4b44f330b5d9bbd77cbf958416295cf7e1ce6bccdc18ff8c07b6",
"gx" : "008bd2aeb9cb7e57cb2c4b482ffc81b7afb9de27e1e3bd23c23a4453bd9ace3262",
"gy" : "547ef835c3dac4fd97f8461a14611dc9c27745132ded8e545c1d54c72f046997",
"h" : 1,
"result" : "valid"
},
{
"tcId" : 9,
"comment" : "",
"flags" : [],
"name" : "brainpoolP320r1",
"oid" : "1.3.36.3.3.2.8.1.1.9",
"ref" : "RFC 5639",
"p" : "00d35e472036bc4fb7e13c785ed201e065f98fcfa6f6f40def4f92b9ec7893ec28fcd412b1f1b32e27",
"n" : "00d35e472036bc4fb7e13c785ed201e065f98fcfa5b68f12a32d482ec7ee8658e98691555b44c59311",
"a" : "3ee30b568fbab0f883ccebd46d3f3bb8a2a73513f5eb79da66190eb085ffa9f492f375a97d860eb4",
"b" : "520883949dfdbc42d3ad198640688a6fe13f41349554b49acc31dccd884539816f5eb4ac8fb1f1a6",
"gx" : "43bd7e9afb53d8b85289bcc48ee5bfe6f20137d10a087eb6e7871e2a10a599c710af8d0d39e20611",
"gy" : "14fdd05545ec1cc8ab4093247f77275e0743ffed117182eaa9c77877aaac6ac7d35245d1692e8ee1",
"h" : 1,
"result" : "valid"
},
{
"tcId" : 10,
"comment" : "",
"flags" : [],
"name" : "brainpoolP384r1",
"oid" : "1.3.36.3.3.2.8.1.1.11",
"ref" : "RFC 5639",
"p" : "008cb91e82a3386d280f5d6f7e50e641df152f7109ed5456b412b1da197fb71123acd3a729901d1a71874700133107ec53",
"n" : "008cb91e82a3386d280f5d6f7e50e641df152f7109ed5456b31f166e6cac0425a7cf3ab6af6b7fc3103b883202e9046565",
"a" : "7bc382c63d8c150c3c72080ace05afa0c2bea28e4fb22787139165efba91f90f8aa5814a503ad4eb04a8c7dd22ce2826",
"b" : "04a8c7dd22ce28268b39b55416f0447c2fb77de107dcd2a62e880ea53eeb62d57cb4390295dbc9943ab78696fa504c11",
"gx" : "1d1c64f068cf45ffa2a63a81b7c13f6b8847a3e77ef14fe3db7fcafe0cbd10e8e826e03436d646aaef87b2e247d4af1e",
"gy" : "008abe1d7520f9c2a45cb1eb8e95cfd55262b70b29feec5864e19c054ff99129280e4646217791811142820341263c5315",
"h" : 1,
"result" : "valid"
},
{
"tcId" : 11,
"comment" : "",
"flags" : [],
"name" : "brainpoolP512r1",
"oid" : "1.3.36.3.3.2.8.1.1.13",
"ref" : "RFC 5639",
"p" : "00aadd9db8dbe9c48b3fd4e6ae33c9fc07cb308db3b3c9d20ed6639cca703308717d4d9b009bc66842aecda12ae6a380e62881ff2f2d82c68528aa6056583a48f3",
"n" : "00aadd9db8dbe9c48b3fd4e6ae33c9fc07cb308db3b3c9d20ed6639cca70330870553e5c414ca92619418661197fac10471db1d381085ddaddb58796829ca90069",
"a" : "7830a3318b603b89e2327145ac234cc594cbdd8d3df91610a83441caea9863bc2ded5d5aa8253aa10a2ef1c98b9ac8b57f1117a72bf2c7b9e7c1ac4d77fc94ca",
"b" : "3df91610a83441caea9863bc2ded5d5aa8253aa10a2ef1c98b9ac8b57f1117a72bf2c7b9e7c1ac4d77fc94cadc083e67984050b75ebae5dd2809bd638016f723",
"gx" : "0081aee4bdd82ed9645a21322e9c4c6a9385ed9f70b5d916c1b43b62eef4d0098eff3b1f78e2d0d48d50d1687b93b97d5f7c6d5047406a5e688b352209bcb9f822",
"gy" : "7dde385d566332ecc0eabfa9cf7822fdf209f70024a57b1aa000c55b881f8111b2dcde494a5f485e5bca4bd88a2763aed1ca2b2fa8f0540678cd1e0f3ad80892",
"h" : 1,
"result" : "valid"
},
{
"tcId" : 12,
"comment" : "",
"flags" : [],
"name" : "brainpoolP224t1",
"oid" : "1.3.36.3.3.2.8.1.1.6",
"ref" : "RFC 5639",
"p" : "00d7c134aa264366862a18302575d1d787b09f075797da89f57ec8c0ff",
"n" : "00d7c134aa264366862a18302575d0fb98d116bc4b6ddebca3a5a7939f",
"a" : "00d7c134aa264366862a18302575d1d787b09f075797da89f57ec8c0fc",
"b" : "4b337d934104cd7bef271bf60ced1ed20da14c08b3bb64f18a60888d",
"gx" : "6ab1e344ce25ff3896424e7ffe14762ecb49f8928ac0c76029b4d580",
"gy" : "0374e9f5143e568cd23f3f4d7c0d4b1e41c8cc0d1c6abd5f1a46db4c",
"h" : 1,
"result" : "valid"
},
{
"tcId" : 13,
"comment" : "",
"flags" : [],
"name" : "brainpoolP256t1",
"oid" : "1.3.36.3.3.2.8.1.1.8",
"ref" : "RFC 5639",
"p" : "00a9fb57dba1eea9bc3e660a909d838d726e3bf623d52620282013481d1f6e5377",
"n" : "00a9fb57dba1eea9bc3e660a909d838d718c397aa3b561a6f7901e0e82974856a7",
"a" : "00a9fb57dba1eea9bc3e660a909d838d726e3bf623d52620282013481d1f6e5374",
"b" : "662c61c430d84ea4fe66a7733d0b76b7bf93ebc4af2f49256ae58101fee92b04",
"gx" : "00a3e8eb3cc1cfe7b7732213b23a656149afa142c47aafbc2b79a191562e1305f4",
"gy" : "2d996c823439c56d7f7b22e14644417e69bcb6de39d027001dabe8f35b25c9be",
"h" : 1,
"result" : "valid"
},
{
"tcId" : 14,
"comment" : "",
"flags" : [],
"name" : "brainpoolP320t1",
"oid" : "1.3.36.3.3.2.8.1.1.10",
"ref" : "RFC 5639",
"p" : "00d35e472036bc4fb7e13c785ed201e065f98fcfa6f6f40def4f92b9ec7893ec28fcd412b1f1b32e27",
"n" : "00d35e472036bc4fb7e13c785ed201e065f98fcfa5b68f12a32d482ec7ee8658e98691555b44c59311",
"a" : "00d35e472036bc4fb7e13c785ed201e065f98fcfa6f6f40def4f92b9ec7893ec28fcd412b1f1b32e24",
"b" : "00a7f561e038eb1ed560b3d147db782013064c19f27ed27c6780aaf77fb8a547ceb5b4fef422340353",
"gx" : "00925be9fb01afc6fb4d3e7d4990010f813408ab106c4f09cb7ee07868cc136fff3357f624a21bed52",
"gy" : "63ba3a7a27483ebf6671dbef7abb30ebee084e58a0b077ad42a5a0989d1ee71b1b9bc0455fb0d2c3",
"h" : 1,
"result" : "valid"
},
{
"tcId" : 15,
"comment" : "",
"flags" : [],
"name" : "brainpoolP384t1",
"oid" : "1.3.36.3.3.2.8.1.1.12",
"ref" : "RFC 5639",
"p" : "008cb91e82a3386d280f5d6f7e50e641df152f7109ed5456b412b1da197fb71123acd3a729901d1a71874700133107ec53",
"n" : "008cb91e82a3386d280f5d6f7e50e641df152f7109ed5456b31f166e6cac0425a7cf3ab6af6b7fc3103b883202e9046565",
"a" : "008cb91e82a3386d280f5d6f7e50e641df152f7109ed5456b412b1da197fb71123acd3a729901d1a71874700133107ec50",
"b" : "7f519eada7bda81bd826dba647910f8c4b9346ed8ccdc64e4b1abd11756dce1d2074aa263b88805ced70355a33b471ee",
"gx" : "18de98b02db9a306f2afcd7235f72a819b80ab12ebd653172476fecd462aabffc4ff191b946a5f54d8d0aa2f418808cc",
"gy" : "25ab056962d30651a114afd2755ad336747f93475b7a1fca3b88f2b6a208ccfe469408584dc2b2912675bf5b9e582928",
"h" : 1,
"result" : "valid"
},
{
"tcId" : 16,
"comment" : "",
"flags" : [],
"name" : "brainpoolP512t1",
"oid" : "1.3.36.3.3.2.8.1.1.14",
"ref" : "RFC 5639",
"p" : "00aadd9db8dbe9c48b3fd4e6ae33c9fc07cb308db3b3c9d20ed6639cca703308717d4d9b009bc66842aecda12ae6a380e62881ff2f2d82c68528aa6056583a48f3",
"n" : "00aadd9db8dbe9c48b3fd4e6ae33c9fc07cb308db3b3c9d20ed6639cca70330870553e5c414ca92619418661197fac10471db1d381085ddaddb58796829ca90069",
"a" : "00aadd9db8dbe9c48b3fd4e6ae33c9fc07cb308db3b3c9d20ed6639cca703308717d4d9b009bc66842aecda12ae6a380e62881ff2f2d82c68528aa6056583a48f0",
"b" : "7cbbbcf9441cfab76e1890e46884eae321f70c0bcb4981527897504bec3e36a62bcdfa2304976540f6450085f2dae145c22553b465763689180ea2571867423e",
"gx" : "640ece5c12788717b9c1ba06cbc2a6feba85842458c56dde9db1758d39c0313d82ba51735cdb3ea499aa77a7d6943a64f7a3f25fe26f06b51baa2696fa9035da",
"gy" : "5b534bd595f5af0fa2c892376c84ace1bb4e3019b71634c01131159cae03cee9d9932184beef216bd71df2dadf86a627306ecff96dbb8bace198b61e00f8b332",
"h" : 1,
"result" : "valid"
},
{
"tcId" : 17,
"comment" : "",
"flags" : [],
"name" : "FRP256v1",
"oid" : "1.2.250.1.223.101.256.1",
"ref" : "https://www.legifrance.gouv.fr/jorf/id/JORFTEXT000024668816",
"p" : "00f1fd178c0b3ad58f10126de8ce42435b3961adbcabc8ca6de8fcf353d86e9c03",
"n" : "00f1fd178c0b3ad58f10126de8ce42435b53dc67e140d2bf941ffdd459c6d655e1",
"a" : "00f1fd178c0b3ad58f10126de8ce42435b3961adbcabc8ca6de8fcf353d86e9c00",
"b" : "00ee353fca5428a9300d4aba754a44c00fdfec0c9ae4b1a1803075ed967b7bb73f",
"gx" : "00b6b3d4c356c139eb31183d4749d423958c27d2dcaf98b70164c97a2dd98f5cff",
"gy" : "6142e0f7c8b204911f9271f0f3ecef8c2701c307e8e4c9e183115a1554062cfb",
"h" : 1,
"result" : "valid"
},
{
"tcId" : 18,
"comment" : "",
"flags" : [],
"name" : "secp192k1",
"oid" : "1.3.132.0.31",
"ref" : "ANSI X9.62",
"p" : "00fffffffffffffffffffffffffffffffffffffffeffffee37",
"n" : "00fffffffffffffffffffffffe26f2fc170f69466a74defd8d",
"a" : "00",
"b" : "03",
"gx" : "00db4ff10ec057e9ae26b07d0280b7f4341da5d1b1eae06c7d",
"gy" : "009b2f2f6d9c5628a7844163d015be86344082aa88d95e2f9d",
"h" : 1,
"result" : "valid"
},
{
"tcId" : 19,
"comment" : "",
"flags" : [],
"name" : "secp192r1",
"oid" : "1.2.840.10045.3.1.1",
"ref" : "ANSI X9.62",
"p" : "00fffffffffffffffffffffffffffffffeffffffffffffffff",
"n" : "00ffffffffffffffffffffffff99def836146bc9b1b4d22831",
"a" : "00fffffffffffffffffffffffffffffffefffffffffffffffc",
"b" : "64210519e59c80e70fa7e9ab72243049feb8deecc146b9b1",
"gx" : "188da80eb03090f67cbf20eb43a18800f4ff0afd82ff1012",
"gy" : "07192b95ffc8da78631011ed6b24cdd573f977a11e794811",
"h" : 1,
"result" : "valid"
},
{
"tcId" : 20,
"comment" : "",
"flags" : [],
"name" : "secp160k1",
"oid" : "1.3.132.0.9",
"ref" : "https://www.secg.org/SEC2-Ver-1.0.pdf",
"p" : "00fffffffffffffffffffffffffffffffeffffac73",
"n" : "0100000000000000000001b8fa16dfab9aca16b6b3",
"a" : "00",
"b" : "07",
"gx" : "3b4c382ce37aa192a4019e763036f4f5dd4d7ebb",
"gy" : "00938cf935318fdced6bc28286531733c3f03c4fee",
"h" : 1,
"result" : "valid"
},
{
"tcId" : 21,
"comment" : "",
"flags" : [],
"name" : "secp160r1",
"oid" : "1.3.132.0.8",
"ref" : "https://www.secg.org/SEC2-Ver-1.0.pdf",
"p" : "00ffffffffffffffffffffffffffffffff7fffffff",
"n" : "0100000000000000000001f4c8f927aed3ca752257",
"a" : "00ffffffffffffffffffffffffffffffff7ffffffc",
"b" : "1c97befc54bd7a8b65acf89f81d4d4adc565fa45",
"gx" : "4a96b5688ef573284664698968c38bb913cbfc82",
"gy" : "23a628553168947d59dcc912042351377ac5fb32",
"h" : 1,
"result" : "valid"
},
{
"tcId" : 22,
"comment" : "",
"flags" : [],
"name" : "secp160r2",
"oid" : "1.3.132.0.30",
"ref" : "https://www.secg.org/SEC2-Ver-1.0.pdf",
"p" : "00fffffffffffffffffffffffffffffffeffffac73",
"n" : "0100000000000000000000351ee786a818f3a1a16b",
"a" : "00fffffffffffffffffffffffffffffffeffffac70",
"b" : "00b4e134d3fb59eb8bab57274904664d5af50388ba",
"gx" : "52dcb034293a117e1f4ff11b30f7199d3144ce6d",
"gy" : "00feaffef2e331f296e071fa0df9982cfea7d43f2e",
"h" : 1,
"result" : "valid"
},
{
"tcId" : 23,
"comment" : "",
"flags" : [],
"name" : "brainpoolP160r1",
"oid" : "1.3.36.3.3.2.8.1.1.1",
"ref" : "RFC 5639",
"p" : "00e95e4a5f737059dc60dfc7ad95b3d8139515620f",
"n" : "00e95e4a5f737059dc60df5991d45029409e60fc09",
"a" : "340e7be2a280eb74e2be61bada745d97e8f7c300",
"b" : "1e589a8595423412134faa2dbdec95c8d8675e58",
"gx" : "00bed5af16ea3f6a4f62938c4631eb5af7bdbcdbc3",
"gy" : "1667cb477a1a8ec338f94741669c976316da6321",
"h" : 1,
"result" : "valid"
},
{
"tcId" : 24,
"comment" : "",
"flags" : [],
"name" : "brainpoolP160t1",
"oid" : "1.3.36.3.3.2.8.1.1.2",
"ref" : "RFC 5639",
"p" : "00e95e4a5f737059dc60dfc7ad95b3d8139515620f",
"n" : "00e95e4a5f737059dc60df5991d45029409e60fc09",
"a" : "00e95e4a5f737059dc60dfc7ad95b3d8139515620c",
"b" : "7a556b6dae535b7b51ed2c4d7daa7a0b5c55f380",
"gx" : "00b199b13b9b34efc1397e64baeb05acc265ff2378",
"gy" : "00add6718b7c7c1961f0991b842443772152c9e0ad",
"h" : 1,
"result" : "valid"
},
{
"tcId" : 25,
"comment" : "",
"flags" : [],
"name" : "brainpoolP192r1",
"oid" : "1.3.36.3.3.2.8.1.1.3",
"ref" : "RFC 5639",
"p" : "00c302f41d932a36cda7a3463093d18db78fce476de1a86297",
"n" : "00c302f41d932a36cda7a3462f9e9e916b5be8f1029ac4acc1",
"a" : "6a91174076b1e0e19c39c031fe8685c1cae040e5c69a28ef",
"b" : "469a28ef7c28cca3dc721d044f4496bcca7ef4146fbf25c9",
"gx" : "00c0a0647eaab6a48753b033c56cb0f0900a2f5c4853375fd6",
"gy" : "14b690866abd5bb88b5f4828c1490002e6773fa2fa299b8f",
"h" : 1,
"result" : "valid"
},
{
"tcId" : 26,
"comment" : "",
"flags" : [],
"name" : "brainpoolP192t1",
"oid" : "1.3.36.3.3.2.8.1.1.4",
"ref" : "RFC 5639",
"p" : "00c302f41d932a36cda7a3463093d18db78fce476de1a86297",
"n" : "00c302f41d932a36cda7a3462f9e9e916b5be8f1029ac4acc1",
"a" : "00c302f41d932a36cda7a3463093d18db78fce476de1a86294",
"b" : "13d56ffaec78681e68f9deb43b35bec2fb68542e27897b79",
"gx" : "3ae9e58c82f63c30282e1fe7bbf43fa72c446af6f4618129",
"gy" : "097e2c5667c2223a902ab5ca449d0084b7e5b3de7ccc01c9",
"h" : 1,
"result" : "valid"
}
]
}
]
}

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

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