43 Commits
0.5.2 ... 0.6.1

Author SHA1 Message Date
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
51 changed files with 8383 additions and 4768 deletions

156
README.md
View File

@@ -6,11 +6,12 @@ Minimal, auditable JS implementation of elliptic curve cryptography.
- ECDSA, EdDSA, Schnorr, BLS signature schemes, ECDH key agreement - ECDSA, EdDSA, Schnorr, BLS signature schemes, ECDH key agreement
- [hash to curve](https://datatracker.ietf.org/doc/draft-irtf-cfrg-hash-to-curve/) - [hash to curve](https://datatracker.ietf.org/doc/draft-irtf-cfrg-hash-to-curve/)
for encoding or hashing an arbitrary string to a point on an elliptic curve for encoding or hashing an arbitrary string to a point on an elliptic curve
- Auditable, [fast](#speed) - [Poseidon](https://www.poseidon-hash.info) ZK-friendly hash
- 🏎 [Ultra-fast](#speed), hand-optimized for caveats of JS engines
- 🔍 Unique tests ensure correctness. Wycheproof vectors included - 🔍 Unique tests ensure correctness. Wycheproof vectors included
- 🔻 Tree-shaking-friendly: there is no entry point, which ensures small size of your app - 🔻 Tree-shaking-friendly: there is no entry point, which ensures small size of your app
There are two parts of the package: Package consists of two parts:
1. `abstract/` directory specifies zero-dependency EC algorithms 1. `abstract/` directory specifies zero-dependency EC algorithms
2. root directory utilizes one dependency `@noble/hashes` and provides ready-to-use: 2. root directory utilizes one dependency `@noble/hashes` and provides ready-to-use:
@@ -24,7 +25,7 @@ Curves incorporate work from previous noble packages
[ed25519](https://github.com/paulmillr/noble-ed25519), [ed25519](https://github.com/paulmillr/noble-ed25519),
[bls12-381](https://github.com/paulmillr/noble-bls12-381)), [bls12-381](https://github.com/paulmillr/noble-bls12-381)),
which had security audits and were developed from 2019 to 2022. which had security audits and were developed from 2019 to 2022.
The goal is to replace them with lean UMD builds based on single-codebase noble-curves. Check out [Upgrading](#upgrading) section if you've used them before.
### This library belongs to _noble_ crypto ### This library belongs to _noble_ crypto
@@ -88,6 +89,7 @@ To define a custom curve, check out API below.
- [abstract/montgomery: Montgomery curve](#abstractmontgomery-montgomery-curve) - [abstract/montgomery: Montgomery curve](#abstractmontgomery-montgomery-curve)
- [abstract/weierstrass: Short Weierstrass curve](#abstractweierstrass-short-weierstrass-curve) - [abstract/weierstrass: Short Weierstrass curve](#abstractweierstrass-short-weierstrass-curve)
- [abstract/hash-to-curve: Hashing strings to curve points](#abstracthash-to-curve-hashing-strings-to-curve-points) - [abstract/hash-to-curve: Hashing strings to curve points](#abstracthash-to-curve-hashing-strings-to-curve-points)
- [abstract/poseidon: Poseidon hash](#abstractposeidon-poseidon-hash)
- [abstract/modular](#abstractmodular) - [abstract/modular](#abstractmodular)
- [abstract/utils](#abstractutils) - [abstract/utils](#abstractutils)
@@ -302,13 +304,12 @@ const shared = secp256k1.getSharedSecret(key, someonesPubkey);
export type CurveFn = { export type CurveFn = {
CURVE: ReturnType<typeof validateOpts>; CURVE: ReturnType<typeof validateOpts>;
getPublicKey: (privateKey: PrivKey, isCompressed?: boolean) => Uint8Array; getPublicKey: (privateKey: PrivKey, isCompressed?: boolean) => Uint8Array;
getSharedSecret: (privateA: PrivKey, publicB: PubKey, isCompressed?: boolean) => Uint8Array; getSharedSecret: (privateA: PrivKey, publicB: Hex, isCompressed?: boolean) => Uint8Array;
sign: (msgHash: Hex, privKey: PrivKey, opts?: SignOpts) => SignatureType; sign: (msgHash: Hex, privKey: PrivKey, opts?: SignOpts) => SignatureType;
signUnhashed: (msg: Uint8Array, privKey: PrivKey, opts?: SignOpts) => SignatureType;
verify: ( verify: (
signature: Hex | SignatureType, signature: Hex | SignatureType,
msgHash: Hex, msgHash: Hex,
publicKey: PubKey, publicKey: Hex,
opts?: { lowS?: boolean } opts?: { lowS?: boolean }
) => boolean; ) => boolean;
Point: PointConstructor; Point: PointConstructor;
@@ -370,6 +371,30 @@ hashes arbitrary-length byte strings to a list of one or more elements of a fini
}; };
``` ```
### abstract/poseidon: Poseidon hash
Implements [Poseidon](https://www.poseidon-hash.info) ZK-friendly hash.
There are many poseidon instances with different constants. We don't provide them,
but we provide ability to specify them manually. For actual usage, check out
stark curve source code.
```ts
import { poseidon } from '@noble/curves/abstract/poseidon';
type PoseidonOpts = {
Fp: Field<bigint>;
t: number;
roundsFull: number;
roundsPartial: number;
sboxPower?: number;
reversePartialPowIdx?: boolean; // Hack for stark
mds: bigint[][];
roundConstants: bigint[][];
};
const instance = poseidon(opts: PoseidonOpts);
```
### abstract/modular ### abstract/modular
Modular arithmetics utilities. Modular arithmetics utilities.
@@ -420,45 +445,94 @@ We consider infrastructure attacks like rogue NPM modules very important; that's
Benchmark results on Apple M2 with node v18.10: Benchmark results on Apple M2 with node v18.10:
``` ```
getPublicKey secp256k1
secp256k1 x 5,241 ops/sec @ 190μs/op init x 57 ops/sec @ 17ms/op
P256 x 7,993 ops/sec @ 125μs/op getPublicKey x 4,946 ops/sec @ 202μs/op
P384 x 3,819 ops/sec @ 261μs/op sign x 3,914 ops/sec @ 255μs/op
P521 x 2,074 ops/sec @ 481μs/op verify x 682 ops/sec @ 1ms/op
ed25519 x 8,390 ops/sec @ 119μs/op getSharedSecret x 427 ops/sec @ 2ms/op
ed448 x 3,224 ops/sec @ 310μs/op recoverPublicKey x 683 ops/sec @ 1ms/op
sign schnorr.sign x 539 ops/sec @ 1ms/op
secp256k1 x 3,934 ops/sec @ 254μs/op schnorr.verify x 716 ops/sec @ 1ms/op
P256 x 5,327 ops/sec @ 187μs/op
P384 x 2,728 ops/sec @ 366μs/op P256
P521 x 1,594 ops/sec @ 626μs/op init x 30 ops/sec @ 32ms/op
ed25519 x 4,233 ops/sec @ 236μs/op getPublicKey x 5,008 ops/sec @ 199μs/op
ed448 x 1,561 ops/sec @ 640μs/op sign x 3,970 ops/sec @ 251μs/op
verify verify x 515 ops/sec @ 1ms/op
secp256k1 x 731 ops/sec @ 1ms/op
P256 x 806 ops/sec @ 1ms/op P384
P384 x 353 ops/sec @ 2ms/op init x 14 ops/sec @ 66ms/op
P521 x 171 ops/sec @ 5ms/op getPublicKey x 2,434 ops/sec @ 410μs/op
ed25519 x 860 ops/sec @ 1ms/op sign x 1,942 ops/sec @ 514μs/op
ed448 x 313 ops/sec @ 3ms/op verify x 206 ops/sec @ 4ms/op
getSharedSecret
secp256k1 x 445 ops/sec @ 2ms/op P521
recoverPublicKey init x 7 ops/sec @ 126ms/op
secp256k1 x 732 ops/sec @ 1ms/op getPublicKey x 1,282 ops/sec @ 779μs/op
==== bls12-381 ==== sign x 1,077 ops/sec @ 928μs/op
getPublicKey x 817 ops/sec @ 1ms/op verify x 110 ops/sec @ 9ms/op
sign x 50 ops/sec @ 19ms/op
verify x 34 ops/sec @ 28ms/op ed25519
pairing x 89 ops/sec @ 11ms/op init x 37 ops/sec @ 26ms/op
==== stark ==== getPublicKey x 8,147 ops/sec @ 122μs/op
sign x 3,979 ops/sec @ 251μs/op
verify x 848 ops/sec @ 1ms/op
ed448
init x 17 ops/sec @ 58ms/op
getPublicKey x 3,083 ops/sec @ 324μs/op
sign x 1,473 ops/sec @ 678μs/op
verify x 323 ops/sec @ 3ms/op
bls12-381
init x 30 ops/sec @ 33ms/op
getPublicKey x 788 ops/sec @ 1ms/op
sign x 45 ops/sec @ 21ms/op
verify x 32 ops/sec @ 30ms/op
pairing x 88 ops/sec @ 11ms/op
stark
init x 31 ops/sec @ 31ms/op
pedersen pedersen
old x 85 ops/sec @ 11ms/op ├─old x 84 ops/sec @ 11ms/op
noble x 1,216 ops/sec @ 822μs/op └─noble x 802 ops/sec @ 1ms/op
poseidon x 7,466 ops/sec @ 133μs/op
verify verify
old x 302 ops/sec @ 3ms/op ├─old x 300 ops/sec @ 3ms/op
noble x 698 ops/sec @ 1ms/op └─noble x 474 ops/sec @ 2ms/op
``` ```
## Upgrading
If you're coming from single-curve noble packages, the following changes need to be kept in mind:
- 2d affine (x, y) points have been removed to reduce complexity and improve speed
- Removed `number` support as a type for private keys. `bigint` is still supported
- `mod`, `invert` are no longer present in `utils`. Use `@noble/curves/abstract/modular.js` now.
Upgrading from @noble/secp256k1 1.7:
- Compressed (33-byte) public keys are now returned by default, instead of uncompressed
- Methods are now synchronous. Setting `secp.utils.hmacSha256` is no longer required
- `sign()`
- `der`, `recovered` options were removed
- `canonical` was renamed to `lowS`
- Return type is now `{ r: bigint, s: bigint, recovery: number }` instance of `Signature`
- `verify()`
- `strict` was renamed to `lowS`
- `recoverPublicKey()`: moved to sig instance `Signature#recoverPublicKey(msgHash)`
- `Point` was removed: use `ProjectivePoint` in xyz coordinates
- `utils`: Many methods were removed, others were moved to `schnorr` namespace
Upgrading from @noble/ed25519 1.7:
- Methods are now synchronous. Setting `secp.utils.hmacSha256` is no longer required
- ed25519ph, ed25519ctx
- `Point` was removed: use `ExtendedPoint` in xyzt coordinates
- `Signature` was removed
- `getSharedSecret` was removed: use separate x25519 sub-module
## Contributing & testing ## Contributing & testing
1. Clone the repository 1. Clone the repository

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

52
benchmark/bls.js Normal file
View File

@@ -0,0 +1,52 @@
import { readFileSync } from 'fs';
import { mark, run } from 'micro-bmark';
import { bls12_381 as bls } from '../lib/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));
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 '../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';
run(async () => {
const RAM = false
for (let kv of Object.entries({ P256, P384, P521, ed25519, ed448 })) {
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();
}
});

View File

@@ -1,416 +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),
},
hashToCurve: {
samples: 500,
noble: () => secp256k1.Point.hashToCurve('abcd'),
},
},
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),
},
hashToCurve: {
samples: 500,
noble: () => ed25519.Point.hashToCurve('abcd'),
},
},
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),
},
hashToCurve: {
samples: 500,
noble: () => ed448.Point.hashToCurve('abcd'),
},
},
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),
},
hashToCurve: {
samples: 500,
P256: () => P256.Point.hashToCurve('abcd'),
P384: () => P384.Point.hashToCurve('abcd'),
P521: () => P521.Point.hashToCurve('abcd'),
},
},
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();
}

View File

@@ -1,26 +1,22 @@
{ {
"name": "benchmark", "name": "benchmark",
"private": true, "private": true,
"version": "0.1.0", "version": "0.1.0",
"description": "benchmarks", "description": "benchmarks",
"main": "index.js", "main": "index.js",
"type": "module", "type": "module",
"scripts": { "scripts": {
"bench": "node index.js" "bench": "node index.js"
}, },
"keywords": [], "keywords": [],
"author": "", "author": "",
"license": "MIT", "license": "MIT",
"devDependencies": { "devDependencies": {
"micro-bmark": "0.2.1" "micro-bmark": "0.3.0"
}, },
"dependencies": { "dependencies": {
"@noble/bls12-381": "^1.4.0", "@noble/hashes": "^1.1.5",
"@noble/ed25519": "^1.7.1", "@starkware-industries/starkware-crypto-utils": "^0.0.2",
"@noble/hashes": "^1.1.5", "elliptic": "^6.5.4"
"@noble/secp256k1": "^1.7.0", }
"@starkware-industries/starkware-crypto-utils": "^0.0.2",
"calculate-correlation": "^1.2.3",
"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 '../lib/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();
});

56
benchmark/stark.js Normal file
View File

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

View File

@@ -1,12 +1,12 @@
{ {
"name": "@noble/curves", "name": "@noble/curves",
"version": "0.5.2", "version": "0.6.1",
"description": "Minimal, auditable JS implementation of elliptic curve cryptography", "description": "Minimal, auditable JS implementation of elliptic curve cryptography",
"files": [ "files": [
"lib" "lib"
], ],
"scripts": { "scripts": {
"bench": "node benchmark/index.js", "bench": "cd benchmark; node secp256k1.js; node curves.js; node stark.js; node bls.js",
"build": "tsc && tsc -p tsconfig.esm.json", "build": "tsc && tsc -p tsconfig.esm.json",
"build:release": "rollup -c rollup.config.js", "build:release": "rollup -c rollup.config.js",
"lint": "prettier --check 'src/**/*.{js,ts}' 'test/*.js'", "lint": "prettier --check 'src/**/*.{js,ts}' 'test/*.js'",
@@ -30,9 +30,9 @@
"@scure/bip39": "~1.1.0", "@scure/bip39": "~1.1.0",
"@types/node": "18.11.3", "@types/node": "18.11.3",
"fast-check": "3.0.0", "fast-check": "3.0.0",
"micro-bmark": "0.2.0", "micro-bmark": "0.3.0",
"micro-should": "0.3.0", "micro-should": "0.4.0",
"prettier": "2.6.2", "prettier": "2.8.3",
"rollup": "2.75.5", "rollup": "2.75.5",
"typescript": "4.7.3" "typescript": "4.7.3"
}, },
@@ -73,16 +73,21 @@
"import": "./lib/esm/abstract/hash-to-curve.js", "import": "./lib/esm/abstract/hash-to-curve.js",
"default": "./lib/abstract/hash-to-curve.js" "default": "./lib/abstract/hash-to-curve.js"
}, },
"./abstract/group": { "./abstract/curve": {
"types": "./lib/abstract/group.d.ts", "types": "./lib/abstract/curve.d.ts",
"import": "./lib/esm/abstract/group.js", "import": "./lib/esm/abstract/curve.js",
"default": "./lib/abstract/group.js" "default": "./lib/abstract/curve.js"
}, },
"./abstract/utils": { "./abstract/utils": {
"types": "./lib/abstract/utils.d.ts", "types": "./lib/abstract/utils.d.ts",
"import": "./lib/esm/abstract/utils.js", "import": "./lib/esm/abstract/utils.js",
"default": "./lib/abstract/utils.js" "default": "./lib/abstract/utils.js"
}, },
"./abstract/poseidon": {
"types": "./lib/abstract/poseidon.d.ts",
"import": "./lib/esm/abstract/poseidon.js",
"default": "./lib/abstract/poseidon.js"
},
"./_shortw_utils": { "./_shortw_utils": {
"types": "./lib/_shortw_utils.d.ts", "types": "./lib/_shortw_utils.d.ts",
"import": "./lib/esm/_shortw_utils.js", "import": "./lib/esm/_shortw_utils.js",
@@ -189,4 +194,4 @@
"url": "https://paulmillr.com/funding/" "url": "https://paulmillr.com/funding/"
} }
] ]
} }

View File

@@ -11,93 +11,100 @@
* We are using Fp for private keys (shorter) and Fp₂ for signatures (longer). * 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. * Some projects may prefer to swap this relation, it is not supported for now.
*/ */
import * as mod from './modular.js'; import { AffinePoint } from './curve.js';
import * as ut from './utils.js'; import { Field, hashToPrivateScalar } from './modular.js';
// Types require separate import import { Hex, PrivKey, CHash, bitLen, bitGet, hexToBytes, bytesToHex } from './utils.js';
import { Hex, PrivKey } from './utils.js'; import * as htf from './hash-to-curve.js';
import { import {
htfOpts, CurvePointsType,
stringToBytes, ProjPointType as ProjPointType,
hash_to_field as hashToField, CurvePointsRes,
expand_message_xmd as expandMessageXMD, weierstrassPoints,
} from './hash-to-curve.js'; } from './weierstrass.js';
import { CurvePointsType, PointType, CurvePointsRes, weierstrassPoints } from './weierstrass.js';
type Fp = bigint; // Can be different field? type Fp = bigint; // Can be different field?
export type SignatureCoder<Fp2> = { export type SignatureCoder<Fp2> = {
decode(hex: Hex): PointType<Fp2>; decode(hex: Hex): ProjPointType<Fp2>;
encode(point: PointType<Fp2>): Uint8Array; encode(point: ProjPointType<Fp2>): Uint8Array;
}; };
export type CurveType<Fp, Fp2, Fp6, Fp12> = { export type CurveType<Fp, Fp2, Fp6, Fp12> = {
r: bigint; r: bigint;
G1: Omit<CurvePointsType<Fp>, 'n'>; G1: Omit<CurvePointsType<Fp>, 'n'> & {
mapToCurve: htf.MapToCurve<Fp>;
htfDefaults: htf.Opts;
};
G2: Omit<CurvePointsType<Fp2>, 'n'> & { G2: Omit<CurvePointsType<Fp2>, 'n'> & {
Signature: SignatureCoder<Fp2>; Signature: SignatureCoder<Fp2>;
mapToCurve: htf.MapToCurve<Fp2>;
htfDefaults: htf.Opts;
}; };
x: bigint; x: bigint;
Fp: mod.Field<Fp>; Fp: Field<Fp>;
Fr: mod.Field<bigint>; Fr: Field<bigint>;
Fp2: mod.Field<Fp2> & { Fp2: Field<Fp2> & {
reim: (num: Fp2) => { re: bigint; im: bigint }; reim: (num: Fp2) => { re: bigint; im: bigint };
multiplyByB: (num: Fp2) => Fp2; multiplyByB: (num: Fp2) => Fp2;
frobeniusMap(num: Fp2, power: number): Fp2; frobeniusMap(num: Fp2, power: number): Fp2;
}; };
Fp6: mod.Field<Fp6>; Fp6: Field<Fp6>;
Fp12: mod.Field<Fp12> & { Fp12: Field<Fp12> & {
frobeniusMap(num: Fp12, power: number): Fp12; frobeniusMap(num: Fp12, power: number): Fp12;
multiplyBy014(num: Fp12, o0: Fp2, o1: Fp2, o4: Fp2): Fp12; multiplyBy014(num: Fp12, o0: Fp2, o1: Fp2, o4: Fp2): Fp12;
conjugate(num: Fp12): Fp12; conjugate(num: Fp12): Fp12;
finalExponentiate(num: Fp12): Fp12; finalExponentiate(num: Fp12): Fp12;
}; };
htfDefaults: htfOpts; htfDefaults: htf.Opts;
hash: ut.CHash; // Because we need outputLen for DRBG hash: CHash; // Because we need outputLen for DRBG
randomBytes: (bytesLength?: number) => Uint8Array; randomBytes: (bytesLength?: number) => Uint8Array;
}; };
export type CurveFn<Fp, Fp2, Fp6, Fp12> = { export type CurveFn<Fp, Fp2, Fp6, Fp12> = {
CURVE: CurveType<Fp, Fp2, Fp6, Fp12>; CURVE: CurveType<Fp, Fp2, Fp6, Fp12>;
Fr: mod.Field<bigint>; Fr: Field<bigint>;
Fp: mod.Field<Fp>; Fp: Field<Fp>;
Fp2: mod.Field<Fp2>; Fp2: Field<Fp2>;
Fp6: mod.Field<Fp6>; Fp6: Field<Fp6>;
Fp12: mod.Field<Fp12>; Fp12: Field<Fp12>;
G1: CurvePointsRes<Fp>; G1: CurvePointsRes<Fp>;
G2: CurvePointsRes<Fp2>; G2: CurvePointsRes<Fp2>;
Signature: SignatureCoder<Fp2>; Signature: SignatureCoder<Fp2>;
millerLoop: (ell: [Fp2, Fp2, Fp2][], g1: [Fp, Fp]) => Fp12; millerLoop: (ell: [Fp2, Fp2, Fp2][], g1: [Fp, Fp]) => Fp12;
calcPairingPrecomputes: (x: Fp2, y: Fp2) => [Fp2, Fp2, Fp2][]; calcPairingPrecomputes: (p: AffinePoint<Fp2>) => [Fp2, Fp2, Fp2][];
pairing: (P: PointType<Fp>, Q: PointType<Fp2>, withFinalExponent?: boolean) => Fp12; // prettier-ignore
hashToCurve: {
G1: ReturnType<(typeof htf.hashToCurve<Fp>)>,
G2: ReturnType<(typeof htf.hashToCurve<Fp2>)>,
},
pairing: (P: ProjPointType<Fp>, Q: ProjPointType<Fp2>, withFinalExponent?: boolean) => Fp12;
getPublicKey: (privateKey: PrivKey) => Uint8Array; getPublicKey: (privateKey: PrivKey) => Uint8Array;
sign: { sign: {
(message: Hex, privateKey: PrivKey): Uint8Array; (message: Hex, privateKey: PrivKey): Uint8Array;
(message: PointType<Fp2>, privateKey: PrivKey): PointType<Fp2>; (message: ProjPointType<Fp2>, privateKey: PrivKey): ProjPointType<Fp2>;
}; };
verify: ( verify: (
signature: Hex | PointType<Fp2>, signature: Hex | ProjPointType<Fp2>,
message: Hex | PointType<Fp2>, message: Hex | ProjPointType<Fp2>,
publicKey: Hex | PointType<Fp> publicKey: Hex | ProjPointType<Fp>
) => boolean; ) => boolean;
aggregatePublicKeys: { aggregatePublicKeys: {
(publicKeys: Hex[]): Uint8Array; (publicKeys: Hex[]): Uint8Array;
(publicKeys: PointType<Fp>[]): PointType<Fp>; (publicKeys: ProjPointType<Fp>[]): ProjPointType<Fp>;
}; };
aggregateSignatures: { aggregateSignatures: {
(signatures: Hex[]): Uint8Array; (signatures: Hex[]): Uint8Array;
(signatures: PointType<Fp2>[]): PointType<Fp2>; (signatures: ProjPointType<Fp2>[]): ProjPointType<Fp2>;
}; };
verifyBatch: ( verifyBatch: (
signature: Hex | PointType<Fp2>, signature: Hex | ProjPointType<Fp2>,
messages: (Hex | PointType<Fp2>)[], messages: (Hex | ProjPointType<Fp2>)[],
publicKeys: (Hex | PointType<Fp>)[] publicKeys: (Hex | ProjPointType<Fp>)[]
) => boolean; ) => boolean;
utils: { utils: {
stringToBytes: typeof stringToBytes; stringToBytes: typeof htf.stringToBytes;
hashToField: typeof hashToField; hashToField: typeof htf.hash_to_field;
expandMessageXMD: typeof expandMessageXMD; expandMessageXMD: typeof htf.expand_message_xmd;
getDSTLabel: () => string;
setDSTLabel(newLabel: string): void;
}; };
}; };
@@ -106,12 +113,13 @@ export function bls<Fp2, Fp6, Fp12>(
): CurveFn<Fp, Fp2, Fp6, Fp12> { ): CurveFn<Fp, Fp2, Fp6, Fp12> {
// Fields looks pretty specific for curve, so for now we need to pass them with options // Fields looks pretty specific for curve, so for now we need to pass them with options
const { Fp, Fr, Fp2, Fp6, Fp12 } = CURVE; const { Fp, Fr, Fp2, Fp6, Fp12 } = CURVE;
const BLS_X_LEN = ut.bitLen(CURVE.x); const BLS_X_LEN = bitLen(CURVE.x);
const groupLen = 32; // TODO: calculate; hardcoded for now const groupLen = 32; // TODO: calculate; hardcoded for now
// Pre-compute coefficients for sparse multiplication // Pre-compute coefficients for sparse multiplication
// Point addition and point double calculations is reused for coefficients // Point addition and point double calculations is reused for coefficients
function calcPairingPrecomputes(x: Fp2, y: Fp2) { function calcPairingPrecomputes(p: AffinePoint<Fp2>) {
const { x, y } = p;
// prettier-ignore // prettier-ignore
const Qx = x, Qy = y, Qz = Fp2.ONE; const Qx = x, Qy = y, Qz = Fp2.ONE;
// prettier-ignore // prettier-ignore
@@ -119,32 +127,32 @@ export function bls<Fp2, Fp6, Fp12>(
let ell_coeff: [Fp2, Fp2, Fp2][] = []; let ell_coeff: [Fp2, Fp2, Fp2][] = [];
for (let i = BLS_X_LEN - 2; i >= 0; i--) { for (let i = BLS_X_LEN - 2; i >= 0; i--) {
// Double // Double
let t0 = Fp2.square(Ry); // Ry² let t0 = Fp2.sqr(Ry); // Ry²
let t1 = Fp2.square(Rz); // Rz² let t1 = Fp2.sqr(Rz); // Rz²
let t2 = Fp2.multiplyByB(Fp2.mul(t1, 3n)); // 3 * T1 * B let t2 = Fp2.multiplyByB(Fp2.mul(t1, 3n)); // 3 * T1 * B
let t3 = Fp2.mul(t2, 3n); // 3 * T2 let t3 = Fp2.mul(t2, 3n); // 3 * T2
let t4 = Fp2.sub(Fp2.sub(Fp2.square(Fp2.add(Ry, Rz)), t1), t0); // (Ry + Rz)² - T1 - T0 let t4 = Fp2.sub(Fp2.sub(Fp2.sqr(Fp2.add(Ry, Rz)), t1), t0); // (Ry + Rz)² - T1 - T0
ell_coeff.push([ ell_coeff.push([
Fp2.sub(t2, t0), // T2 - T0 Fp2.sub(t2, t0), // T2 - T0
Fp2.mul(Fp2.square(Rx), 3n), // 3 * Rx² Fp2.mul(Fp2.sqr(Rx), 3n), // 3 * Rx²
Fp2.negate(t4), // -T4 Fp2.neg(t4), // -T4
]); ]);
Rx = Fp2.div(Fp2.mul(Fp2.mul(Fp2.sub(t0, t3), Rx), Ry), 2n); // ((T0 - T3) * Rx * Ry) / 2 Rx = Fp2.div(Fp2.mul(Fp2.mul(Fp2.sub(t0, t3), Rx), Ry), 2n); // ((T0 - T3) * Rx * Ry) / 2
Ry = Fp2.sub(Fp2.square(Fp2.div(Fp2.add(t0, t3), 2n)), Fp2.mul(Fp2.square(t2), 3n)); // ((T0 + T3) / 2)² - 3 * T2² Ry = Fp2.sub(Fp2.sqr(Fp2.div(Fp2.add(t0, t3), 2n)), Fp2.mul(Fp2.sqr(t2), 3n)); // ((T0 + T3) / 2)² - 3 * T2²
Rz = Fp2.mul(t0, t4); // T0 * T4 Rz = Fp2.mul(t0, t4); // T0 * T4
if (ut.bitGet(CURVE.x, i)) { if (bitGet(CURVE.x, i)) {
// Addition // Addition
let t0 = Fp2.sub(Ry, Fp2.mul(Qy, Rz)); // Ry - Qy * Rz let t0 = Fp2.sub(Ry, Fp2.mul(Qy, Rz)); // Ry - Qy * Rz
let t1 = Fp2.sub(Rx, Fp2.mul(Qx, Rz)); // Rx - Qx * Rz let t1 = Fp2.sub(Rx, Fp2.mul(Qx, Rz)); // Rx - Qx * Rz
ell_coeff.push([ ell_coeff.push([
Fp2.sub(Fp2.mul(t0, Qx), Fp2.mul(t1, Qy)), // T0 * Qx - T1 * Qy Fp2.sub(Fp2.mul(t0, Qx), Fp2.mul(t1, Qy)), // T0 * Qx - T1 * Qy
Fp2.negate(t0), // -T0 Fp2.neg(t0), // -T0
t1, // T1 t1, // T1
]); ]);
let t2 = Fp2.square(t1); // T1² let t2 = Fp2.sqr(t1); // T1²
let t3 = Fp2.mul(t2, t1); // T2 * T1 let t3 = Fp2.mul(t2, t1); // T2 * T1
let t4 = Fp2.mul(t2, Rx); // T2 * Rx let t4 = Fp2.mul(t2, Rx); // T2 * Rx
let t5 = Fp2.add(Fp2.sub(t3, Fp2.mul(t4, 2n)), Fp2.mul(Fp2.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 Rx = Fp2.mul(t1, t5); // T1 * T5
Ry = Fp2.sub(Fp2.mul(Fp2.sub(t4, t5), t0), Fp2.mul(t3, Ry)); // (T4 - T5) * T0 - T3 * Ry Ry = Fp2.sub(Fp2.mul(Fp2.sub(t4, t5), t0), Fp2.mul(t3, Ry)); // (T4 - T5) * T0 - T3 * Ry
Rz = Fp2.mul(Rz, t3); // Rz * T3 Rz = Fp2.mul(Rz, t3); // Rz * T3
@@ -161,39 +169,31 @@ export function bls<Fp2, Fp6, Fp12>(
for (let j = 0, i = BLS_X_LEN - 2; i >= 0; i--, j++) { for (let j = 0, i = BLS_X_LEN - 2; i >= 0; i--, j++) {
const E = ell[j]; const E = ell[j];
f12 = Fp12.multiplyBy014(f12, E[0], Fp2.mul(E[1], Px), Fp2.mul(E[2], Py)); f12 = Fp12.multiplyBy014(f12, E[0], Fp2.mul(E[1], Px), Fp2.mul(E[2], Py));
if (ut.bitGet(x, i)) { if (bitGet(x, i)) {
j += 1; j += 1;
const F = ell[j]; const F = ell[j];
f12 = Fp12.multiplyBy014(f12, F[0], Fp2.mul(F[1], Px), Fp2.mul(F[2], Py)); 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); return Fp12.conjugate(f12);
} }
const utils = { const utils = {
hexToBytes: ut.hexToBytes, hexToBytes: hexToBytes,
bytesToHex: ut.bytesToHex, bytesToHex: bytesToHex,
stringToBytes: stringToBytes, stringToBytes: htf.stringToBytes,
// TODO: do we need to export it here? // TODO: do we need to export it here?
hashToField: ( hashToField: (
msg: Uint8Array, msg: Uint8Array,
count: number, count: number,
options: Partial<typeof CURVE.htfDefaults> = {} options: Partial<typeof CURVE.htfDefaults> = {}
) => hashToField(msg, count, { ...CURVE.htfDefaults, ...options }), ) => htf.hash_to_field(msg, count, { ...CURVE.htfDefaults, ...options }),
expandMessageXMD: (msg: Uint8Array, DST: Uint8Array, lenInBytes: number, H = CURVE.hash) => expandMessageXMD: (msg: Uint8Array, DST: Uint8Array, lenInBytes: number, H = CURVE.hash) =>
expandMessageXMD(msg, DST, lenInBytes, H), htf.expand_message_xmd(msg, DST, lenInBytes, H),
hashToPrivateKey: (hash: Hex): Uint8Array => Fr.toBytes(ut.hashToPrivateScalar(hash, CURVE.r)), hashToPrivateKey: (hash: Hex): Uint8Array => Fr.toBytes(hashToPrivateScalar(hash, CURVE.r)),
randomBytes: (bytesLength: number = groupLen): Uint8Array => CURVE.randomBytes(bytesLength), randomBytes: (bytesLength: number = groupLen): Uint8Array => CURVE.randomBytes(bytesLength),
randomPrivateKey: (): Uint8Array => utils.hashToPrivateKey(utils.randomBytes(groupLen + 8)), randomPrivateKey: (): Uint8Array => utils.hashToPrivateKey(utils.randomBytes(groupLen + 8)),
getDSTLabel: () => CURVE.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');
}
CURVE.htfDefaults.DST = newLabel;
},
}; };
// Point on G1 curve: (x, y) // Point on G1 curve: (x, y)
@@ -201,6 +201,10 @@ export function bls<Fp2, Fp6, Fp12>(
n: Fr.ORDER, n: Fr.ORDER,
...CURVE.G1, ...CURVE.G1,
}); });
const G1HashToCurve = htf.hashToCurve(G1.ProjectivePoint, CURVE.G1.mapToCurve, {
...CURVE.htfDefaults,
...CURVE.G1.htfDefaults,
});
// Sparse multiplication against precomputed coefficients // Sparse multiplication against precomputed coefficients
// TODO: replace with weakmap? // TODO: replace with weakmap?
@@ -208,83 +212,93 @@ export function bls<Fp2, Fp6, Fp12>(
function pairingPrecomputes(point: G2): [Fp2, Fp2, Fp2][] { function pairingPrecomputes(point: G2): [Fp2, Fp2, Fp2][] {
const p = point as G2 & withPairingPrecomputes; const p = point as G2 & withPairingPrecomputes;
if (p._PPRECOMPUTES) return p._PPRECOMPUTES; if (p._PPRECOMPUTES) return p._PPRECOMPUTES;
p._PPRECOMPUTES = calcPairingPrecomputes(p.x, p.y); p._PPRECOMPUTES = calcPairingPrecomputes(point.toAffine());
return p._PPRECOMPUTES; return p._PPRECOMPUTES;
} }
function clearPairingPrecomputes(point: G2) { // TODO: export
const p = point as G2 & withPairingPrecomputes; // function clearPairingPrecomputes(point: G2) {
p._PPRECOMPUTES = undefined; // 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]);
}
// Point on G2 curve (complex numbers): (x₁, x₂+i), (y₁, y₂+i) // Point on G2 curve (complex numbers): (x₁, x₂+i), (y₁, y₂+i)
const G2 = weierstrassPoints({ const G2 = weierstrassPoints({
n: Fr.ORDER, n: Fr.ORDER,
...CURVE.G2, ...CURVE.G2,
}); });
const C = G2.ProjectivePoint as htf.H2CPointConstructor<Fp2>; // TODO: fix
const G2HashToCurve = htf.hashToCurve(C, CURVE.G2.mapToCurve, {
...CURVE.htfDefaults,
...CURVE.G2.htfDefaults,
});
const { Signature } = CURVE.G2; const { Signature } = CURVE.G2;
// Calculates bilinear pairing // Calculates bilinear pairing
function pairing(P: G1, Q: G2, withFinalExponent: boolean = true): Fp12 { function pairing(Q: G1, P: G2, withFinalExponent: boolean = true): Fp12 {
if (P.equals(G1.Point.ZERO) || Q.equals(G2.Point.ZERO)) if (Q.equals(G1.ProjectivePoint.ZERO) || P.equals(G2.ProjectivePoint.ZERO))
throw new Error('No pairings at point of Infinity'); throw new Error('pairing is not available for ZERO point');
P.assertValidity();
Q.assertValidity(); Q.assertValidity();
P.assertValidity();
// Performance: 9ms for millerLoop and ~14ms for exp. // 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; return withFinalExponent ? Fp12.finalExponentiate(looped) : looped;
} }
type G1 = typeof G1.Point.BASE; type G1 = typeof G1.ProjectivePoint.BASE;
type G2 = typeof G2.Point.BASE; type G2 = typeof G2.ProjectivePoint.BASE;
type G1Hex = Hex | G1; type G1Hex = Hex | G1;
type G2Hex = Hex | G2; type G2Hex = Hex | G2;
function normP1(point: G1Hex): G1 { 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 { function normP2(point: G2Hex): G2 {
return point instanceof G2.Point ? point : Signature.decode(point); return point instanceof G2.ProjectivePoint ? point : Signature.decode(point);
} }
function normP2Hash(point: G2Hex): G2 { function normP2Hash(point: G2Hex, htfOpts?: htf.htfBasicOpts): G2 {
return point instanceof G2.Point ? point : G2.Point.hashToCurve(point); return point instanceof G2.ProjectivePoint
? point
: (G2HashToCurve.hashToCurve(point, htfOpts) as G2);
} }
// Multiplies generator by private key. // Multiplies generator by private key.
// P = pk x G // P = pk x G
function getPublicKey(privateKey: PrivKey): Uint8Array { 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. // Executes `hashToCurve` on the message and then multiplies the result by private key.
// S = pk x H(m) // S = pk x H(m)
function sign(message: Hex, privateKey: PrivKey): Uint8Array; function sign(message: Hex, privateKey: PrivKey, htfOpts?: htf.htfBasicOpts): Uint8Array;
function sign(message: G2, privateKey: PrivKey): G2; function sign(message: G2, privateKey: PrivKey, htfOpts?: htf.htfBasicOpts): G2;
function sign(message: G2Hex, privateKey: PrivKey): Uint8Array | G2 { function sign(message: G2Hex, privateKey: PrivKey, htfOpts?: htf.htfBasicOpts): Uint8Array | G2 {
const msgPoint = normP2Hash(message); const msgPoint = normP2Hash(message, htfOpts);
msgPoint.assertValidity(); msgPoint.assertValidity();
const sigPoint = msgPoint.multiply(G1.normalizePrivateKey(privateKey)); const sigPoint = msgPoint.multiply(G1.normalizePrivateKey(privateKey));
if (message instanceof G2.Point) return sigPoint; if (message instanceof G2.ProjectivePoint) return sigPoint;
return Signature.encode(sigPoint); return Signature.encode(sigPoint);
} }
// Checks if pairing of public key & hash is equal to pairing of generator & signature. // Checks if pairing of public key & hash is equal to pairing of generator & signature.
// e(P, H(m)) == e(G, S) // e(P, H(m)) == e(G, S)
function verify(signature: G2Hex, message: G2Hex, publicKey: G1Hex): boolean { function verify(
signature: G2Hex,
message: G2Hex,
publicKey: G1Hex,
htfOpts?: htf.htfBasicOpts
): boolean {
const P = normP1(publicKey); const P = normP1(publicKey);
const Hm = normP2Hash(message); const Hm = normP2Hash(message, htfOpts);
const G = G1.Point.BASE; const G = G1.ProjectivePoint.BASE;
const S = normP2(signature); const S = normP2(signature);
// Instead of doing 2 exponentiations, we use property of billinear maps // Instead of doing 2 exponentiations, we use property of billinear maps
// and do one exp after multiplying 2 points. // and do one exp after multiplying 2 points.
const ePHm = pairing(P.negate(), Hm, false); const ePHm = pairing(P.negate(), Hm, false);
const eGS = pairing(G, S, false); const eGS = pairing(G, S, false);
const exp = Fp12.finalExponentiate(Fp12.mul(eGS, ePHm)); 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. // Adds a bunch of public key points together.
@@ -293,11 +307,9 @@ export function bls<Fp2, Fp6, Fp12>(
function aggregatePublicKeys(publicKeys: G1[]): G1; function aggregatePublicKeys(publicKeys: G1[]): G1;
function aggregatePublicKeys(publicKeys: G1Hex[]): Uint8Array | G1 { function aggregatePublicKeys(publicKeys: G1Hex[]): Uint8Array | G1 {
if (!publicKeys.length) throw new Error('Expected non-empty array'); if (!publicKeys.length) throw new Error('Expected non-empty array');
const agg = publicKeys const agg = publicKeys.map(normP1).reduce((sum, p) => sum.add(p), G1.ProjectivePoint.ZERO);
.map(normP1) const aggAffine = agg; //.toAffine();
.reduce((sum, p) => sum.add(G1.ProjectivePoint.fromAffine(p)), G1.ProjectivePoint.ZERO); if (publicKeys[0] instanceof G1.ProjectivePoint) {
const aggAffine = agg.toAffine();
if (publicKeys[0] instanceof G1.Point) {
aggAffine.assertValidity(); aggAffine.assertValidity();
return aggAffine; return aggAffine;
} }
@@ -310,11 +322,9 @@ export function bls<Fp2, Fp6, Fp12>(
function aggregateSignatures(signatures: G2[]): G2; function aggregateSignatures(signatures: G2[]): G2;
function aggregateSignatures(signatures: G2Hex[]): Uint8Array | G2 { function aggregateSignatures(signatures: G2Hex[]): Uint8Array | G2 {
if (!signatures.length) throw new Error('Expected non-empty array'); if (!signatures.length) throw new Error('Expected non-empty array');
const agg = signatures const agg = signatures.map(normP2).reduce((sum, s) => sum.add(s), G2.ProjectivePoint.ZERO);
.map(normP2) const aggAffine = agg; //.toAffine();
.reduce((sum, s) => sum.add(G2.ProjectivePoint.fromAffine(s)), G2.ProjectivePoint.ZERO); if (signatures[0] instanceof G2.ProjectivePoint) {
const aggAffine = agg.toAffine();
if (signatures[0] instanceof G2.Point) {
aggAffine.assertValidity(); aggAffine.assertValidity();
return aggAffine; return aggAffine;
} }
@@ -323,12 +333,20 @@ export function bls<Fp2, Fp6, Fp12>(
// https://ethresear.ch/t/fast-verification-of-multiple-bls-signatures/5407 // https://ethresear.ch/t/fast-verification-of-multiple-bls-signatures/5407
// e(G, S) = e(G, SUM(n)(Si)) = MUL(n)(e(G, Si)) // e(G, S) = e(G, SUM(n)(Si)) = MUL(n)(e(G, Si))
function verifyBatch(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 (!messages.length) throw new Error('Expected non-empty messages array');
if (publicKeys.length !== messages.length) if (publicKeys.length !== messages.length)
throw new Error('Pubkey count should equal msg count'); throw new Error('Pubkey count should equal msg count');
const sig = normP2(signature); const sig = normP2(signature);
const nMessages = messages.map(normP2Hash); const nMessages = messages.map((i) => normP2Hash(i, htfOpts));
const nPublicKeys = publicKeys.map(normP1); const nPublicKeys = publicKeys.map(normP1);
try { try {
const paired = []; const paired = [];
@@ -336,23 +354,23 @@ export function bls<Fp2, Fp6, Fp12>(
const groupPublicKey = nMessages.reduce( const groupPublicKey = nMessages.reduce(
(groupPublicKey, subMessage, i) => (groupPublicKey, subMessage, i) =>
subMessage === message ? groupPublicKey.add(nPublicKeys[i]) : groupPublicKey, subMessage === message ? groupPublicKey.add(nPublicKeys[i]) : groupPublicKey,
G1.Point.ZERO G1.ProjectivePoint.ZERO
); );
// const msg = message instanceof PointG2 ? message : await PointG2.hashToCurve(message); // const msg = message instanceof PointG2 ? message : await PointG2.hashToCurve(message);
// Possible to batch pairing for same msg with different groupPublicKey here // Possible to batch pairing for same msg with different groupPublicKey here
paired.push(pairing(groupPublicKey, message, false)); 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 product = paired.reduce((a, b) => Fp12.mul(a, b), Fp12.ONE);
const exp = Fp12.finalExponentiate(product); const exp = Fp12.finalExponentiate(product);
return Fp12.equals(exp, Fp12.ONE); return Fp12.eql(exp, Fp12.ONE);
} catch { } catch {
return false; return false;
} }
} }
// Pre-compute points. Refer to README. G1.ProjectivePoint.BASE._setWindowSize(4);
G1.Point.BASE._setWindowSize(4);
return { return {
CURVE, CURVE,
Fr, Fr,
@@ -365,6 +383,7 @@ export function bls<Fp2, Fp6, Fp12>(
Signature, Signature,
millerLoop, millerLoop,
calcPairingPrecomputes, calcPairingPrecomputes,
hashToCurve: { G1: G1HashToCurve, G2: G2HashToCurve },
pairing, pairing,
getPublicKey, getPublicKey,
sign, sign,

View File

@@ -1,22 +1,32 @@
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */ /*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
// Abelian group utilities // Abelian group utilities
import { Field, validateField, nLength } from './modular.js';
import { validateObject } from './utils.js';
const _0n = BigInt(0); const _0n = BigInt(0);
const _1n = BigInt(1); const _1n = BigInt(1);
export type AffinePoint<T> = {
x: T;
y: T;
} & { z?: never; t?: never };
export interface Group<T extends Group<T>> { export interface Group<T extends Group<T>> {
double(): T; double(): T;
negate(): T; negate(): T;
add(other: T): T; add(other: T): T;
subtract(other: T): T; subtract(other: T): T;
equals(other: T): boolean; equals(other: T): boolean;
multiply(scalar: number | bigint): T; multiply(scalar: bigint): T;
} }
export type GroupConstructor<T> = { export type GroupConstructor<T> = {
BASE: T; BASE: T;
ZERO: 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. Complicated and fragile. Uses wNAF method.
// Windowed method is 10% faster, but takes 2x longer to generate & consumes 2x memory.
export function wNAF<T extends Group<T>>(c: GroupConstructor<T>, bits: number) { export function wNAF<T extends Group<T>>(c: GroupConstructor<T>, bits: number) {
const constTimeNegate = (condition: boolean, item: T): T => { const constTimeNegate = (condition: boolean, item: T): T => {
const neg = item.negate(); const neg = item.negate();
@@ -125,5 +135,52 @@ export function wNAF<T extends Group<T>>(c: GroupConstructor<T>, bits: number) {
// which makes it less const-time: around 1 bigint multiply. // which makes it less const-time: around 1 bigint multiply.
return { p, f }; 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: Field<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 } as const);
}

View File

@@ -1,19 +1,9 @@
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */ /*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
// Twisted Edwards curve. The formula is: ax² + y² = 1 + dx²y² // Twisted Edwards curve. The formula is: ax² + y² = 1 + dx²y²
import { mod } from './modular.js';
// Differences from @noble/ed25519 1.7:
// 1. Variable field element lengths between EDDSA/ECDH:
// EDDSA (RFC8032) is 456 bits / 57 bytes, ECDH (RFC7748) is 448 bits / 56 bytes
// 2. Different addition formula (doubling is same)
// 3. uvRatio differs between curves (half-expected, not only pow fn changes)
// 4. Point decompression code is different (unexpected), now using generalized formula
// 5. Domain function was no-op for ed25519, but adds some data even with empty context for ed448
import * as mod from './modular.js';
import * as ut from './utils.js'; import * as ut from './utils.js';
import { ensureBytes, Hex, PrivKey } from './utils.js'; import { ensureBytes, FHash, Hex } from './utils.js';
import { Group, GroupConstructor, wNAF } from './group.js'; import { Group, GroupConstructor, wNAF, BasicCurve, validateBasic, AffinePoint } from './curve.js';
import { hash_to_field as hashToField, htfOpts, validateHTFOpts } from './hash-to-curve.js';
// Be friendly to bad ECMAScript parsers by not using bigint literals like 123n // Be friendly to bad ECMAScript parsers by not using bigint literals like 123n
const _0n = BigInt(0); const _0n = BigInt(0);
@@ -22,142 +12,92 @@ const _2n = BigInt(2);
const _8n = BigInt(8); const _8n = BigInt(8);
// Edwards curves must declare params a & d. // Edwards curves must declare params a & d.
export type CurveType = ut.BasicCurve<bigint> & { export type CurveType = BasicCurve<bigint> & {
// Params: a, d a: bigint; // curve param a
a: bigint; d: bigint; // curve param d
d: bigint; hash: FHash; // Hashing
// Hashes randomBytes: (bytesLength?: number) => Uint8Array; // CSPRNG
// The interface, because we need outputLen for DRBG adjustScalarBytes?: (bytes: Uint8Array) => Uint8Array; // clears bits to get valid field elemtn
hash: ut.CHash; domain?: (data: Uint8Array, ctx: Uint8Array, phflag: boolean) => Uint8Array; // Used for hashing
// CSPRNG uvRatio?: (u: bigint, v: bigint) => { isValid: boolean; value: bigint }; // Ratio √(u/v)
randomBytes: (bytesLength?: number) => Uint8Array; preHash?: FHash; // RFC 8032 pre-hashing of messages to sign() / verify()
// Probably clears bits in a byte array to produce a valid field element mapToCurve?: (scalar: bigint[]) => AffinePoint<bigint>; // for hash-to-curve standard
adjustScalarBytes?: (bytes: Uint8Array) => Uint8Array;
// Used during hashing
domain?: (data: Uint8Array, ctx: Uint8Array, phflag: boolean) => Uint8Array;
// Ratio √(u/v)
uvRatio?: (u: bigint, v: bigint) => { isValid: boolean; value: bigint };
// RFC 8032 pre-hashing of messages to sign() / verify()
preHash?: ut.CHash;
// Hash to field options
htfDefaults?: htfOpts;
mapToCurve?: (scalar: bigint[]) => { x: bigint; y: bigint };
}; };
function validateOpts(curve: CurveType) { function validateOpts(curve: CurveType) {
const opts = ut.validateOpts(curve); const opts = validateBasic(curve);
if (typeof opts.hash !== 'function' || !ut.isPositiveInt(opts.hash.outputLen)) ut.validateObject(
throw new Error('Invalid hash function'); curve,
for (const i of ['a', 'd'] as const) { {
const val = opts[i]; hash: 'function',
if (typeof val !== 'bigint') throw new Error(`Invalid curve param ${i}=${val} (${typeof val})`); a: 'bigint',
} d: 'bigint',
for (const fn of ['randomBytes'] as const) { randomBytes: 'function',
if (typeof opts[fn] !== 'function') throw new Error(`Invalid ${fn} function`); },
} {
for (const fn of ['adjustScalarBytes', 'domain', 'uvRatio', 'mapToCurve'] as const) { adjustScalarBytes: 'function',
if (opts[fn] === undefined) continue; // Optional domain: 'function',
if (typeof opts[fn] !== 'function') throw new Error(`Invalid ${fn} function`); uvRatio: 'function',
} mapToCurve: 'function',
if (opts.htfDefaults !== undefined) validateHTFOpts(opts.htfDefaults); }
);
// Set defaults // Set defaults
return Object.freeze({ ...opts } as const); return Object.freeze({ ...opts } as const);
} }
// Instance
export interface SignatureType {
readonly r: PointType;
readonly s: bigint;
assertValidity(): SignatureType;
toRawBytes(): Uint8Array;
toHex(): string;
}
// Static methods
export type SignatureConstructor = {
new (r: PointType, s: bigint): SignatureType;
fromHex(hex: Hex): SignatureType;
};
// Instance of Extended Point with coordinates in X, Y, Z, T // Instance of Extended Point with coordinates in X, Y, Z, T
export interface ExtendedPointType extends Group<ExtendedPointType> { export interface ExtPointType extends Group<ExtPointType> {
readonly x: bigint; readonly ex: bigint;
readonly y: bigint; readonly ey: bigint;
readonly z: bigint; readonly ez: bigint;
readonly t: bigint; readonly et: bigint;
multiply(scalar: number | bigint, affinePoint?: PointType): ExtendedPointType; assertValidity(): void;
multiplyUnsafe(scalar: number | bigint): ExtendedPointType; multiply(scalar: bigint): ExtPointType;
multiplyUnsafe(scalar: bigint): ExtPointType;
isSmallOrder(): boolean; isSmallOrder(): boolean;
isTorsionFree(): boolean; isTorsionFree(): boolean;
toAffine(invZ?: bigint): PointType; clearCofactor(): ExtPointType;
clearCofactor(): ExtendedPointType; toAffine(iz?: bigint): AffinePoint<bigint>;
} }
// Static methods of Extended Point with coordinates in X, Y, Z, T // Static methods of Extended Point with coordinates in X, Y, Z, T
export interface ExtendedPointConstructor extends GroupConstructor<ExtendedPointType> { export interface ExtPointConstructor extends GroupConstructor<ExtPointType> {
new (x: bigint, y: bigint, z: bigint, t: bigint): ExtendedPointType; new (x: bigint, y: bigint, z: bigint, t: bigint): ExtPointType;
fromAffine(p: PointType): ExtendedPointType; fromAffine(p: AffinePoint<bigint>): ExtPointType;
toAffineBatch(points: ExtendedPointType[]): PointType[]; fromHex(hex: Hex): ExtPointType;
normalizeZ(points: ExtendedPointType[]): ExtendedPointType[]; fromPrivateKey(privateKey: Hex): ExtPointType;
} }
// Instance of Affine Point with coordinates in X, Y
export interface PointType extends Group<PointType> {
readonly x: bigint;
readonly y: bigint;
_setWindowSize(windowSize: number): void;
toRawBytes(isCompressed?: boolean): Uint8Array;
toHex(isCompressed?: boolean): string;
isTorsionFree(): boolean;
clearCofactor(): PointType;
}
// Static methods of Affine Point with coordinates in X, Y
export interface PointConstructor extends GroupConstructor<PointType> {
new (x: bigint, y: bigint): PointType;
fromHex(hex: Hex): PointType;
fromPrivateKey(privateKey: PrivKey): PointType;
hashToCurve(msg: Hex, options?: Partial<htfOpts>): PointType;
encodeToCurve(msg: Hex, options?: Partial<htfOpts>): PointType;
}
export type PubKey = Hex | PointType;
export type SigType = Hex | SignatureType;
export type CurveFn = { export type CurveFn = {
CURVE: ReturnType<typeof validateOpts>; CURVE: ReturnType<typeof validateOpts>;
getPublicKey: (privateKey: PrivKey, isCompressed?: boolean) => Uint8Array; getPublicKey: (privateKey: Hex) => Uint8Array;
sign: (message: Hex, privateKey: Hex) => Uint8Array; sign: (message: Hex, privateKey: Hex) => Uint8Array;
verify: (sig: SigType, message: Hex, publicKey: PubKey) => boolean; verify: (sig: Hex, message: Hex, publicKey: Hex) => boolean;
Point: PointConstructor; ExtendedPoint: ExtPointConstructor;
ExtendedPoint: ExtendedPointConstructor;
Signature: SignatureConstructor;
utils: { utils: {
randomPrivateKey: () => Uint8Array; randomPrivateKey: () => Uint8Array;
getExtendedPublicKey: (key: PrivKey) => { getExtendedPublicKey: (key: Hex) => {
head: Uint8Array; head: Uint8Array;
prefix: Uint8Array; prefix: Uint8Array;
scalar: bigint; scalar: bigint;
point: PointType; point: ExtPointType;
pointBytes: Uint8Array; pointBytes: Uint8Array;
}; };
}; };
}; };
// NOTE: it is not generic twisted curve for now, but ed25519/ed448 generic implementation // It is not generic twisted curve for now, but ed25519/ed448 generic implementation
export function twistedEdwards(curveDef: CurveType): CurveFn { export function twistedEdwards(curveDef: CurveType): CurveFn {
const CURVE = validateOpts(curveDef) as ReturnType<typeof validateOpts>; const CURVE = validateOpts(curveDef) as ReturnType<typeof validateOpts>;
const Fp = CURVE.Fp; const { Fp, n: CURVE_ORDER, preHash, hash: cHash, randomBytes, nByteLength, h: cofactor } = CURVE;
const CURVE_ORDER = CURVE.n; const MASK = _2n ** BigInt(nByteLength * 8);
const maxGroupElement = _2n ** BigInt(CURVE.nByteLength * 8); const modP = Fp.create; // Function overrides
// Function overrides
const { randomBytes } = CURVE;
const modP = Fp.create;
// sqrt(u/v) // sqrt(u/v)
const uvRatio = const uvRatio =
CURVE.uvRatio || CURVE.uvRatio ||
((u: bigint, v: bigint) => { ((u: bigint, v: bigint) => {
try { try {
return { isValid: true, value: Fp.sqrt(u * Fp.invert(v)) }; return { isValid: true, value: Fp.sqrt(u * Fp.inv(v)) };
} catch (e) { } catch (e) {
return { isValid: false, value: _0n }; return { isValid: false, value: _0n };
} }
@@ -169,41 +109,76 @@ export function twistedEdwards(curveDef: CurveType): CurveFn {
if (ctx.length || phflag) throw new Error('Contexts/pre-hash are not supported'); if (ctx.length || phflag) throw new Error('Contexts/pre-hash are not supported');
return data; return data;
}); // NOOP }); // NOOP
const inBig = (n: bigint) => typeof n === 'bigint' && 0n < n; // n in [1..]
const inRange = (n: bigint, max: bigint) => inBig(n) && inBig(max) && n < max; // n in [1..max-1]
const in0MaskRange = (n: bigint) => n === _0n || inRange(n, MASK); // n in [0..MASK-1]
function assertInRange(n: bigint, max: bigint) {
// n in [1..max-1]
if (inRange(n, max)) return n;
throw new Error(`Expected valid scalar < ${max}, got ${typeof n} ${n}`);
}
function assertGE0(n: bigint) {
// n in [0..CURVE_ORDER-1]
return n === _0n ? n : assertInRange(n, CURVE_ORDER); // GE = prime subgroup, not full group
}
const pointPrecomputes = new Map<Point, Point[]>();
function isPoint(other: unknown) {
if (!(other instanceof Point)) throw new Error('ExtendedPoint expected');
}
// Extended Point works in extended coordinates: (x, y, z, t) ∋ (x=x/z, y=y/z, t=xy).
// https://en.wikipedia.org/wiki/Twisted_Edwards_curve#Extended_coordinates
class Point implements ExtPointType {
static readonly BASE = new Point(CURVE.Gx, CURVE.Gy, _1n, modP(CURVE.Gx * CURVE.Gy));
static readonly ZERO = new Point(_0n, _1n, _1n, _0n); // 0, 1, 1, 0
/** constructor(
* Extended Point works in extended coordinates: (x, y, z, t) ∋ (x=x/z, y=y/z, t=xy). readonly ex: bigint,
* Default Point works in affine coordinates: (x, y) readonly ey: bigint,
* https://en.wikipedia.org/wiki/Twisted_Edwards_curve#Extended_coordinates readonly ez: bigint,
*/ readonly et: bigint
class ExtendedPoint implements ExtendedPointType { ) {
constructor(readonly x: bigint, readonly y: bigint, readonly z: bigint, readonly t: bigint) {} if (!in0MaskRange(ex)) throw new Error('x required');
if (!in0MaskRange(ey)) throw new Error('y required');
static BASE = new ExtendedPoint(CURVE.Gx, CURVE.Gy, _1n, modP(CURVE.Gx * CURVE.Gy)); if (!in0MaskRange(ez)) throw new Error('z required');
static ZERO = new ExtendedPoint(_0n, _1n, _1n, _0n); if (!in0MaskRange(et)) throw new Error('t required');
static fromAffine(p: Point): ExtendedPoint {
if (!(p instanceof Point)) {
throw new TypeError('ExtendedPoint#fromAffine: expected Point');
}
if (p.equals(Point.ZERO)) return ExtendedPoint.ZERO;
return new ExtendedPoint(p.x, p.y, _1n, modP(p.x * p.y));
}
// Takes a bunch of Jacobian Points but executes only one
// invert on all of them. invert is very slow operation,
// so this improves performance massively.
static toAffineBatch(points: ExtendedPoint[]): Point[] {
const toInv = Fp.invertBatch(points.map((p) => p.z));
return points.map((p, i) => p.toAffine(toInv[i]));
} }
static normalizeZ(points: ExtendedPoint[]): ExtendedPoint[] { get x(): bigint {
return this.toAffineBatch(points).map(this.fromAffine); return this.toAffine().x;
} }
get y(): bigint {
return this.toAffine().y;
}
static fromAffine(p: AffinePoint<bigint>): Point {
if (p instanceof Point) throw new Error('extended point not allowed');
const { x, y } = p || {};
if (!in0MaskRange(x) || !in0MaskRange(y)) throw new Error('invalid affine point');
return new Point(x, y, _1n, modP(x * y));
}
static normalizeZ(points: Point[]): Point[] {
const toInv = Fp.invertBatch(points.map((p) => p.ez));
return points.map((p, i) => p.toAffine(toInv[i])).map(Point.fromAffine);
}
// We calculate precomputes for elliptic curve point multiplication
// using windowed method. This specifies window size and
// stores precomputed values. Usually only base point would be precomputed.
_WINDOW_SIZE?: number;
// "Private method", don't use it directly
_setWindowSize(windowSize: number) {
this._WINDOW_SIZE = windowSize;
pointPrecomputes.delete(this);
}
assertValidity(): void {}
// Compare one point to another. // Compare one point to another.
equals(other: ExtendedPoint): boolean { equals(other: Point): boolean {
assertExtPoint(other); isPoint(other);
const { x: X1, y: Y1, z: Z1 } = this; const { ex: X1, ey: Y1, ez: Z1 } = this;
const { x: X2, y: Y2, z: Z2 } = other; const { ex: X2, ey: Y2, ez: Z2 } = other;
const X1Z2 = modP(X1 * Z2); const X1Z2 = modP(X1 * Z2);
const X2Z1 = modP(X2 * Z1); const X2Z1 = modP(X2 * Z1);
const Y1Z2 = modP(Y1 * Z2); const Y1Z2 = modP(Y1 * Z2);
@@ -211,17 +186,21 @@ export function twistedEdwards(curveDef: CurveType): CurveFn {
return X1Z2 === X2Z1 && Y1Z2 === Y2Z1; return X1Z2 === X2Z1 && Y1Z2 === Y2Z1;
} }
// Inverses point to one corresponding to (x, -y) in Affine coordinates. protected is0(): boolean {
negate(): ExtendedPoint { return this.equals(Point.ZERO);
return new ExtendedPoint(modP(-this.x), this.y, this.z, modP(-this.t)); }
negate(): Point {
// Flips point sign to a negative one (-x, y in affine coords)
return new Point(modP(-this.ex), this.ey, this.ez, modP(-this.et));
} }
// Fast algo for doubling Extended Point. // Fast algo for doubling Extended Point.
// https://hyperelliptic.org/EFD/g1p/auto-twisted-extended.html#doubling-dbl-2008-hwcd // https://hyperelliptic.org/EFD/g1p/auto-twisted-extended.html#doubling-dbl-2008-hwcd
// Cost: 4M + 4S + 1*a + 6add + 1*2. // Cost: 4M + 4S + 1*a + 6add + 1*2.
double(): ExtendedPoint { double(): Point {
const { a } = CURVE; const { a } = CURVE;
const { x: X1, y: Y1, z: Z1 } = this; const { ex: X1, ey: Y1, ez: Z1 } = this;
const A = modP(X1 * X1); // A = X12 const A = modP(X1 * X1); // A = X12
const B = modP(Y1 * Y1); // B = Y12 const B = modP(Y1 * Y1); // B = Y12
const C = modP(_2n * modP(Z1 * Z1)); // C = 2*Z12 const C = modP(_2n * modP(Z1 * Z1)); // C = 2*Z12
@@ -235,17 +214,17 @@ export function twistedEdwards(curveDef: CurveType): CurveFn {
const Y3 = modP(G * H); // Y3 = G*H const Y3 = modP(G * H); // Y3 = G*H
const T3 = modP(E * H); // T3 = E*H const T3 = modP(E * H); // T3 = E*H
const Z3 = modP(F * G); // Z3 = F*G const Z3 = modP(F * G); // Z3 = F*G
return new ExtendedPoint(X3, Y3, Z3, T3); return new Point(X3, Y3, Z3, T3);
} }
// Fast algo for adding 2 Extended Points. // Fast algo for adding 2 Extended Points.
// https://hyperelliptic.org/EFD/g1p/auto-twisted-extended.html#addition-add-2008-hwcd // https://hyperelliptic.org/EFD/g1p/auto-twisted-extended.html#addition-add-2008-hwcd
// Cost: 9M + 1*a + 1*d + 7add. // Cost: 9M + 1*a + 1*d + 7add.
add(other: ExtendedPoint) { add(other: Point) {
assertExtPoint(other); isPoint(other);
const { a, d } = CURVE; const { a, d } = CURVE;
const { x: X1, y: Y1, z: Z1, t: T1 } = this; const { ex: X1, ey: Y1, ez: Z1, et: T1 } = this;
const { x: X2, y: Y2, z: Z2, t: T2 } = other; const { ex: X2, ey: Y2, ez: Z2, et: T2 } = other;
// Faster algo for adding 2 Extended Points when curve's a=-1. // Faster algo for adding 2 Extended Points when curve's a=-1.
// http://hyperelliptic.org/EFD/g1p/auto-twisted-extended-1.html#addition-add-2008-hwcd-4 // http://hyperelliptic.org/EFD/g1p/auto-twisted-extended-1.html#addition-add-2008-hwcd-4
// Cost: 8M + 8add + 2*2. // Cost: 8M + 8add + 2*2.
@@ -264,7 +243,7 @@ export function twistedEdwards(curveDef: CurveType): CurveFn {
const Y3 = modP(G * H); const Y3 = modP(G * H);
const T3 = modP(E * H); const T3 = modP(E * H);
const Z3 = modP(F * G); const Z3 = modP(F * G);
return new ExtendedPoint(X3, Y3, Z3, T3); return new Point(X3, Y3, Z3, T3);
} }
const A = modP(X1 * X2); // A = X1*X2 const A = modP(X1 * X2); // A = X1*X2
const B = modP(Y1 * Y2); // B = Y1*Y2 const B = modP(Y1 * Y2); // B = Y1*Y2
@@ -279,44 +258,31 @@ export function twistedEdwards(curveDef: CurveType): CurveFn {
const T3 = modP(E * H); // T3 = E*H const T3 = modP(E * H); // T3 = E*H
const Z3 = modP(F * G); // Z3 = F*G const Z3 = modP(F * G); // Z3 = F*G
return new ExtendedPoint(X3, Y3, Z3, T3); return new Point(X3, Y3, Z3, T3);
} }
subtract(other: ExtendedPoint): ExtendedPoint { subtract(other: Point): Point {
return this.add(other.negate()); return this.add(other.negate());
} }
private wNAF(n: bigint, affinePoint?: Point): ExtendedPoint { private wNAF(n: bigint): { p: Point; f: Point } {
if (!affinePoint && this.equals(ExtendedPoint.BASE)) affinePoint = Point.BASE; return wnaf.wNAFCached(this, pointPrecomputes, n, Point.normalizeZ);
const W = (affinePoint && affinePoint._WINDOW_SIZE) || 1;
let precomputes = affinePoint && pointPrecomputes.get(affinePoint);
if (!precomputes) {
precomputes = wnaf.precomputeWindow(this, W) as ExtendedPoint[];
if (affinePoint && W !== 1) {
precomputes = ExtendedPoint.normalizeZ(precomputes);
pointPrecomputes.set(affinePoint, precomputes);
}
}
const { p, f } = wnaf.wNAF(W, precomputes, n);
return ExtendedPoint.normalizeZ([p, f])[0];
} }
// Constant time multiplication. // Constant-time multiplication.
// Uses wNAF method. Windowed method may be 10% faster, multiply(scalar: bigint): Point {
// but takes 2x longer to generate and consumes 2x memory. const { p, f } = this.wNAF(assertInRange(scalar, CURVE_ORDER));
multiply(scalar: number | bigint, affinePoint?: Point): ExtendedPoint { return Point.normalizeZ([p, f])[0];
return this.wNAF(normalizeScalar(scalar, CURVE_ORDER), affinePoint);
} }
// Non-constant-time multiplication. Uses double-and-add algorithm. // Non-constant-time multiplication. Uses double-and-add algorithm.
// It's faster, but should only be used when you don't care about // It's faster, but should only be used when you don't care about
// an exposed private key e.g. sig verification. // an exposed private key e.g. sig verification.
multiplyUnsafe(scalar: number | bigint): ExtendedPoint { multiplyUnsafe(scalar: bigint): Point {
let n = normalizeScalar(scalar, CURVE_ORDER, false); let n = assertGE0(scalar);
const P0 = ExtendedPoint.ZERO; if (n === _0n) return I;
if (n === _0n) return P0; if (this.equals(I) || n === _1n) return this;
if (this.equals(P0) || n === _1n) return this; if (this.equals(G)) return this.wNAF(n).p;
if (this.equals(ExtendedPoint.BASE)) return this.wNAF(n);
return wnaf.unsafeLadder(this, n); return wnaf.unsafeLadder(this, n);
} }
@@ -325,350 +291,156 @@ export function twistedEdwards(curveDef: CurveType): CurveFn {
// point with torsion component. // point with torsion component.
// Multiplies point by cofactor and checks if the result is 0. // Multiplies point by cofactor and checks if the result is 0.
isSmallOrder(): boolean { isSmallOrder(): boolean {
return this.multiplyUnsafe(CURVE.h).equals(ExtendedPoint.ZERO); return this.multiplyUnsafe(cofactor).is0();
} }
// Multiplies point by curve order (very big scalar CURVE.n) and checks if the result is 0. // Multiplies point by curve order and checks if the result is 0.
// Returns `false` is the point is dirty. // Returns `false` is the point is dirty.
isTorsionFree(): boolean { isTorsionFree(): boolean {
return wnaf.unsafeLadder(this, CURVE_ORDER).equals(ExtendedPoint.ZERO); return wnaf.unsafeLadder(this, CURVE_ORDER).is0();
} }
// Converts Extended point to default (x, y) coordinates. // Converts Extended point to default (x, y) coordinates.
// Can accept precomputed Z^-1 - for example, from invertBatch. // Can accept precomputed Z^-1 - for example, from invertBatch.
toAffine(invZ?: bigint): Point { toAffine(iz?: bigint): AffinePoint<bigint> {
const { x, y, z } = this; const { ex: x, ey: y, ez: z } = this;
const is0 = this.equals(ExtendedPoint.ZERO); const is0 = this.is0();
if (invZ == null) invZ = is0 ? _8n : (Fp.invert(z) as bigint); // 8 was chosen arbitrarily if (iz == null) iz = is0 ? _8n : (Fp.inv(z) as bigint); // 8 was chosen arbitrarily
const ax = modP(x * invZ); const ax = modP(x * iz);
const ay = modP(y * invZ); const ay = modP(y * iz);
const zz = modP(z * invZ); const zz = modP(z * iz);
if (is0) return Point.ZERO; if (is0) return { x: _0n, y: _1n };
if (zz !== _1n) throw new Error('invZ was invalid'); if (zz !== _1n) throw new Error('invZ was invalid');
return new Point(ax, ay); return { x: ax, y: ay };
} }
clearCofactor(): ExtendedPoint {
clearCofactor(): Point {
const { h: cofactor } = CURVE; const { h: cofactor } = CURVE;
if (cofactor === _1n) return this; if (cofactor === _1n) return this;
return this.multiplyUnsafe(cofactor); return this.multiplyUnsafe(cofactor);
} }
}
const wnaf = wNAF(ExtendedPoint, CURVE.nByteLength * 8);
function assertExtPoint(other: unknown) {
if (!(other instanceof ExtendedPoint)) throw new TypeError('ExtendedPoint expected');
}
// Stores precomputed values for points.
const pointPrecomputes = new WeakMap<Point, ExtendedPoint[]>();
/**
* Default Point works in affine coordinates: (x, y)
*/
class Point implements PointType {
// Base point aka generator
// public_key = Point.BASE * private_key
static BASE: Point = new Point(CURVE.Gx, CURVE.Gy);
// Identity point aka point at infinity
// point = point + zero_point
static ZERO: Point = new Point(_0n, _1n);
// We calculate precomputes for elliptic curve point multiplication
// using windowed method. This specifies window size and
// stores precomputed values. Usually only base point would be precomputed.
_WINDOW_SIZE?: number;
constructor(readonly x: bigint, readonly y: bigint) {}
// "Private method", don't use it directly.
_setWindowSize(windowSize: number) {
this._WINDOW_SIZE = windowSize;
pointPrecomputes.delete(this);
}
// Converts hash string or Uint8Array to Point. // Converts hash string or Uint8Array to Point.
// Uses algo from RFC8032 5.1.3. // Uses algo from RFC8032 5.1.3.
static fromHex(hex: Hex, strict = true) { static fromHex(hex: Hex, strict = true): Point {
const { d, a } = CURVE; const { d, a } = CURVE;
const len = Fp.BYTES; const len = Fp.BYTES;
hex = ensureBytes(hex, len); hex = ensureBytes(hex, len); // copy hex to a new array
// 1. First, interpret the string as an integer in little-endian const normed = hex.slice(); // copy again, we'll manipulate it
// representation. Bit 255 of this number is the least significant const lastByte = hex[len - 1]; // select last byte
// bit of the x-coordinate and denote this value x_0. The normed[len - 1] = lastByte & ~0x80; // clear last bit
// y-coordinate is recovered simply by clearing this bit. If the
// resulting value is >= p, decoding fails.
const normed = hex.slice();
const lastByte = hex[len - 1];
normed[len - 1] = lastByte & ~0x80;
const y = ut.bytesToNumberLE(normed); const y = ut.bytesToNumberLE(normed);
if (y === _0n) {
if (strict && y >= Fp.ORDER) throw new Error('Expected 0 < hex < P'); // y=0 is allowed
if (!strict && y >= maxGroupElement) throw new Error('Expected 0 < hex < CURVE.n');
// 2. To recover the x-coordinate, the curve equation implies
// Ed25519: x² = (y² - 1) / (d y² + 1) (mod p).
// Ed448: x² = (y² - 1) / (d y² - 1) (mod p).
// For generic case:
// a*x²+y²=1+d*x²*y²
// -> y²-1 = d*x²*y²-a*x²
// -> y²-1 = x² (d*y²-a)
// -> x² = (y²-1) / (d*y²-a)
// The denominator is always non-zero mod p. Let u = y² - 1 and v = d y² + 1.
const y2 = modP(y * y);
const u = modP(y2 - _1n);
const v = modP(d * y2 - a);
let { isValid, value: x } = uvRatio(u, v);
if (!isValid) throw new Error('Point.fromHex: invalid y coordinate');
// 4. Finally, use the x_0 bit to select the right square root. If
// x = 0, and x_0 = 1, decoding fails. Otherwise, if x_0 != x mod
// 2, set x <-- p - x. Return the decoded point (x,y).
const isXOdd = (x & _1n) === _1n;
const isLastByteOdd = (lastByte & 0x80) !== 0;
if (isLastByteOdd !== isXOdd) x = modP(-x);
return new Point(x, y);
}
static fromPrivateKey(privateKey: PrivKey) {
return getExtendedPublicKey(privateKey).point;
}
// There can always be only two x values (x, -x) for any y
// When compressing point, it's enough to only store its y coordinate
// and use the last byte to encode sign of x.
toRawBytes(): Uint8Array {
const bytes = ut.numberToBytesLE(this.y, Fp.BYTES);
bytes[Fp.BYTES - 1] |= this.x & _1n ? 0x80 : 0;
return bytes;
}
// Same as toRawBytes, but returns string.
toHex(): string {
return ut.bytesToHex(this.toRawBytes());
}
// Determines if point is in prime-order subgroup.
// Returns `false` is the point is dirty.
isTorsionFree(): boolean {
return ExtendedPoint.fromAffine(this).isTorsionFree();
}
equals(other: Point): boolean {
if (!(other instanceof Point)) throw new TypeError('Point#equals: expected Point');
return this.x === other.x && this.y === other.y;
}
negate(): Point {
return new Point(modP(-this.x), this.y);
}
double(): Point {
return ExtendedPoint.fromAffine(this).double().toAffine();
}
add(other: Point) {
return ExtendedPoint.fromAffine(this).add(ExtendedPoint.fromAffine(other)).toAffine();
}
subtract(other: Point) {
return this.add(other.negate());
}
/**
* Constant time multiplication.
* @param scalar Big-Endian number
* @returns new point
*/
multiply(scalar: number | bigint): Point {
return ExtendedPoint.fromAffine(this).multiply(scalar, this).toAffine();
}
clearCofactor() {
return ExtendedPoint.fromAffine(this).clearCofactor().toAffine();
}
// Encodes byte string to elliptic curve
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-3
static hashToCurve(msg: Hex, options?: Partial<htfOpts>) {
const { mapToCurve, htfDefaults } = CURVE;
if (!mapToCurve) throw new Error('No mapToCurve defined for curve');
const u = hashToField(ensureBytes(msg), 2, { ...htfDefaults, ...options } as htfOpts);
const { x: x0, y: y0 } = mapToCurve(u[0]);
const { x: x1, y: y1 } = mapToCurve(u[1]);
const p = new Point(x0, y0).add(new Point(x1, y1)).clearCofactor();
return p;
}
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-16#section-3
static encodeToCurve(msg: Hex, options?: Partial<htfOpts>) {
const { mapToCurve, htfDefaults } = CURVE;
if (!mapToCurve) throw new Error('No mapToCurve defined for curve');
const u = hashToField(ensureBytes(msg), 1, { ...htfDefaults, ...options } as htfOpts);
const { x, y } = mapToCurve(u[0]);
return new Point(x, y).clearCofactor();
}
}
/**
* EDDSA signature.
*/
class Signature implements SignatureType {
constructor(readonly r: Point, readonly s: bigint) {
this.assertValidity();
}
static fromHex(hex: Hex) {
const len = Fp.BYTES;
const bytes = ensureBytes(hex, 2 * len);
const r = Point.fromHex(bytes.slice(0, len), false);
const s = ut.bytesToNumberLE(bytes.slice(len, 2 * len));
return new Signature(r, s);
}
assertValidity() {
const { r, s } = this;
if (!(r instanceof Point)) throw new Error('Expected Point instance');
// 0 <= s < l
normalizeScalar(s, CURVE_ORDER, false);
return this;
}
toRawBytes() {
return ut.concatBytes(this.r.toRawBytes(), ut.numberToBytesLE(this.s, Fp.BYTES));
}
toHex() {
return ut.bytesToHex(this.toRawBytes());
}
}
// Little-endian SHA512 with modulo n
function modnLE(hash: Uint8Array): bigint {
return mod.mod(ut.bytesToNumberLE(hash), CURVE_ORDER);
}
/**
* 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 (ut.isPositiveInt(num)) num = BigInt(num);
if (typeof num === 'bigint' && num < max) {
if (strict) {
if (_0n < num) return num;
} else { } else {
if (_0n <= num) return num; // RFC8032 prohibits >= p, but ZIP215 doesn't
if (strict) assertInRange(y, Fp.ORDER); // strict=true [1..P-1] (2^255-19-1 for ed25519)
else assertInRange(y, MASK); // strict=false [1..MASK-1] (2^256-1 for ed25519)
} }
// Ed25519: x² = (y²-1)/(dy²+1) mod p. Ed448: x² = (y²-1)/(dy²-1) mod p. Generic case:
// ax²+y²=1+dx²y² => y²-1=dx²y²-ax² => y²-1=x²(dy²-a) => x²=(y²-1)/(dy²-a)
const y2 = modP(y * y); // denominator is always non-0 mod p.
const u = modP(y2 - _1n); // u = y² - 1
const v = modP(d * y2 - a); // v = d y² + 1.
let { isValid, value: x } = uvRatio(u, v); // √(u/v)
if (!isValid) throw new Error('Point.fromHex: invalid y coordinate');
const isXOdd = (x & _1n) === _1n; // There are 2 square roots. Use x_0 bit to select proper
const isLastByteOdd = (lastByte & 0x80) !== 0; // if x=0 and x_0 = 1, fail
if (isLastByteOdd !== isXOdd) x = modP(-x); // if x_0 != x mod 2, set x = p-x
return Point.fromAffine({ x, y });
} }
throw new TypeError(`Expected valid scalar: 0 < scalar < ${max}`); static fromPrivateKey(privKey: Hex) {
return getExtendedPublicKey(privKey).point;
}
toRawBytes(): Uint8Array {
const { x, y } = this.toAffine();
const bytes = ut.numberToBytesLE(y, Fp.BYTES); // each y has 2 x values (x, -y)
bytes[bytes.length - 1] |= x & _1n ? 0x80 : 0; // when compressing, it's enough to store y
return bytes; // and use the last byte to encode sign of x
}
toHex(): string {
return ut.bytesToHex(this.toRawBytes()); // Same as toRawBytes, but returns string.
}
}
const { BASE: G, ZERO: I } = Point;
const wnaf = wNAF(Point, nByteLength * 8);
function modN(a: bigint) {
return mod(a, CURVE_ORDER);
}
// Little-endian SHA512 with modulo n
function modN_LE(hash: Uint8Array): bigint {
return modN(ut.bytesToNumberLE(hash));
}
function isHex(item: Hex, err: string) {
if (typeof item !== 'string' && !(item instanceof Uint8Array))
throw new Error(`${err} must be hex string or Uint8Array`);
} }
/** Convenience method that creates public key and other stuff. RFC8032 5.1.5 */ /** Convenience method that creates public key and other stuff. RFC8032 5.1.5 */
function getExtendedPublicKey(key: PrivKey) { function getExtendedPublicKey(key: Hex) {
const groupLen = CURVE.nByteLength; isHex(key, 'private key');
// Normalize bigint / number / string to Uint8Array const len = nByteLength;
const keyb =
typeof key === 'bigint' || typeof key === 'number'
? ut.numberToBytesLE(normalizeScalar(key, maxGroupElement), groupLen)
: key;
// Hash private key with curve's hash function to produce uniformingly random input // Hash private key with curve's hash function to produce uniformingly random input
// We check byte lengths e.g.: ensureBytes(64, hash(ensureBytes(32, key))) // Check byte lengths: ensure(64, h(ensure(32, key)))
const hashed = ensureBytes(CURVE.hash(ensureBytes(keyb, groupLen)), 2 * groupLen); const hashed = ensureBytes(cHash(ensureBytes(key, len)), 2 * len);
const head = adjustScalarBytes(hashed.slice(0, len)); // clear first half bits, produce FE
// First half's bits are cleared to produce a random field element. const prefix = hashed.slice(len, 2 * len); // second half is called key prefix (5.1.6)
const head = adjustScalarBytes(hashed.slice(0, groupLen)); const scalar = modN_LE(head); // The actual private scalar
// Second half is called key prefix (5.1.6) const point = G.multiply(scalar); // Point on Edwards curve aka public key
const prefix = hashed.slice(groupLen, 2 * groupLen); const pointBytes = point.toRawBytes(); // Uint8Array representation
// The actual private scalar
const scalar = modnLE(head);
// Point on Edwards curve aka public key
const point = Point.BASE.multiply(scalar);
// Uint8Array representation
const pointBytes = point.toRawBytes();
return { head, prefix, scalar, point, pointBytes }; return { head, prefix, scalar, point, pointBytes };
} }
/** // Calculates EdDSA pub key. RFC8032 5.1.5. Privkey is hashed. Use first half with 3 bits cleared
* Calculates ed25519 public key. RFC8032 5.1.5 function getPublicKey(privKey: Hex): Uint8Array {
* 1. private key is hashed with sha512, then first 32 bytes are taken from the hash return getExtendedPublicKey(privKey).pointBytes;
* 2. 3 least significant bits of the first byte are cleared
*/
function getPublicKey(privateKey: PrivKey): Uint8Array {
return getExtendedPublicKey(privateKey).pointBytes;
} }
const EMPTY = new Uint8Array(); // int('LE', SHA512(dom2(F, C) || msgs)) mod N
function hashDomainToScalar(message: Uint8Array, context: Hex = EMPTY) { function hashDomainToScalar(context: Hex = new Uint8Array(), ...msgs: Uint8Array[]) {
context = ensureBytes(context); const msg = ut.concatBytes(...msgs);
return modnLE(CURVE.hash(domain(message, context, !!CURVE.preHash))); return modN_LE(cHash(domain(msg, ensureBytes(context), !!preHash)));
} }
/** Signs message with privateKey. RFC8032 5.1.6 */ /** Signs message with privateKey. RFC8032 5.1.6 */
function sign(message: Hex, privateKey: Hex, context?: Hex): Uint8Array { function sign(msg: Hex, privKey: Hex, context?: Hex): Uint8Array {
message = ensureBytes(message); isHex(msg, 'message');
if (CURVE.preHash) message = CURVE.preHash(message); msg = ensureBytes(msg);
const { prefix, scalar, pointBytes } = getExtendedPublicKey(privateKey); if (preHash) msg = preHash(msg); // for ed25519ph etc.
const r = hashDomainToScalar(ut.concatBytes(prefix, message), context); const { prefix, scalar, pointBytes } = getExtendedPublicKey(privKey);
const R = Point.BASE.multiply(r); // R = rG const r = hashDomainToScalar(context, prefix, msg); // r = dom2(F, C) || prefix || PH(M)
const k = hashDomainToScalar(ut.concatBytes(R.toRawBytes(), pointBytes, message), context); // k = hash(R+P+msg) const R = G.multiply(r).toRawBytes(); // R = rG
const s = mod.mod(r + k * scalar, CURVE_ORDER); // s = r + kp const k = hashDomainToScalar(context, R, pointBytes, msg); // R || A || PH(M)
return new Signature(R, s).toRawBytes(); const s = modN(r + k * scalar); // S = (r + k * s) mod L
assertGE0(s); // 0 <= s < l
const res = ut.concatBytes(R, ut.numberToBytesLE(s, Fp.BYTES));
return ensureBytes(res, nByteLength * 2); // 64-byte signature
} }
/** function verify(sig: Hex, msg: Hex, publicKey: Hex, context?: Hex): boolean {
* Verifies EdDSA signature against message and public key. isHex(sig, 'sig');
* An extended group equation is checked. isHex(msg, 'message');
* RFC8032 5.1.7 const len = Fp.BYTES; // Verifies EdDSA signature against message and public key. RFC8032 5.1.7.
* Compliant with ZIP215: sig = ensureBytes(sig, 2 * len); // An extended group equation is checked.
* 0 <= sig.R/publicKey < 2**256 (can be >= curve.P) msg = ensureBytes(msg); // ZIP215 compliant, which means not fully RFC8032 compliant.
* 0 <= sig.s < l if (preHash) msg = preHash(msg); // for ed25519ph, etc
* Not compliant with RFC8032: it's not possible to comply to both ZIP & RFC at the same time. const A = Point.fromHex(publicKey, false); // Check for s bounds, hex validity
*/ const R = Point.fromHex(sig.slice(0, len), false); // 0 <= R < 2^256: ZIP215 R can be >= P
function verify(sig: SigType, message: Hex, publicKey: PubKey, context?: Hex): boolean { const s = ut.bytesToNumberLE(sig.slice(len, 2 * len)); // 0 <= s < l
message = ensureBytes(message); const SB = G.multiplyUnsafe(s);
if (CURVE.preHash) message = CURVE.preHash(message); const k = hashDomainToScalar(context, R.toRawBytes(), A.toRawBytes(), msg);
// When hex is passed, we check public key fully. const RkA = R.add(A.multiplyUnsafe(k));
// When Point instance is passed, we assume it has already been checked, for performance.
// If user passes Point/Sig instance, we assume it has been already verified.
// We don't check its equations for performance. We do check for valid bounds for s though
// We always check for: a) s bounds. b) hex validity
if (publicKey instanceof Point) {
// ignore
} else if (publicKey instanceof Uint8Array || typeof publicKey === 'string') {
publicKey = Point.fromHex(publicKey, false);
} else {
throw new Error(`Invalid publicKey: ${publicKey}`);
}
if (sig instanceof Signature) sig.assertValidity();
else if (sig instanceof Uint8Array || typeof sig === 'string') sig = Signature.fromHex(sig);
else throw new Error(`Wrong signature: ${sig}`);
const { r, s } = sig;
const SB = ExtendedPoint.BASE.multiplyUnsafe(s);
const k = hashDomainToScalar(
ut.concatBytes(r.toRawBytes(), publicKey.toRawBytes(), message),
context
);
const kA = ExtendedPoint.fromAffine(publicKey).multiplyUnsafe(k);
const RkA = ExtendedPoint.fromAffine(r).add(kA);
// [8][S]B = [8]R + [8][k]A' // [8][S]B = [8]R + [8][k]A'
return RkA.subtract(SB).clearCofactor().equals(ExtendedPoint.ZERO); return RkA.subtract(SB).clearCofactor().equals(Point.ZERO);
} }
// Enable precomputes. Slows down first publicKey computation by 20ms. G._setWindowSize(8); // Enable precomputes. Slows down first publicKey computation by 20ms.
Point.BASE._setWindowSize(8);
const utils = { const utils = {
getExtendedPublicKey, getExtendedPublicKey,
/** // ed25519 private keys are uniform 32b. No need to check for modulo bias, like in secp256k1.
* Not needed for ed25519 private keys. Needed if you use scalars directly (rare).
*/
hashToPrivateScalar: (hash: Hex): bigint => ut.hashToPrivateScalar(hash, CURVE_ORDER, true),
/**
* ed25519 private keys are uniform 32-bit strings. We do not need to check for
* modulo bias like we do in secp256k1 randomPrivateKey()
*/
randomPrivateKey: (): Uint8Array => randomBytes(Fp.BYTES), randomPrivateKey: (): Uint8Array => randomBytes(Fp.BYTES),
/** /**
@@ -677,11 +449,10 @@ export function twistedEdwards(curveDef: CurveType): CurveFn {
* but allows to speed-up subsequent getPublicKey() calls up to 20x. * but allows to speed-up subsequent getPublicKey() calls up to 20x.
* @param windowSize 2, 4, 8, 16 * @param windowSize 2, 4, 8, 16
*/ */
precompute(windowSize = 8, point = Point.BASE): Point { precompute(windowSize = 8, point = Point.BASE): typeof Point.BASE {
const cached = point.equals(Point.BASE) ? point : new Point(point.x, point.y); point._setWindowSize(windowSize);
cached._setWindowSize(windowSize); point.multiply(BigInt(3));
cached.multiply(_2n); return point;
return cached;
}, },
}; };
@@ -690,9 +461,7 @@ export function twistedEdwards(curveDef: CurveType): CurveFn {
getPublicKey, getPublicKey,
sign, sign,
verify, verify,
ExtendedPoint, ExtendedPoint: Point,
Point,
Signature,
utils, utils,
}; };
} }

View File

@@ -1,11 +1,13 @@
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */ /*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
import { CHash, concatBytes } from './utils.js'; import type { Group, GroupConstructor, AffinePoint } from './curve.js';
import * as mod from './modular.js'; import { mod, Field } from './modular.js';
import { CHash, Hex, concatBytes, ensureBytes } from './utils.js';
export type htfOpts = { export type Opts = {
// DST: a domain separation tag // DST: a domain separation tag
// defined in section 2.2.5 // defined in section 2.2.5
DST: string; DST: string;
encodeDST: string;
// p: the characteristic of F // p: the characteristic of F
// where F is a finite field of characteristic p and order q = p^m // where F is a finite field of characteristic p and order q = p^m
p: bigint; p: bigint;
@@ -25,7 +27,7 @@ export type htfOpts = {
hash: CHash; hash: CHash;
}; };
export function validateHTFOpts(opts: htfOpts) { export function validateOpts(opts: Opts) {
if (typeof opts.DST !== 'string') throw new Error('Invalid htf/DST'); 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.p !== 'bigint') throw new Error('Invalid htf/p');
if (typeof opts.m !== 'number') throw new Error('Invalid htf/m'); if (typeof opts.m !== 'number') throw new Error('Invalid htf/m');
@@ -36,12 +38,16 @@ export function validateHTFOpts(opts: htfOpts) {
throw new Error('Invalid htf/hash function'); throw new Error('Invalid htf/hash function');
} }
// UTF8 to ui8a // Global symbols in both browsers and Node.js since v11
// TODO: looks broken, ASCII only, why not TextEncoder/TextDecoder? it is in hashes anyway // See https://github.com/microsoft/TypeScript/issues/31535
export function stringToBytes(str: string) { declare const TextEncoder: any;
const bytes = new Uint8Array(str.length); declare const TextDecoder: any;
for (let i = 0; i < str.length; i++) bytes[i] = str.charCodeAt(i);
return bytes; export function stringToBytes(str: string): Uint8Array {
if (typeof str !== 'string') {
throw new Error(`utf8ToBytes expected string, got ${typeof str}`);
}
return new TextEncoder().encode(str);
} }
// Octet Stream to Integer (bytesToNumberBE) // Octet Stream to Integer (bytesToNumberBE)
@@ -137,7 +143,7 @@ export function expand_message_xof(
* @param options `{DST: string, p: bigint, m: number, k: number, expand: 'xmd' | 'xof', hash: H}` * @param options `{DST: string, p: bigint, m: number, k: number, expand: 'xmd' | 'xof', hash: H}`
* @returns [u_0, ..., u_(count - 1)], a list of field elements. * @returns [u_0, ..., u_(count - 1)], a list of field elements.
*/ */
export function hash_to_field(msg: Uint8Array, count: number, options: htfOpts): bigint[][] { export function hash_to_field(msg: Uint8Array, count: number, options: Opts): bigint[][] {
// if options is provided but incomplete, fill any missing fields with the // if options is provided but incomplete, fill any missing fields with the
// value in hftDefaults (ie hash to G2). // value in hftDefaults (ie hash to G2).
const log2p = options.p.toString(2).length; const log2p = options.p.toString(2).length;
@@ -156,14 +162,14 @@ export function hash_to_field(msg: Uint8Array, count: number, options: htfOpts):
for (let j = 0; j < options.m; j++) { for (let j = 0; j < options.m; j++) {
const elm_offset = L * (j + i * options.m); const elm_offset = L * (j + i * options.m);
const tv = pseudo_random_bytes.subarray(elm_offset, elm_offset + L); const tv = pseudo_random_bytes.subarray(elm_offset, elm_offset + L);
e[j] = mod.mod(os2ip(tv), options.p); e[j] = mod(os2ip(tv), options.p);
} }
u[i] = e; u[i] = e;
} }
return u; return u;
} }
export function isogenyMap<T, F extends mod.Field<T>>(field: F, map: [T[], T[], T[], T[]]) { export function isogenyMap<T, F extends Field<T>>(field: F, map: [T[], T[], T[], T[]]) {
// Make same order as in spec // Make same order as in spec
const COEFF = map.map((i) => Array.from(i).reverse()); const COEFF = map.map((i) => Array.from(i).reverse());
return (x: T, y: T) => { return (x: T, y: T) => {
@@ -175,3 +181,56 @@ export function isogenyMap<T, F extends mod.Field<T>>(field: F, map: [T[], T[],
return { x, y }; 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: string;
};
export function hashToCurve<T>(
Point: H2CPointConstructor<T>,
mapToCurve: MapToCurve<T>,
def: Opts
) {
validateOpts(def);
if (typeof mapToCurve !== 'function')
throw new Error('hashToCurve: mapToCurve() has not been defined');
return {
// Encodes byte string to elliptic curve
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-3
hashToCurve(msg: Hex, options?: htfBasicOpts) {
if (!mapToCurve) throw new Error('CURVE.mapToCurve() has not been defined');
msg = ensureBytes(msg);
const u = hash_to_field(msg, 2, { ...def, DST: def.DST, ...options } as Opts);
const P = Point.fromAffine(mapToCurve(u[0]))
.add(Point.fromAffine(mapToCurve(u[1])))
.clearCofactor();
P.assertValidity();
return P;
},
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-16#section-3
encodeToCurve(msg: Hex, options?: htfBasicOpts) {
if (!mapToCurve) throw new Error('CURVE.mapToCurve() has not been defined');
msg = ensureBytes(msg);
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,7 +1,14 @@
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */ /*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
// TODO: remove circular imports
import * as utils from './utils.js';
// Utilities for modular arithmetics and finite fields // Utilities for modular arithmetics and finite fields
import {
bitMask,
numberToBytesBE,
numberToBytesLE,
bytesToNumberBE,
bytesToNumberLE,
ensureBytes,
validateObject,
} from './utils.js';
// prettier-ignore // prettier-ignore
const _0n = BigInt(0), _1n = BigInt(1), _2n = BigInt(2), _3n = BigInt(3); const _0n = BigInt(0), _1n = BigInt(1), _2n = BigInt(2), _3n = BigInt(3);
// prettier-ignore // prettier-ignore
@@ -34,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) // 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 { export function pow2(x: bigint, power: bigint, modulo: bigint): bigint {
let res = x; let res = x;
while (power-- > _0n) { while (power-- > _0n) {
@@ -92,7 +98,7 @@ export function tonelliShanks(P: bigint) {
const p1div4 = (P + _1n) / _4n; const p1div4 = (P + _1n) / _4n;
return function tonelliFast<T>(Fp: Field<T>, n: T) { return function tonelliFast<T>(Fp: Field<T>, n: T) {
const root = Fp.pow(n, p1div4); const root = Fp.pow(n, p1div4);
if (!Fp.equals(Fp.square(root), n)) throw new Error('Cannot find square root'); if (!Fp.eql(Fp.sqr(root), n)) throw new Error('Cannot find square root');
return root; return root;
}; };
} }
@@ -101,24 +107,24 @@ export function tonelliShanks(P: bigint) {
const Q1div2 = (Q + _1n) / _2n; const Q1div2 = (Q + _1n) / _2n;
return function tonelliSlow<T>(Fp: Field<T>, n: T): T { return function tonelliSlow<T>(Fp: Field<T>, n: T): T {
// Step 0: Check that n is indeed a square: (n | p) should not be ≡ -1 // Step 0: Check that n is indeed a square: (n | p) should not be ≡ -1
if (Fp.pow(n, legendreC) === Fp.negate(Fp.ONE)) throw new Error('Cannot find square root'); if (Fp.pow(n, legendreC) === Fp.neg(Fp.ONE)) throw new Error('Cannot find square root');
let r = S; let r = S;
// TODO: will fail at Fp2/etc // TODO: will fail at Fp2/etc
let g = Fp.pow(Fp.mul(Fp.ONE, Z), Q); // will update both x and b 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 x = Fp.pow(n, Q1div2); // first guess at the square root
let b = Fp.pow(n, Q); // first guess at the fudge factor let b = Fp.pow(n, Q); // first guess at the fudge factor
while (!Fp.equals(b, Fp.ONE)) { while (!Fp.eql(b, Fp.ONE)) {
if (Fp.equals(b, Fp.ZERO)) return Fp.ZERO; // https://en.wikipedia.org/wiki/Tonelli%E2%80%93Shanks_algorithm (4. If t = 0, return r = 0) 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 // Find m such b^(2^m)==1
let m = 1; let m = 1;
for (let t2 = Fp.square(b); m < r; m++) { for (let t2 = Fp.sqr(b); m < r; m++) {
if (Fp.equals(t2, Fp.ONE)) break; if (Fp.eql(t2, Fp.ONE)) break;
t2 = Fp.square(t2); // t2 *= t2 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 // 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) const ge = Fp.pow(g, _1n << BigInt(r - m - 1)); // ge = 2^(r-m-1)
g = Fp.square(ge); // g = ge * ge g = Fp.sqr(ge); // g = ge * ge
x = Fp.mul(x, ge); // x *= ge x = Fp.mul(x, ge); // x *= ge
b = Fp.mul(b, g); // b *= g b = Fp.mul(b, g); // b *= g
r = m; r = m;
@@ -142,7 +148,7 @@ export function FpSqrt(P: bigint) {
return function sqrt3mod4<T>(Fp: Field<T>, n: T) { return function sqrt3mod4<T>(Fp: Field<T>, n: T) {
const root = Fp.pow(n, p1div4); const root = Fp.pow(n, p1div4);
// Throw if root**2 != n // Throw if root**2 != n
if (!Fp.equals(Fp.square(root), n)) throw new Error('Cannot find square root'); if (!Fp.eql(Fp.sqr(root), n)) throw new Error('Cannot find square root');
return root; return root;
}; };
} }
@@ -156,7 +162,7 @@ export function FpSqrt(P: bigint) {
const nv = Fp.mul(n, v); const nv = Fp.mul(n, v);
const i = Fp.mul(Fp.mul(nv, _2n), v); const i = Fp.mul(Fp.mul(nv, _2n), v);
const root = Fp.mul(nv, Fp.sub(i, Fp.ONE)); const root = Fp.mul(nv, Fp.sub(i, Fp.ONE));
if (!Fp.equals(Fp.square(root), n)) throw new Error('Cannot find square root'); if (!Fp.eql(Fp.sqr(root), n)) throw new Error('Cannot find square root');
return root; return root;
}; };
} }
@@ -206,13 +212,13 @@ export interface Field<T> {
// 1-arg // 1-arg
create: (num: T) => T; create: (num: T) => T;
isValid: (num: T) => boolean; isValid: (num: T) => boolean;
isZero: (num: T) => boolean; is0: (num: T) => boolean;
negate(num: T): T; neg(num: T): T;
invert(num: T): T; inv(num: T): T;
sqrt(num: T): T; sqrt(num: T): T;
square(num: T): T; sqr(num: T): T;
// 2-args // 2-args
equals(lhs: T, rhs: T): boolean; eql(lhs: T, rhs: T): boolean;
add(lhs: T, rhs: T): T; add(lhs: T, rhs: T): T;
sub(lhs: T, rhs: T): T; sub(lhs: T, rhs: T): T;
mul(lhs: T, rhs: T | bigint): T; mul(lhs: T, rhs: T | bigint): T;
@@ -222,13 +228,13 @@ export interface Field<T> {
addN(lhs: T, rhs: T): T; addN(lhs: T, rhs: T): T;
subN(lhs: T, rhs: T): T; subN(lhs: T, rhs: T): T;
mulN(lhs: T, rhs: T | bigint): T; mulN(lhs: T, rhs: T | bigint): T;
squareN(num: T): T; sqrN(num: T): T;
// Optional // Optional
// Should be same as sgn0 function in https://datatracker.ietf.org/doc/draft-irtf-cfrg-hash-to-curve/ // Should be same as sgn0 function in 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. // NOTE: sgn0 is 'negative in LE', which is same as odd. And negative in LE is kinda strange definition anyway.
isOdd?(num: T): boolean; // Odd instead of even since we have it for Fp2 isOdd?(num: T): boolean; // Odd instead of even since we have it for Fp2
legendre?(num: T): T; // legendre?(num: T): T;
pow(lhs: T, power: bigint): T; pow(lhs: T, power: bigint): T;
invertBatch: (lst: T[]) => T[]; invertBatch: (lst: T[]) => T[];
toBytes(num: T): Uint8Array; toBytes(num: T): Uint8Array;
@@ -238,23 +244,22 @@ export interface Field<T> {
} }
// prettier-ignore // prettier-ignore
const FIELD_FIELDS = [ const FIELD_FIELDS = [
'create', 'isValid', 'isZero', 'negate', 'invert', 'sqrt', 'square', 'create', 'isValid', 'is0', 'neg', 'inv', 'sqrt', 'sqr',
'equals', 'add', 'sub', 'mul', 'pow', 'div', 'eql', 'add', 'sub', 'mul', 'pow', 'div',
'addN', 'subN', 'mulN', 'squareN' 'addN', 'subN', 'mulN', 'sqrN'
] as const; ] as const;
export function validateField<T>(field: Field<T>) { export function validateField<T>(field: Field<T>) {
for (const i of ['ORDER', 'MASK'] as const) { const initial = {
if (typeof field[i] !== 'bigint') ORDER: 'bigint',
throw new Error(`Invalid field param ${i}=${field[i]} (${typeof field[i]})`); MASK: 'bigint',
} BYTES: 'isSafeInteger',
for (const i of ['BYTES', 'BITS'] as const) { BITS: 'isSafeInteger',
if (typeof field[i] !== 'number') } as Record<string, string>;
throw new Error(`Invalid field param ${i}=${field[i]} (${typeof field[i]})`); const opts = FIELD_FIELDS.reduce((map, val: string) => {
} map[val] = 'function';
for (const i of FIELD_FIELDS) { return map;
if (typeof field[i] !== 'function') }, initial);
throw new Error(`Invalid field param ${i}=${field[i]} (${typeof field[i]})`); return validateObject(field, opts);
}
} }
// Generic field functions // Generic field functions
@@ -268,7 +273,7 @@ export function FpPow<T>(f: Field<T>, num: T, power: bigint): T {
let d = num; let d = num;
while (power > _0n) { while (power > _0n) {
if (power & _1n) p = f.mul(p, d); if (power & _1n) p = f.mul(p, d);
d = f.square(d); d = f.sqr(d);
power >>= 1n; power >>= 1n;
} }
return p; return p;
@@ -278,15 +283,15 @@ export function FpInvertBatch<T>(f: Field<T>, nums: T[]): T[] {
const tmp = new Array(nums.length); const tmp = new Array(nums.length);
// Walk from first to last, multiply them by each other MOD p // Walk from first to last, multiply them by each other MOD p
const lastMultiplied = nums.reduce((acc, num, i) => { const lastMultiplied = nums.reduce((acc, num, i) => {
if (f.isZero(num)) return acc; if (f.is0(num)) return acc;
tmp[i] = acc; tmp[i] = acc;
return f.mul(acc, num); return f.mul(acc, num);
}, f.ONE); }, f.ONE);
// Invert last element // 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 // Walk from last to first, multiply them by inverted each other MOD p
nums.reduceRight((acc, num, i) => { nums.reduceRight((acc, num, i) => {
if (f.isZero(num)) return acc; if (f.is0(num)) return acc;
tmp[i] = f.mul(acc, tmp[i]); tmp[i] = f.mul(acc, tmp[i]);
return f.mul(acc, num); return f.mul(acc, num);
}, inverted); }, inverted);
@@ -294,7 +299,7 @@ export function FpInvertBatch<T>(f: Field<T>, nums: T[]): T[] {
} }
export function FpDiv<T>(f: Field<T>, lhs: T, rhs: T | bigint): T { 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)); return f.mul(lhs, typeof rhs === 'bigint' ? invert(rhs, f.ORDER) : f.inv(rhs));
} }
// This function returns True whenever the value x is a square in the field F. // This function returns True whenever the value x is a square in the field F.
@@ -302,10 +307,18 @@ export function FpIsSquare<T>(f: Field<T>) {
const legendreConst = (f.ORDER - _1n) / _2n; // Integer arithmetic const legendreConst = (f.ORDER - _1n) / _2n; // Integer arithmetic
return (x: T): boolean => { return (x: T): boolean => {
const p = f.pow(x, legendreConst); const p = f.pow(x, legendreConst);
return f.equals(p, f.ZERO) || f.equals(p, f.ONE); 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 };
}
// NOTE: very fragile, always bench. Major performance points: // NOTE: very fragile, always bench. Major performance points:
// - NonNormalized ops // - NonNormalized ops
// - Object.freeze // - Object.freeze
@@ -318,28 +331,28 @@ export function Fp(
redef: Partial<Field<bigint>> = {} redef: Partial<Field<bigint>> = {}
): Readonly<FpField> { ): Readonly<FpField> {
if (ORDER <= _0n) throw new Error(`Expected Fp ORDER > 0, got ${ORDER}`); 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'); if (BYTES > 2048) throw new Error('Field lengths over 2048 bytes are not supported');
const sqrtP = FpSqrt(ORDER); const sqrtP = FpSqrt(ORDER);
const f: Readonly<FpField> = Object.freeze({ const f: Readonly<FpField> = Object.freeze({
ORDER, ORDER,
BITS, BITS,
BYTES, BYTES,
MASK: utils.bitMask(BITS), MASK: bitMask(BITS),
ZERO: _0n, ZERO: _0n,
ONE: _1n, ONE: _1n,
create: (num) => mod(num, ORDER), create: (num) => mod(num, ORDER),
isValid: (num) => { isValid: (num) => {
if (typeof num !== 'bigint') if (typeof num !== 'bigint')
throw new Error(`Invalid field element: expected bigint, got ${typeof num}`); 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, isOdd: (num) => (num & _1n) === _1n,
negate: (num) => mod(-num, ORDER), neg: (num) => mod(-num, ORDER),
equals: (lhs, rhs) => lhs === rhs, eql: (lhs, rhs) => lhs === rhs,
square: (num) => mod(num * num, ORDER), sqr: (num) => mod(num * num, ORDER),
add: (lhs, rhs) => mod(lhs + rhs, ORDER), add: (lhs, rhs) => mod(lhs + rhs, ORDER),
sub: (lhs, rhs) => mod(lhs - rhs, ORDER), sub: (lhs, rhs) => mod(lhs - rhs, ORDER),
mul: (lhs, rhs) => mod(lhs * rhs, ORDER), mul: (lhs, rhs) => mod(lhs * rhs, ORDER),
@@ -347,24 +360,22 @@ export function Fp(
div: (lhs, rhs) => mod(lhs * invert(rhs, ORDER), ORDER), div: (lhs, rhs) => mod(lhs * invert(rhs, ORDER), ORDER),
// Same as above, but doesn't normalize // Same as above, but doesn't normalize
squareN: (num) => num * num, sqrN: (num) => num * num,
addN: (lhs, rhs) => lhs + rhs, addN: (lhs, rhs) => lhs + rhs,
subN: (lhs, rhs) => lhs - rhs, subN: (lhs, rhs) => lhs - rhs,
mulN: (lhs, rhs) => lhs * rhs, mulN: (lhs, rhs) => lhs * rhs,
invert: (num) => invert(num, ORDER), inv: (num) => invert(num, ORDER),
sqrt: redef.sqrt || ((n) => sqrtP(f, n)), sqrt: redef.sqrt || ((n) => sqrtP(f, n)),
invertBatch: (lst) => FpInvertBatch(f, lst), invertBatch: (lst) => FpInvertBatch(f, lst),
// TODO: do we really need constant cmov? // TODO: do we really need constant cmov?
// We don't have const-time bigints anyway, so probably will be not very useful // We don't have const-time bigints anyway, so probably will be not very useful
cmov: (a, b, c) => (c ? b : a), cmov: (a, b, c) => (c ? b : a),
toBytes: (num) => toBytes: (num) => (isLE ? numberToBytesLE(num, BYTES) : numberToBytesBE(num, BYTES)),
isLE ? utils.numberToBytesLE(num, BYTES) : utils.numberToBytesBE(num, BYTES),
fromBytes: (bytes) => { fromBytes: (bytes) => {
if (bytes.length !== BYTES) if (bytes.length !== BYTES)
throw new Error(`Fp.fromBytes: expected ${BYTES}, got ${bytes.length}`); 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 FpField); } as FpField);
return Object.freeze(f); return Object.freeze(f);
@@ -373,11 +384,34 @@ export function Fp(
export function FpSqrtOdd<T>(Fp: Field<T>, elm: T) { export function FpSqrtOdd<T>(Fp: Field<T>, elm: T) {
if (!Fp.isOdd) throw new Error(`Field doesn't have isOdd`); if (!Fp.isOdd) throw new Error(`Field doesn't have isOdd`);
const root = Fp.sqrt(elm); const root = Fp.sqrt(elm);
return Fp.isOdd(root) ? root : Fp.negate(root); return Fp.isOdd(root) ? root : Fp.neg(root);
} }
export function FpSqrtEven<T>(Fp: Field<T>, elm: T) { export function FpSqrtEven<T>(Fp: Field<T>, elm: T) {
if (!Fp.isOdd) throw new Error(`Field doesn't have isOdd`); if (!Fp.isOdd) throw new Error(`Field doesn't have isOdd`);
const root = Fp.sqrt(elm); const root = Fp.sqrt(elm);
return Fp.isOdd(root) ? Fp.negate(root) : root; 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 neglible.
* Needs at least 40 bytes of input for 32-byte private key.
* https://research.kudelskisecurity.com/2020/07/28/the-definitive-guide-to-modulo-bias-and-how-to-avoid-it/
* @param hash hash output from SHA3 or a similar function
* @returns valid private scalar
*/
export function hashToPrivateScalar(
hash: string | Uint8Array,
groupOrder: bigint,
isLE = false
): bigint {
hash = ensureBytes(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,14 +1,13 @@
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */ /*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
import * as mod from './modular.js'; import { mod, pow } from './modular.js';
import { ensureBytes, numberToBytesLE, bytesToNumberLE, isPositiveInt } from './utils.js'; import { bytesToNumberLE, ensureBytes, numberToBytesLE, validateObject } from './utils.js';
const _0n = BigInt(0); const _0n = BigInt(0);
const _1n = BigInt(1); const _1n = BigInt(1);
type Hex = string | Uint8Array; type Hex = string | Uint8Array;
export type CurveType = { export type CurveType = {
// Field over which we'll do calculations. Verify with: P: bigint; // finite field prime
P: bigint;
nByteLength: number; nByteLength: number;
adjustScalarBytes?: (bytes: Uint8Array) => Uint8Array; adjustScalarBytes?: (bytes: Uint8Array) => Uint8Array;
domain?: (data: Uint8Array, ctx: Uint8Array, phflag: boolean) => Uint8Array; domain?: (data: Uint8Array, ctx: Uint8Array, phflag: boolean) => Uint8Array;
@@ -27,26 +26,21 @@ export type CurveFn = {
}; };
function validateOpts(curve: CurveType) { function validateOpts(curve: CurveType) {
for (const i of ['a24'] as const) { validateObject(
if (typeof curve[i] !== 'bigint') curve,
throw new Error(`Invalid curve param ${i}=${curve[i]} (${typeof curve[i]})`); {
} a24: 'bigint',
for (const i of ['montgomeryBits', 'nByteLength'] as const) { },
if (curve[i] === undefined) continue; // Optional {
if (!isPositiveInt(curve[i])) montgomeryBits: 'isSafeInteger',
throw new Error(`Invalid curve param ${i}=${curve[i]} (${typeof curve[i]})`); nByteLength: 'isSafeInteger',
} adjustScalarBytes: 'function',
for (const fn of ['adjustScalarBytes', 'domain', 'powPminus2'] as const) { domain: 'function',
if (curve[fn] === undefined) continue; // Optional powPminus2: 'function',
if (typeof curve[fn] !== 'function') throw new Error(`Invalid ${fn} function`); Gu: 'string',
} }
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]})`);
}
// Set defaults // Set defaults
// ...nLength(curve.n, curve.nBitLength),
return Object.freeze({ ...curve } as const); return Object.freeze({ ...curve } as const);
} }
@@ -55,34 +49,14 @@ function validateOpts(curve: CurveType) {
export function montgomery(curveDef: CurveType): CurveFn { export function montgomery(curveDef: CurveType): CurveFn {
const CURVE = validateOpts(curveDef); const CURVE = validateOpts(curveDef);
const { P } = CURVE; const { P } = CURVE;
const modP = (a: bigint) => mod.mod(a, P); const modP = (a: bigint) => mod(a, P);
const montgomeryBits = CURVE.montgomeryBits; const montgomeryBits = CURVE.montgomeryBits;
const montgomeryBytes = Math.ceil(montgomeryBits / 8); const montgomeryBytes = Math.ceil(montgomeryBits / 8);
const fieldLen = CURVE.nByteLength; const fieldLen = CURVE.nByteLength;
const adjustScalarBytes = CURVE.adjustScalarBytes || ((bytes: Uint8Array) => bytes); 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));
/** // cswap from RFC7748. But it is not from RFC7748!
* 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(swap, x_2, x_3): cswap(swap, x_2, x_3):
dummy = mask(swap) AND (x_2 XOR x_3) dummy = mask(swap) AND (x_2 XOR x_3)
@@ -99,6 +73,11 @@ export function montgomery(curveDef: CurveType): CurveFn {
return [x_2, x_3]; return [x_2, x_3];
} }
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 // x25519 from 4
/** /**
* *
@@ -107,11 +86,10 @@ export function montgomery(curveDef: CurveType): CurveFn {
* @returns new Point on Montgomery curve * @returns new Point on Montgomery curve
*/ */
function montgomeryLadder(pointU: bigint, scalar: bigint): bigint { function montgomeryLadder(pointU: bigint, scalar: bigint): bigint {
const { P } = CURVE; const u = assertFieldElement(pointU);
const u = normalizeScalar(pointU, P);
// Section 5: Implementations MUST accept non-canonical values and process them as // Section 5: Implementations MUST accept non-canonical values and process them as
// if they had been reduced modulo the field prime. // if they had been reduced modulo the field prime.
const k = normalizeScalar(scalar, P); const k = assertFieldElement(scalar);
// The constant a24 is (486662 - 2) / 4 = 121665 for curve25519/X25519 // The constant a24 is (486662 - 2) / 4 = 121665 for curve25519/X25519
const a24 = CURVE.a24; const a24 = CURVE.a24;
const x_1 = u; const x_1 = u;
@@ -167,28 +145,20 @@ export function montgomery(curveDef: CurveType): CurveFn {
} }
function decodeUCoordinate(uEnc: Hex): bigint { function decodeUCoordinate(uEnc: Hex): bigint {
const u = ensureBytes(uEnc, montgomeryBytes);
// Section 5: When receiving such an array, implementations of X25519 // Section 5: When receiving such an array, implementations of X25519
// MUST mask the most significant bit in the final byte. // MUST mask the most significant bit in the final byte.
// This is very ugly way, but it works because fieldLen-1 is outside of bounds for X448, so this becomes NOOP // 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 // fieldLen - scalaryBytes = 1 for X448 and = 0 for X25519
const u = ensureBytes(uEnc, montgomeryBytes);
u[fieldLen - 1] &= 127; // 0b0111_1111 u[fieldLen - 1] &= 127; // 0b0111_1111
return bytesToNumberLE(u); return bytesToNumberLE(u);
} }
function decodeScalar(n: Hex): bigint { function decodeScalar(n: Hex): bigint {
const bytes = ensureBytes(n); const bytes = ensureBytes(n);
if (bytes.length !== montgomeryBytes && bytes.length !== fieldLen) if (bytes.length !== montgomeryBytes && bytes.length !== fieldLen)
throw new Error(`Expected ${montgomeryBytes} or ${fieldLen} bytes, got ${bytes.length}`); throw new Error(`Expected ${montgomeryBytes} or ${fieldLen} bytes, got ${bytes.length}`);
return bytesToNumberLE(adjustScalarBytes(bytes)); 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 { function scalarMult(scalar: Hex, u: Hex): Uint8Array {
const pointU = decodeUCoordinate(u); const pointU = decodeUCoordinate(u);
const _scalar = decodeScalar(scalar); const _scalar = decodeScalar(scalar);
@@ -198,12 +168,7 @@ export function montgomery(curveDef: CurveType): CurveFn {
if (pu === _0n) throw new Error('Invalid private or public key received'); if (pu === _0n) throw new Error('Invalid private or public key received');
return encodeUCoordinate(pu); return encodeUCoordinate(pu);
} }
/** // Computes public key from private. By doing scalar multiplication of base point.
* Computes public key from private.
* Executes scalar multiplication of curve's base point by scalar.
* @param scalar private key
* @returns new public key
*/
function scalarMultBase(scalar: Hex): Uint8Array { function scalarMultBase(scalar: Hex): Uint8Array {
return scalarMult(scalar, CURVE.Gu); return scalarMult(scalar, CURVE.Gu);
} }

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 { Field, 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: Field<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,75 +1,28 @@
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */ /*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
import * as mod from './modular.js';
const _0n = BigInt(0); const _0n = BigInt(0);
const _1n = BigInt(1); const _1n = BigInt(1);
const _2n = BigInt(2); const _2n = BigInt(2);
const u8a = (a: any): a is Uint8Array => a instanceof Uint8Array;
// We accept hex strings besides Uint8Array for simplicity // We accept hex strings besides Uint8Array for simplicity
export type Hex = Uint8Array | string; export type Hex = Uint8Array | string;
// Very few implementations accept numbers, we do it to ease learning curve // Very few implementations accept numbers, we do it to ease learning curve
export type PrivKey = Hex | bigint | number; export type PrivKey = Hex | bigint;
export type CHash = { export type CHash = {
(message: Uint8Array | string): Uint8Array; (message: Uint8Array | string): Uint8Array;
blockLen: number; blockLen: number;
outputLen: number; outputLen: number;
create(opts?: { dkLen?: number }): any; // For shake create(opts?: { dkLen?: number }): any; // For shake
}; };
export type FHash = (message: Uint8Array | string) => Uint8Array;
// 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;
};
// Bans floats and integers above 2^53-1
export function isPositiveInt(num: any): num is number {
return typeof num === 'number' && Number.isSafeInteger(num) && num > 0;
}
export function validateOpts<FP, T>(curve: BasicCurve<FP> & T) {
mod.validateField(curve.Fp);
for (const i of ['n', 'h'] as const) {
const val = curve[i];
if (typeof val !== 'bigint') throw new Error(`Invalid curve param ${i}=${val} (${typeof val})`);
}
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) {
const val = curve[i];
if (val === undefined) continue; // Optional
if (!isPositiveInt(val)) throw new Error(`Invalid curve param ${i}=${val} (${typeof val})`);
}
// Set defaults
return Object.freeze({ ...nLength(curve.n, curve.nBitLength), ...curve } as const);
}
const hexes = Array.from({ length: 256 }, (v, i) => i.toString(16).padStart(2, '0')); const hexes = Array.from({ length: 256 }, (v, i) => i.toString(16).padStart(2, '0'));
export function bytesToHex(uint8a: Uint8Array): string { export function bytesToHex(bytes: Uint8Array): string {
if (!(uint8a instanceof Uint8Array)) throw new Error('Expected Uint8Array'); if (!u8a(bytes)) throw new Error('Uint8Array expected');
// pre-caching improves the speed 6x // pre-caching improves the speed 6x
let hex = ''; let hex = '';
for (let i = 0; i < uint8a.length; i++) { for (let i = 0; i < bytes.length; i++) {
hex += hexes[uint8a[i]]; hex += hexes[bytes[i]];
} }
return hex; return hex;
} }
@@ -80,25 +33,21 @@ export function numberToHexUnpadded(num: number | bigint): string {
} }
export function hexToNumber(hex: string): bigint { export function hexToNumber(hex: string): bigint {
if (typeof hex !== 'string') { if (typeof hex !== 'string') throw new Error('string expected, got ' + typeof hex);
throw new TypeError('hexToNumber: expected string, got ' + typeof hex);
}
// Big Endian // Big Endian
return BigInt(`0x${hex}`); return BigInt(`0x${hex}`);
} }
// Caching slows it down 2-3x // Caching slows it down 2-3x
export function hexToBytes(hex: string): Uint8Array { export function hexToBytes(hex: string): Uint8Array {
if (typeof hex !== 'string') { if (typeof hex !== 'string') throw new Error('string expected, got ' + typeof hex);
throw new TypeError('hexToBytes: expected string, got ' + typeof hex); if (hex.length % 2) throw new Error('hex string is invalid: unpadded ' + hex.length);
}
if (hex.length % 2) throw new Error('hexToBytes: received invalid unpadded hex ' + hex.length);
const array = new Uint8Array(hex.length / 2); const array = new Uint8Array(hex.length / 2);
for (let i = 0; i < array.length; i++) { for (let i = 0; i < array.length; i++) {
const j = i * 2; const j = i * 2;
const hexByte = hex.slice(j, j + 2); const hexByte = hex.slice(j, j + 2);
const byte = Number.parseInt(hexByte, 16); const byte = Number.parseInt(hexByte, 16);
if (Number.isNaN(byte) || byte < 0) throw new Error('Invalid byte sequence'); if (Number.isNaN(byte) || byte < 0) throw new Error('invalid byte sequence');
array[i] = byte; array[i] = byte;
} }
return array; return array;
@@ -108,63 +57,36 @@ export function hexToBytes(hex: string): Uint8Array {
export function bytesToNumberBE(bytes: Uint8Array): bigint { export function bytesToNumberBE(bytes: Uint8Array): bigint {
return hexToNumber(bytesToHex(bytes)); return hexToNumber(bytesToHex(bytes));
} }
export function bytesToNumberLE(uint8a: Uint8Array): bigint { export function bytesToNumberLE(bytes: Uint8Array): bigint {
if (!(uint8a instanceof Uint8Array)) throw new Error('Expected Uint8Array'); if (!u8a(bytes)) throw new Error('Uint8Array expected');
return BigInt('0x' + bytesToHex(Uint8Array.from(uint8a).reverse())); return hexToNumber(bytesToHex(Uint8Array.from(bytes).reverse()));
} }
export const numberToBytesBE = (n: bigint, len: number) => export const numberToBytesBE = (n: bigint, len: number) =>
hexToBytes(n.toString(16).padStart(len * 2, '0')); hexToBytes(n.toString(16).padStart(len * 2, '0'));
export const numberToBytesLE = (n: bigint, len: number) => numberToBytesBE(n, len).reverse(); export const numberToBytesLE = (n: bigint, len: number) => numberToBytesBE(n, len).reverse();
// Returns variable number bytes (minimal bigint encoding?)
export const numberToVarBytesBE = (n: bigint) => hexToBytes(numberToHexUnpadded(n));
export function ensureBytes(hex: Hex, expectedLength?: number): Uint8Array { export function ensureBytes(hex: Hex, expectedLength?: number): Uint8Array {
// Uint8Array.from() instead of hash.slice() because node.js Buffer // Uint8Array.from() instead of hash.slice() because node.js Buffer
// is instance of Uint8Array, and its slice() creates **mutable** copy // is instance of Uint8Array, and its slice() creates **mutable** copy
const bytes = hex instanceof Uint8Array ? Uint8Array.from(hex) : hexToBytes(hex); const bytes = u8a(hex) ? Uint8Array.from(hex) : hexToBytes(hex);
if (typeof expectedLength === 'number' && bytes.length !== expectedLength) if (typeof expectedLength === 'number' && bytes.length !== expectedLength)
throw new Error(`Expected ${expectedLength} bytes`); throw new Error(`Expected ${expectedLength} bytes`);
return bytes; return bytes;
} }
// Copies several Uint8Arrays into one. // Copies several Uint8Arrays into one.
export function concatBytes(...arrays: Uint8Array[]): Uint8Array { export function concatBytes(...arrs: Uint8Array[]): Uint8Array {
if (!arrays.every((b) => b instanceof Uint8Array)) throw new Error('Uint8Array list expected'); const r = new Uint8Array(arrs.reduce((sum, a) => sum + a.length, 0));
if (arrays.length === 1) return arrays[0]; let pad = 0; // walk through each item, ensure they have proper type
const length = arrays.reduce((a, arr) => a + arr.length, 0); arrs.forEach((a) => {
const result = new Uint8Array(length); if (!u8a(a)) throw new Error('Uint8Array expected');
for (let i = 0, pad = 0; i < arrays.length; i++) { r.set(a, pad);
const arr = arrays[i]; pad += a.length;
result.set(arr, pad); });
pad += arr.length; return r;
}
return result;
}
// 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 };
}
/**
* FIPS 186 B.4.1-compliant "constant-time" private key generation utility.
* Can take (n+8) or more bytes of uniform input e.g. from CSPRNG or KDF
* and convert them into private scalar, with the modulo bias being neglible.
* Needs at least 40 bytes of input for 32-byte private key.
* https://research.kudelskisecurity.com/2020/07/28/the-definitive-guide-to-modulo-bias-and-how-to-avoid-it/
* @param hash hash output from SHA3 or a similar function
* @returns valid private scalar
*/
export function hashToPrivateScalar(hash: Hex, groupOrder: bigint, isLE = false): bigint {
hash = ensureBytes(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.mod(num, groupOrder - _1n) + _1n;
} }
export function equalBytes(b1: Uint8Array, b2: Uint8Array) { export function equalBytes(b1: Uint8Array, b2: Uint8Array) {
@@ -191,3 +113,32 @@ export const bitSet = (n: bigint, pos: number, value: boolean) =>
// Return mask for N bits (Same as BigInt(`0b${Array(i).fill('1').join('')}`)) // Return mask for N bits (Same as BigInt(`0b${Array(i).fill('1').join('')}`))
// Not using ** operator with bigints for old engines. // Not using ** operator with bigints for old engines.
export const bitMask = (n: number) => (_2n << BigInt(n - 1)) - _1n; export const bitMask = (n: number) => (_2n << BigInt(n - 1)) - _1n;
type ValMap = Record<string, string>;
export function validateObject(object: object, validators: ValMap, optValidators: ValMap = {}) {
const validatorFns: Record<string, (val: any) => boolean> = {
bigint: (val) => typeof val === 'bigint',
function: (val) => typeof val === 'function',
boolean: (val) => typeof val === 'boolean',
string: (val) => typeof val === 'string',
isSafeInteger: (val) => Number.isSafeInteger(val),
array: (val) => Array.isArray(val),
field: (val) => (object as any).Fp.isValid(val),
hash: (val) => typeof val === 'function' && Number.isSafeInteger(val.outputLen),
};
// type Key = keyof typeof validators;
const checkField = (fieldName: string, type: string, 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)) {
throw new Error(`Invalid param ${fieldName}=${val} (${typeof val}), expected ${type}`);
}
};
for (let [fieldName, type] of Object.entries(validators)) checkField(fieldName, type, false);
for (let [fieldName, type] of Object.entries(optValidators)) checkField(fieldName, type, true);
return object;
}

File diff suppressed because it is too large Load Diff

View File

@@ -16,7 +16,7 @@ import { randomBytes } from '@noble/hashes/utils';
import { bls, CurveFn } from './abstract/bls.js'; import { bls, CurveFn } from './abstract/bls.js';
import * as mod from './abstract/modular.js'; import * as mod from './abstract/modular.js';
import { import {
concatBytes, concatBytes as concatB,
ensureBytes, ensureBytes,
numberToBytesBE, numberToBytesBE,
bytesToNumberBE, bytesToNumberBE,
@@ -28,10 +28,10 @@ import {
} from './abstract/utils.js'; } from './abstract/utils.js';
// Types // Types
import { import {
PointType, ProjPointType,
ProjectivePointType, ProjConstructor,
ProjectiveConstructor,
mapToCurveSimpleSWU, mapToCurveSimpleSWU,
AffinePoint,
} from './abstract/weierstrass.js'; } from './abstract/weierstrass.js';
import { isogenyMap } from './abstract/hash-to-curve.js'; import { isogenyMap } from './abstract/hash-to-curve.js';
@@ -99,25 +99,24 @@ const Fp2: mod.Field<Fp2> & Fp2Utils = {
ONE: { c0: Fp.ONE, c1: Fp.ZERO }, ONE: { c0: Fp.ONE, c1: Fp.ZERO },
create: (num) => num, create: (num) => num,
isValid: ({ c0, c1 }) => typeof c0 === 'bigint' && typeof c1 === 'bigint', isValid: ({ c0, c1 }) => typeof c0 === 'bigint' && typeof c1 === 'bigint',
isZero: ({ c0, c1 }) => Fp.isZero(c0) && Fp.isZero(c1), is0: ({ c0, c1 }) => Fp.is0(c0) && Fp.is0(c1),
equals: ({ c0, c1 }: Fp2, { c0: r0, c1: r1 }: Fp2) => Fp.equals(c0, r0) && Fp.equals(c1, r1), eql: ({ c0, c1 }: Fp2, { c0: r0, c1: r1 }: Fp2) => Fp.eql(c0, r0) && Fp.eql(c1, r1),
negate: ({ c0, c1 }) => ({ c0: Fp.negate(c0), c1: Fp.negate(c1) }), neg: ({ c0, c1 }) => ({ c0: Fp.neg(c0), c1: Fp.neg(c1) }),
pow: (num, power) => mod.FpPow(Fp2, num, power), pow: (num, power) => mod.FpPow(Fp2, num, power),
invertBatch: (nums) => mod.FpInvertBatch(Fp2, nums), invertBatch: (nums) => mod.FpInvertBatch(Fp2, nums),
// Normalized // Normalized
add: Fp2Add, add: Fp2Add,
sub: Fp2Subtract, sub: Fp2Subtract,
mul: Fp2Multiply, mul: Fp2Multiply,
square: Fp2Square, sqr: Fp2Square,
// NonNormalized stuff // NonNormalized stuff
addN: Fp2Add, addN: Fp2Add,
subN: Fp2Subtract, subN: Fp2Subtract,
mulN: Fp2Multiply, mulN: Fp2Multiply,
squareN: Fp2Square, sqrN: Fp2Square,
// Why inversion for bigint inside Fp instead of Fp2? it is even used in that context? // Why inversion for bigint inside Fp instead of Fp2? it is even used in that context?
div: (lhs, rhs) => div: (lhs, rhs) => Fp2.mul(lhs, typeof rhs === 'bigint' ? Fp.inv(Fp.create(rhs)) : Fp2.inv(rhs)),
Fp2.mul(lhs, typeof rhs === 'bigint' ? Fp.invert(Fp.create(rhs)) : Fp2.invert(rhs)), inv: ({ c0: a, c1: b }) => {
invert: ({ c0: a, c1: b }) => {
// We wish to find the multiplicative inverse of a nonzero // We wish to find the multiplicative inverse of a nonzero
// element a + bu in Fp2. We leverage an identity // element a + bu in Fp2. We leverage an identity
// //
@@ -131,11 +130,11 @@ const Fp2: mod.Field<Fp2> & Fp2Utils = {
// This gives that (a - bu)/(a² + b²) is the inverse // This gives that (a - bu)/(a² + b²) is the inverse
// of (a + bu). Importantly, this can be computing using // of (a + bu). Importantly, this can be computing using
// only a single inversion in Fp. // only a single inversion in Fp.
const factor = Fp.invert(Fp.create(a * a + b * b)); const factor = Fp.inv(Fp.create(a * a + b * b));
return { c0: Fp.mul(factor, Fp.create(a)), c1: Fp.mul(factor, Fp.create(-b)) }; return { c0: Fp.mul(factor, Fp.create(a)), c1: Fp.mul(factor, Fp.create(-b)) };
}, },
sqrt: (num) => { sqrt: (num) => {
if (Fp2.equals(num, Fp2.ZERO)) return Fp2.ZERO; // Algo doesn't handles this case if (Fp2.eql(num, Fp2.ZERO)) return Fp2.ZERO; // Algo doesn't handles this case
// TODO: Optimize this line. It's extremely slow. // TODO: Optimize this line. It's extremely slow.
// Speeding this up would boost aggregateSignatures. // Speeding this up would boost aggregateSignatures.
// https://eprint.iacr.org/2012/685.pdf applicable? // https://eprint.iacr.org/2012/685.pdf applicable?
@@ -143,15 +142,15 @@ const Fp2: mod.Field<Fp2> & Fp2Utils = {
// https://github.com/supranational/blst/blob/aae0c7d70b799ac269ff5edf29d8191dbd357876/src/exp2.c#L1 // https://github.com/supranational/blst/blob/aae0c7d70b799ac269ff5edf29d8191dbd357876/src/exp2.c#L1
// Inspired by https://github.com/dalek-cryptography/curve25519-dalek/blob/17698df9d4c834204f83a3574143abacb4fc81a5/src/field.rs#L99 // Inspired by https://github.com/dalek-cryptography/curve25519-dalek/blob/17698df9d4c834204f83a3574143abacb4fc81a5/src/field.rs#L99
const candidateSqrt = Fp2.pow(num, (Fp2.ORDER + 8n) / 16n); const candidateSqrt = Fp2.pow(num, (Fp2.ORDER + 8n) / 16n);
const check = Fp2.div(Fp2.square(candidateSqrt), num); // candidateSqrt.square().div(this); const check = Fp2.div(Fp2.sqr(candidateSqrt), num); // candidateSqrt.square().div(this);
const R = FP2_ROOTS_OF_UNITY; const R = FP2_ROOTS_OF_UNITY;
const divisor = [R[0], R[2], R[4], R[6]].find((r) => Fp2.equals(r, check)); const divisor = [R[0], R[2], R[4], R[6]].find((r) => Fp2.eql(r, check));
if (!divisor) throw new Error('No root'); if (!divisor) throw new Error('No root');
const index = R.indexOf(divisor); const index = R.indexOf(divisor);
const root = R[index / 2]; const root = R[index / 2];
if (!root) throw new Error('Invalid root'); if (!root) throw new Error('Invalid root');
const x1 = Fp2.div(candidateSqrt, root); const x1 = Fp2.div(candidateSqrt, root);
const x2 = Fp2.negate(x1); const x2 = Fp2.neg(x1);
const { re: re1, im: im1 } = Fp2.reim(x1); const { re: re1, im: im1 } = Fp2.reim(x1);
const { re: re2, im: im2 } = Fp2.reim(x2); const { re: re2, im: im2 } = Fp2.reim(x2);
if (im1 > im2 || (im1 === im2 && re1 > re2)) return x1; if (im1 > im2 || (im1 === im2 && re1 > re2)) return x1;
@@ -170,7 +169,7 @@ const Fp2: mod.Field<Fp2> & Fp2Utils = {
if (b.length !== Fp2.BYTES) throw new Error(`fromBytes wrong length=${b.length}`); if (b.length !== Fp2.BYTES) throw new Error(`fromBytes wrong length=${b.length}`);
return { c0: Fp.fromBytes(b.subarray(0, Fp.BYTES)), c1: Fp.fromBytes(b.subarray(Fp.BYTES)) }; return { c0: Fp.fromBytes(b.subarray(0, Fp.BYTES)), c1: Fp.fromBytes(b.subarray(Fp.BYTES)) };
}, },
toBytes: ({ c0, c1 }) => concatBytes(Fp.toBytes(c0), Fp.toBytes(c1)), toBytes: ({ c0, c1 }) => concatB(Fp.toBytes(c0), Fp.toBytes(c1)),
cmov: ({ c0, c1 }, { c0: r0, c1: r1 }, c) => ({ cmov: ({ c0, c1 }, { c0: r0, c1: r1 }, c) => ({
c0: Fp.cmov(c0, r0, c), c0: Fp.cmov(c0, r0, c),
c1: Fp.cmov(c1, r1, c), c1: Fp.cmov(c1, r1, c),
@@ -280,18 +279,15 @@ const Fp6Multiply = ({ c0, c1, c2 }: Fp6, rhs: Fp6 | bigint) => {
}; };
}; };
const Fp6Square = ({ c0, c1, c2 }: Fp6) => { const Fp6Square = ({ c0, c1, c2 }: Fp6) => {
let t0 = Fp2.square(c0); // c0² let t0 = Fp2.sqr(c0); // c0²
let t1 = Fp2.mul(Fp2.mul(c0, c1), 2n); // 2 * c0 * c1 let t1 = Fp2.mul(Fp2.mul(c0, c1), 2n); // 2 * c0 * c1
let t3 = Fp2.mul(Fp2.mul(c1, c2), 2n); // 2 * c1 * c2 let t3 = Fp2.mul(Fp2.mul(c1, c2), 2n); // 2 * c1 * c2
let t4 = Fp2.square(c2); // c2² let t4 = Fp2.sqr(c2); // c2²
return { return {
c0: Fp2.add(Fp2.mulByNonresidue(t3), t0), // T3 * (u + 1) + T0 c0: Fp2.add(Fp2.mulByNonresidue(t3), t0), // T3 * (u + 1) + T0
c1: Fp2.add(Fp2.mulByNonresidue(t4), t1), // T4 * (u + 1) + T1 c1: Fp2.add(Fp2.mulByNonresidue(t4), t1), // T4 * (u + 1) + T1
// T1 + (c0 - c1 + c2)² + T3 - T0 - T4 // T1 + (c0 - c1 + c2)² + T3 - T0 - T4
c2: Fp2.sub( c2: Fp2.sub(Fp2.sub(Fp2.add(Fp2.add(t1, Fp2.sqr(Fp2.add(Fp2.sub(c0, c1), c2))), t3), t0), t4),
Fp2.sub(Fp2.add(Fp2.add(t1, Fp2.square(Fp2.add(Fp2.sub(c0, c1), c2))), t3), t0),
t4
),
}; };
}; };
type Fp6Utils = { type Fp6Utils = {
@@ -312,35 +308,34 @@ const Fp6: mod.Field<Fp6> & Fp6Utils = {
ONE: { c0: Fp2.ONE, c1: Fp2.ZERO, c2: Fp2.ZERO }, ONE: { c0: Fp2.ONE, c1: Fp2.ZERO, c2: Fp2.ZERO },
create: (num) => num, create: (num) => num,
isValid: ({ c0, c1, c2 }) => Fp2.isValid(c0) && Fp2.isValid(c1) && Fp2.isValid(c2), isValid: ({ c0, c1, c2 }) => Fp2.isValid(c0) && Fp2.isValid(c1) && Fp2.isValid(c2),
isZero: ({ c0, c1, c2 }) => Fp2.isZero(c0) && Fp2.isZero(c1) && Fp2.isZero(c2), is0: ({ c0, c1, c2 }) => Fp2.is0(c0) && Fp2.is0(c1) && Fp2.is0(c2),
negate: ({ c0, c1, c2 }) => ({ c0: Fp2.negate(c0), c1: Fp2.negate(c1), c2: Fp2.negate(c2) }), neg: ({ c0, c1, c2 }) => ({ c0: Fp2.neg(c0), c1: Fp2.neg(c1), c2: Fp2.neg(c2) }),
equals: ({ c0, c1, c2 }, { c0: r0, c1: r1, c2: r2 }) => eql: ({ c0, c1, c2 }, { c0: r0, c1: r1, c2: r2 }) =>
Fp2.equals(c0, r0) && Fp2.equals(c1, r1) && Fp2.equals(c2, r2), Fp2.eql(c0, r0) && Fp2.eql(c1, r1) && Fp2.eql(c2, r2),
sqrt: () => { sqrt: () => {
throw new Error('Not implemented'); throw new Error('Not implemented');
}, },
// Do we need division by bigint at all? Should be done via order: // Do we need division by bigint at all? Should be done via order:
div: (lhs, rhs) => div: (lhs, rhs) => Fp6.mul(lhs, typeof rhs === 'bigint' ? Fp.inv(Fp.create(rhs)) : Fp6.inv(rhs)),
Fp6.mul(lhs, typeof rhs === 'bigint' ? Fp.invert(Fp.create(rhs)) : Fp6.invert(rhs)),
pow: (num, power) => mod.FpPow(Fp6, num, power), pow: (num, power) => mod.FpPow(Fp6, num, power),
invertBatch: (nums) => mod.FpInvertBatch(Fp6, nums), invertBatch: (nums) => mod.FpInvertBatch(Fp6, nums),
// Normalized // Normalized
add: Fp6Add, add: Fp6Add,
sub: Fp6Subtract, sub: Fp6Subtract,
mul: Fp6Multiply, mul: Fp6Multiply,
square: Fp6Square, sqr: Fp6Square,
// NonNormalized stuff // NonNormalized stuff
addN: Fp6Add, addN: Fp6Add,
subN: Fp6Subtract, subN: Fp6Subtract,
mulN: Fp6Multiply, mulN: Fp6Multiply,
squareN: Fp6Square, sqrN: Fp6Square,
invert: ({ c0, c1, c2 }) => { inv: ({ c0, c1, c2 }) => {
let t0 = Fp2.sub(Fp2.square(c0), Fp2.mulByNonresidue(Fp2.mul(c2, c1))); // c0² - c2 * c1 * (u + 1) let t0 = Fp2.sub(Fp2.sqr(c0), Fp2.mulByNonresidue(Fp2.mul(c2, c1))); // c0² - c2 * c1 * (u + 1)
let t1 = Fp2.sub(Fp2.mulByNonresidue(Fp2.square(c2)), Fp2.mul(c0, c1)); // c2² * (u + 1) - c0 * c1 let t1 = Fp2.sub(Fp2.mulByNonresidue(Fp2.sqr(c2)), Fp2.mul(c0, c1)); // c2² * (u + 1) - c0 * c1
let t2 = Fp2.sub(Fp2.square(c1), Fp2.mul(c0, c2)); // c1² - c0 * c2 let t2 = Fp2.sub(Fp2.sqr(c1), Fp2.mul(c0, c2)); // c1² - c0 * c2
// 1/(((c2 * T1 + c1 * T2) * v) + c0 * T0) // 1/(((c2 * T1 + c1 * T2) * v) + c0 * T0)
let t4 = Fp2.invert( let t4 = Fp2.inv(
Fp2.add(Fp2.mulByNonresidue(Fp2.add(Fp2.mul(c2, t1), Fp2.mul(c1, t2))), Fp2.mul(c0, t0)) Fp2.add(Fp2.mulByNonresidue(Fp2.add(Fp2.mul(c2, t1), Fp2.mul(c1, t2))), Fp2.mul(c0, t0))
); );
return { c0: Fp2.mul(t4, t0), c1: Fp2.mul(t4, t1), c2: Fp2.mul(t4, t2) }; return { c0: Fp2.mul(t4, t0), c1: Fp2.mul(t4, t1), c2: Fp2.mul(t4, t2) };
@@ -355,7 +350,7 @@ const Fp6: mod.Field<Fp6> & Fp6Utils = {
}; };
}, },
toBytes: ({ c0, c1, c2 }): Uint8Array => toBytes: ({ c0, c1, c2 }): Uint8Array =>
concatBytes(Fp2.toBytes(c0), Fp2.toBytes(c1), Fp2.toBytes(c2)), concatB(Fp2.toBytes(c0), Fp2.toBytes(c1), Fp2.toBytes(c2)),
cmov: ({ c0, c1, c2 }: Fp6, { c0: r0, c1: r1, c2: r2 }: Fp6, c) => ({ cmov: ({ c0, c1, c2 }: Fp6, { c0: r0, c1: r1, c2: r2 }: Fp6, c) => ({
c0: Fp2.cmov(c0, r0, c), c0: Fp2.cmov(c0, r0, c),
c1: Fp2.cmov(c1, r1, c), c1: Fp2.cmov(c1, r1, c),
@@ -498,11 +493,11 @@ const Fp12Square = ({ c0, c1 }: Fp12) => {
}; // AB + AB }; // AB + AB
}; };
function Fp4Square(a: Fp2, b: Fp2): { first: Fp2; second: Fp2 } { function Fp4Square(a: Fp2, b: Fp2): { first: Fp2; second: Fp2 } {
const a2 = Fp2.square(a); const a2 = Fp2.sqr(a);
const b2 = Fp2.square(b); const b2 = Fp2.sqr(b);
return { return {
first: Fp2.add(Fp2.mulByNonresidue(b2), a2), // b² * Nonresidue + a² first: Fp2.add(Fp2.mulByNonresidue(b2), a2), // b² * Nonresidue + a²
second: Fp2.sub(Fp2.sub(Fp2.square(Fp2.add(a, b)), a2), b2), // (a + b)² - a² - b² second: Fp2.sub(Fp2.sub(Fp2.sqr(Fp2.add(a, b)), a2), b2), // (a + b)² - a² - b²
}; };
} }
type Fp12Utils = { type Fp12Utils = {
@@ -525,30 +520,30 @@ const Fp12: mod.Field<Fp12> & Fp12Utils = {
ONE: { c0: Fp6.ONE, c1: Fp6.ZERO }, ONE: { c0: Fp6.ONE, c1: Fp6.ZERO },
create: (num) => num, create: (num) => num,
isValid: ({ c0, c1 }) => Fp6.isValid(c0) && Fp6.isValid(c1), isValid: ({ c0, c1 }) => Fp6.isValid(c0) && Fp6.isValid(c1),
isZero: ({ c0, c1 }) => Fp6.isZero(c0) && Fp6.isZero(c1), is0: ({ c0, c1 }) => Fp6.is0(c0) && Fp6.is0(c1),
negate: ({ c0, c1 }) => ({ c0: Fp6.negate(c0), c1: Fp6.negate(c1) }), neg: ({ c0, c1 }) => ({ c0: Fp6.neg(c0), c1: Fp6.neg(c1) }),
equals: ({ c0, c1 }, { c0: r0, c1: r1 }) => Fp6.equals(c0, r0) && Fp6.equals(c1, r1), eql: ({ c0, c1 }, { c0: r0, c1: r1 }) => Fp6.eql(c0, r0) && Fp6.eql(c1, r1),
sqrt: () => { sqrt: () => {
throw new Error('Not implemented'); throw new Error('Not implemented');
}, },
invert: ({ c0, c1 }) => { inv: ({ c0, c1 }) => {
let t = Fp6.invert(Fp6.sub(Fp6.square(c0), Fp6.mulByNonresidue(Fp6.square(c1)))); // 1 / (c0² - c1² * v) let t = Fp6.inv(Fp6.sub(Fp6.sqr(c0), Fp6.mulByNonresidue(Fp6.sqr(c1)))); // 1 / (c0² - c1² * v)
return { c0: Fp6.mul(c0, t), c1: Fp6.negate(Fp6.mul(c1, t)) }; // ((C0 * T) * T) + (-C1 * T) * w return { c0: Fp6.mul(c0, t), c1: Fp6.neg(Fp6.mul(c1, t)) }; // ((C0 * T) * T) + (-C1 * T) * w
}, },
div: (lhs, rhs) => div: (lhs, rhs) =>
Fp12.mul(lhs, typeof rhs === 'bigint' ? Fp.invert(Fp.create(rhs)) : Fp12.invert(rhs)), Fp12.mul(lhs, typeof rhs === 'bigint' ? Fp.inv(Fp.create(rhs)) : Fp12.inv(rhs)),
pow: (num, power) => mod.FpPow(Fp12, num, power), pow: (num, power) => mod.FpPow(Fp12, num, power),
invertBatch: (nums) => mod.FpInvertBatch(Fp12, nums), invertBatch: (nums) => mod.FpInvertBatch(Fp12, nums),
// Normalized // Normalized
add: Fp12Add, add: Fp12Add,
sub: Fp12Subtract, sub: Fp12Subtract,
mul: Fp12Multiply, mul: Fp12Multiply,
square: Fp12Square, sqr: Fp12Square,
// NonNormalized stuff // NonNormalized stuff
addN: Fp12Add, addN: Fp12Add,
subN: Fp12Subtract, subN: Fp12Subtract,
mulN: Fp12Multiply, mulN: Fp12Multiply,
squareN: Fp12Square, sqrN: Fp12Square,
// Bytes utils // Bytes utils
fromBytes: (b: Uint8Array): Fp12 => { fromBytes: (b: Uint8Array): Fp12 => {
@@ -558,7 +553,7 @@ const Fp12: mod.Field<Fp12> & Fp12Utils = {
c1: Fp6.fromBytes(b.subarray(Fp6.BYTES)), c1: Fp6.fromBytes(b.subarray(Fp6.BYTES)),
}; };
}, },
toBytes: ({ c0, c1 }): Uint8Array => concatBytes(Fp6.toBytes(c0), Fp6.toBytes(c1)), toBytes: ({ c0, c1 }): Uint8Array => concatB(Fp6.toBytes(c0), Fp6.toBytes(c1)),
cmov: ({ c0, c1 }, { c0: r0, c1: r1 }, c) => ({ cmov: ({ c0, c1 }, { c0: r0, c1: r1 }, c) => ({
c0: Fp6.cmov(c0, r0, c), c0: Fp6.cmov(c0, r0, c),
c1: Fp6.cmov(c1, r1, c), c1: Fp6.cmov(c1, r1, c),
@@ -602,7 +597,7 @@ const Fp12: mod.Field<Fp12> & Fp12Utils = {
c0: Fp6.multiplyByFp2(c0, rhs), c0: Fp6.multiplyByFp2(c0, rhs),
c1: Fp6.multiplyByFp2(c1, rhs), c1: Fp6.multiplyByFp2(c1, rhs),
}), }),
conjugate: ({ c0, c1 }): Fp12 => ({ c0, c1: Fp6.negate(c1) }), conjugate: ({ c0, c1 }): Fp12 => ({ c0, c1: Fp6.neg(c1) }),
// A cyclotomic group is a subgroup of Fp^n defined by // A cyclotomic group is a subgroup of Fp^n defined by
// GΦₙ(p) = {α ∈ Fpⁿ : α^Φₙ(p) = 1} // GΦₙ(p) = {α ∈ Fpⁿ : α^Φₙ(p) = 1}
@@ -886,7 +881,7 @@ function psi(x: Fp2, y: Fp2): [Fp2, Fp2] {
return [x2, y2]; return [x2, y2];
} }
// Ψ endomorphism // Ψ endomorphism
function G2psi(c: ProjectiveConstructor<Fp2>, P: ProjectivePointType<Fp2>) { function G2psi(c: ProjConstructor<Fp2>, P: ProjPointType<Fp2>) {
const affine = P.toAffine(); const affine = P.toAffine();
const p = psi(affine.x, affine.y); const p = psi(affine.x, affine.y);
return new c(p[0], p[1], Fp2.ONE); return new c(p[0], p[1], Fp2.ONE);
@@ -897,9 +892,9 @@ const PSI2_C1 =
0x1a0111ea397fe699ec02408663d4de85aa0d857d89759ad4897d29650fb85f9b409427eb4f49fffd8bfd00000000aaacn; 0x1a0111ea397fe699ec02408663d4de85aa0d857d89759ad4897d29650fb85f9b409427eb4f49fffd8bfd00000000aaacn;
function psi2(x: Fp2, y: Fp2): [Fp2, Fp2] { function psi2(x: Fp2, y: Fp2): [Fp2, Fp2] {
return [Fp2.mul(x, PSI2_C1), Fp2.negate(y)]; return [Fp2.mul(x, PSI2_C1), Fp2.neg(y)];
} }
function G2psi2(c: ProjectiveConstructor<Fp2>, P: ProjectivePointType<Fp2>) { function G2psi2(c: ProjConstructor<Fp2>, P: ProjPointType<Fp2>) {
const affine = P.toAffine(); const affine = P.toAffine();
const p = psi2(affine.x, affine.y); const p = psi2(affine.x, affine.y);
return new c(p[0], p[1], Fp2.ONE); return new c(p[0], p[1], Fp2.ONE);
@@ -920,6 +915,7 @@ const htfDefaults = {
// defined in section 2.2.5 // defined in section 2.2.5
// Use utils.getDSTLabel(), utils.setDSTLabel(value) // Use utils.getDSTLabel(), utils.setDSTLabel(value)
DST: 'BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_NUL_', DST: 'BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_NUL_',
encodeDST: 'BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_NUL_',
// p: the characteristic of F // p: the characteristic of F
// where F is a finite field of characteristic p and order q = p^m // where F is a finite field of characteristic p and order q = p^m
p: Fp.ORDER, p: Fp.ORDER,
@@ -989,7 +985,7 @@ export const bls12_381: CurveFn<Fp, Fp2, Fp6, Fp12> = bls({
// φ endomorphism // φ endomorphism
const cubicRootOfUnityModP = const cubicRootOfUnityModP =
0x5f19672fdf76ce51ba69c6076a0f77eaddb3a93be6f89688de17d813620a00022e01fffffffefffen; 0x5f19672fdf76ce51ba69c6076a0f77eaddb3a93be6f89688de17d813620a00022e01fffffffefffen;
const phi = new c(Fp.mul(point.x, cubicRootOfUnityModP), point.y, point.z); const phi = new c(Fp.mul(point.px, cubicRootOfUnityModP), point.py, point.pz);
// todo: unroll // todo: unroll
const xP = point.multiplyUnsafe(bls12_381.CURVE.x).negate(); // [x]P const xP = point.multiplyUnsafe(bls12_381.CURVE.x).negate(); // [x]P
@@ -1018,7 +1014,7 @@ export const bls12_381: CurveFn<Fp, Fp2, Fp6, Fp12> = bls({
const { x, y } = G1_SWU(Fp.create(scalars[0])); const { x, y } = G1_SWU(Fp.create(scalars[0]));
return isogenyMapG1(x, y); return isogenyMapG1(x, y);
}, },
fromBytes: (bytes: Uint8Array): { x: Fp; y: Fp } => { fromBytes: (bytes: Uint8Array): AffinePoint<Fp> => {
if (bytes.length === 48) { if (bytes.length === 48) {
const P = Fp.ORDER; const P = Fp.ORDER;
const compressedValue = bytesToNumberBE(bytes); const compressedValue = bytesToNumberBE(bytes);
@@ -1030,11 +1026,11 @@ export const bls12_381: CurveFn<Fp, Fp2, Fp6, Fp12> = bls({
let y = Fp.sqrt(right); let y = Fp.sqrt(right);
if (!y) throw new Error('Invalid compressed G1 point'); if (!y) throw new Error('Invalid compressed G1 point');
const aflag = bitGet(compressedValue, C_BIT_POS); const aflag = bitGet(compressedValue, C_BIT_POS);
if ((y * 2n) / P !== aflag) y = Fp.negate(y); if ((y * 2n) / P !== aflag) y = Fp.neg(y);
return { x: Fp.create(x), y: Fp.create(y) }; return { x: Fp.create(x), y: Fp.create(y) };
} else if (bytes.length === 96) { } else if (bytes.length === 96) {
// Check if the infinity flag is set // Check if the infinity flag is set
if ((bytes[0] & (1 << 6)) !== 0) return bls12_381.G1.Point.ZERO; if ((bytes[0] & (1 << 6)) !== 0) return bls12_381.G1.ProjectivePoint.ZERO.toAffine();
const x = bytesToNumberBE(bytes.slice(0, Fp.BYTES)); const x = bytesToNumberBE(bytes.slice(0, Fp.BYTES));
const y = bytesToNumberBE(bytes.slice(Fp.BYTES)); const y = bytesToNumberBE(bytes.slice(Fp.BYTES));
return { x: Fp.create(x), y: Fp.create(y) }; return { x: Fp.create(x), y: Fp.create(y) };
@@ -1044,7 +1040,7 @@ export const bls12_381: CurveFn<Fp, Fp2, Fp6, Fp12> = bls({
}, },
toBytes: (c, point, isCompressed) => { toBytes: (c, point, isCompressed) => {
const isZero = point.equals(c.ZERO); const isZero = point.equals(c.ZERO);
const { x, y } = point; const { x, y } = point.toAffine();
if (isCompressed) { if (isCompressed) {
if (isZero) return COMPRESSED_ZERO.slice(); if (isZero) return COMPRESSED_ZERO.slice();
const P = Fp.ORDER; const P = Fp.ORDER;
@@ -1055,10 +1051,10 @@ export const bls12_381: CurveFn<Fp, Fp2, Fp6, Fp12> = bls({
} else { } else {
if (isZero) { if (isZero) {
// 2x PUBLIC_KEY_LENGTH // 2x PUBLIC_KEY_LENGTH
const x = concatBytes(new Uint8Array([0x40]), new Uint8Array(2 * Fp.BYTES - 1)); const x = concatB(new Uint8Array([0x40]), new Uint8Array(2 * Fp.BYTES - 1));
return x; return x;
} else { } else {
return concatBytes(numberToBytesBE(x, Fp.BYTES), numberToBytesBE(y, Fp.BYTES)); return concatB(numberToBytesBE(x, Fp.BYTES), numberToBytesBE(y, Fp.BYTES));
} }
} }
}, },
@@ -1120,7 +1116,7 @@ export const bls12_381: CurveFn<Fp, Fp2, Fp6, Fp12> = bls({
const Q = t3.subtract(P); // Ψ²(2P) - Ψ(P) + [x²]P - [x]Ψ(P) + [x]P - 1P const Q = t3.subtract(P); // Ψ²(2P) - Ψ(P) + [x²]P - [x]Ψ(P) + [x]P - 1P
return Q; // [x²-x-1]P + [x-1]Ψ(P) + Ψ²(2P) return Q; // [x²-x-1]P + [x-1]Ψ(P) + Ψ²(2P)
}, },
fromBytes: (bytes: Uint8Array): { x: Fp2; y: Fp2 } => { fromBytes: (bytes: Uint8Array): AffinePoint<Fp2> => {
const m_byte = bytes[0] & 0xe0; const m_byte = bytes[0] & 0xe0;
if (m_byte === 0x20 || m_byte === 0x60 || m_byte === 0xe0) { if (m_byte === 0x20 || m_byte === 0x60 || m_byte === 0xe0) {
throw new Error('Invalid encoding flag: ' + m_byte); throw new Error('Invalid encoding flag: ' + m_byte);
@@ -1128,6 +1124,8 @@ export const bls12_381: CurveFn<Fp, Fp2, Fp6, Fp12> = bls({
const bitC = m_byte & 0x80; // compression bit const bitC = m_byte & 0x80; // compression bit
const bitI = m_byte & 0x40; // point at infinity bit const bitI = m_byte & 0x40; // point at infinity bit
const bitS = m_byte & 0x20; // sign bit const bitS = m_byte & 0x20; // sign bit
const L = Fp.BYTES;
const slc = (b: Uint8Array, from: number, to?: number) => bytesToNumberBE(b.slice(from, to));
if (bytes.length === 96 && bitC) { if (bytes.length === 96 && bitC) {
const { b } = bls12_381.CURVE.G2; const { b } = bls12_381.CURVE.G2;
const P = Fp.ORDER; const P = Fp.ORDER;
@@ -1140,23 +1138,23 @@ export const bls12_381: CurveFn<Fp, Fp2, Fp6, Fp12> = bls({
} }
return { x: Fp2.ZERO, y: Fp2.ZERO }; return { x: Fp2.ZERO, y: Fp2.ZERO };
} }
const x_1 = bytesToNumberBE(bytes.slice(0, Fp.BYTES)); const x_1 = slc(bytes, 0, L);
const x_0 = bytesToNumberBE(bytes.slice(Fp.BYTES)); const x_0 = slc(bytes, L, 2 * L);
const x = Fp2.create({ c0: Fp.create(x_0), c1: Fp.create(x_1) }); const x = Fp2.create({ c0: Fp.create(x_0), c1: Fp.create(x_1) });
const right = Fp2.add(Fp2.pow(x, 3n), b); // y² = x³ + 4 * (u+1) = x³ + b const right = Fp2.add(Fp2.pow(x, 3n), b); // y² = x³ + 4 * (u+1) = x³ + b
let y = Fp2.sqrt(right); let y = Fp2.sqrt(right);
const Y_bit = y.c1 === 0n ? (y.c0 * 2n) / P : (y.c1 * 2n) / P ? 1n : 0n; const Y_bit = y.c1 === 0n ? (y.c0 * 2n) / P : (y.c1 * 2n) / P ? 1n : 0n;
y = bitS > 0 && Y_bit > 0 ? y : Fp2.negate(y); y = bitS > 0 && Y_bit > 0 ? y : Fp2.neg(y);
return { x, y }; return { x, y };
} else if (bytes.length === 192 && !bitC) { } else if (bytes.length === 192 && !bitC) {
// Check if the infinity flag is set // Check if the infinity flag is set
if ((bytes[0] & (1 << 6)) !== 0) { if ((bytes[0] & (1 << 6)) !== 0) {
return { x: Fp2.ZERO, y: Fp2.ZERO }; return { x: Fp2.ZERO, y: Fp2.ZERO };
} }
const x1 = bytesToNumberBE(bytes.slice(0, Fp.BYTES)); const x1 = slc(bytes, 0, L);
const x0 = bytesToNumberBE(bytes.slice(Fp.BYTES, 2 * Fp.BYTES)); const x0 = slc(bytes, L, 2 * L);
const y1 = bytesToNumberBE(bytes.slice(2 * Fp.BYTES, 3 * Fp.BYTES)); const y1 = slc(bytes, 2 * L, 3 * L);
const y0 = bytesToNumberBE(bytes.slice(3 * Fp.BYTES)); const y0 = slc(bytes, 3 * L, 4 * L);
return { x: Fp2.fromBigTuple([x0, x1]), y: Fp2.fromBigTuple([y0, y1]) }; return { x: Fp2.fromBigTuple([x0, x1]), y: Fp2.fromBigTuple([y0, y1]) };
} else { } else {
throw new Error('Invalid point G2, expected 96/192 bytes'); throw new Error('Invalid point G2, expected 96/192 bytes');
@@ -1164,20 +1162,20 @@ export const bls12_381: CurveFn<Fp, Fp2, Fp6, Fp12> = bls({
}, },
toBytes: (c, point, isCompressed) => { toBytes: (c, point, isCompressed) => {
const isZero = point.equals(c.ZERO); const isZero = point.equals(c.ZERO);
const { x, y } = point; const { x, y } = point.toAffine();
if (isCompressed) { if (isCompressed) {
const P = Fp.ORDER; const P = Fp.ORDER;
if (isZero) return concatBytes(COMPRESSED_ZERO, numberToBytesBE(0n, Fp.BYTES)); if (isZero) return concatB(COMPRESSED_ZERO, numberToBytesBE(0n, Fp.BYTES));
const flag = Boolean(y.c1 === 0n ? (y.c0 * 2n) / P : (y.c1 * 2n) / P); const flag = Boolean(y.c1 === 0n ? (y.c0 * 2n) / P : (y.c1 * 2n) / P);
// set compressed & sign bits (looks like different offsets than for G1/Fp?) // set compressed & sign bits (looks like different offsets than for G1/Fp?)
let x_1 = bitSet(x.c1, C_BIT_POS, flag); let x_1 = bitSet(x.c1, C_BIT_POS, flag);
x_1 = bitSet(x_1, S_BIT_POS, true); x_1 = bitSet(x_1, S_BIT_POS, true);
return concatBytes(numberToBytesBE(x_1, Fp.BYTES), numberToBytesBE(x.c0, Fp.BYTES)); return concatB(numberToBytesBE(x_1, Fp.BYTES), numberToBytesBE(x.c0, Fp.BYTES));
} else { } else {
if (isZero) return concatBytes(new Uint8Array([0x40]), new Uint8Array(4 * Fp.BYTES - 1)); // bytes[0] |= 1 << 6; if (isZero) return concatB(new Uint8Array([0x40]), new Uint8Array(4 * Fp.BYTES - 1)); // bytes[0] |= 1 << 6;
const { re: x0, im: x1 } = Fp2.reim(x); const { re: x0, im: x1 } = Fp2.reim(x);
const { re: y0, im: y1 } = Fp2.reim(y); const { re: y0, im: y1 } = Fp2.reim(y);
return concatBytes( return concatB(
numberToBytesBE(x1, Fp.BYTES), numberToBytesBE(x1, Fp.BYTES),
numberToBytesBE(x0, Fp.BYTES), numberToBytesBE(x0, Fp.BYTES),
numberToBytesBE(y1, Fp.BYTES), numberToBytesBE(y1, Fp.BYTES),
@@ -1187,7 +1185,7 @@ export const bls12_381: CurveFn<Fp, Fp2, Fp6, Fp12> = bls({
}, },
Signature: { Signature: {
// TODO: Optimize, it's very slow because of sqrt. // TODO: Optimize, it's very slow because of sqrt.
decode(hex: Hex): PointType<Fp2> { decode(hex: Hex): ProjPointType<Fp2> {
hex = ensureBytes(hex); hex = ensureBytes(hex);
const P = Fp.ORDER; const P = Fp.ORDER;
const half = hex.length / 2; const half = hex.length / 2;
@@ -1197,7 +1195,7 @@ export const bls12_381: CurveFn<Fp, Fp2, Fp6, Fp12> = bls({
const z2 = bytesToNumberBE(hex.slice(half)); const z2 = bytesToNumberBE(hex.slice(half));
// Indicates the infinity point // Indicates the infinity point
const bflag1 = bitGet(z1, I_BIT_POS); const bflag1 = bitGet(z1, I_BIT_POS);
if (bflag1 === 1n) return bls12_381.G2.Point.ZERO; if (bflag1 === 1n) return bls12_381.G2.ProjectivePoint.ZERO;
const x1 = Fp.create(z1 & Fp.MASK); const x1 = Fp.create(z1 & Fp.MASK);
const x2 = Fp.create(z2); const x2 = Fp.create(z2);
@@ -1213,23 +1211,25 @@ export const bls12_381: CurveFn<Fp, Fp2, Fp6, Fp12> = bls({
const aflag1 = bitGet(z1, 381); const aflag1 = bitGet(z1, 381);
const isGreater = y1 > 0n && (y1 * 2n) / P !== aflag1; const isGreater = y1 > 0n && (y1 * 2n) / P !== aflag1;
const isZero = y1 === 0n && (y0 * 2n) / P !== aflag1; const isZero = y1 === 0n && (y0 * 2n) / P !== aflag1;
if (isGreater || isZero) y = Fp2.negate(y); if (isGreater || isZero) y = Fp2.neg(y);
const point = new bls12_381.G2.Point(x, y); const point = bls12_381.G2.ProjectivePoint.fromAffine({ x, y });
// console.log('Signature.decode', point);
point.assertValidity(); point.assertValidity();
return point; return point;
}, },
encode(point: PointType<Fp2>) { encode(point: ProjPointType<Fp2>) {
// NOTE: by some reasons it was missed in bls12-381, looks like bug // NOTE: by some reasons it was missed in bls12-381, looks like bug
point.assertValidity(); point.assertValidity();
if (point.equals(bls12_381.G2.Point.ZERO)) if (point.equals(bls12_381.G2.ProjectivePoint.ZERO))
return concatBytes(COMPRESSED_ZERO, numberToBytesBE(0n, Fp.BYTES)); return concatB(COMPRESSED_ZERO, numberToBytesBE(0n, Fp.BYTES));
const { re: x0, im: x1 } = Fp2.reim(point.x); const a = point.toAffine();
const { re: y0, im: y1 } = Fp2.reim(point.y); const { re: x0, im: x1 } = Fp2.reim(a.x);
const { re: y0, im: y1 } = Fp2.reim(a.y);
const tmp = y1 > 0n ? y1 * 2n : y0 * 2n; const tmp = y1 > 0n ? y1 * 2n : y0 * 2n;
const aflag1 = Boolean((tmp / Fp.ORDER) & 1n); const aflag1 = Boolean((tmp / Fp.ORDER) & 1n);
const z1 = bitSet(bitSet(x1, 381, aflag1), S_BIT_POS, true); const z1 = bitSet(bitSet(x1, 381, aflag1), S_BIT_POS, true);
const z2 = x0; const z2 = x0;
return concatBytes(numberToBytesBE(z1, Fp.BYTES), numberToBytesBE(z2, Fp.BYTES)); return concatB(numberToBytesBE(z1, Fp.BYTES), numberToBytesBE(z2, Fp.BYTES));
}, },
}, },
}, },

View File

@@ -1,6 +1,6 @@
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */ /*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
import { weierstrass } from './abstract/weierstrass.js';
import { sha256 } from '@noble/hashes/sha256'; import { sha256 } from '@noble/hashes/sha256';
import { weierstrass } from './abstract/weierstrass.js';
import { getHash } from './_shortw_utils.js'; import { getHash } from './_shortw_utils.js';
import { Fp } from './abstract/modular.js'; import { Fp } from './abstract/modular.js';
/** /**

View File

@@ -1,7 +1,7 @@
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */ /*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
import { sha512 } from '@noble/hashes/sha512'; import { sha512 } from '@noble/hashes/sha512';
import { concatBytes, randomBytes, utf8ToBytes } from '@noble/hashes/utils'; import { concatBytes, randomBytes, utf8ToBytes } from '@noble/hashes/utils';
import { twistedEdwards, ExtendedPointType } from './abstract/edwards.js'; import { twistedEdwards, ExtPointType } from './abstract/edwards.js';
import { montgomery } from './abstract/montgomery.js'; import { montgomery } from './abstract/montgomery.js';
import { mod, pow2, isNegativeLE, Fp as Field, FpSqrtEven } from './abstract/modular.js'; import { mod, pow2, isNegativeLE, Fp as Field, FpSqrtEven } from './abstract/modular.js';
import { import {
@@ -12,6 +12,7 @@ import {
numberToBytesLE, numberToBytesLE,
Hex, Hex,
} from './abstract/utils.js'; } from './abstract/utils.js';
import * as htf from './abstract/hash-to-curve.js';
/** /**
* ed25519 Twisted Edwards curve with following addons: * ed25519 Twisted Edwards curve with following addons:
@@ -93,79 +94,6 @@ export const ED25519_TORSION_SUBGROUP = [
const Fp = Field(ED25519_P, undefined, true); const Fp = Field(ED25519_P, undefined, true);
// 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.negate(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.square(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.negate(ELL2_J); // 4. x1n = -J # x1 = x1n / xd = -J / (1 + 2 * u^2)
let tv2 = Fp.square(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.square(gxd); // 11. tv3 = gxd^2
tv2 = Fp.square(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.square(y11); // 19. tv2 = y11^2
tv2 = Fp.mul(tv2, gxd); // 20. tv2 = tv2 * gxd
let e1 = Fp.equals(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.square(y21); // 28. tv2 = y21^2
tv2 = Fp.mul(tv2, gxd); // 29. tv2 = tv2 * gxd
let e2 = Fp.equals(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.square(y1); // 32. tv2 = y1^2
tv2 = Fp.mul(tv2, gxd); // 33. tv2 = tv2 * gxd
let e3 = Fp.equals(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.negate(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.negate(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.equals(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 ED25519_DEF = { const ED25519_DEF = {
// Param: a // Param: a
a: BigInt(-1), a: BigInt(-1),
@@ -189,15 +117,6 @@ const ED25519_DEF = {
// Ratio of u to v. Allows us to combine inversion and square root. Uses algo from RFC8032 5.1.3. // Ratio of u to v. Allows us to combine inversion and square root. Uses algo from RFC8032 5.1.3.
// Constant-time, u/√v // Constant-time, u/√v
uvRatio, uvRatio,
htfDefaults: {
DST: 'edwards25519_XMD:SHA-512_ELL2_RO_',
p: Fp.ORDER,
m: 1,
k: 128,
expand: 'xmd',
hash: sha512,
},
mapToCurve: (scalars: bigint[]) => map_to_curve_elligator2_edwards25519(scalars[0]),
} as const; } as const;
export const ed25519 = twistedEdwards(ED25519_DEF); export const ed25519 = twistedEdwards(ED25519_DEF);
@@ -232,8 +151,95 @@ export const x25519 = montgomery({
adjustScalarBytes, adjustScalarBytes,
}); });
// 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 { hashToCurve, encodeToCurve } = htf.hashToCurve(
ed25519.ExtendedPoint,
(scalars: bigint[]) => map_to_curve_elligator2_edwards25519(scalars[0]),
{
DST: 'edwards25519_XMD:SHA-512_ELL2_RO_',
encodeDST: 'edwards25519_XMD:SHA-512_ELL2_NU_',
p: Fp.ORDER,
m: 1,
k: 128,
expand: 'xmd',
hash: sha512,
}
);
export { hashToCurve, encodeToCurve };
function assertRstPoint(other: unknown) { function assertRstPoint(other: unknown) {
if (!(other instanceof RistrettoPoint)) throw new TypeError('RistrettoPoint expected'); if (!(other instanceof RistrettoPoint)) throw new Error('RistrettoPoint expected');
} }
// √(-1) aka √(a) aka 2^((p-1)/4) // √(-1) aka √(a) aka 2^((p-1)/4)
const SQRT_M1 = BigInt( const SQRT_M1 = BigInt(
@@ -262,7 +268,7 @@ const MAX_255B = BigInt('0x7ffffffffffffffffffffffffffffffffffffffffffffffffffff
const bytes255ToNumberLE = (bytes: Uint8Array) => const bytes255ToNumberLE = (bytes: Uint8Array) =>
ed25519.CURVE.Fp.create(bytesToNumberLE(bytes) & MAX_255B); ed25519.CURVE.Fp.create(bytesToNumberLE(bytes) & MAX_255B);
type ExtendedPoint = ExtendedPointType; type ExtendedPoint = ExtPointType;
// Computes Elligator map for Ristretto // Computes Elligator map for Ristretto
// https://ristretto.group/formulas/elligator.html // https://ristretto.group/formulas/elligator.html
@@ -355,7 +361,7 @@ export class RistrettoPoint {
* https://ristretto.group/formulas/encoding.html * https://ristretto.group/formulas/encoding.html
*/ */
toRawBytes(): Uint8Array { 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 P = ed25519.CURVE.Fp.ORDER;
const mod = ed25519.CURVE.Fp.create; const mod = ed25519.CURVE.Fp.create;
const u1 = mod(mod(z + y) * mod(z - y)); // 1 const u1 = mod(mod(z + y) * mod(z - y)); // 1
@@ -393,12 +399,12 @@ export class RistrettoPoint {
// Compare one point to another. // Compare one point to another.
equals(other: RistrettoPoint): boolean { equals(other: RistrettoPoint): boolean {
assertRstPoint(other); assertRstPoint(other);
const a = this.ep; const { ex: X1, ey: Y1 } = this.ep;
const b = other.ep; const { ex: X2, ey: Y2 } = this.ep;
const mod = ed25519.CURVE.Fp.create; const mod = ed25519.CURVE.Fp.create;
// (x1 * y2 == y1 * x2) | (y1 * y2 == x1 * x2) // (x1 * y2 == y1 * x2) | (y1 * y2 == x1 * x2)
const one = mod(a.x * b.y) === mod(a.y * b.x); const one = mod(X1 * Y2) === mod(Y1 * X2);
const two = mod(a.y * b.y) === mod(a.x * b.x); const two = mod(Y1 * Y2) === mod(X1 * X2);
return one || two; return one || two;
} }
@@ -412,11 +418,11 @@ export class RistrettoPoint {
return new RistrettoPoint(this.ep.subtract(other.ep)); return new RistrettoPoint(this.ep.subtract(other.ep));
} }
multiply(scalar: number | bigint): RistrettoPoint { multiply(scalar: bigint): RistrettoPoint {
return new RistrettoPoint(this.ep.multiply(scalar)); return new RistrettoPoint(this.ep.multiply(scalar));
} }
multiplyUnsafe(scalar: number | bigint): RistrettoPoint { multiplyUnsafe(scalar: bigint): RistrettoPoint {
return new RistrettoPoint(this.ep.multiplyUnsafe(scalar)); return new RistrettoPoint(this.ep.multiplyUnsafe(scalar));
} }
} }

View File

@@ -4,6 +4,7 @@ import { concatBytes, randomBytes, utf8ToBytes, wrapConstructor } from '@noble/h
import { twistedEdwards } from './abstract/edwards.js'; import { twistedEdwards } from './abstract/edwards.js';
import { mod, pow2, Fp as Field } from './abstract/modular.js'; import { mod, pow2, Fp as Field } from './abstract/modular.js';
import { montgomery } from './abstract/montgomery.js'; import { montgomery } from './abstract/montgomery.js';
import * as htf from './abstract/hash-to-curve.js';
/** /**
* Edwards448 (not Ed448-Goldilocks) curve with following addons: * Edwards448 (not Ed448-Goldilocks) curve with following addons:
@@ -54,81 +55,6 @@ function adjustScalarBytes(bytes: Uint8Array): Uint8Array {
const Fp = Field(ed448P, 456, true); const Fp = Field(ed448P, 456, true);
// 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.square(u); // 1. tv1 = u^2
let e1 = Fp.equals(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.negate(ELL2_J); // 5. x1n = -J
let tv2 = Fp.square(xd); // 6. tv2 = xd^2
let gxd = Fp.mul(tv2, xd); // 7. gxd = tv2 * xd # gxd = xd^3
let gx1 = Fp.mul(tv1, Fp.negate(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.square(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.negate(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.square(y1); // 20. tv2 = y1^2
tv2 = Fp.mul(tv2, gxd); // 21. tv2 = tv2 * gxd
let e2 = Fp.equals(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.negate(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.square(xn); // 2. xn2 = xn^2
let xd2 = Fp.square(xd); // 3. xd2 = xd^2
let xd4 = Fp.square(xd2); // 4. xd4 = xd2^2
let yn2 = Fp.square(yn); // 5. yn2 = yn^2
let yd2 = Fp.square(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.equals(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 ED448_DEF = { const ED448_DEF = {
// Param: a // Param: a
a: BigInt(1), a: BigInt(1),
@@ -189,15 +115,6 @@ const ED448_DEF = {
// square root exists, and the decoding fails. // square root exists, and the decoding fails.
return { isValid: mod(x2 * v, P) === u, value: x }; return { isValid: mod(x2 * v, P) === u, value: x };
}, },
htfDefaults: {
DST: 'edwards448_XOF:SHAKE256_ELL2_RO_',
p: Fp.ORDER,
m: 1,
k: 224,
expand: 'xof',
hash: shake256,
},
mapToCurve: (scalars: bigint[]) => map_to_curve_elligator2_edwards448(scalars[0]),
} as const; } as const;
export const ed448 = twistedEdwards(ED448_DEF); export const ed448 = twistedEdwards(ED448_DEF);
@@ -232,3 +149,93 @@ export const x448 = montgomery({
// return numberToBytesLE(u, 56); // return numberToBytesLE(u, 56);
// }, // },
}); });
// 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 { hashToCurve, encodeToCurve } = htf.hashToCurve(
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 { hashToCurve, encodeToCurve };

View File

@@ -39,7 +39,7 @@ export function groupHash(tag: Uint8Array, personalization: Uint8Array) {
h.update(GH_FIRST_BLOCK); h.update(GH_FIRST_BLOCK);
h.update(tag); h.update(tag);
// NOTE: returns ExtendedPoint, in case it will be multiplied later // 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 // NOTE: cannot replace with isSmallOrder, returns Point*8
p = p.multiply(jubjub.CURVE.h); p = p.multiply(jubjub.CURVE.h);
if (p.equals(jubjub.ExtendedPoint.ZERO)) throw new Error('Point has small order'); if (p.equals(jubjub.ExtendedPoint.ZERO)) throw new Error('Point has small order');

View File

@@ -10,7 +10,7 @@ export const P224 = createCurve(
// Params: a, b // Params: a, b
a: BigInt('0xfffffffffffffffffffffffffffffffefffffffffffffffffffffffe'), a: BigInt('0xfffffffffffffffffffffffffffffffefffffffffffffffffffffffe'),
b: BigInt('0xb4050a850c04b3abf54132565044b0b7d7bfd8ba270b39432355ffb4'), b: BigInt('0xb4050a850c04b3abf54132565044b0b7d7bfd8ba270b39432355ffb4'),
// Field over which we'll do calculations; 2n**224n - 2n**96n + 1n // Field over which we'll do calculations;
Fp: Fp(BigInt('0xffffffffffffffffffffffffffffffff000000000000000000000001')), Fp: Fp(BigInt('0xffffffffffffffffffffffffffffffff000000000000000000000001')),
// Curve order, total count of valid points in the field // Curve order, total count of valid points in the field
n: BigInt('0xffffffffffffffffffffffffffff16a2e0b8f03e13dd29455c5c2a3d'), n: BigInt('0xffffffffffffffffffffffffffff16a2e0b8f03e13dd29455c5c2a3d'),

View File

@@ -3,6 +3,7 @@ import { createCurve } from './_shortw_utils.js';
import { sha256 } from '@noble/hashes/sha256'; import { sha256 } from '@noble/hashes/sha256';
import { Fp as Field } from './abstract/modular.js'; import { Fp as Field } from './abstract/modular.js';
import { mapToCurveSimpleSWU } from './abstract/weierstrass.js'; import { mapToCurveSimpleSWU } from './abstract/weierstrass.js';
import * as htf from './abstract/hash-to-curve.js';
// NIST secp256r1 aka P256 // NIST secp256r1 aka P256
// https://www.secg.org/sec2-v2.pdf, https://neuromancer.sk/std/nist/P-256 // https://www.secg.org/sec2-v2.pdf, https://neuromancer.sk/std/nist/P-256
@@ -31,16 +32,22 @@ export const P256 = createCurve(
Gy: BigInt('0x4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5'), Gy: BigInt('0x4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5'),
h: BigInt(1), h: BigInt(1),
lowS: false, lowS: false,
mapToCurve: (scalars: bigint[]) => mapSWU(scalars[0]),
htfDefaults: {
DST: 'P256_XMD:SHA-256_SSWU_RO_',
p: Fp.ORDER,
m: 1,
k: 128,
expand: 'xmd',
hash: sha256,
},
} as const, } as const,
sha256 sha256
); );
export const secp256r1 = P256; export const secp256r1 = P256;
const { hashToCurve, encodeToCurve } = htf.hashToCurve(
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: 'xmd',
hash: sha256,
}
);
export { hashToCurve, encodeToCurve };

View File

@@ -3,6 +3,7 @@ import { createCurve } from './_shortw_utils.js';
import { sha384 } from '@noble/hashes/sha512'; import { sha384 } from '@noble/hashes/sha512';
import { Fp as Field } from './abstract/modular.js'; import { Fp as Field } from './abstract/modular.js';
import { mapToCurveSimpleSWU } from './abstract/weierstrass.js'; import { mapToCurveSimpleSWU } from './abstract/weierstrass.js';
import * as htf from './abstract/hash-to-curve.js';
// NIST secp384r1 aka P384 // NIST secp384r1 aka P384
// https://www.secg.org/sec2-v2.pdf, https://neuromancer.sk/std/nist/P-384 // https://www.secg.org/sec2-v2.pdf, https://neuromancer.sk/std/nist/P-384
@@ -35,16 +36,22 @@ export const P384 = createCurve({
Gy: BigInt('0x3617de4a96262c6f5d9e98bf9292dc29f8f41dbd289a147ce9da3113b5f0b8c00a60b1ce1d7e819d7a431d7c90ea0e5f'), Gy: BigInt('0x3617de4a96262c6f5d9e98bf9292dc29f8f41dbd289a147ce9da3113b5f0b8c00a60b1ce1d7e819d7a431d7c90ea0e5f'),
h: BigInt(1), h: BigInt(1),
lowS: false, lowS: false,
mapToCurve: (scalars: bigint[]) => mapSWU(scalars[0]),
htfDefaults: {
DST: 'P384_XMD:SHA-384_SSWU_RO_',
p: Fp.ORDER,
m: 1,
k: 192,
expand: 'xmd',
hash: sha384,
},
} as const, } as const,
sha384 sha384
); );
export const secp384r1 = P384; export const secp384r1 = P384;
const { hashToCurve, encodeToCurve } = htf.hashToCurve(
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: 'xmd',
hash: sha384,
}
);
export { hashToCurve, encodeToCurve };

View File

@@ -1,9 +1,9 @@
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */ /*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
import { createCurve } from './_shortw_utils.js'; import { createCurve } from './_shortw_utils.js';
import { sha512 } from '@noble/hashes/sha512'; import { sha512 } from '@noble/hashes/sha512';
import { bytesToHex, PrivKey } from './abstract/utils.js';
import { Fp as Field } from './abstract/modular.js'; import { Fp as Field } from './abstract/modular.js';
import { mapToCurveSimpleSWU } from './abstract/weierstrass.js'; import { mapToCurveSimpleSWU } from './abstract/weierstrass.js';
import * as htf from './abstract/hash-to-curve.js';
// NIST secp521r1 aka P521 // NIST secp521r1 aka P521
// Note that it's 521, which differs from 512 of its hash function. // Note that it's 521, which differs from 512 of its hash function.
@@ -37,25 +37,21 @@ export const P521 = createCurve({
Gy: BigInt('0x011839296a789a3bc0045c8a5fb42c7d1bd998f54449579b446817afbd17273e662c97ee72995ef42640c550b9013fad0761353c7086a272c24088be94769fd16650'), Gy: BigInt('0x011839296a789a3bc0045c8a5fb42c7d1bd998f54449579b446817afbd17273e662c97ee72995ef42640c550b9013fad0761353c7086a272c24088be94769fd16650'),
h: BigInt(1), h: BigInt(1),
lowS: false, lowS: false,
// P521 keys could be 130, 131, 132 bytes - which doesn't play nicely. allowedPrivateKeyLengths: [130, 131, 132] // P521 keys are variable-length. Normalize to 132b
// We ensure all keys are 132 bytes. } as const, sha512);
// Does not replace validation; invalid keys would still be rejected. export const secp521r1 = P521;
normalizePrivateKey(key: PrivKey) {
if (typeof key === 'bigint') return key; const { hashToCurve, encodeToCurve } = htf.hashToCurve(
if (key instanceof Uint8Array) key = bytesToHex(key); secp521r1.ProjectivePoint,
if (typeof key !== 'string' || !([130, 131, 132].includes(key.length))) { (scalars: bigint[]) => mapSWU(scalars[0]),
throw new Error('Invalid key'); {
}
return key.padStart(66 * 2, '0');
},
mapToCurve: (scalars: bigint[]) => mapSWU(scalars[0]),
htfDefaults: {
DST: 'P521_XMD:SHA-512_SSWU_RO_', DST: 'P521_XMD:SHA-512_SSWU_RO_',
encodeDST: 'P521_XMD:SHA-512_SSWU_NU_',
p: Fp.ORDER, p: Fp.ORDER,
m: 1, m: 1,
k: 256, k: 256,
expand: 'xmd', expand: 'xmd',
hash: sha512, hash: sha512,
}, }
} as const, sha512); );
export const secp521r1 = P521; export { hashToCurve, encodeToCurve };

View File

@@ -2,17 +2,17 @@
import { sha256 } from '@noble/hashes/sha256'; import { sha256 } from '@noble/hashes/sha256';
import { Fp as Field, mod, pow2 } from './abstract/modular.js'; import { Fp as Field, mod, pow2 } from './abstract/modular.js';
import { createCurve } from './_shortw_utils.js'; import { createCurve } from './_shortw_utils.js';
import { PointType, mapToCurveSimpleSWU } from './abstract/weierstrass.js'; import { ProjPointType as PointType, mapToCurveSimpleSWU } from './abstract/weierstrass.js';
import { import {
ensureBytes, ensureBytes,
concatBytes, concatBytes,
Hex, Hex,
hexToBytes, bytesToNumberBE as bytesToInt,
bytesToNumberBE,
PrivKey, PrivKey,
numberToBytesBE,
} from './abstract/utils.js'; } from './abstract/utils.js';
import { randomBytes } from '@noble/hashes/utils'; import { randomBytes } from '@noble/hashes/utils';
import { isogenyMap } from './abstract/hash-to-curve.js'; import * as htf from './abstract/hash-to-curve.js';
/** /**
* secp256k1 belongs to Koblitz curves: it has efficiently computable endomorphism. * secp256k1 belongs to Koblitz curves: it has efficiently computable endomorphism.
@@ -29,10 +29,7 @@ const _2n = BigInt(2);
const divNearest = (a: bigint, b: bigint) => (a + b / _2n) / b; const divNearest = (a: bigint, b: bigint) => (a + b / _2n) / b;
/** /**
* Allows to compute square root √y 2x faster. * √n = n^((p+1)/4) for fields p = 3 mod 4. We unwrap the loop and multiply bit-by-bit.
* 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.
* (P+1n/4n).toString(2) would produce bits [223x 1, 0, 22x 1, 4x 0, 11, 00] * (P+1n/4n).toString(2) would produce bits [223x 1, 0, 22x 1, 4x 0, 11, 00]
*/ */
function sqrtMod(y: bigint): bigint { function sqrtMod(y: bigint): bigint {
@@ -55,52 +52,13 @@ function sqrtMod(y: bigint): bigint {
const t1 = (pow2(b223, _23n, P) * b22) % P; const t1 = (pow2(b223, _23n, P) * b22) % P;
const t2 = (pow2(t1, _6n, P) * b2) % P; const t2 = (pow2(t1, _6n, P) * b2) % P;
const root = pow2(t2, _2n, P); const root = pow2(t2, _2n, P);
if (!Fp.equals(Fp.square(root), y)) throw new Error('Cannot find square root'); if (!Fp.eql(Fp.sqr(root), y)) throw new Error('Cannot find square root');
return root; return root;
} }
const Fp = Field(secp256k1P, undefined, undefined, { sqrt: sqrtMod }); const Fp = Field(secp256k1P, undefined, undefined, { sqrt: sqrtMod });
type Fp = bigint; type Fp = bigint;
const isoMap = isogenyMap(
Fp,
[
// xNum
[
'0x8e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38daaaaa8c7',
'0x7d3d4c80bc321d5b9f315cea7fd44c5d595d2fc0bf63b92dfff1044f17c6581',
'0x534c328d23f234e6e2a413deca25caece4506144037c40314ecbd0b53d9dd262',
'0x8e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38daaaaa88c',
],
// xDen
[
'0xd35771193d94918a9ca34ccbb7b640dd86cd409542f8487d9fe6b745781eb49b',
'0xedadc6f64383dc1df7c4b2d51b54225406d36b641f5e41bbc52a56612a8c6d14',
'0x0000000000000000000000000000000000000000000000000000000000000001', // LAST 1
],
// yNum
[
'0x4bda12f684bda12f684bda12f684bda12f684bda12f684bda12f684b8e38e23c',
'0xc75e0c32d5cb7c0fa9d0a54b12a0a6d5647ab046d686da6fdffc90fc201d71a3',
'0x29a6194691f91a73715209ef6512e576722830a201be2018a765e85a9ecee931',
'0x2f684bda12f684bda12f684bda12f684bda12f684bda12f684bda12f38e38d84',
],
// yDen
[
'0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffff93b',
'0x7a06534bb8bdb49fd5e9e6632722c2989467c1bfc8e8d978dfb425d2685c2573',
'0x6484aa716545ca2cf3a70c3fa8fe337e0a3d21162f0d6299a7bf8192bfd2a76f',
'0x0000000000000000000000000000000000000000000000000000000000000001', // LAST 1
],
].map((i) => i.map((j) => BigInt(j))) as [Fp[], Fp[], Fp[], Fp[]]
);
const mapSWU = mapToCurveSimpleSWU(Fp, {
A: BigInt('0x3f8731abdd661adca08a5558f0f5d272e953d363cb6f0e5d405447c01a444533'),
B: BigInt('1771'),
Z: Fp.create(BigInt('-11')),
});
export const secp256k1 = createCurve( export const secp256k1 = createCurve(
{ {
// Params: a, b // Params: a, b
@@ -143,52 +101,16 @@ export const secp256k1 = createCurve(
return { k1neg, k1, k2neg, k2 }; return { k1neg, k1, k2neg, k2 };
}, },
}, },
mapToCurve: (scalars: bigint[]) => {
const { x, y } = mapSWU(Fp.create(scalars[0]));
return isoMap(x, y);
},
htfDefaults: {
DST: 'secp256k1_XMD:SHA-256_SSWU_RO_',
p: Fp.ORDER,
m: 1,
k: 128,
expand: 'xmd',
hash: sha256,
},
}, },
sha256 sha256
); );
// Schnorr // Schnorr signatures are superior to ECDSA from above.
// Below is Schnorr-specific code as per BIP0340.
// https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki
const _0n = BigInt(0); const _0n = BigInt(0);
const numTo32b = secp256k1.utils._bigintToBytes; const fe = (x: bigint) => typeof x === 'bigint' && _0n < x && x < secp256k1P;
const numTo32bStr = secp256k1.utils._bigintToString; const ge = (x: bigint) => typeof x === 'bigint' && _0n < x && x < secp256k1N;
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) throw new Error('Schnorr pubkeys must be 32 bytes');
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;
}
}
const isWithinCurveOrder = secp256k1.utils._isWithinCurveOrder;
const isValidFieldElement = secp256k1.utils._isValidFieldElement;
const TAGS = { const TAGS = {
challenge: 'BIP0340/challenge', challenge: 'BIP0340/challenge',
@@ -198,7 +120,7 @@ const TAGS = {
/** An object mapping tags to their tagged hash prefix of [SHA256(tag) | SHA256(tag)] */ /** An object mapping tags to their tagged hash prefix of [SHA256(tag) | SHA256(tag)] */
const TAGGED_HASH_PREFIXES: { [tag: string]: Uint8Array } = {}; const TAGGED_HASH_PREFIXES: { [tag: string]: Uint8Array } = {};
export function taggedHash(tag: string, ...messages: Uint8Array[]): Uint8Array { function taggedHash(tag: string, ...messages: Uint8Array[]): Uint8Array {
let tagP = TAGGED_HASH_PREFIXES[tag]; let tagP = TAGGED_HASH_PREFIXES[tag];
if (tagP === undefined) { if (tagP === undefined) {
const tagH = sha256(Uint8Array.from(tag, (c) => c.charCodeAt(0))); const tagH = sha256(Uint8Array.from(tag, (c) => c.charCodeAt(0)));
@@ -208,73 +130,58 @@ export function taggedHash(tag: string, ...messages: Uint8Array[]): Uint8Array {
return sha256(concatBytes(tagP, ...messages)); return sha256(concatBytes(tagP, ...messages));
} }
const toRawX = (point: PointType<bigint>) => point.toRawBytes(true).slice(1); const pointToBytes = (point: PointType<bigint>) => point.toRawBytes(true).slice(1);
const numTo32b = (n: bigint) => numberToBytesBE(n, 32);
// Schnorr signatures are superior to ECDSA from above. const modN = (x: bigint) => mod(x, secp256k1N);
// Below is Schnorr-specific code as per BIP0340. const Point = secp256k1.ProjectivePoint;
function schnorrChallengeFinalize(ch: Uint8Array): bigint { const GmulAdd = (Q: PointType<bigint>, a: bigint, b: bigint) =>
return mod(bytesToNumberBE(ch), secp256k1.CURVE.n); Point.BASE.multiplyAndAddUnsafe(Q, a, b);
const hex32ToInt = (key: Hex) => bytesToInt(ensureBytes(key, 32));
function schnorrGetExtPubKey(priv: PrivKey) {
let d = typeof priv === 'bigint' ? priv : hex32ToInt(priv);
const point = Point.fromPrivateKey(d); // P = d'⋅G; 0 < d' < n check is done inside
const scalar = point.hasEvenY() ? d : modN(-d); // d = d' if has_even_y(P), otherwise d = n-d'
return { point, scalar, bytes: pointToBytes(point) };
} }
// Do we need this at all for Schnorr? function lift_x(x: bigint): PointType<bigint> {
class SchnorrSignature { if (!fe(x)) throw new Error('bad x: need 0 < x < p'); // Fail if x ≥ p.
constructor(readonly r: bigint, readonly s: bigint) { const c = mod(x * x * x + BigInt(7), secp256k1P); // Let c = x³ + 7 mod p.
this.assertValidity(); let y = sqrtMod(c); // Let y = c^(p+1)/4 mod p.
} if (y % 2n !== 0n) y = mod(-y, secp256k1P); // Return the unique point P such that x(P) = x and
static fromHex(hex: Hex) { const p = new Point(x, y, _1n); // y(P) = y if y mod 2 = 0 or y(P) = p-y otherwise.
const bytes = ensureBytes(hex); p.assertValidity();
const len = 32; // group length return p;
if (bytes.length !== 2 * len) }
throw new TypeError( function challenge(...args: Uint8Array[]): bigint {
`SchnorrSignature.fromHex: expected ${2 * len} bytes, not ${bytes.length}` return modN(bytesToInt(taggedHash(TAGS.challenge, ...args)));
);
const r = bytesToNumberBE(bytes.subarray(0, len));
const s = bytesToNumberBE(bytes.subarray(len, 2 * len));
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) { // Schnorr's pubkey is just `x` of Point (BIP340)
const point = secp256k1.Point.fromPrivateKey(priv); function schnorrGetPublicKey(privateKey: Hex): Uint8Array {
const scalar = point.hasEvenY() ? priv : secp256k1.CURVE.n - priv; return schnorrGetExtPubKey(privateKey).bytes; // d'=int(sk). Fail if d'=0 or d'≥n. Ret bytes(d'⋅G)
return { point, scalar, x: toRawX(point) };
} }
/**
* Synchronously creates Schnorr signature. Improved security: verifies itself before // Creates Schnorr signature as per BIP340. Verifies itself before returning anything.
* producing an output. // auxRand is optional and is not the sole source of k generation: bad CSPRNG won't be dangerous
* @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( function schnorrSign(
message: Hex, message: Hex,
privateKey: PrivKey, privateKey: PrivKey,
auxRand: Hex = randomBytes(32) auxRand: Hex = randomBytes(32)
): Uint8Array { ): Uint8Array {
if (message == null) throw new TypeError(`sign: Expected valid message, not "${message}"`); if (message == null) throw new Error(`sign: Expected valid message, not "${message}"`);
const m = ensureBytes(message); const m = ensureBytes(message); // checks for isWithinCurveOrder
// checks for isWithinCurveOrder const { bytes: px, scalar: d } = schnorrGetExtPubKey(privateKey);
const { x: px, scalar: d } = schnorrGetScalar(normalizePrivateKey(privateKey)); const a = ensureBytes(auxRand, 32); // Auxiliary random data a: a 32-byte array
const rand = ensureBytes(auxRand); const t = numTo32b(d ^ bytesToInt(taggedHash(TAGS.aux, a))); // Let t be the byte-wise xor of bytes(d) and hash/aux(a)
if (rand.length !== 32) throw new TypeError('sign: Expected 32 bytes of aux randomness'); const rand = taggedHash(TAGS.nonce, t, px, m); // Let rand = hash/nonce(t || bytes(P) || m)
const tag = taggedHash; const k_ = modN(bytesToInt(rand)); // Let k' = int(rand) mod n
const t0h = tag(TAGS.aux, rand); if (k_ === _0n) throw new Error('sign failed: k is zero'); // Fail if k' = 0.
const t = numTo32b(d ^ bytesToNumberBE(t0h)); const { point: R, bytes: rx, scalar: k } = schnorrGetExtPubKey(k_); // Let R = k'⋅G.
const k0h = tag(TAGS.nonce, t, px, m); const e = challenge(rx, px, m); // Let e = int(hash/challenge(bytes(R) || bytes(P) || m)) mod n.
const k0 = mod(bytesToNumberBE(k0h), secp256k1.CURVE.n); const sig = new Uint8Array(64); // Let sig = bytes(R) || bytes((k + ed) mod n).
if (k0 === _0n) throw new Error('sign: Creation of signature failed. k is zero'); sig.set(numTo32b(R.px), 0);
const { point: R, x: rx, scalar: k } = schnorrGetScalar(k0); sig.set(numTo32b(modN(k + e * d)), 32);
const e = schnorrChallengeFinalize(tag(TAGS.challenge, rx, px, m)); // If Verify(bytes(P), m, sig) (see below) returns failure, abort
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'); if (!schnorrVerify(sig, m, px)) throw new Error('sign: Invalid signature produced');
return sig; return sig;
} }
@@ -284,34 +191,88 @@ function schnorrSign(
*/ */
function schnorrVerify(signature: Hex, message: Hex, publicKey: Hex): boolean { function schnorrVerify(signature: Hex, message: Hex, publicKey: Hex): boolean {
try { try {
const raw = signature instanceof SchnorrSignature; const P = lift_x(hex32ToInt(publicKey)); // P = lift_x(int(pk)); fail if that fails
const sig: SchnorrSignature = raw ? signature : SchnorrSignature.fromHex(signature); const sig = ensureBytes(signature, 64);
if (raw) sig.assertValidity(); // just in case const r = bytesToInt(sig.subarray(0, 32)); // Let r = int(sig[0:32]); fail if r ≥ p.
if (!fe(r)) return false;
const { r, s } = sig; const s = bytesToInt(sig.subarray(32, 64)); // Let s = int(sig[32:64]); fail if s ≥ n.
if (!ge(s)) return false;
const m = ensureBytes(message); const m = ensureBytes(message);
const P = normalizePublicKey(publicKey); const e = challenge(numTo32b(r), pointToBytes(P), m); // int(challenge(bytes(r)||bytes(P)||m)) mod n
const e = schnorrChallengeFinalize(taggedHash(TAGS.challenge, numTo32b(r), toRawX(P), m)); const R = GmulAdd(P, s, modN(-e)); // R = s⋅G - e⋅P
// Finalize if (!R || !R.hasEvenY() || R.toAffine().x !== r) return false; // -eP == (n-e)P
// R = s⋅G - e⋅P return true; // Fail if is_infinite(R) / not has_even_y(R) / x(R) ≠ r.
// -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) { } catch (error) {
return false; return false;
} }
} }
export const schnorr = { export const schnorr = {
Signature: SchnorrSignature, getPublicKey: schnorrGetPublicKey,
// Schnorr's pubkey is just `x` of Point (BIP340)
getPublicKey: (privateKey: PrivKey): Uint8Array =>
toRawX(secp256k1.Point.fromPrivateKey(privateKey)),
sign: schnorrSign, sign: schnorrSign,
verify: schnorrVerify, verify: schnorrVerify,
utils: {
getExtendedPublicKey: schnorrGetExtPubKey,
lift_x,
pointToBytes,
numberToBytesBE,
bytesToNumberBE: bytesToInt,
taggedHash,
mod,
},
}; };
const isoMap = htf.isogenyMap(
Fp,
[
// xNum
[
'0x8e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38daaaaa8c7',
'0x7d3d4c80bc321d5b9f315cea7fd44c5d595d2fc0bf63b92dfff1044f17c6581',
'0x534c328d23f234e6e2a413deca25caece4506144037c40314ecbd0b53d9dd262',
'0x8e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38daaaaa88c',
],
// xDen
[
'0xd35771193d94918a9ca34ccbb7b640dd86cd409542f8487d9fe6b745781eb49b',
'0xedadc6f64383dc1df7c4b2d51b54225406d36b641f5e41bbc52a56612a8c6d14',
'0x0000000000000000000000000000000000000000000000000000000000000001', // LAST 1
],
// yNum
[
'0x4bda12f684bda12f684bda12f684bda12f684bda12f684bda12f684b8e38e23c',
'0xc75e0c32d5cb7c0fa9d0a54b12a0a6d5647ab046d686da6fdffc90fc201d71a3',
'0x29a6194691f91a73715209ef6512e576722830a201be2018a765e85a9ecee931',
'0x2f684bda12f684bda12f684bda12f684bda12f684bda12f684bda12f38e38d84',
],
// yDen
[
'0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffff93b',
'0x7a06534bb8bdb49fd5e9e6632722c2989467c1bfc8e8d978dfb425d2685c2573',
'0x6484aa716545ca2cf3a70c3fa8fe337e0a3d21162f0d6299a7bf8192bfd2a76f',
'0x0000000000000000000000000000000000000000000000000000000000000001', // LAST 1
],
].map((i) => i.map((j) => BigInt(j))) as [Fp[], Fp[], Fp[], Fp[]]
);
const mapSWU = mapToCurveSimpleSWU(Fp, {
A: BigInt('0x3f8731abdd661adca08a5558f0f5d272e953d363cb6f0e5d405447c01a444533'),
B: BigInt('1771'),
Z: Fp.create(BigInt('-11')),
});
const { hashToCurve, encodeToCurve } = htf.hashToCurve(
secp256k1.ProjectivePoint,
(scalars: bigint[]) => {
const { x, y } = mapSWU(Fp.create(scalars[0]));
return isoMap(x, y);
},
{
DST: 'secp256k1_XMD:SHA-256_SSWU_RO_',
encodeDST: 'secp256k1_XMD:SHA-256_SSWU_NU_',
p: Fp.ORDER,
m: 1,
k: 128,
expand: 'xmd',
hash: sha256,
}
);
export { hashToCurve, encodeToCurve };

View File

@@ -1,12 +1,14 @@
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */ /*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
import { keccak_256 } from '@noble/hashes/sha3'; import { keccak_256 } from '@noble/hashes/sha3';
import { sha256 } from '@noble/hashes/sha256'; import { sha256 } from '@noble/hashes/sha256';
import { weierstrass, ProjectivePointType } from './abstract/weierstrass.js'; import { weierstrass, ProjPointType } from './abstract/weierstrass.js';
import * as cutils from './abstract/utils.js'; import * as cutils from './abstract/utils.js';
import { Fp } from './abstract/modular.js'; import { Fp, mod, Field, validateField } from './abstract/modular.js';
import { getHash } from './_shortw_utils.js'; import { getHash } from './_shortw_utils.js';
import * as poseidon from './abstract/poseidon.js';
import { utf8ToBytes } from '@noble/hashes/utils';
type ProjectivePoint = ProjectivePointType<bigint>; type ProjectivePoint = ProjPointType<bigint>;
// Stark-friendly elliptic curve // Stark-friendly elliptic curve
// https://docs.starkware.co/starkex/stark-curve.html // https://docs.starkware.co/starkex/stark-curve.html
@@ -14,6 +16,15 @@ const CURVE_N = BigInt(
'3618502788666131213697322783095070105526743751716087489154079457884512865583' '3618502788666131213697322783095070105526743751716087489154079457884512865583'
); );
const nBitLength = 252; const nBitLength = 252;
// Copy-pasted from weierstrass.ts
function bits2int(bytes: Uint8Array): bigint {
const delta = bytes.length * 8 - nBitLength;
const num = cutils.bytesToNumberBE(bytes);
return delta > 0 ? num >> BigInt(delta) : num;
}
function bits2int_modN(bytes: Uint8Array): bigint {
return mod(bits2int(bytes), CURVE_N);
}
export const starkCurve = weierstrass({ export const starkCurve = weierstrass({
// Params: a, b // Params: a, b
a: BigInt(1), a: BigInt(1),
@@ -31,31 +42,27 @@ export const starkCurve = weierstrass({
// Default options // Default options
lowS: false, lowS: false,
...getHash(sha256), ...getHash(sha256),
truncateHash: (hash: Uint8Array, truncateOnly = false): bigint => { // Custom truncation routines for stark curve
// TODO: cleanup, ugly code bits2int: (bytes: Uint8Array): bigint => {
// Fix truncation while (bytes[0] === 0) bytes = bytes.subarray(1);
if (!truncateOnly) { return bits2int(bytes);
let hashS = bytesToNumber0x(hash).toString(16); },
if (hashS.length === 63) { bits2int_modN: (bytes: Uint8Array): bigint => {
hashS += '0'; let hashS = cutils.bytesToNumberBE(bytes).toString(16);
hash = hexToBytes0x(hashS); if (hashS.length === 63) {
} hashS += '0';
bytes = hexToBytes0x(hashS);
} }
// Truncate zero bytes on left (compat with elliptic) // Truncate zero bytes on left (compat with elliptic)
while (hash[0] === 0) hash = hash.subarray(1); while (bytes[0] === 0) bytes = bytes.subarray(1);
const byteLength = hash.length; return bits2int_modN(bytes);
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 // Custom Starknet type conversion functions that can handle 0x and unpadded hex
function hexToBytes0x(hex: string): Uint8Array { function hexToBytes0x(hex: string): Uint8Array {
if (typeof hex !== 'string') { if (typeof hex !== 'string') {
throw new TypeError('hexToBytes: expected string, got ' + typeof hex); throw new Error('hexToBytes: expected string, got ' + typeof hex);
} }
hex = strip0x(hex); hex = strip0x(hex);
if (hex.length & 1) hex = '0' + hex; // padding if (hex.length & 1) hex = '0' + hex; // padding
@@ -72,7 +79,7 @@ function hexToBytes0x(hex: string): Uint8Array {
} }
function hexToNumber0x(hex: string): bigint { function hexToNumber0x(hex: string): bigint {
if (typeof hex !== 'string') { if (typeof hex !== 'string') {
throw new TypeError('hexToNumber: expected string, got ' + typeof hex); throw new Error('hexToNumber: expected string, got ' + typeof hex);
} }
// Big Endian // Big Endian
// TODO: strip vs no strip? // TODO: strip vs no strip?
@@ -88,9 +95,9 @@ function ensureBytes0x(hex: Hex): Uint8Array {
} }
function normalizePrivateKey(privKey: Hex) { function normalizePrivateKey(privKey: Hex) {
return cutils.bytesToHex(ensureBytes0x(privKey)).padStart(32 * 2, '0'); return cutils.bytesToHex(ensureBytes0x(privKey)).padStart(64, '0');
} }
function getPublicKey0x(privKey: Hex, isCompressed?: boolean) { function getPublicKey0x(privKey: Hex, isCompressed = false) {
return starkCurve.getPublicKey(normalizePrivateKey(privKey), isCompressed); return starkCurve.getPublicKey(normalizePrivateKey(privKey), isCompressed);
} }
function getSharedSecret0x(privKeyA: Hex, pubKeyB: Hex) { function getSharedSecret0x(privKeyA: Hex, pubKeyB: Hex) {
@@ -106,11 +113,10 @@ function verify0x(signature: Hex, msgHash: Hex, pubKey: Hex) {
return starkCurve.verify(sig, ensureBytes0x(msgHash), ensureBytes0x(pubKey)); return starkCurve.verify(sig, ensureBytes0x(msgHash), ensureBytes0x(pubKey));
} }
const { CURVE, Point, ProjectivePoint, Signature } = starkCurve; const { CURVE, ProjectivePoint, Signature } = starkCurve;
export const utils = starkCurve.utils; export const utils = starkCurve.utils;
export { export {
CURVE, CURVE,
Point,
Signature, Signature,
ProjectivePoint, ProjectivePoint,
getPublicKey0x as getPublicKey, getPublicKey0x as getPublicKey,
@@ -132,18 +138,18 @@ type Hex = Uint8Array | string;
function hashKeyWithIndex(key: Uint8Array, index: number) { function hashKeyWithIndex(key: Uint8Array, index: number) {
let indexHex = cutils.numberToHexUnpadded(index); let indexHex = cutils.numberToHexUnpadded(index);
if (indexHex.length & 1) indexHex = '0' + indexHex; if (indexHex.length & 1) indexHex = '0' + indexHex;
return bytesToNumber0x(sha256(cutils.concatBytes(key, hexToBytes0x(indexHex)))); return sha256Num(cutils.concatBytes(key, hexToBytes0x(indexHex)));
} }
export function grindKey(seed: Hex) { export function grindKey(seed: Hex) {
const _seed = ensureBytes0x(seed); const _seed = ensureBytes0x(seed);
const sha256mask = 2n ** 256n; const sha256mask = 2n ** 256n;
const Fn = Fp(CURVE.n);
const limit = sha256mask - Fn.create(sha256mask); const limit = sha256mask - mod(sha256mask, CURVE_N);
for (let i = 0; ; i++) { for (let i = 0; ; i++) {
const key = hashKeyWithIndex(_seed, i); const key = hashKeyWithIndex(_seed, i);
// key should be in [0, limit) // key should be in [0, limit)
if (key < limit) return Fn.create(key).toString(16); if (key < limit) return mod(key, CURVE_N).toString(16);
} }
} }
@@ -165,37 +171,42 @@ export function getAccountPath(
ethereumAddress: string, ethereumAddress: string,
index: number index: number
) { ) {
const layerNum = int31(bytesToNumber0x(sha256(layer))); const layerNum = int31(sha256Num(layer));
const applicationNum = int31(bytesToNumber0x(sha256(application))); const applicationNum = int31(sha256Num(application));
const eth = hexToNumber0x(ethereumAddress); const eth = hexToNumber0x(ethereumAddress);
return `m/2645'/${layerNum}'/${applicationNum}'/${int31(eth)}'/${int31(eth >> 31n)}'/${index}`; return `m/2645'/${layerNum}'/${applicationNum}'/${int31(eth)}'/${int31(eth >> 31n)}'/${index}`;
} }
// https://docs.starkware.co/starkex/pedersen-hash-function.html // https://docs.starkware.co/starkex/pedersen-hash-function.html
const PEDERSEN_POINTS_AFFINE = [ const PEDERSEN_POINTS_AFFINE = [
new Point( new ProjectivePoint(
2089986280348253421170679821480865132823066470938446095505822317253594081284n, 2089986280348253421170679821480865132823066470938446095505822317253594081284n,
1713931329540660377023406109199410414810705867260802078187082345529207694986n 1713931329540660377023406109199410414810705867260802078187082345529207694986n,
1n
), ),
new Point( new ProjectivePoint(
996781205833008774514500082376783249102396023663454813447423147977397232763n, 996781205833008774514500082376783249102396023663454813447423147977397232763n,
1668503676786377725805489344771023921079126552019160156920634619255970485781n 1668503676786377725805489344771023921079126552019160156920634619255970485781n,
1n
), ),
new Point( new ProjectivePoint(
2251563274489750535117886426533222435294046428347329203627021249169616184184n, 2251563274489750535117886426533222435294046428347329203627021249169616184184n,
1798716007562728905295480679789526322175868328062420237419143593021674992973n 1798716007562728905295480679789526322175868328062420237419143593021674992973n,
1n
), ),
new Point( new ProjectivePoint(
2138414695194151160943305727036575959195309218611738193261179310511854807447n, 2138414695194151160943305727036575959195309218611738193261179310511854807447n,
113410276730064486255102093846540133784865286929052426931474106396135072156n 113410276730064486255102093846540133784865286929052426931474106396135072156n,
1n
), ),
new Point( new ProjectivePoint(
2379962749567351885752724891227938183011949129833673362440656643086021394946n, 2379962749567351885752724891227938183011949129833673362440656643086021394946n,
776496453633298175483985398648758586525933812536653089401905292063708816422n 776496453633298175483985398648758586525933812536653089401905292063708816422n,
1n
), ),
]; ];
// for (const p of PEDERSEN_POINTS) p._setWindowSize(8); // for (const p of PEDERSEN_POINTS) p._setWindowSize(8);
const PEDERSEN_POINTS = PEDERSEN_POINTS_AFFINE.map(ProjectivePoint.fromAffine); const PEDERSEN_POINTS = PEDERSEN_POINTS_AFFINE;
function pedersenPrecompute(p1: ProjectivePoint, p2: ProjectivePoint): ProjectivePoint[] { function pedersenPrecompute(p1: ProjectivePoint, p2: ProjectivePoint): ProjectivePoint[] {
const out: ProjectivePoint[] = []; const out: ProjectivePoint[] = [];
@@ -234,7 +245,7 @@ function pedersenSingle(point: ProjectivePoint, value: PedersenArg, constants: P
let x = pedersenArg(value); let x = pedersenArg(value);
for (let j = 0; j < 252; j++) { for (let j = 0; j < 252; j++) {
const pt = constants[j]; const pt = constants[j];
if (pt.x === point.x) throw new Error('Same point'); if (pt.px === point.px) throw new Error('Same point');
if ((x & 1n) !== 0n) point = point.add(pt); if ((x & 1n) !== 0n) point = point.add(pt);
x >>= 1n; x >>= 1n;
} }
@@ -246,7 +257,7 @@ export function pedersen(x: PedersenArg, y: PedersenArg) {
let point: ProjectivePoint = PEDERSEN_POINTS[0]; let point: ProjectivePoint = PEDERSEN_POINTS[0];
point = pedersenSingle(point, x, PEDERSEN_POINTS1); point = pedersenSingle(point, x, PEDERSEN_POINTS1);
point = pedersenSingle(point, y, PEDERSEN_POINTS2); point = pedersenSingle(point, y, PEDERSEN_POINTS2);
return bytesToHexEth(point.toAffine().toRawBytes(true).slice(1)); return bytesToHexEth(point.toRawBytes(true).slice(1));
} }
export function hashChain(data: PedersenArg[], fn = pedersen) { export function hashChain(data: PedersenArg[], fn = pedersen) {
@@ -261,5 +272,85 @@ export function hashChain(data: PedersenArg[], fn = pedersen) {
export const computeHashOnElements = (data: PedersenArg[], fn = pedersen) => export const computeHashOnElements = (data: PedersenArg[], fn = pedersen) =>
[0, ...data, data.length].reduce((x, y) => fn(x, y)); [0, ...data, data.length].reduce((x, y) => fn(x, y));
const MASK_250 = 2n ** 250n - 1n; const MASK_250 = cutils.bitMask(250);
export const keccak = (data: Uint8Array) => bytesToNumber0x(keccak_256(data)) & MASK_250; export const keccak = (data: Uint8Array): bigint => bytesToNumber0x(keccak_256(data)) & MASK_250;
const sha256Num = (data: Uint8Array | string): bigint => cutils.bytesToNumberBE(sha256(data));
// Poseidon hash
export const Fp253 = Fp(
BigInt('14474011154664525231415395255581126252639794253786371766033694892385558855681')
); // 2^253 + 2^199 + 1
export const Fp251 = Fp(
BigInt('3618502788666131213697322783095070105623107215331596699973092056135872020481')
); // 2^251 + 17 * 2^192 + 1
function poseidonRoundConstant(Fp: Field<bigint>, name: string, idx: number) {
const val = Fp.fromBytes(sha256(utf8ToBytes(`${name}${idx}`)));
return Fp.create(val);
}
// NOTE: doesn't check eiginvalues and possible can create unsafe matrix. But any filtration here will break compatibility with starknet
// Please use only if you really know what you doing.
// https://eprint.iacr.org/2019/458.pdf Section 2.3 (Avoiding Insecure Matrices)
export function _poseidonMDS(Fp: Field<bigint>, name: string, m: number, attempt = 0) {
const x_values: bigint[] = [];
const y_values: bigint[] = [];
for (let i = 0; i < m; i++) {
x_values.push(poseidonRoundConstant(Fp, `${name}x`, attempt * m + i));
y_values.push(poseidonRoundConstant(Fp, `${name}y`, attempt * m + i));
}
if (new Set([...x_values, ...y_values]).size !== 2 * m)
throw new Error('X and Y values are not distinct');
return x_values.map((x) => y_values.map((y) => Fp.inv(Fp.sub(x, y))));
}
const MDS_SMALL = [
[3, 1, 1],
[1, -1, 1],
[1, 1, -2],
].map((i) => i.map(BigInt));
export type PoseidonOpts = {
Fp: Field<bigint>;
rate: number;
capacity: number;
roundsFull: number;
roundsPartial: number;
};
export function poseidonBasic(opts: PoseidonOpts, mds: bigint[][]) {
validateField(opts.Fp);
if (!Number.isSafeInteger(opts.rate) || !Number.isSafeInteger(opts.capacity))
throw new Error(`Wrong poseidon opts: ${opts}`);
const m = opts.rate + opts.capacity;
const rounds = opts.roundsFull + opts.roundsPartial;
const roundConstants = [];
for (let i = 0; i < rounds; i++) {
const row = [];
for (let j = 0; j < m; j++) row.push(poseidonRoundConstant(opts.Fp, 'Hades', m * i + j));
roundConstants.push(row);
}
return poseidon.poseidon({
...opts,
t: m,
sboxPower: 3,
reversePartialPowIdx: true, // Why?!
mds,
roundConstants,
});
}
export function poseidonCreate(opts: PoseidonOpts, mdsAttempt = 0) {
const m = opts.rate + opts.capacity;
if (!Number.isSafeInteger(mdsAttempt)) throw new Error(`Wrong mdsAttempt=${mdsAttempt}`);
return poseidonBasic(opts, _poseidonMDS(opts.Fp, 'HadesMDS', m, mdsAttempt));
}
export const poseidonSmall = poseidonBasic(
{ Fp: Fp251, rate: 2, capacity: 1, roundsFull: 8, roundsPartial: 83 },
MDS_SMALL
);
export function poseidonHash(x: bigint, y: bigint, fn = poseidonSmall) {
return fn([x, y, 2n])[0];
}

View File

@@ -68,8 +68,8 @@ for (const c in FIELDS) {
fc.property(FC_BIGINT, (num) => { fc.property(FC_BIGINT, (num) => {
const a = create(num); const a = create(num);
const b = create(num); const b = create(num);
deepStrictEqual(Fp.equals(a, b), true); deepStrictEqual(Fp.eql(a, b), true);
deepStrictEqual(Fp.equals(b, a), true); deepStrictEqual(Fp.eql(b, a), true);
}) })
); );
}); });
@@ -78,8 +78,8 @@ for (const c in FIELDS) {
fc.property(FC_BIGINT, FC_BIGINT, (num1, num2) => { fc.property(FC_BIGINT, FC_BIGINT, (num1, num2) => {
const a = create(num1); const a = create(num1);
const b = create(num2); const b = create(num2);
deepStrictEqual(Fp.equals(a, b), num1 === num2); deepStrictEqual(Fp.eql(a, b), num1 === num2);
deepStrictEqual(Fp.equals(b, a), num1 === num2); deepStrictEqual(Fp.eql(b, a), num1 === num2);
}) })
); );
}); });
@@ -124,8 +124,8 @@ for (const c in FIELDS) {
fc.property(FC_BIGINT, (num1) => { fc.property(FC_BIGINT, (num1) => {
const a = create(num1); const a = create(num1);
const b = create(num1); const b = create(num1);
deepStrictEqual(Fp.sub(Fp.ZERO, a), Fp.negate(a)); deepStrictEqual(Fp.sub(Fp.ZERO, a), Fp.neg(a));
deepStrictEqual(Fp.sub(a, b), Fp.add(a, Fp.negate(b))); 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)))); deepStrictEqual(Fp.sub(a, b), Fp.add(a, Fp.mul(b, Fp.create(-1n))));
}) })
); );
@@ -134,13 +134,13 @@ for (const c in FIELDS) {
fc.assert( fc.assert(
fc.property(FC_BIGINT, (num) => { fc.property(FC_BIGINT, (num) => {
const a = create(num); const a = create(num);
deepStrictEqual(Fp.negate(a), Fp.sub(Fp.ZERO, a)); deepStrictEqual(Fp.neg(a), Fp.sub(Fp.ZERO, a));
deepStrictEqual(Fp.negate(a), Fp.mul(a, Fp.create(-1n))); deepStrictEqual(Fp.neg(a), Fp.mul(a, Fp.create(-1n)));
}) })
); );
}); });
should('negate(0)', () => { should('negate(0)', () => {
deepStrictEqual(Fp.negate(Fp.ZERO), Fp.ZERO); deepStrictEqual(Fp.neg(Fp.ZERO), Fp.ZERO);
}); });
should('multiply/commutativity', () => { should('multiply/commutativity', () => {
@@ -190,7 +190,7 @@ for (const c in FIELDS) {
fc.assert( fc.assert(
fc.property(FC_BIGINT, (num) => { fc.property(FC_BIGINT, (num) => {
const a = create(num); const a = create(num);
deepStrictEqual(Fp.square(a), Fp.mul(a, a)); deepStrictEqual(Fp.sqr(a), Fp.mul(a, a));
}) })
); );
}); });
@@ -207,18 +207,18 @@ for (const c in FIELDS) {
}); });
should('square(0)', () => { should('square(0)', () => {
deepStrictEqual(Fp.square(Fp.ZERO), Fp.ZERO); deepStrictEqual(Fp.sqr(Fp.ZERO), Fp.ZERO);
deepStrictEqual(Fp.mul(Fp.ZERO, Fp.ZERO), Fp.ZERO); deepStrictEqual(Fp.mul(Fp.ZERO, Fp.ZERO), Fp.ZERO);
}); });
should('square(1)', () => { should('square(1)', () => {
deepStrictEqual(Fp.square(Fp.ONE), Fp.ONE); deepStrictEqual(Fp.sqr(Fp.ONE), Fp.ONE);
deepStrictEqual(Fp.mul(Fp.ONE, Fp.ONE), Fp.ONE); deepStrictEqual(Fp.mul(Fp.ONE, Fp.ONE), Fp.ONE);
}); });
should('square(-1)', () => { should('square(-1)', () => {
const minus1 = Fp.negate(Fp.ONE); const minus1 = Fp.neg(Fp.ONE);
deepStrictEqual(Fp.square(minus1), Fp.ONE); deepStrictEqual(Fp.sqr(minus1), Fp.ONE);
deepStrictEqual(Fp.mul(minus1, minus1), Fp.ONE); deepStrictEqual(Fp.mul(minus1, minus1), Fp.ONE);
}); });
@@ -237,8 +237,8 @@ for (const c in FIELDS) {
return; return;
} }
deepStrictEqual(isSquare(a), true); deepStrictEqual(isSquare(a), true);
deepStrictEqual(Fp.equals(Fp.square(root), a), true, 'sqrt(a)^2 == a'); deepStrictEqual(Fp.eql(Fp.sqr(root), a), true, 'sqrt(a)^2 == a');
deepStrictEqual(Fp.equals(Fp.square(Fp.negate(root)), a), true, '(-sqrt(a))^2 == a'); deepStrictEqual(Fp.eql(Fp.sqr(Fp.neg(root)), a), true, '(-sqrt(a))^2 == a');
}) })
); );
}); });
@@ -247,7 +247,7 @@ for (const c in FIELDS) {
deepStrictEqual(Fp.sqrt(Fp.ZERO), Fp.ZERO); deepStrictEqual(Fp.sqrt(Fp.ZERO), Fp.ZERO);
const sqrt1 = Fp.sqrt(Fp.ONE); const sqrt1 = Fp.sqrt(Fp.ONE);
deepStrictEqual( deepStrictEqual(
Fp.equals(sqrt1, Fp.ONE) || Fp.equals(sqrt1, Fp.negate(Fp.ONE)), Fp.eql(sqrt1, Fp.ONE) || Fp.eql(sqrt1, Fp.neg(Fp.ONE)),
true, true,
'sqrt(1) = 1 or -1' 'sqrt(1) = 1 or -1'
); );
@@ -258,7 +258,7 @@ for (const c in FIELDS) {
fc.assert( fc.assert(
fc.property(FC_BIGINT, (num) => { fc.property(FC_BIGINT, (num) => {
const a = create(num); const a = create(num);
if (Fp.equals(a, Fp.ZERO)) return; // No division by zero if (Fp.eql(a, Fp.ZERO)) return; // No division by zero
deepStrictEqual(Fp.div(a, Fp.ONE), a); deepStrictEqual(Fp.div(a, Fp.ONE), a);
deepStrictEqual(Fp.div(a, a), Fp.ONE); deepStrictEqual(Fp.div(a, a), Fp.ONE);
}) })
@@ -287,7 +287,7 @@ for (const c in FIELDS) {
fc.property(FC_BIGINT, FC_BIGINT, (num1, num2) => { fc.property(FC_BIGINT, FC_BIGINT, (num1, num2) => {
const a = create(num1); const a = create(num1);
const b = create(num2); const b = create(num2);
deepStrictEqual(Fp.div(a, b), Fp.mul(a, Fp.invert(b))); deepStrictEqual(Fp.div(a, b), Fp.mul(a, Fp.inv(b)));
}) })
); );
}); });
@@ -313,12 +313,12 @@ const NUM_RUNS = 5;
const getXY = (p) => ({ x: p.x, y: p.y }); const getXY = (p) => ({ x: p.x, y: p.y });
function equal(a, b, comment) { function equal(a, b, comment) {
deepStrictEqual(a.equals(b), true, 'eq(${comment})'); deepStrictEqual(a.equals(b), true, `eq(${comment})`);
if (a.toAffine && b.toAffine) { if (a.toAffine && b.toAffine) {
deepStrictEqual(getXY(a.toAffine()), getXY(b.toAffine()), 'eqToAffine(${comment})'); deepStrictEqual(getXY(a.toAffine()), getXY(b.toAffine()), `eqToAffine(${comment})`);
} else if (!a.toAffine && !b.toAffine) { } else if (!a.toAffine && !b.toAffine) {
// Already affine // Already affine
deepStrictEqual(getXY(a), getXY(b), 'eqAffine(${comment})'); deepStrictEqual(getXY(a), getXY(b), `eqAffine(${comment})`);
} else throw new Error('Different point types'); } else throw new Error('Different point types');
} }
@@ -342,50 +342,50 @@ for (const name in CURVES) {
if (!p) continue; if (!p) continue;
const G = [p.ZERO, p.BASE]; 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}`; const title = `${name}/${pointName}`;
describe(title, () => { describe(title, () => {
describe('basic group laws', () => { describe('basic group laws', () => {
// Here we check basic group laws, to verify that points works as group // Here we check basic group laws, to verify that points works as group
should('(zero)', () => { should('zero', () => {
equal(G[0].double(), G[0], '(0*G).double() = 0'); 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].add(G[0]), G[0], '0*G + 0*G = 0');
equal(G[0].subtract(G[0]), G[0], '0*G - 0*G = 0'); equal(G[0].subtract(G[0]), G[0], '0*G - 0*G = 0');
equal(G[0].negate(), G[0], '-0 = 0'); equal(G[0].negate(), G[0], '-0 = 0');
for (let i = 0; i < G.length; i++) { for (let i = 0; i < G.length; i++) {
const p = G[i]; const p = G[i];
equal(p, p.add(G[0]), '${i}*G + 0 = ${i}*G'); 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('(one)', () => { should('one', () => {
equal(G[1].double(), G[2], '(1*G).double() = 2*G'); 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].subtract(G[1]), G[0], '1*G - 1*G = 0');
equal(G[1].add(G[1]), G[2], '1*G + 1*G = 2*G'); equal(G[1].add(G[1]), G[2], '1*G + 1*G = 2*G');
}); });
should('(sanity tests)', () => { should('sanity tests', () => {
equal(G[2].double(), G[4], '(2*G).double() = 4*G'); 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[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'); equal(G[7].add(G[3].negate()), G[4], '7*G - 3*G = 4*G');
}); });
should('(addition commutativity)', () => { 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[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'); 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('(double)', () => { should('double', () => {
equal(G[3].double(), G[6], '(3*G).double() = 6*G'); equal(G[3].double(), G[6], '(3*G).double() = 6*G');
}); });
should('(multiply)', () => { should('multiply', () => {
equal(G[2].multiply(3), G[6], '(2*G).multiply(3) = 6*G'); equal(G[2].multiply(3n), G[6], '(2*G).multiply(3) = 6*G');
}); });
should('(same point addition)', () => { should('add same-point', () => {
equal(G[3].add(G[3]), G[6], '3*G + 3*G = 6*G'); equal(G[3].add(G[3]), G[6], '3*G + 3*G = 6*G');
}); });
should('(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].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'); equal(G[3].subtract(G[3]), G[0], '3*G - 3*G = 0*G');
}); });
should('(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[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 - 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'); equal(G[1].multiply(CURVE_ORDER - 2n).add(G[2]), G[0], '(N-2)*G + 2*G = 0');
@@ -393,7 +393,7 @@ for (const name in CURVES) {
const carry = CURVE_ORDER % 2n === 1n ? G[1] : G[0]; 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'); equal(G[1].multiply(half).double().add(carry), G[0], '((N/2) * G).double() = 0');
}); });
should('(inversion)', () => { should('inversion', () => {
const a = 1234n; const a = 1234n;
const b = 5678n; const b = 5678n;
const c = a * b; const c = a * b;
@@ -401,7 +401,7 @@ for (const name in CURVES) {
const inv = mod.invert(b, CURVE_ORDER); 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'); equal(G[1].multiply(c).multiply(inv), G[1].multiply(a), 'c*G * (1/b)*G = a*G');
}); });
should('(multiply, rand)', () => should('multiply, rand', () =>
fc.assert( fc.assert(
fc.property(FC_BIGINT, FC_BIGINT, (a, b) => { fc.property(FC_BIGINT, FC_BIGINT, (a, b) => {
const c = mod.mod(a + b, CURVE_ORDER); const c = mod.mod(a + b, CURVE_ORDER);
@@ -415,7 +415,7 @@ for (const name in CURVES) {
{ numRuns: NUM_RUNS } { numRuns: NUM_RUNS }
) )
); );
should('(multiply2, rand)', () => should('multiply2, rand', () =>
fc.assert( fc.assert(
fc.property(FC_BIGINT, FC_BIGINT, (a, b) => { fc.property(FC_BIGINT, FC_BIGINT, (a, b) => {
const c = mod.mod(a * b, CURVE_ORDER); const c = mod.mod(a * b, CURVE_ORDER);
@@ -436,9 +436,16 @@ for (const name in CURVES) {
throws(() => G[1][op](0n), '0n'); throws(() => G[1][op](0n), '0n');
G[1][op](G[2]); G[1][op](G[2]);
throws(() => G[1][op](CURVE_ORDER), 'CURVE_ORDER'); 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](123.456), '123.456');
throws(() => G[1][op](true), 'true'); 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]('1'), "'1'");
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( throws(
() => G[1][op]({ x: 1n, y: 1n, z: 1n, t: 1n }), () => G[1][op]({ x: 1n, y: 1n, z: 1n, t: 1n }),
'{ x: 1n, y: 1n, z: 1n, t: 1n }' '{ x: 1n, y: 1n, z: 1n, t: 1n }'
@@ -447,8 +454,8 @@ for (const name in CURVES) {
throws(() => G[1][op](new Uint8Array([0])), 'ui8a([0])'); throws(() => G[1][op](new Uint8Array([0])), 'ui8a([0])');
throws(() => G[1][op](new Uint8Array([1])), 'ui8a([1])'); throws(() => G[1][op](new Uint8Array([1])), 'ui8a([1])');
throws(() => G[1][op](new Uint8Array(4096).fill(1)), 'ui8a(4096*[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'); throws(() => G[1][op](o.BASE), `${op}/other curve point`);
}); });
}); });
} }
@@ -468,7 +475,7 @@ for (const name in CURVES) {
throws(() => G[1].equals(new Uint8Array([0])), 'ui8a([0])'); throws(() => G[1].equals(new Uint8Array([0])), 'ui8a([0])');
throws(() => G[1].equals(new Uint8Array([1])), 'ui8a([1])'); throws(() => G[1].equals(new Uint8Array([1])), 'ui8a([1])');
throws(() => G[1].equals(new Uint8Array(4096).fill(1)), 'ui8a(4096*[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'); throws(() => G[1].equals(o.BASE), 'other curve point');
}); });
@@ -497,18 +504,18 @@ for (const name in CURVES) {
}); });
} }
// Complex point (Extended/Jacobian/Projective?) // Complex point (Extended/Jacobian/Projective?)
if (p.BASE.toAffine) { // if (p.BASE.toAffine && C.Point) {
should('toAffine()', () => { // should('toAffine()', () => {
equal(p.ZERO.toAffine(), C.Point.ZERO, '0 = 0'); // equal(p.ZERO.toAffine(), C.Point.ZERO, '0 = 0');
equal(p.BASE.toAffine(), C.Point.BASE, '1 = 1'); // equal(p.BASE.toAffine(), C.Point.BASE, '1 = 1');
}); // });
} // }
if (p.fromAffine) { // if (p.fromAffine && C.Point) {
should('fromAffine()', () => { // should('fromAffine()', () => {
equal(p.ZERO, p.fromAffine(C.Point.ZERO), '0 = 0'); // equal(p.ZERO, p.fromAffine(C.Point.ZERO), '0 = 0');
equal(p.BASE, p.fromAffine(C.Point.BASE), '1 = 1'); // equal(p.BASE, p.fromAffine(C.Point.BASE), '1 = 1');
}); // });
} // }
// toHex/fromHex (if available) // toHex/fromHex (if available)
if (p.fromHex && p.BASE.toHex) { if (p.fromHex && p.BASE.toHex) {
should('fromHex(toHex()) roundtrip', () => { should('fromHex(toHex()) roundtrip', () => {
@@ -524,20 +531,26 @@ for (const name in CURVES) {
} }
describe(name, () => { describe(name, () => {
// Generic complex things (getPublicKey/sign/verify/getSharedSecret) // Generic complex things (getPublicKey/sign/verify/getSharedSecret)
should('getPublicKey type check', () => { should('.getPublicKey() type check', () => {
throws(() => C.getPublicKey(0), '0'); throws(() => C.getPublicKey(0), '0');
throws(() => C.getPublicKey(0n), '0n'); 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(123.456), '123.456');
throws(() => C.getPublicKey(true), 'true'); throws(() => C.getPublicKey(true), 'true');
throws(() => C.getPublicKey(false), 'false');
throws(() => C.getPublicKey(null), 'null');
throws(() => C.getPublicKey(undefined), 'undefined');
throws(() => C.getPublicKey(''), "''"); throws(() => C.getPublicKey(''), "''");
// NOTE: passes because of disabled hex padding checks for starknet, maybe enable? // 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('key'), "'key'");
throws(() => C.getPublicKey({}));
throws(() => C.getPublicKey(new Uint8Array([]))); throws(() => C.getPublicKey(new Uint8Array([])));
throws(() => C.getPublicKey(new Uint8Array([0]))); throws(() => C.getPublicKey(new Uint8Array([0])));
throws(() => C.getPublicKey(new Uint8Array([1]))); throws(() => C.getPublicKey(new Uint8Array([1])));
throws(() => C.getPublicKey(new Uint8Array(4096).fill(1))); throws(() => C.getPublicKey(new Uint8Array(4096).fill(1)));
throws(() => C.getPublicKey(Array(32).fill(1)));
}); });
should('.verify() should verify random signatures', () => should('.verify() should verify random signatures', () =>
fc.assert( fc.assert(
@@ -557,15 +570,29 @@ for (const name in CURVES) {
should('.sign() edge cases', () => { should('.sign() edge cases', () => {
throws(() => C.sign()); throws(() => C.sign());
throws(() => C.sign('')); throws(() => C.sign(''));
throws(() => C.sign('', ''));
throws(() => C.sign(new Uint8Array(), new Uint8Array()));
}); });
should('.verify() should not verify signature with wrong hash', () => { describe('verify()', () => {
const MSG = '01'.repeat(32); const msg = '01'.repeat(32);
const PRIV_KEY = 0x2n; should('true for proper signatures', () => {
const WRONG_MSG = '11'.repeat(32); const priv = C.utils.randomPrivateKey();
const signature = C.sign(MSG, PRIV_KEY); const sig = C.sign(msg, priv);
const publicKey = C.getPublicKey(PRIV_KEY); const pub = C.getPublicKey(priv);
deepStrictEqual(C.verify(signature, WRONG_MSG, publicKey), false); 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);
});
}); });
// NOTE: fails for ed, because of empty message. Since we convert it to scalar, // 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? // need to check what other implementations do. Empty message != new Uint8Array([0]), but what scalar should be in that case?
@@ -618,10 +645,10 @@ should('secp224k1 sqrt bug', () => {
23621584063597419797792593680131996961517196803742576047493035507225n 23621584063597419797792593680131996961517196803742576047493035507225n
); );
deepStrictEqual( deepStrictEqual(
Fp.negate(sqrtMinus1), Fp.neg(sqrtMinus1),
3338362603553219996874421406887633712040719456283732096017030791656n 3338362603553219996874421406887633712040719456283732096017030791656n
); );
deepStrictEqual(Fp.square(sqrtMinus1), Fp.create(-1n)); deepStrictEqual(Fp.sqr(sqrtMinus1), Fp.create(-1n));
}); });
// ESM is broken. // ESM is broken.

File diff suppressed because it is too large Load Diff

View File

@@ -20,6 +20,7 @@ import { default as x25519vectors } from './wycheproof/x25519_test.json' assert
describe('ed25519', () => { describe('ed25519', () => {
const ed = ed25519; const ed = ed25519;
const hex = bytesToHex; const hex = bytesToHex;
const Point = ed.ExtendedPoint;
function to32Bytes(numOrStr) { function to32Bytes(numOrStr) {
let hex = typeof numOrStr === 'string' ? numOrStr : numOrStr.toString(16); let hex = typeof numOrStr === 'string' ? numOrStr : numOrStr.toString(16);
@@ -28,7 +29,7 @@ describe('ed25519', () => {
function utf8ToBytes(str) { function utf8ToBytes(str) {
if (typeof str !== 'string') { if (typeof str !== 'string') {
throw new TypeError(`utf8ToBytes expected string, got ${typeof str}`); throw new Error(`utf8ToBytes expected string, got ${typeof str}`);
} }
return new TextEncoder().encode(str); return new TextEncoder().encode(str);
} }
@@ -78,6 +79,7 @@ describe('ed25519', () => {
); );
}); });
const privKey = to32Bytes('a665a45920422f9d417e4867ef'); const privKey = to32Bytes('a665a45920422f9d417e4867ef');
const wrongPriv = to32Bytes('a675a45920422f9d417e4867ef');
const msg = hexToBytes('874f9960c5d2b7a9b5fad383e1ba44719ebb743a'); const msg = hexToBytes('874f9960c5d2b7a9b5fad383e1ba44719ebb743a');
const wrongMsg = hexToBytes('589d8c7f1da0a24bc07b7381ad48b1cfc211af1c'); const wrongMsg = hexToBytes('589d8c7f1da0a24bc07b7381ad48b1cfc211af1c');
describe('basic methods', () => { describe('basic methods', () => {
@@ -86,16 +88,6 @@ describe('ed25519', () => {
const signature = ed.sign(msg, privKey); const signature = ed.sign(msg, privKey);
deepStrictEqual(ed.verify(signature, msg, publicKey), true); deepStrictEqual(ed.verify(signature, msg, publicKey), true);
}); });
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('not verify signature with wrong hash', () => {
const publicKey = ed.getPublicKey(privKey);
const signature = ed.sign(msg, privKey);
deepStrictEqual(ed.verify(signature, wrongMsg, publicKey), false);
});
}); });
describe('sync methods', () => { describe('sync methods', () => {
should('sign and verify', () => { should('sign and verify', () => {
@@ -104,7 +96,7 @@ describe('ed25519', () => {
deepStrictEqual(ed.verify(signature, msg, publicKey), true); deepStrictEqual(ed.verify(signature, msg, publicKey), true);
}); });
should('not verify signature with wrong public key', () => { should('not verify signature with wrong public key', () => {
const publicKey = ed.getPublicKey(12); const publicKey = ed.getPublicKey(wrongPriv);
const signature = ed.sign(msg, privKey); const signature = ed.sign(msg, privKey);
deepStrictEqual(ed.verify(signature, msg, publicKey), false); deepStrictEqual(ed.verify(signature, msg, publicKey), false);
}); });
@@ -114,55 +106,45 @@ describe('ed25519', () => {
deepStrictEqual(ed.verify(signature, wrongMsg, publicKey), false); deepStrictEqual(ed.verify(signature, wrongMsg, publicKey), false);
}); });
}); });
// https://xmr.llcoins.net/addresstests.html describe('BASE_POINT.multiply()', () => {
should( // https://xmr.llcoins.net/addresstests.html
'ed25519/BASE_POINT.multiply()/should create right publicKey without SHA-512 hashing TEST 1', should('create right publicKey without SHA-512 hashing TEST 1', () => {
() => {
const publicKey = const publicKey =
ed.Point.BASE.multiply(0x90af56259a4b6bfbc4337980d5d75fbe3c074630368ff3804d33028e5dbfa77n); Point.BASE.multiply(0x90af56259a4b6bfbc4337980d5d75fbe3c074630368ff3804d33028e5dbfa77n);
deepStrictEqual( deepStrictEqual(
publicKey.toHex(), publicKey.toHex(),
'0f3b913371411b27e646b537e888f685bf929ea7aab93c950ed84433f064480d' '0f3b913371411b27e646b537e888f685bf929ea7aab93c950ed84433f064480d'
); );
} });
); should('create right publicKey without SHA-512 hashing TEST 2', () => {
should(
'ed25519/BASE_POINT.multiply()/should create right publicKey without SHA-512 hashing TEST 2',
() => {
const publicKey = const publicKey =
ed.Point.BASE.multiply(0x364e8711a60780382a5d57b061c126f039940f28a9e91fe039d4d3094d8b88n); Point.BASE.multiply(0x364e8711a60780382a5d57b061c126f039940f28a9e91fe039d4d3094d8b88n);
deepStrictEqual( deepStrictEqual(
publicKey.toHex(), publicKey.toHex(),
'ad545340b58610f0cd62f17d55af1ab11ecde9c084d5476865ddb4dbda015349' 'ad545340b58610f0cd62f17d55af1ab11ecde9c084d5476865ddb4dbda015349'
); );
} });
); should('create right publicKey without SHA-512 hashing TEST 3', () => {
should(
'ed25519/BASE_POINT.multiply()/should create right publicKey without SHA-512 hashing TEST 3',
() => {
const publicKey = const publicKey =
ed.Point.BASE.multiply(0xb9bf90ff3abec042752cac3a07a62f0c16cfb9d32a3fc2305d676ec2d86e941n); Point.BASE.multiply(0xb9bf90ff3abec042752cac3a07a62f0c16cfb9d32a3fc2305d676ec2d86e941n);
deepStrictEqual( deepStrictEqual(
publicKey.toHex(), publicKey.toHex(),
'e097c4415fe85724d522b2e449e8fd78dd40d20097bdc9ae36fe8ec6fe12cb8c' 'e097c4415fe85724d522b2e449e8fd78dd40d20097bdc9ae36fe8ec6fe12cb8c'
); );
} });
); should('create right publicKey without SHA-512 hashing TEST 4', () => {
should(
'ed25519/BASE_POINT.multiply()/should create right publicKey without SHA-512 hashing TEST 4',
() => {
const publicKey = const publicKey =
ed.Point.BASE.multiply(0x69d896f02d79524c9878e080308180e2859d07f9f54454e0800e8db0847a46en); Point.BASE.multiply(0x69d896f02d79524c9878e080308180e2859d07f9f54454e0800e8db0847a46en);
deepStrictEqual( deepStrictEqual(
publicKey.toHex(), publicKey.toHex(),
'f12cb7c43b59971395926f278ce7c2eaded9444fbce62ca717564cb508a0db1d' 'f12cb7c43b59971395926f278ce7c2eaded9444fbce62ca717564cb508a0db1d'
); );
} });
); should('throw Point#multiply on TEST 5', () => {
should('ed25519/BASE_POINT.multiply()/should throw Point#multiply on TEST 5', () => { for (const num of [0n, 0, -1n, -1, 1.1]) {
for (const num of [0n, 0, -1n, -1, 1.1]) { throws(() => Point.BASE.multiply(num));
throws(() => ed.Point.BASE.multiply(num)); }
} });
}); });
// https://ed25519.cr.yp.to/python/sign.py // https://ed25519.cr.yp.to/python/sign.py
@@ -184,7 +166,7 @@ describe('ed25519', () => {
// Calculate // Calculate
const pub = ed.getPublicKey(to32Bytes(priv)); const pub = ed.getPublicKey(to32Bytes(priv));
deepStrictEqual(hex(pub), expectedPub); deepStrictEqual(hex(pub), expectedPub);
deepStrictEqual(pub, ed.Point.fromHex(pub).toRawBytes()); deepStrictEqual(pub, Point.fromHex(pub).toRawBytes());
const signature = hex(ed.sign(msg, priv)); const signature = hex(ed.sign(msg, priv));
// console.log('vector', i); // console.log('vector', i);
@@ -382,7 +364,7 @@ describe('ed25519', () => {
throws(() => RistrettoPoint.fromHex(b), badBytes); throws(() => RistrettoPoint.fromHex(b), badBytes);
} }
}); });
should('ristretto255/should create right points from uniform hash', async () => { should('ristretto255/should create right points from uniform hash', () => {
const labels = [ const labels = [
'Ristretto is traditionally a short shot of espresso coffee', 'Ristretto is traditionally a short shot of espresso coffee',
'made with the normal amount of ground coffee but extracted with', 'made with the normal amount of ground coffee but extracted with',
@@ -440,13 +422,14 @@ describe('ed25519', () => {
} catch (e) { } catch (e) {
noble = false; noble = false;
} }
deepStrictEqual(noble, v.valid_zip215); deepStrictEqual(noble, v.valid_zip215, JSON.stringify(v));
} }
}); });
should('ZIP-215 compliance tests/disallows sig.s >= CURVE.n', () => { should('ZIP-215 compliance tests/disallows sig.s >= CURVE.n', () => {
const sig = new ed.Signature(ed.Point.BASE, 1n); // sig.R = BASE, sig.s = N+1
sig.s = ed.CURVE.n + 1n; const sig =
throws(() => ed.verify(sig, 'deadbeef', ed.Point.BASE)); '5866666666666666666666666666666666666666666666666666666666666666eed3f55c1a631258d69cf7a2def9de1400000000000000000000000000000010';
throws(() => ed.verify(sig, 'deadbeef', Point.BASE));
}); });
const rfc7748Mul = [ const rfc7748Mul = [
@@ -511,7 +494,7 @@ describe('ed25519', () => {
// should('X25519: should convert base point to montgomery using fromPoint', () => { // should('X25519: should convert base point to montgomery using fromPoint', () => {
// deepStrictEqual( // deepStrictEqual(
// hex(ed.montgomeryCurve.UfromPoint(ed.Point.BASE)), // hex(ed.montgomeryCurve.UfromPoint(Point.BASE)),
// ed.montgomeryCurve.BASE_POINT_U // ed.montgomeryCurve.BASE_POINT_U
// ); // );
// }); // });
@@ -655,16 +638,16 @@ describe('ed25519', () => {
} }
should('X25519 base point', () => { should('X25519 base point', () => {
const { y } = ed25519.Point.BASE; const { y } = ed25519.ExtendedPoint.BASE;
const { Fp } = ed25519.CURVE; const { Fp } = ed25519.CURVE;
const u = Fp.create((y + 1n) * Fp.invert(1n - y)); const u = Fp.create((y + 1n) * Fp.inv(1n - y));
deepStrictEqual(hex(numberToBytesLE(u, 32)), x25519.Gu); deepStrictEqual(hex(numberToBytesLE(u, 32)), x25519.Gu);
}); });
should('isTorsionFree()', () => { should('isTorsionFree()', () => {
const orig = ed.utils.getExtendedPublicKey(ed.utils.randomPrivateKey()).point; const orig = ed.utils.getExtendedPublicKey(ed.utils.randomPrivateKey()).point;
for (const hex of ED25519_TORSION_SUBGROUP.slice(1)) { for (const hex of ED25519_TORSION_SUBGROUP.slice(1)) {
const dirty = orig.add(ed.Point.fromHex(hex)); const dirty = orig.add(Point.fromHex(hex));
const cleared = dirty.clearCofactor(); const cleared = dirty.clearCofactor();
strictEqual(orig.isTorsionFree(), true, `orig must be torsionFree: ${hex}`); strictEqual(orig.isTorsionFree(), true, `orig must be torsionFree: ${hex}`);
strictEqual(dirty.isTorsionFree(), false, `dirty must not be torsionFree: ${hex}`); strictEqual(dirty.isTorsionFree(), false, `dirty must not be torsionFree: ${hex}`);

View File

@@ -11,9 +11,10 @@ describe('ed448', () => {
const ed = ed448; const ed = ed448;
const hex = bytesToHex; const hex = bytesToHex;
ed.utils.precompute(4); ed.utils.precompute(4);
const Point = ed.ExtendedPoint;
should(`Basic`, () => { should(`Basic`, () => {
const G1 = ed.Point.BASE; const G1 = Point.BASE.toAffine();
deepStrictEqual( deepStrictEqual(
G1.x, G1.x,
224580040295924300187604334099896036246789641632564134246125461686950415467406032909029192869357953282578032075146446173674602635247710n 224580040295924300187604334099896036246789641632564134246125461686950415467406032909029192869357953282578032075146446173674602635247710n
@@ -22,7 +23,7 @@ describe('ed448', () => {
G1.y, G1.y,
298819210078481492676017930443930673437544040154080242095928241372331506189835876003536878655418784733982303233503462500531545062832660n 298819210078481492676017930443930673437544040154080242095928241372331506189835876003536878655418784733982303233503462500531545062832660n
); );
const G2 = ed.Point.BASE.multiply(2n); const G2 = Point.BASE.multiply(2n).toAffine();
deepStrictEqual( deepStrictEqual(
G2.x, G2.x,
484559149530404593699549205258669689569094240458212040187660132787056912146709081364401144455726350866276831544947397859048262938744149n 484559149530404593699549205258669689569094240458212040187660132787056912146709081364401144455726350866276831544947397859048262938744149n
@@ -31,7 +32,7 @@ describe('ed448', () => {
G2.y, G2.y,
494088759867433727674302672526735089350544552303727723746126484473087719117037293890093462157703888342865036477787453078312060500281069n 494088759867433727674302672526735089350544552303727723746126484473087719117037293890093462157703888342865036477787453078312060500281069n
); );
const G3 = ed.Point.BASE.multiply(3n); const G3 = Point.BASE.multiply(3n).toAffine();
deepStrictEqual( deepStrictEqual(
G3.x, G3.x,
23839778817283171003887799738662344287085130522697782688245073320169861206004018274567429238677677920280078599146891901463786155880335n 23839778817283171003887799738662344287085130522697782688245073320169861206004018274567429238677677920280078599146891901463786155880335n
@@ -43,12 +44,12 @@ describe('ed448', () => {
}); });
should('Basic/decompress', () => { should('Basic/decompress', () => {
const G1 = ed.Point.BASE; const G1 = Point.BASE;
const G2 = ed.Point.BASE.multiply(2n); const G2 = Point.BASE.multiply(2n);
const G3 = ed.Point.BASE.multiply(3n); const G3 = Point.BASE.multiply(3n);
const points = [G1, G2, G3]; const points = [G1, G2, G3];
const getXY = (p) => ({ x: p.x, y: p.y }); const getXY = (p) => p.toAffine();
for (const p of points) deepStrictEqual(getXY(ed.Point.fromHex(p.toHex())), getXY(p)); for (const p of points) deepStrictEqual(getXY(Point.fromHex(p.toHex())), getXY(p));
}); });
const VECTORS_RFC8032 = [ const VECTORS_RFC8032 = [
@@ -315,16 +316,18 @@ describe('ed448', () => {
}, },
]; ];
for (let i = 0; i < VECTORS_RFC8032.length; i++) { describe('RFC8032', () => {
const v = VECTORS_RFC8032[i]; for (let i = 0; i < VECTORS_RFC8032.length; i++) {
should(`RFC8032/${i}`, () => { const v = VECTORS_RFC8032[i];
deepStrictEqual(hex(ed.getPublicKey(v.secretKey)), v.publicKey); should(`${i}`, () => {
deepStrictEqual(hex(ed.sign(v.message, v.secretKey)), v.signature); deepStrictEqual(hex(ed.getPublicKey(v.secretKey)), v.publicKey);
deepStrictEqual(ed.verify(v.signature, v.message, v.publicKey), true); deepStrictEqual(hex(ed.sign(v.message, v.secretKey)), v.signature);
}); deepStrictEqual(ed.verify(v.signature, v.message, v.publicKey), true);
} });
}
});
should('not accept >57byte private keys', async () => { should('not accept >57byte private keys', () => {
const invalidPriv = const invalidPriv =
100000000000000000000000000000000000009000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000090000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800073278156000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000n; 100000000000000000000000000000000000009000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000090000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800073278156000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000n;
throws(() => ed.getPublicKey(invalidPriv)); throws(() => ed.getPublicKey(invalidPriv));
@@ -382,7 +385,24 @@ describe('ed448', () => {
deepStrictEqual(ed.verify(signature, msg, publicKey), true); deepStrictEqual(ed.verify(signature, msg, publicKey), true);
}); });
should('not verify signature with wrong public key', () => { should('not verify signature with wrong public key', () => {
const publicKey = ed.getPublicKey(12); const publicKey = ed.getPublicKey(ed.utils.randomPrivateKey());
const signature = ed.sign(msg, privKey);
deepStrictEqual(ed.verify(signature, msg, publicKey), false);
});
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);
});
});
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('not verify signature with wrong public key', () => {
const publicKey = ed.getPublicKey(ed.utils.randomPrivateKey());
const signature = ed.sign(msg, privKey); const signature = ed.sign(msg, privKey);
deepStrictEqual(ed.verify(signature, msg, publicKey), false); deepStrictEqual(ed.verify(signature, msg, publicKey), false);
}); });
@@ -393,27 +413,9 @@ describe('ed448', () => {
}); });
}); });
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('not verify signature with wrong public key', async () => {
const publicKey = ed.getPublicKey(12);
const signature = ed.sign(msg, privKey);
deepStrictEqual(ed.verify(signature, msg, publicKey), false);
});
should('not verify signature with wrong hash', async () => {
const publicKey = ed.getPublicKey(privKey);
const signature = ed.sign(msg, privKey);
deepStrictEqual(ed.verify(signature, wrongMsg, publicKey), false);
});
});
should('BASE_POINT.multiply() throws in 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]) { for (const num of [0n, 0, -1n, -1, 1.1]) {
throws(() => ed.Point.BASE.multiply(num)); throws(() => ed.ExtendedPoint.BASE.multiply(num));
} }
}); });
@@ -437,14 +439,14 @@ describe('ed448', () => {
} }
}); });
{ describe('wycheproof', () => {
for (let g = 0; g < ed448vectors.testGroups.length; g++) { for (let g = 0; g < ed448vectors.testGroups.length; g++) {
const group = ed448vectors.testGroups[g]; const group = ed448vectors.testGroups[g];
const key = group.key; const key = group.key;
should(`Wycheproof/ED448(${g}, public)`, () => { should(`ED448(${g}, public)`, () => {
deepStrictEqual(hex(ed.getPublicKey(key.sk)), key.pk); deepStrictEqual(hex(ed.getPublicKey(key.sk)), key.pk);
}); });
should(`Wycheproof/ED448`, () => { should(`ED448`, () => {
for (let i = 0; i < group.tests.length; i++) { for (let i = 0; i < group.tests.length; i++) {
const v = group.tests[i]; const v = group.tests[i];
const index = `${g}/${i} ${v.comment}`; const index = `${g}/${i} ${v.comment}`;
@@ -463,7 +465,7 @@ describe('ed448', () => {
} }
}); });
} }
} });
// ECDH // ECDH
const rfc7748Mul = [ const rfc7748Mul = [
@@ -482,12 +484,14 @@ describe('ed448', () => {
'884a02576239ff7a2f2f63b2db6a9ff37047ac13568e1e30fe63c4a7ad1b3ee3a5700df34321d62077e63633c575c1c954514e99da7c179d', '884a02576239ff7a2f2f63b2db6a9ff37047ac13568e1e30fe63c4a7ad1b3ee3a5700df34321d62077e63633c575c1c954514e99da7c179d',
}, },
]; ];
for (let i = 0; i < rfc7748Mul.length; i++) { describe('RFC7748', () => {
const v = rfc7748Mul[i]; for (let i = 0; i < rfc7748Mul.length; i++) {
should(`RFC7748: scalarMult (${i})`, () => { const v = rfc7748Mul[i];
deepStrictEqual(hex(x448.scalarMult(v.scalar, v.u)), v.outputU); should(`scalarMult (${i})`, () => {
}); deepStrictEqual(hex(x448.scalarMult(v.scalar, v.u)), v.outputU);
} });
}
});
const rfc7748Iter = [ const rfc7748Iter = [
{ {
@@ -528,9 +532,9 @@ describe('ed448', () => {
deepStrictEqual(hex(x448.scalarMult(bobPrivate, alicePublic)), shared); deepStrictEqual(hex(x448.scalarMult(bobPrivate, alicePublic)), shared);
}); });
{ describe('wycheproof', () => {
const group = x448vectors.testGroups[0]; const group = x448vectors.testGroups[0];
should(`Wycheproof/X448`, () => { should(`X448`, () => {
for (let i = 0; i < group.tests.length; i++) { for (let i = 0; i < group.tests.length; i++) {
const v = group.tests[i]; const v = group.tests[i];
const index = `(${i}, ${v.result}) ${v.comment}`; const index = `(${i}, ${v.result}) ${v.comment}`;
@@ -556,11 +560,11 @@ describe('ed448', () => {
} else throw new Error('unknown test result'); } else throw new Error('unknown test result');
} }
}); });
} });
// should('X448: should convert base point to montgomery using fromPoint', () => { // should('X448: should convert base point to montgomery using fromPoint', () => {
// deepStrictEqual( // deepStrictEqual(
// hex(ed.montgomeryCurve.UfromPoint(ed.Point.BASE)), // hex(ed.montgomeryCurve.UfromPoint(Point.BASE)),
// ed.montgomeryCurve.BASE_POINT_U // ed.montgomeryCurve.BASE_POINT_U
// ); // );
// }); // });
@@ -655,7 +659,7 @@ describe('ed448', () => {
} }
should('X448 base point', () => { should('X448 base point', () => {
const { x, y } = ed448.Point.BASE; const { x, y } = Point.BASE;
const { Fp } = ed448.CURVE; const { Fp } = ed448.CURVE;
// const invX = Fp.invert(x * x); // x² // const invX = Fp.invert(x * x); // x²
const u = Fp.div(Fp.create(y * y), Fp.create(x * x)); // (y²/x²) const u = Fp.div(Fp.create(y * y), Fp.create(x * x)); // (y²/x²)

View File

@@ -5,12 +5,12 @@ import { bytesToHex } from '@noble/hashes/utils';
import { sha256 } from '@noble/hashes/sha256'; import { sha256 } from '@noble/hashes/sha256';
import { sha512 } from '@noble/hashes/sha512'; import { sha512 } from '@noble/hashes/sha512';
import { shake128, shake256 } from '@noble/hashes/sha3'; import { shake128, shake256 } from '@noble/hashes/sha3';
import { secp256r1 } from '../lib/esm/p256.js'; import * as secp256r1 from '../lib/esm/p256.js';
import { secp384r1 } from '../lib/esm/p384.js'; import * as secp384r1 from '../lib/esm/p384.js';
import { secp521r1 } from '../lib/esm/p521.js'; import * as secp521r1 from '../lib/esm/p521.js';
import { ed25519 } from '../lib/esm/ed25519.js'; import * as ed25519 from '../lib/esm/ed25519.js';
import { ed448 } from '../lib/esm/ed448.js'; import * as ed448 from '../lib/esm/ed448.js';
import { secp256k1 } from '../lib/esm/secp256k1.js'; import * as secp256k1 from '../lib/esm/secp256k1.js';
import { bls12_381 } from '../lib/esm/bls12-381.js'; import { bls12_381 } from '../lib/esm/bls12-381.js';
import { import {
stringToBytes, stringToBytes,
@@ -111,9 +111,11 @@ function testCurve(curve, ro, nu) {
for (let i = 0; i < ro.vectors.length; i++) { for (let i = 0; i < ro.vectors.length; i++) {
const t = ro.vectors[i]; const t = ro.vectors[i];
should(`(${i})`, () => { should(`(${i})`, () => {
const p = curve.Point.hashToCurve(stringToBytes(t.msg), { const p = curve
DST: ro.dst, .hashToCurve(stringToBytes(t.msg), {
}); DST: ro.dst,
})
.toAffine();
deepStrictEqual(p.x, stringToFp(t.P.x), 'Px'); deepStrictEqual(p.x, stringToFp(t.P.x), 'Px');
deepStrictEqual(p.y, stringToFp(t.P.y), 'Py'); deepStrictEqual(p.y, stringToFp(t.P.y), 'Py');
}); });
@@ -123,9 +125,11 @@ function testCurve(curve, ro, nu) {
for (let i = 0; i < nu.vectors.length; i++) { for (let i = 0; i < nu.vectors.length; i++) {
const t = nu.vectors[i]; const t = nu.vectors[i];
should(`(${i})`, () => { should(`(${i})`, () => {
const p = curve.Point.encodeToCurve(stringToBytes(t.msg), { const p = curve
DST: nu.dst, .encodeToCurve(stringToBytes(t.msg), {
}); DST: nu.dst,
})
.toAffine();
deepStrictEqual(p.x, stringToFp(t.P.x), 'Px'); deepStrictEqual(p.x, stringToFp(t.P.x), 'Px');
deepStrictEqual(p.y, stringToFp(t.P.y), 'Py'); deepStrictEqual(p.y, stringToFp(t.P.y), 'Py');
}); });
@@ -136,9 +140,8 @@ function testCurve(curve, ro, nu) {
testCurve(secp256r1, p256_ro, p256_nu); testCurve(secp256r1, p256_ro, p256_nu);
testCurve(secp384r1, p384_ro, p384_nu); testCurve(secp384r1, p384_ro, p384_nu);
testCurve(secp521r1, p521_ro, p521_nu); testCurve(secp521r1, p521_ro, p521_nu);
// TODO: remove same tests from bls12 testCurve(bls12_381.hashToCurve.G1, g1_ro, g1_nu);
testCurve(bls12_381.G1, g1_ro, g1_nu); testCurve(bls12_381.hashToCurve.G2, g2_ro, g2_nu);
testCurve(bls12_381.G2, g2_ro, g2_nu);
testCurve(secp256k1, secp256k1_ro, secp256k1_nu); testCurve(secp256k1, secp256k1_ro, secp256k1_nu);
testCurve(ed25519, ed25519_ro, ed25519_nu); testCurve(ed25519, ed25519_ro, ed25519_nu);
testCurve(ed448, ed448_ro, ed448_nu); testCurve(ed448, ed448_ro, ed448_nu);

View File

@@ -6,7 +6,7 @@ import './nist.test.js';
import './ed448.test.js'; import './ed448.test.js';
import './ed25519.test.js'; import './ed25519.test.js';
import './secp256k1.test.js'; import './secp256k1.test.js';
import './stark/stark.test.js'; import './stark/index.test.js';
import './jubjub.test.js'; import './jubjub.test.js';
import './bls12-381.test.js'; import './bls12-381.test.js';
import './hash-to-curve.test.js'; import './hash-to-curve.test.js';

View File

@@ -1,15 +1,15 @@
import { jubjub, findGroupHash } from '../lib/esm/jubjub.js'; import { jubjub, findGroupHash } from '../lib/esm/jubjub.js';
import { describe, should } from 'micro-should'; import { describe, should } from 'micro-should';
import { deepStrictEqual, throws } from 'assert'; import { deepStrictEqual, throws } from 'assert';
import { hexToBytes, bytesToHex } from '@noble/hashes/utils'; const Point = jubjub.ExtendedPoint;
const G_SPEND = new jubjub.ExtendedPoint( const G_SPEND = new Point(
0x055f1f24f0f0512287e51c3c5a0a6903fc0baf8711de9eafd7c0e66f69d8d2dbn, 0x055f1f24f0f0512287e51c3c5a0a6903fc0baf8711de9eafd7c0e66f69d8d2dbn,
0x566178b2505fdd52132a5007d80a04652842e78ffb376897588f406278214ed7n, 0x566178b2505fdd52132a5007d80a04652842e78ffb376897588f406278214ed7n,
0x0141fafa1f11088a3b2007c14d652375888f3b37838ba6bdffae096741ceddfen, 0x0141fafa1f11088a3b2007c14d652375888f3b37838ba6bdffae096741ceddfen,
0x12eada93c0b7d595f5f04f5ebfb4b7d033ef2884136475cab5e41ce17db5be9cn 0x12eada93c0b7d595f5f04f5ebfb4b7d033ef2884136475cab5e41ce17db5be9cn
); );
const G_PROOF = new jubjub.ExtendedPoint( const G_PROOF = new Point(
0x0174d54ce9fad258a2f8a86a1deabf15c7a2b51106b0fbcd9d29020f78936f71n, 0x0174d54ce9fad258a2f8a86a1deabf15c7a2b51106b0fbcd9d29020f78936f71n,
0x16871d6d877dcd222e4ec3bccb3f37cb1865a2d37dd3a5dcbc032a69b62b4445n, 0x16871d6d877dcd222e4ec3bccb3f37cb1865a2d37dd3a5dcbc032a69b62b4445n,
0x57a3cd31e496d82bd4aa78bd5ecd751cfb76d54a5d3f4560866379f9fc11c9b3n, 0x57a3cd31e496d82bd4aa78bd5ecd751cfb76d54a5d3f4560866379f9fc11c9b3n,
@@ -22,7 +22,7 @@ describe('jubjub', () => {
should('toHex/fromHex', () => { should('toHex/fromHex', () => {
// More than field // More than field
throws(() => throws(() =>
jubjub.Point.fromHex( Point.fromHex(
new Uint8Array([ 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, 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,
@@ -31,14 +31,14 @@ describe('jubjub', () => {
); );
// Multiplicative generator (sqrt == null), not on curve. // Multiplicative generator (sqrt == null), not on curve.
throws(() => throws(() =>
jubjub.Point.fromHex( Point.fromHex(
new Uint8Array([ 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, 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, 0, 0,
]) ])
) )
); );
const tmp = jubjub.Point.fromHex( const tmp = Point.fromHex(
new Uint8Array([ 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, 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,
@@ -47,14 +47,14 @@ describe('jubjub', () => {
deepStrictEqual(tmp.x, 0x8d51ccce760304d0ec030002760300000001000000000000n); deepStrictEqual(tmp.x, 0x8d51ccce760304d0ec030002760300000001000000000000n);
deepStrictEqual(tmp.y, 0n); deepStrictEqual(tmp.y, 0n);
const S = G_SPEND.toAffine().toRawBytes(); const S = G_SPEND.toRawBytes();
const S2 = G_SPEND.double().toAffine().toRawBytes(); const S2 = G_SPEND.double().toRawBytes();
const P = G_PROOF.toAffine().toRawBytes(); const P = G_PROOF.toRawBytes();
const P2 = G_PROOF.double().toAffine().toRawBytes(); const P2 = G_PROOF.double().toRawBytes();
const S_exp = jubjub.Point.fromHex(S); const S_exp = Point.fromHex(S);
const S2_exp = jubjub.Point.fromHex(S2); const S2_exp = Point.fromHex(S2);
const P_exp = jubjub.Point.fromHex(P); const P_exp = Point.fromHex(P);
const P2_exp = jubjub.Point.fromHex(P2); const P2_exp = Point.fromHex(P2);
deepStrictEqual(getXY(G_SPEND.toAffine()), getXY(S_exp)); deepStrictEqual(getXY(G_SPEND.toAffine()), getXY(S_exp));
deepStrictEqual(getXY(G_SPEND.double().toAffine()), getXY(S2_exp)); deepStrictEqual(getXY(G_SPEND.double().toAffine()), getXY(S2_exp));
deepStrictEqual(getXY(G_PROOF.toAffine()), getXY(P_exp)); deepStrictEqual(getXY(G_PROOF.toAffine()), getXY(P_exp));

View File

@@ -11,164 +11,11 @@ import { default as ecdsa } from './wycheproof/ecdsa_test.json' assert { type: '
import { default as ecdh } from './wycheproof/ecdh_test.json' assert { type: 'json' }; import { default as ecdh } from './wycheproof/ecdh_test.json' assert { type: 'json' };
import { default as rfc6979 } from './fixtures/rfc6979.json' assert { type: 'json' }; import { default as rfc6979 } from './fixtures/rfc6979.json' assert { type: 'json' };
const hex = bytesToHex;
// prettier-ignore
const NIST = {
secp192r1, P192,
secp224r1, P224,
secp256r1, P256,
secp384r1, P384,
secp521r1, P521,
secp256k1,
};
should('Curve Fields', () => {
const vectors = {
secp192r1: 0xfffffffffffffffffffffffffffffffeffffffffffffffffn,
secp224r1: 0xffffffffffffffffffffffffffffffff000000000000000000000001n,
secp256r1: 0xffffffff00000001000000000000000000000000ffffffffffffffffffffffffn,
secp256k1: 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2fn,
secp384r1:
0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffff0000000000000000ffffffffn,
secp521r1:
0x01ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffn,
};
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', () => {
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;
for (const test of group.tests) {
if (test.result === 'valid' || test.result === 'acceptable') {
try {
const pub = CURVE.Point.fromHex(test.public);
} catch (e) {
if (e.message.includes('Point.fromHex: received invalid point.')) continue;
throw e;
}
const shared = CURVE.getSharedSecret(test.private, test.public);
deepStrictEqual(shared, test.shared, 'valid');
} else if (test.result === 'invalid') {
let failed = false;
try {
CURVE.getSharedSecret(test.private, test.public);
} catch (error) {
failed = true;
}
deepStrictEqual(failed, true, 'invalid');
} else throw new Error('unknown test result');
}
}
});
import { default as ecdh_secp224r1_test } from './wycheproof/ecdh_secp224r1_test.json' assert { type: 'json' }; import { default as ecdh_secp224r1_test } from './wycheproof/ecdh_secp224r1_test.json' assert { type: 'json' };
import { default as ecdh_secp256r1_test } from './wycheproof/ecdh_secp256r1_test.json' assert { type: 'json' }; import { default as ecdh_secp256r1_test } from './wycheproof/ecdh_secp256r1_test.json' assert { type: 'json' };
import { default as ecdh_secp256k1_test } from './wycheproof/ecdh_secp256k1_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_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' }; 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,
tests: [ecdh_secp224r1_test],
},
P256: {
curve: P256,
tests: [ecdh_secp256r1_test],
},
secp256k1: {
curve: secp256k1,
tests: [ecdh_secp256k1_test],
},
P384: {
curve: P384,
tests: [ecdh_secp384r1_test],
},
P521: {
curve: P521,
tests: [ecdh_secp521r1_test],
},
};
for (const name in WYCHEPROOF_ECDH) {
const { curve, tests } = WYCHEPROOF_ECDH[name];
for (let i = 0; i < tests.length; i++) {
const test = tests[i];
for (let j = 0; j < test.testGroups.length; j++) {
const group = test.testGroups[j];
should(`Wycheproof/ECDH ${name} (${i}/${j})`, () => {
for (const test of group.tests) {
if (test.result === 'valid' || test.result === 'acceptable') {
try {
const pub = curve.Point.fromHex(test.public);
} catch (e) {
if (e.message.includes('Point.fromHex: received invalid point.')) continue;
throw e;
}
const shared = curve.getSharedSecret(test.private, test.public);
deepStrictEqual(hex(shared), test.shared, 'valid');
} else if (test.result === 'invalid') {
let failed = false;
try {
curve.getSharedSecret(test.private, test.public);
} catch (error) {
failed = true;
}
deepStrictEqual(failed, true, 'invalid');
} else throw new Error('unknown test result');
}
});
}
}
}
// Tests with custom hashes // Tests with custom hashes
import { default as secp224r1_sha224_test } from './wycheproof/ecdsa_secp224r1_sha224_test.json' assert { type: 'json' }; 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_sha256_test } from './wycheproof/ecdsa_secp224r1_sha256_test.json' assert { type: 'json' };
@@ -199,6 +46,123 @@ import { sha3_224, sha3_256, sha3_384, sha3_512 } from '@noble/hashes/sha3';
import { sha512, sha384 } from '@noble/hashes/sha512'; import { sha512, sha384 } from '@noble/hashes/sha512';
import { sha224, sha256 } from '@noble/hashes/sha256'; import { sha224, sha256 } from '@noble/hashes/sha256';
const hex = bytesToHex;
// prettier-ignore
const NIST = {
secp192r1, P192,
secp224r1, P224,
secp256r1, P256,
secp384r1, P384,
secp521r1, P521,
secp256k1,
};
describe('NIST curves', () => {});
should('fields', () => {
const vectors = {
secp192r1: 0xfffffffffffffffffffffffffffffffeffffffffffffffffn,
secp224r1: 0xffffffffffffffffffffffffffffffff000000000000000000000001n,
secp256r1: 0xffffffff00000001000000000000000000000000ffffffffffffffffffffffffn,
secp256k1: 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2fn,
secp384r1:
0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffff0000000000000000ffffffffn,
secp521r1:
0x01ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffn,
};
for (const n in vectors) deepStrictEqual(NIST[n].CURVE.Fp.ORDER, vectors[n]);
});
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.ProjectivePoint.fromHex(test.public);
} catch (e) {
// Our strict validation filter doesn't let weird-length DER vectors
if (e.message.startsWith('Point of length')) continue;
throw e;
}
const shared = CURVE.getSharedSecret(test.private, test.public);
deepStrictEqual(shared, test.shared, 'valid');
} else if (test.result === 'invalid') {
let failed = false;
try {
CURVE.getSharedSecret(test.private, test.public);
} catch (error) {
failed = true;
}
deepStrictEqual(failed, true, 'invalid');
} else throw new Error('unknown test result');
}
});
}
// More per curve tests
const WYCHEPROOF_ECDH = {
P224: {
curve: P224,
tests: [ecdh_secp224r1_test],
},
P256: {
curve: P256,
tests: [ecdh_secp256r1_test],
},
secp256k1: {
curve: secp256k1,
tests: [ecdh_secp256k1_test],
},
P384: {
curve: P384,
tests: [ecdh_secp384r1_test],
},
P521: {
curve: P521,
tests: [ecdh_secp521r1_test],
},
};
for (const name in WYCHEPROOF_ECDH) {
const { curve, tests } = WYCHEPROOF_ECDH[name];
for (let i = 0; i < tests.length; i++) {
const test = tests[i];
for (let j = 0; j < test.testGroups.length; j++) {
const group = test.testGroups[j];
should(`additional ${name} (${i}/${j})`, () => {
for (const test of group.tests) {
if (test.result === 'valid' || test.result === 'acceptable') {
try {
const pub = curve.ProjectivePoint.fromHex(test.public);
} catch (e) {
// Our strict validation filter doesn't let weird-length DER vectors
if (e.message.includes('Point of length')) continue;
throw e;
}
const shared = curve.getSharedSecret(test.private, test.public);
deepStrictEqual(hex(shared), test.shared, 'valid');
} else if (test.result === 'invalid') {
let failed = false;
try {
curve.getSharedSecret(test.private, test.public);
} catch (error) {
failed = true;
}
deepStrictEqual(failed, true, 'invalid');
} else throw new Error('unknown test result');
}
});
}
}
}
});
const WYCHEPROOF_ECDSA = { const WYCHEPROOF_ECDSA = {
P224: { P224: {
curve: P224, curve: P224,
@@ -232,7 +196,6 @@ const WYCHEPROOF_ECDSA = {
secp256k1: { secp256k1: {
curve: secp256k1, curve: secp256k1,
hashes: { hashes: {
// TODO: debug why fails, can be bug
sha256: { sha256: {
hash: sha256, hash: sha256,
tests: [secp256k1_sha256_test], tests: [secp256k1_sha256_test],
@@ -309,31 +272,33 @@ const WYCHEPROOF_ECDSA = {
}; };
function runWycheproof(name, CURVE, group, index) { function runWycheproof(name, CURVE, group, index) {
const pubKey = CURVE.Point.fromHex(group.key.uncompressed); const pubKey = CURVE.ProjectivePoint.fromHex(group.key.uncompressed);
deepStrictEqual(pubKey.x, BigInt(`0x${group.key.wx}`)); deepStrictEqual(pubKey.x, BigInt(`0x${group.key.wx}`));
deepStrictEqual(pubKey.y, BigInt(`0x${group.key.wy}`)); deepStrictEqual(pubKey.y, BigInt(`0x${group.key.wy}`));
const pubR = pubKey.toRawBytes();
for (const test of group.tests) { for (const test of group.tests) {
const m = CURVE.CURVE.hash(hexToBytes(test.msg)); const m = CURVE.CURVE.hash(hexToBytes(test.msg));
const { sig } = test;
if (test.result === 'valid' || test.result === 'acceptable') { if (test.result === 'valid' || test.result === 'acceptable') {
try { try {
CURVE.Signature.fromDER(test.sig); CURVE.Signature.fromDER(sig);
} catch (e) { } catch (e) {
// Some tests has invalid signature which we don't accept // Some tests has invalid signature which we don't accept
if (e.message.includes('Invalid signature: incorrect length')) continue; if (e.message.includes('Invalid signature: incorrect length')) continue;
throw e; throw e;
} }
const verified = CURVE.verify(test.sig, m, pubKey); const verified = CURVE.verify(sig, m, pubR);
if (name === 'secp256k1') { if (name === 'secp256k1') {
// lowS: true for 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 { } else {
deepStrictEqual(verified, true, `${index}: valid`); deepStrictEqual(verified, true, `${index}: valid`);
} }
} else if (test.result === 'invalid') { } else if (test.result === 'invalid') {
let failed = false; let failed = false;
try { try {
failed = !CURVE.verify(test.sig, m, pubKey); failed = !CURVE.verify(sig, m, pubR);
} catch (error) { } catch (error) {
failed = true; failed = true;
} }
@@ -342,42 +307,85 @@ function runWycheproof(name, CURVE, group, index) {
} }
} }
for (const name in WYCHEPROOF_ECDSA) { describe('wycheproof ECDSA', () => {
const { curve, hashes } = WYCHEPROOF_ECDSA[name]; should('generic', () => {
describe('Wycheproof/WYCHEPROOF_ECDSA', () => { for (const group of ecdsa.testGroups) {
for (const hName in hashes) { // Tested in secp256k1.test.js
const { hash, tests } = hashes[hName]; if (group.key.curve === 'secp256k1') continue;
const CURVE = curve.create(hash); let CURVE = NIST[group.key.curve];
should(`${name}/${hName}`, () => { if (!CURVE) continue;
for (let i = 0; i < tests.length; i++) { if (group.key.curve === 'secp224r1' && group.sha !== 'SHA-224') {
const groups = tests[i].testGroups; if (group.sha === 'SHA-256') CURVE = CURVE.create(sha256);
for (let j = 0; j < groups.length; j++) { }
const group = groups[j]; const pubKey = CURVE.ProjectivePoint.fromHex(group.key.uncompressed);
runWycheproof(name, CURVE, group, `${i}/${j}`); 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.toHex());
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(`${name}/${hName}`, () => {
for (let i = 0; i < tests.length; i++) {
const groups = tests[i].testGroups;
for (let j = 0; j < groups.length; j++) {
const group = groups[j];
runWycheproof(name, CURVE, group, `${i}/${j}`);
}
}
});
}
});
}
});
const hexToBigint = (hex) => BigInt(`0x${hex}`); const hexToBigint = (hex) => BigInt(`0x${hex}`);
should('RFC6979', () => { describe('RFC6979', () => {
for (const v of rfc6979) { for (const v of rfc6979) {
const curve = NIST[v.curve]; should(v.curve, () => {
deepStrictEqual(curve.CURVE.n, hexToBigint(v.q)); const curve = NIST[v.curve];
const pubKey = curve.getPublicKey(v.private); deepStrictEqual(curve.CURVE.n, hexToBigint(v.q));
const pubPoint = curve.Point.fromHex(pubKey); const pubKey = curve.getPublicKey(v.private);
deepStrictEqual(pubPoint.x, hexToBigint(v.Ux)); const pubPoint = curve.ProjectivePoint.fromHex(pubKey);
deepStrictEqual(pubPoint.y, hexToBigint(v.Uy)); deepStrictEqual(pubPoint.x, hexToBigint(v.Ux));
for (const c of v.cases) { deepStrictEqual(pubPoint.y, hexToBigint(v.Uy));
const h = curve.CURVE.hash(c.message); for (const c of v.cases) {
const sigObj = curve.sign(h, v.private); const h = curve.CURVE.hash(c.message);
deepStrictEqual(sigObj.r, hexToBigint(c.r), 'R'); const sigObj = curve.sign(h, v.private);
deepStrictEqual(sigObj.s, hexToBigint(c.s), 'S'); deepStrictEqual(sigObj.r, hexToBigint(c.r), 'R');
deepStrictEqual(curve.verify(sigObj.toDERRawBytes(), h, pubKey), true, 'verify(1)'); deepStrictEqual(sigObj.s, hexToBigint(c.s), 'S');
deepStrictEqual(curve.verify(sigObj, h, pubKey), true, 'verify(2)'); deepStrictEqual(curve.verify(sigObj.toDERRawBytes(), h, pubKey), true, 'verify(1)');
} deepStrictEqual(curve.verify(sigObj, h, pubKey), true, 'verify(2)');
}
});
} }
}); });

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

@@ -0,0 +1,375 @@
import { deepStrictEqual, throws } from 'assert';
import { should, describe } from 'micro-should';
import * as poseidon from '../lib/esm/abstract/poseidon.js';
import * as stark from '../lib/esm/stark.js';
import * as mod from '../lib/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.Fp(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.Fp(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.Fp(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.Fp(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

@@ -1,6 +1,7 @@
import * as fc from 'fast-check'; import * as fc from 'fast-check';
import { secp256k1, schnorr } from '../lib/esm/secp256k1.js'; import { secp256k1, schnorr } from '../lib/esm/secp256k1.js';
import { Fp } from '../lib/esm/abstract/modular.js'; import { Fp } from '../lib/esm/abstract/modular.js';
import { bytesToNumberBE, ensureBytes, numberToBytesBE } from '../lib/esm/abstract/utils.js';
import { readFileSync } from 'fs'; import { readFileSync } from 'fs';
import { default as ecdsa } from './vectors/ecdsa.json' assert { type: 'json' }; 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 ecdh } from './vectors/ecdh.json' assert { type: 'json' };
@@ -13,6 +14,7 @@ import { hexToBytes, bytesToHex } from '@noble/hashes/utils';
const hex = bytesToHex; const hex = bytesToHex;
const secp = secp256k1; const secp = secp256k1;
const Point = secp.ProjectivePoint;
const privatesTxt = readFileSync('./test/vectors/privates-2.txt', 'utf-8'); const privatesTxt = readFileSync('./test/vectors/privates-2.txt', 'utf-8');
const schCsv = readFileSync('./test/vectors/schnorr.csv', 'utf-8'); const schCsv = readFileSync('./test/vectors/schnorr.csv', 'utf-8');
@@ -24,7 +26,7 @@ const toBEHex = (n) => n.toString(16).padStart(64, '0');
function hexToNumber(hex) { function hexToNumber(hex) {
if (typeof hex !== 'string') { if (typeof hex !== 'string') {
throw new TypeError('hexToNumber: expected string, got ' + typeof hex); throw new Error('hexToNumber: expected string, got ' + typeof hex);
} }
// Big Endian // Big Endian
return BigInt(`0x${hex}`); return BigInt(`0x${hex}`);
@@ -37,15 +39,15 @@ describe('secp256k1', () => {
.filter((line) => line) .filter((line) => line)
.map((line) => line.split(':')); .map((line) => line.split(':'));
for (let [priv, x, y] of data) { 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.x), x);
deepStrictEqual(toBEHex(point.y), y); 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.x), x);
deepStrictEqual(toBEHex(point2.y), y); 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.x), x);
deepStrictEqual(toBEHex(point3.y), y); deepStrictEqual(toBEHex(point3.y), y);
} }
@@ -62,106 +64,108 @@ describe('secp256k1', () => {
.filter((line) => line) .filter((line) => line)
.map((line) => line.split(':')); .map((line) => line.split(':'));
for (let [priv, x, y] of data) { 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.x), x);
deepStrictEqual(toBEHex(point.y), y); 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.x), x);
deepStrictEqual(toBEHex(point2.y), y); 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.x), x);
deepStrictEqual(toBEHex(point3.y), y); deepStrictEqual(toBEHex(point3.y), y);
} }
}); });
should('Point.isValidPoint()', () => { describe('Point', () => {
for (const vector of points.valid.isPoint) { should('fromHex() assertValidity', () => {
const { P, expected } = vector; for (const vector of points.valid.isPoint) {
if (expected) { const { P, expected } = vector;
secp.Point.fromHex(P); if (expected) {
} else { Point.fromHex(P);
throws(() => secp.Point.fromHex(P)); } else {
} throws(() => Point.fromHex(P));
}
});
should('Point.fromPrivateKey()', () => {
for (const vector of points.valid.pointFromScalar) {
const { d, expected } = vector;
let p = secp.Point.fromPrivateKey(d);
deepStrictEqual(p.toHex(true), expected);
}
});
should('Point#toHex(compressed)', () => {
for (const vector of points.valid.pointCompress) {
const { P, compress, expected } = vector;
let p = secp.Point.fromHex(P);
deepStrictEqual(p.toHex(compress), expected);
}
});
should('Point#toHex() roundtrip (failed case)', () => {
const point1 =
secp.Point.fromPrivateKey(
88572218780422190464634044548753414301110513745532121983949500266768436236425n
);
// const hex = point1.toHex(true);
// deepStrictEqual(secp.Point.fromHex(hex).toHex(true), hex);
});
should('Point#toHex() roundtrip', () => {
fc.assert(
fc.property(FC_BIGINT, (x) => {
const point1 = secp.Point.fromPrivateKey(x);
const hex = point1.toHex(true);
deepStrictEqual(secp.Point.fromHex(hex).toHex(true), hex);
})
);
});
should('Point#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);
if (expected) {
deepStrictEqual(p.add(q).toHex(true), expected);
} else {
if (!p.equals(q.negate())) {
throws(() => p.add(q).toHex(true));
} }
} }
} });
});
should('Point#multiply(privateKey)', () => { should('.fromPrivateKey()', () => {
for (const vector of points.valid.pointMultiply) { for (const vector of points.valid.pointFromScalar) {
const { P, d, expected } = vector; const { d, expected } = vector;
const p = secp.Point.fromHex(P); let p = Point.fromPrivateKey(d);
if (expected) { deepStrictEqual(p.toHex(true), expected);
deepStrictEqual(p.multiply(hexToNumber(d)).toHex(true), expected);
} else {
throws(() => {
p.multiply(hexToNumber(d)).toHex(true);
});
} }
} });
for (const vector of points.invalid.pointMultiply) { should('#toHex(compressed)', () => {
const { P, d } = vector; for (const vector of points.valid.pointCompress) {
if (hexToNumber(d) < secp.CURVE.n) { const { P, compress, expected } = vector;
throws(() => { let p = Point.fromHex(P);
const p = secp.Point.fromHex(P); deepStrictEqual(p.toHex(compress), expected);
p.multiply(hexToNumber(d)).toHex(true);
});
} }
} });
for (const num of [0n, 0, -1n, -1, 1.1]) {
throws(() => secp.Point.BASE.multiply(num)); should('#toHex() roundtrip (failed case)', () => {
} const point1 =
Point.fromPrivateKey(
88572218780422190464634044548753414301110513745532121983949500266768436236425n
);
// const hex = point1.toHex(true);
// deepStrictEqual(Point.fromHex(hex).toHex(true), hex);
});
should('#toHex() roundtrip', () => {
fc.assert(
fc.property(FC_BIGINT, (x) => {
const point1 = Point.fromPrivateKey(x);
const hex = point1.toHex(true);
deepStrictEqual(Point.fromHex(hex).toHex(true), hex);
})
);
});
should('#add(other)', () => {
for (const vector of points.valid.pointAdd) {
const { P, Q, expected } = vector;
let p = Point.fromHex(P);
let q = Point.fromHex(Q);
if (expected) {
deepStrictEqual(p.add(q).toHex(true), expected);
} else {
if (!p.equals(q.negate())) {
throws(() => p.add(q).toHex(true));
}
}
}
});
should('#multiply(privateKey)', () => {
for (const vector of points.valid.pointMultiply) {
const { P, d, expected } = vector;
const p = Point.fromHex(P);
if (expected) {
deepStrictEqual(p.multiply(hexToNumber(d)).toHex(true), expected, P);
} else {
throws(() => {
p.multiply(hexToNumber(d)).toHex(true);
});
}
}
for (const vector of points.invalid.pointMultiply) {
const { P, d } = vector;
if (hexToNumber(d) < secp.CURVE.n) {
throws(() => {
const p = Point.fromHex(P);
p.multiply(hexToNumber(d)).toHex(true);
});
}
}
for (const num of [0n, 0, -1n, -1, 1.1]) {
throws(() => Point.BASE.multiply(num));
}
});
}); });
// multiply() should equal multiplyUnsafe() // multiply() should equal multiplyUnsafe()
@@ -175,262 +179,269 @@ describe('secp256k1', () => {
// console.log(p0.multiply(z)); // console.log(p0.multiply(z));
// console.log(secp.ProjectivePoint.normalizeZ([p0.multiplyUnsafe(z)])[0]) // 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('Signature.fromCompactHex() roundtrip', () => { should('.fromDERHex() roundtrip', () => {
fc.assert( fc.assert(
fc.property(FC_BIGINT, FC_BIGINT, (r, s) => { fc.property(FC_BIGINT, FC_BIGINT, (r, s) => {
const sig = new secp.Signature(r, s); const sig = new secp.Signature(r, s);
deepStrictEqual(secp.Signature.fromCompact(sig.toCompactHex()), sig); deepStrictEqual(secp.Signature.fromDER(sig.toDERHex()), sig);
}) })
); );
});
}); });
should('Signature.fromDERHex() roundtrip', () => { describe('sign()', () => {
fc.assert( should('create deterministic signatures with RFC 6979', () => {
fc.property(FC_BIGINT, FC_BIGINT, (r, s) => { for (const vector of ecdsa.valid) {
const sig = new secp.Signature(r, s); let usig = secp.sign(vector.m, vector.d);
deepStrictEqual(secp.Signature.fromDER(sig.toDERHex()), sig); let sig = usig.toCompactHex();
}) const vsig = vector.signature;
); deepStrictEqual(sig.slice(0, 64), vsig.slice(0, 64));
}); deepStrictEqual(sig.slice(64, 128), vsig.slice(64, 128));
}
});
should('sign()/should create deterministic signatures with RFC 6979', () => { should('not create invalid deterministic signatures with RFC 6979', () => {
for (const vector of ecdsa.valid) {
let usig = secp.sign(vector.m, vector.d);
let sig = usig.toCompactHex();
const vsig = vector.signature;
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',
() => {
for (const vector of ecdsa.invalid.sign) { for (const vector of ecdsa.invalid.sign) {
throws(() => secp.sign(vector.m, vector.d)); throws(() => secp.sign(vector.m, vector.d));
} }
} });
);
should('sign()/edge cases', () => { should('edge cases', () => {
throws(() => secp.sign()); throws(() => secp.sign());
throws(() => secp.sign('')); throws(() => secp.sign(''));
});
should('create correct DER encoding against libsecp256k1', () => {
const CASES = [
[
'd1a9dc8ed4e46a6a3e5e594615ca351d7d7ef44df1e4c94c1802f3592183794b',
'304402203de2559fccb00c148574997f660e4d6f40605acc71267ee38101abf15ff467af02200950abdf40628fd13f547792ba2fc544681a485f2fdafb5c3b909a4df7350e6b',
],
[
'5f97983254982546d3976d905c6165033976ee449d300d0e382099fa74deaf82',
'3045022100c046d9ff0bd2845b9aa9dff9f997ecebb31e52349f80fe5a5a869747d31dcb88022011f72be2a6d48fe716b825e4117747b397783df26914a58139c3f4c5cbb0e66c',
],
[
'0d7017a96b97cd9be21cf28aada639827b2814a654a478c81945857196187808',
'3045022100d18990bba7832bb283e3ecf8700b67beb39acc73f4200ed1c331247c46edccc602202e5c8bbfe47ae159512c583b30a3fa86575cddc62527a03de7756517ae4c6c73',
],
];
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);
}
});
should('handle {extraData} option', () => {
const ent1 = '0000000000000000000000000000000000000000000000000000000000000000';
const ent2 = '0000000000000000000000000000000000000000000000000000000000000001';
const ent3 = '6e723d3fd94ed5d2b6bdd4f123364b0f3ca52af829988a63f8afe91d29db1c33';
const ent4 = 'fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141';
const ent5 = 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff';
for (const e of ecdsa.extraEntropy) {
const sign = (extraEntropy) => {
const s = secp.sign(e.m, e.d, { extraEntropy }).toCompactHex();
return s;
};
deepStrictEqual(sign(), e.signature);
deepStrictEqual(sign(ent1), e.extraEntropy0);
deepStrictEqual(sign(ent2), e.extraEntropy1);
deepStrictEqual(sign(ent3), e.extraEntropyRand);
deepStrictEqual(sign(ent4), e.extraEntropyN);
deepStrictEqual(sign(ent5), e.extraEntropyMax);
}
});
}); });
should('sign()/should create correct DER encoding against libsecp256k1', () => { describe('verify()', () => {
const CASES = [ should('verify signature', () => {
[ const MSG = '01'.repeat(32);
'd1a9dc8ed4e46a6a3e5e594615ca351d7d7ef44df1e4c94c1802f3592183794b', const PRIV_KEY = 0x2n;
'304402203de2559fccb00c148574997f660e4d6f40605acc71267ee38101abf15ff467af02200950abdf40628fd13f547792ba2fc544681a485f2fdafb5c3b909a4df7350e6b', const signature = secp.sign(MSG, PRIV_KEY);
], const publicKey = secp.getPublicKey(PRIV_KEY);
[ deepStrictEqual(publicKey.length, 33);
'5f97983254982546d3976d905c6165033976ee449d300d0e382099fa74deaf82', deepStrictEqual(secp.verify(signature, MSG, publicKey), true);
'3045022100c046d9ff0bd2845b9aa9dff9f997ecebb31e52349f80fe5a5a869747d31dcb88022011f72be2a6d48fe716b825e4117747b397783df26914a58139c3f4c5cbb0e66c', });
], should(' not verify signature with wrong public key', () => {
[ const MSG = '01'.repeat(32);
'0d7017a96b97cd9be21cf28aada639827b2814a654a478c81945857196187808', const PRIV_KEY = '01'.repeat(32);
'3045022100d18990bba7832bb283e3ecf8700b67beb39acc73f4200ed1c331247c46edccc602202e5c8bbfe47ae159512c583b30a3fa86575cddc62527a03de7756517ae4c6c73', const WRONG_PRIV_KEY = '02'.repeat(32);
], const signature = secp.sign(MSG, PRIV_KEY);
]; const publicKey = Point.fromPrivateKey(WRONG_PRIV_KEY).toHex();
const privKey = hexToBytes('0101010101010101010101010101010101010101010101010101010101010101'); deepStrictEqual(publicKey.length, 66);
for (const [msg, exp] of CASES) { deepStrictEqual(secp.verify(signature, MSG, publicKey), false);
const res = secp.sign(msg, privKey, { extraEntropy: undefined }); });
deepStrictEqual(res.toDERHex(), exp); should('not verify signature with wrong hash', () => {
const rs = secp.Signature.fromDER(res.toDERHex()).toCompactHex(); const MSG = '01'.repeat(32);
deepStrictEqual(secp.Signature.fromCompact(rs).toDERHex(), exp); const PRIV_KEY = 0x2n;
} const WRONG_MSG = '11'.repeat(32);
}); const signature = secp.sign(MSG, PRIV_KEY);
should('sign()/sign ecdsa extraData', () => { const publicKey = secp.getPublicKey(PRIV_KEY);
const ent1 = '0000000000000000000000000000000000000000000000000000000000000000'; deepStrictEqual(publicKey.length, 33);
const ent2 = '0000000000000000000000000000000000000000000000000000000000000001'; deepStrictEqual(secp.verify(signature, WRONG_MSG, publicKey), false);
const ent3 = '6e723d3fd94ed5d2b6bdd4f123364b0f3ca52af829988a63f8afe91d29db1c33'; });
const ent4 = 'fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141'; should('verify random signatures', () =>
const ent5 = 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff'; fc.assert(
fc.property(FC_BIGINT, fc.hexaString({ minLength: 64, maxLength: 64 }), (privKey, msg) => {
const pub = secp.getPublicKey(privKey);
const sig = secp.sign(msg, privKey);
deepStrictEqual(secp.verify(sig, msg, pub), true);
})
)
);
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,
]);
const x = 100260381870027870612475458630405506840396644859280795015145920502443964769584n;
const y = 41096923727651821103518389640356553930186852801619204169823347832429067794568n;
const r = 1n;
const s = 115792089237316195423570985008687907852837564279074904382605163141518162728904n;
for (const e of ecdsa.extraEntropy) { const pub = new Point(x, y, 1n).toRawBytes();
const sign = (extraEntropy) => { const signature = new secp.Signature(2n, 2n);
const s = secp.sign(e.m, e.d, { extraEntropy }).toCompactHex(); signature.r = r;
return s; signature.s = s;
};
deepStrictEqual(sign(), e.signature);
deepStrictEqual(sign(ent1), e.extraEntropy0);
deepStrictEqual(sign(ent2), e.extraEntropy1);
deepStrictEqual(sign(ent3), e.extraEntropyRand);
deepStrictEqual(sign(ent4), e.extraEntropyN);
deepStrictEqual(sign(ent5), e.extraEntropyMax);
}
});
should('verify()/should verify signature', () => { const verified = secp.verify(signature, msg, pub);
const MSG = '01'.repeat(32); // Verifies, but it shouldn't, because signature S > curve order
const PRIV_KEY = 0x2n; deepStrictEqual(verified, false);
const signature = secp.sign(MSG, PRIV_KEY); });
const publicKey = secp.getPublicKey(PRIV_KEY); should('not verify msg = curve order', () => {
deepStrictEqual(publicKey.length, 65); const msg = 'fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141';
deepStrictEqual(secp.verify(signature, MSG, publicKey), true); const x = 55066263022277343669578718895168534326250603453777594175500187360389116729240n;
}); const y = 32670510020758816978083085130507043184471273380659243275938904335757337482424n;
should('verify()/should not verify signature with wrong public key', () => { const r = 104546003225722045112039007203142344920046999340768276760147352389092131869133n;
const MSG = '01'.repeat(32); const s = 96900796730960181123786672629079577025401317267213807243199432755332205217369n;
const PRIV_KEY = 0x2n; const pub = new Point(x, y, 1n).toRawBytes();
const WRONG_PRIV_KEY = 0x22n; const sig = new secp.Signature(r, s);
const signature = secp.sign(MSG, PRIV_KEY); deepStrictEqual(secp.verify(sig, msg, pub), false);
const publicKey = secp.Point.fromPrivateKey(WRONG_PRIV_KEY).toHex(); });
deepStrictEqual(publicKey.length, 130); should('verify non-strict msg bb5a...', () => {
deepStrictEqual(secp.verify(signature, MSG, publicKey), false); const msg = 'bb5a52f42f9c9261ed4361f59422a1e30036e7c32b270c8807a419feca605023';
}); const x = 3252872872578928810725465493269682203671229454553002637820453004368632726370n;
should('verify()/should not verify signature with wrong hash', () => { const y = 17482644437196207387910659778872952193236850502325156318830589868678978890912n;
const MSG = '01'.repeat(32); const r = 432420386565659656852420866390673177323n;
const PRIV_KEY = 0x2n; const s = 115792089237316195423570985008687907852837564279074904382605163141518161494334n;
const WRONG_MSG = '11'.repeat(32); const pub = new Point(x, y, 1n).toRawBytes();
const signature = secp.sign(MSG, PRIV_KEY); const sig = new secp.Signature(r, s);
const publicKey = secp.getPublicKey(PRIV_KEY); deepStrictEqual(secp.verify(sig, msg, pub, { strict: false }), true);
deepStrictEqual(publicKey.length, 65); });
deepStrictEqual(secp.verify(signature, WRONG_MSG, publicKey), false); should('not verify invalid deterministic signatures with RFC 6979', () => {
});
should('verify()/should verify random signatures', () =>
fc.assert(
fc.property(FC_BIGINT, fc.hexaString({ minLength: 64, maxLength: 64 }), (privKey, msg) => {
const pub = secp.getPublicKey(privKey);
const sig = secp.sign(msg, privKey);
deepStrictEqual(secp.verify(sig, msg, pub), true);
})
)
);
should('verify()/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,
]);
const x = 100260381870027870612475458630405506840396644859280795015145920502443964769584n;
const y = 41096923727651821103518389640356553930186852801619204169823347832429067794568n;
const r = 1n;
const s = 115792089237316195423570985008687907852837564279074904382605163141518162728904n;
const pub = new secp.Point(x, y);
const signature = new secp.Signature(2n, 2n);
signature.r = r;
signature.s = s;
const verified = secp.verify(signature, msg, pub);
// Verifies, but it shouldn't, because signature S > curve order
deepStrictEqual(verified, false);
});
should('verify()/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 sig = new secp.Signature(r, s);
deepStrictEqual(secp.verify(sig, msg, pub), false);
});
should('verify()/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 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',
() => {
for (const vector of ecdsa.invalid.verify) { for (const vector of ecdsa.invalid.verify) {
const res = secp.verify(vector.signature, vector.m, vector.Q); const res = secp.verify(vector.signature, vector.m, vector.Q);
deepStrictEqual(res, false); deepStrictEqual(res, false);
} }
} });
); });
// index,secret key,public key,aux_rand,message,signature,verification result,comment describe('schnorr.sign()', () => {
const vectors = schCsv // index,secret key,public key,aux_rand,message,signature,verification result,comment
.split('\n') const vectors = schCsv
.map((line) => line.split(',')) .split('\n')
.slice(1, -1); .map((line) => line.split(','))
for (let vec of vectors) { .slice(1, -1);
const [index, sec, pub, rnd, msg, expSig, passes, comment] = vec; for (let vec of vectors) {
should(`sign with Schnorr scheme vector ${index}`, () => { const [index, sec, pub, rnd, msg, expSig, passes, comment] = vec;
if (sec) { should(`${comment || 'vector ' + index}`, () => {
deepStrictEqual(hex(schnorr.getPublicKey(sec)), pub.toLowerCase()); if (sec) {
const sig = schnorr.sign(msg, sec, rnd); deepStrictEqual(hex(schnorr.getPublicKey(sec)), pub.toLowerCase());
deepStrictEqual(hex(sig), expSig.toLowerCase()); const sig = schnorr.sign(msg, sec, rnd);
deepStrictEqual(schnorr.verify(sig, msg, pub), true); deepStrictEqual(hex(sig), expSig.toLowerCase());
} else { deepStrictEqual(schnorr.verify(sig, msg, pub), true);
const passed = schnorr.verify(expSig, msg, pub); } else {
deepStrictEqual(passed, passes === 'TRUE'); const passed = schnorr.verify(expSig, msg, pub);
deepStrictEqual(passed, passes === 'TRUE');
}
});
}
});
describe('recoverPublicKey()', () => {
should('recover public key from recovery bit', () => {
const message = '00000000000000000000000000000000000000000000000000000000deadbeef';
const privateKey = 123456789n;
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(false), publicKey);
deepStrictEqual(secp.verify(sig, message, publicKey), true);
});
should('not recover zero points', () => {
const msgHash = '6b8d2c81b11b2d699528dde488dbdf2f94293d0d33c32e347f255fa4a6c1f0a9';
const sig =
'79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f817986b8d2c81b11b2d699528dde488dbdf2f94293d0d33c32e347f255fa4a6c1f0a9';
const recovery = 0;
throws(() => secp.recoverPublicKey(msgHash, sig, recovery));
});
should('handle all-zeros msghash', () => {
const privKey = secp.utils.randomPrivateKey();
const pub = secp.getPublicKey(privKey);
const zeros = '0000000000000000000000000000000000000000000000000000000000000000';
const sig = secp.sign(zeros, privKey);
const recoveredKey = sig.recoverPublicKey(zeros);
deepStrictEqual(recoveredKey.toRawBytes(), pub);
});
should('handle RFC 6979 vectors', () => {
for (const vector of ecdsa.valid) {
let usig = secp.sign(vector.m, vector.d);
let sig = usig.toDERHex();
const vpub = secp.getPublicKey(vector.d);
const recovered = usig.recoverPublicKey(vector.m);
deepStrictEqual(recovered.toHex(), hex(vpub));
} }
}); });
}
should('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 sig = secp.sign(message, privateKey);
const recoveredPubkey = sig.recoverPublicKey(message);
// const recoveredPubkey = secp.recoverPublicKey(message, signature, recovery);
deepStrictEqual(recoveredPubkey !== null, true);
deepStrictEqual(recoveredPubkey.toHex(false), publicKey);
deepStrictEqual(secp.verify(sig, message, publicKey), true);
});
should('recoverPublicKey()/should not recover zero points', () => {
const msgHash = '6b8d2c81b11b2d699528dde488dbdf2f94293d0d33c32e347f255fa4a6c1f0a9';
const sig =
'79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f817986b8d2c81b11b2d699528dde488dbdf2f94293d0d33c32e347f255fa4a6c1f0a9';
const recovery = 0;
throws(() => secp.recoverPublicKey(msgHash, sig, recovery));
});
should('recoverPublicKey()/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 recoveredKey = sig.recoverPublicKey(zeros);
deepStrictEqual(recoveredKey.toRawBytes(), pub);
});
should('recoverPublicKey()/should handle RFC 6979 vectors', () => {
for (const vector of ecdsa.valid) {
let usig = secp.sign(vector.m, vector.d);
let sig = usig.toDERHex();
const vpub = secp.getPublicKey(vector.d);
const recovered = usig.recoverPublicKey(vector.m);
deepStrictEqual(recovered.toHex(), hex(vpub));
}
}); });
// TODO: Real implementation. describe('getSharedSecret()', () => {
function derToPub(der) { // TODO: Real implementation.
return der.slice(46); function derToPub(der) {
} return der.slice(46);
should('getSharedSecret()/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) {
throws(() => {
secp.getSharedSecret(vector.private, derToPub(vector.public), true);
});
} else if (vector.result === 'valid') {
const res = secp.getSharedSecret(vector.private, derToPub(vector.public), true);
deepStrictEqual(hex(res.slice(1)), `${vector.shared}`);
}
} }
}); should('produce correct results', () => {
should('getSharedSecret()/priv/pub order matters', () => { // TODO: Once der is there, run all tests.
for (const vector of ecdh.testGroups[0].tests.slice(0, 100)) { for (const vector of ecdh.testGroups[0].tests.slice(0, 230)) {
if (vector.result === 'valid') { if (vector.result === 'invalid' || vector.private.length !== 64) {
let priv = vector.private; throws(() => {
priv = priv.length === 66 ? priv.slice(2) : priv; secp.getSharedSecret(vector.private, derToPub(vector.public), true);
throws(() => secp.getSharedSecret(derToPub(vector.public), priv, true)); });
} else if (vector.result === 'valid') {
const res = secp.getSharedSecret(vector.private, derToPub(vector.public), true);
deepStrictEqual(hex(res.slice(1)), `${vector.shared}`);
}
} }
} });
}); should('priv/pub order matters', () => {
should('getSharedSecret()/rejects invalid keys', () => { for (const vector of ecdh.testGroups[0].tests.slice(0, 100)) {
throws(() => secp.getSharedSecret('01', '02')); if (vector.result === 'valid') {
let priv = vector.private;
priv = priv.length === 66 ? priv.slice(2) : priv;
throws(() => secp.getSharedSecret(derToPub(vector.public), priv, true));
}
}
});
should('reject invalid keys', () => {
throws(() => secp.getSharedSecret('01', '02'));
});
}); });
should('utils.isValidPrivateKey()', () => { should('utils.isValidPrivateKey()', () => {
@@ -442,82 +453,77 @@ describe('secp256k1', () => {
should('have proper curve equation in assertValidity()', () => { should('have proper curve equation in assertValidity()', () => {
throws(() => { throws(() => {
const { Fp } = secp.CURVE; const { Fp } = secp.CURVE;
let point = new secp.Point(Fp.create(-2n), Fp.create(-1n)); let point = new Point(Fp.create(-2n), Fp.create(-1n), Fp.create(1n));
point.assertValidity(); point.assertValidity();
}); });
}); });
const Fn = Fp(secp.CURVE.n); describe('tweak utilities (legacy)', () => {
const normal = secp.utils._normalizePrivateKey; const Fn = Fp(secp.CURVE.n);
const tweakUtils = { const normal = secp.utils._normalizePrivateKey;
privateAdd: (privateKey, tweak) => { const tweakUtils = {
const p = normal(privateKey); privateAdd: (privateKey, tweak) => {
const t = normal(tweak); return numberToBytesBE(Fn.add(normal(privateKey), normal(tweak)), 32);
return secp.utils._bigintToBytes(Fn.create(p + t)); },
},
privateNegate: (privateKey) => { privateNegate: (privateKey) => {
const p = normal(privateKey); return numberToBytesBE(Fn.neg(normal(privateKey)), 32);
return secp.utils._bigintToBytes(Fn.negate(p)); },
},
pointAddScalar: (p, tweak, isCompressed) => { pointAddScalar: (p, tweak, isCompressed) => {
const P = secp.Point.fromHex(p); // Will throw if tweaked point is at infinity
const t = normal(tweak); return Point.fromHex(p).add(Point.fromPrivateKey(tweak)).toRawBytes(isCompressed);
const Q = secp.Point.BASE.multiplyAndAddUnsafe(P, t, 1n); },
if (!Q) throw new Error('Tweaked point at infinity');
return Q.toRawBytes(isCompressed);
},
pointMultiply: (p, tweak, isCompressed) => { pointMultiply: (p, tweak, isCompressed) => {
const P = secp.Point.fromHex(p); const t = bytesToNumberBE(ensureBytes(tweak));
const h = typeof tweak === 'string' ? tweak : bytesToHex(tweak); return Point.fromHex(p).multiply(t).toRawBytes(isCompressed);
const t = BigInt(`0x${h}`); },
return P.multiply(t).toRawBytes(isCompressed); };
},
};
should('privateAdd()', () => { should('privateAdd()', () => {
for (const vector of privates.valid.add) { for (const vector of privates.valid.add) {
const { a, b, expected } = vector; const { a, b, expected } = vector;
deepStrictEqual(bytesToHex(tweakUtils.privateAdd(a, b)), expected); deepStrictEqual(bytesToHex(tweakUtils.privateAdd(a, b)), expected);
} }
}); });
should('privateNegate()', () => { should('privateNegate()', () => {
for (const vector of privates.valid.negate) { for (const vector of privates.valid.negate) {
const { a, expected } = vector; const { a, expected } = vector;
deepStrictEqual(bytesToHex(tweakUtils.privateNegate(a)), expected); deepStrictEqual(bytesToHex(tweakUtils.privateNegate(a)), expected);
} }
}); });
should('pointAddScalar()', () => { should('pointAddScalar()', () => {
for (const vector of points.valid.pointAddScalar) { for (const vector of points.valid.pointAddScalar) {
const { description, P, d, expected } = vector; const { description, P, d, expected } = vector;
const compressed = !!expected && expected.length === 66; // compressed === 33 bytes const compressed = !!expected && expected.length === 66; // compressed === 33 bytes
deepStrictEqual(bytesToHex(tweakUtils.pointAddScalar(P, d, compressed)), expected); deepStrictEqual(bytesToHex(tweakUtils.pointAddScalar(P, d, compressed)), expected);
} }
}); });
should('pointAddScalar() invalid', () => { should('pointAddScalar() invalid', () => {
for (const vector of points.invalid.pointAddScalar) { for (const vector of points.invalid.pointAddScalar) {
const { P, d, exception } = vector; const { P, d, exception } = vector;
throws(() => tweakUtils.pointAddScalar(P, d)); throws(() => tweakUtils.pointAddScalar(P, d));
} }
}); });
should('pointMultiply()', () => { should('pointMultiply()', () => {
for (const vector of points.valid.pointMultiply) { for (const vector of points.valid.pointMultiply) {
const { P, d, expected } = vector; const { P, d, expected } = vector;
deepStrictEqual(bytesToHex(tweakUtils.pointMultiply(P, d, true)), expected); deepStrictEqual(bytesToHex(tweakUtils.pointMultiply(P, d, true)), expected);
} }
}); });
should('pointMultiply() invalid', () => { should('pointMultiply() invalid', () => {
for (const vector of points.invalid.pointMultiply) { for (const vector of points.invalid.pointMultiply) {
const { P, d, exception } = vector; const { P, d, exception } = vector;
throws(() => tweakUtils.pointMultiply(P, d)); throws(() => tweakUtils.pointMultiply(P, d));
} }
});
}); });
should('wychenproof vectors', () => { should('wycheproof vectors', () => {
for (let group of wp.testGroups) { for (let group of wp.testGroups) {
const pubKey = secp.Point.fromHex(group.key.uncompressed); // const pubKey = Point.fromHex().toRawBytes();
const pubKey = group.key.uncompressed;
for (let test of group.tests) { for (let test of group.tests) {
const m = secp.CURVE.hash(hexToBytes(test.msg)); const m = secp.CURVE.hash(hexToBytes(test.msg));
if (test.result === 'valid' || test.result === 'acceptable') { if (test.result === 'valid' || test.result === 'acceptable') {

View File

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

View File

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

114
test/stark/poseidon.test.js Normal file
View File

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

201
test/stark/poseidon/LICENSE Normal file
View File

@@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@@ -0,0 +1,35 @@
# StarkWare's Poseidon Hash
[Poseidon](https://www.poseidon-hash.info/) is a family of hash functions designed for being very efficient as algebraic circuits.
As such, they may be very useful in ZK proving systems such as STARKs and others.
This repository provides the official parameters of StarkWare's Poseidon hash implementations.
All the instances are over the prime field:
p = 2^251 + 17 * 2^192 + 1 = 3618502788666131213697322783095070105623107215331596699973092056135872020481
A few examples hash results with the different parameters:
```
Poseidon3([0,0,0]) = [3446325744004048536138401612021367625846492093718951375866996507163446763827,
1590252087433376791875644726012779423683501236913937337746052470473806035332,
867921192302518434283879514999422690776342565400001269945778456016268852423]
Poseidon4([0,0,0,0]) = [535071095200566880914603862188010633478042591441142518549720701573192347548,
3567335813488551850156302853280844225974867890860330236555401145692518003968,
229995103310401763929738317978722680640995513996113588430855556460153357543,
3513983790849716360905369754287999509206472929684378838050290392634812839312]
Poseidon5([0,0,0,0,0]) = [2337689130971531876049206831496963607805116499042700598724344149414565980684,
3230969295497815870174763682436655274044379544854667759151474216427142025631,
3297330512217530111610698859408044542971696143761201570393504997742535648562,
2585480844700786541432072704002477919020588246983274666988914431019064343941,
3595308260654382824623573767385493361624474708214823462901432822513585995028]
Poseidon9([0,0,0,0,0,0,0,0,0]) = [1534116856660032929112709488204491699743182428465681149262739677337223235050,
1710856073207389764546990138116985223517553616229641666885337928044617114700,
3165864635055638516987240200217592641540231237468651257819894959934472989427,
1003007637710164252047715558598366312649052908276423203724288341354608811559,
68117303579957054409211824649914588822081700129416361923518488718489651489,
1123395637839379807713801282868237406546107732595903195840754789810160564711,
478590974834311070537087181212389392308746075734019180430422247431982932503,
835322726024358888065061514739954009068852229059154336727219387089732433787,
3129703030204995742174502162918848446737407262178341733578946634564864233056]
```

View File

@@ -0,0 +1,462 @@
Rate = 2
Capacity = 1
FullRounds = 8
PartialRounds = 83
MDS = [[3, 1, 1], [1, -1, 1], [1, 1, -2]]
RoundKeys = [
[
2950795762459345168613727575620414179244544320470208355568817838579231751791,
1587446564224215276866294500450702039420286416111469274423465069420553242820,
1645965921169490687904413452218868659025437693527479459426157555728339600137,
],
[
2782373324549879794752287702905278018819686065818504085638398966973694145741,
3409172630025222641379726933524480516420204828329395644967085131392375707302,
2379053116496905638239090788901387719228422033660130943198035907032739387135,
],
[
2570819397480941104144008784293466051718826502582588529995520356691856497111,
3546220846133880637977653625763703334841539452343273304410918449202580719746,
2720682389492889709700489490056111332164748138023159726590726667539759963454,
],
[
1899653471897224903834726250400246354200311275092866725547887381599836519005,
2369443697923857319844855392163763375394720104106200469525915896159690979559,
2354174693689535854311272135513626412848402744119855553970180659094265527996,
],
[
2404084503073127963385083467393598147276436640877011103379112521338973185443,
950320777137731763811524327595514151340412860090489448295239456547370725376,
2121140748740143694053732746913428481442990369183417228688865837805149503386,
],
[
2372065044800422557577242066480215868569521938346032514014152523102053709709,
2618497439310693947058545060953893433487994458443568169824149550389484489896,
3518297267402065742048564133910509847197496119850246255805075095266319996916,
],
[
340529752683340505065238931581518232901634742162506851191464448040657139775,
1954876811294863748406056845662382214841467408616109501720437541211031966538,
813813157354633930267029888722341725864333883175521358739311868164460385261,
],
[
71901595776070443337150458310956362034911936706490730914901986556638720031,
2789761472166115462625363403490399263810962093264318361008954888847594113421,
2628791615374802560074754031104384456692791616314774034906110098358135152410,
],
[
3617032588734559635167557152518265808024917503198278888820567553943986939719,
2624012360209966117322788103333497793082705816015202046036057821340914061980,
149101987103211771991327927827692640556911620408176100290586418839323044234,
],
[
1039927963829140138166373450440320262590862908847727961488297105916489431045,
2213946951050724449162431068646025833746639391992751674082854766704900195669,
2792724903541814965769131737117981991997031078369482697195201969174353468597,
],
[
3212031629728871219804596347439383805499808476303618848198208101593976279441,
3343514080098703935339621028041191631325798327656683100151836206557453199613,
614054702436541219556958850933730254992710988573177298270089989048553060199,
],
[
148148081026449726283933484730968827750202042869875329032965774667206931170,
1158283532103191908366672518396366136968613180867652172211392033571980848414,
1032400527342371389481069504520755916075559110755235773196747439146396688513,
],
[
806900704622005851310078578853499250941978435851598088619290797134710613736,
462498083559902778091095573017508352472262817904991134671058825705968404510,
1003580119810278869589347418043095667699674425582646347949349245557449452503,
],
[
619074932220101074089137133998298830285661916867732916607601635248249357793,
2635090520059500019661864086615522409798872905401305311748231832709078452746,
978252636251682252755279071140187792306115352460774007308726210405257135181,
],
[
1766912167973123409669091967764158892111310474906691336473559256218048677083,
1663265127259512472182980890707014969235283233442916350121860684522654120381,
3532407621206959585000336211742670185380751515636605428496206887841428074250,
],
[
2507023127157093845256722098502856938353143387711652912931112668310034975446,
3321152907858462102434883844787153373036767230808678981306827073335525034593,
3039253036806065280643845548147711477270022154459620569428286684179698125661,
],
[
103480338868480851881924519768416587261556021758163719199282794248762465380,
2394049781357087698434751577708655768465803975478348134669006211289636928495,
2660531560345476340796109810821127229446538730404600368347902087220064379579,
],
[
3603166934034556203649050570865466556260359798872408576857928196141785055563,
1553799760191949768532188139643704561532896296986025007089826672890485412324,
2744284717053657689091306578463476341218866418732695211367062598446038965164,
],
[
320745764922149897598257794663594419839885234101078803811049904310835548856,
979382242100682161589753881721708883681034024104145498709287731138044566302,
1860426855810549882740147175136418997351054138609396651615467358416651354991,
],
[
336173081054369235994909356892506146234495707857220254489443629387613956145,
1632470326779699229772327605759783482411227247311431865655466227711078175883,
921958250077481394074960433988881176409497663777043304881055317463712938502,
],
[
3034358982193370602048539901033542101022185309652879937418114324899281842797,
25626282149517463867572353922222474817434101087272320606729439087234878607,
3002662261401575565838149305485737102400501329139562227180277188790091853682,
],
[
2939684373453383817196521641512509179310654199629514917426341354023324109367,
1076484609897998179434851570277297233169621096172424141759873688902355505136,
2575095284833160494841112025725243274091830284746697961080467506739203605049,
],
[
3565075264617591783581665711620369529657840830498005563542124551465195621851,
2197016502533303822395077038351174326125210255869204501838837289716363437993,
331415322883530754594261416546036195982886300052707474899691116664327869405,
],
[
1935011233711290003793244296594669823169522055520303479680359990463281661839,
3495901467168087413996941216661589517270845976538454329511167073314577412322,
954195417117133246453562983448451025087661597543338750600301835944144520375,
],
[
1271840477709992894995746871435810599280944810893784031132923384456797925777,
2565310762274337662754531859505158700827688964841878141121196528015826671847,
3365022288251637014588279139038152521653896670895105540140002607272936852513,
],
[
1660592021628965529963974299647026602622092163312666588591285654477111176051,
970104372286014048279296575474974982288801187216974504035759997141059513421,
2617024574317953753849168721871770134225690844968986289121504184985993971227,
],
[
999899815343607746071464113462778273556695659506865124478430189024755832262,
2228536129413411161615629030408828764980855956560026807518714080003644769896,
2701953891198001564547196795777701119629537795442025393867364730330476403227,
],
[
837078355588159388741598313782044128527494922918203556465116291436461597853,
2121749601840466143704862369657561429793951309962582099604848281796392359214,
771812260179247428733132708063116523892339056677915387749121983038690154755,
],
[
3317336423132806446086732225036532603224267214833263122557471741829060578219,
481570067997721834712647566896657604857788523050900222145547508314620762046,
242195042559343964206291740270858862066153636168162642380846129622127460192,
],
[
2855462178889999218204481481614105202770810647859867354506557827319138379686,
3525521107148375040131784770413887305850308357895464453970651672160034885202,
1320839531502392535964065058804908871811967681250362364246430459003920305799,
],
[
2514191518588387125173345107242226637171897291221681115249521904869763202419,
2798335750958827619666318316247381695117827718387653874070218127140615157902,
2808467767967035643407948058486565877867906577474361783201337540214875566395,
],
[
3551834385992706206273955480294669176699286104229279436819137165202231595747,
1219439673853113792340300173186247996249367102884530407862469123523013083971,
761519904537984520554247997444508040636526566551719396202550009393012691157,
],
[
3355402549169351700500518865338783382387571349497391475317206324155237401353,
199541098009731541347317515995192175813554789571447733944970283654592727138,
192100490643078165121235261796864975568292640203635147901612231594408079071,
],
[
1187019357602953326192019968809486933768550466167033084944727938441427050581,
189525349641911362389041124808934468936759383310282010671081989585219065700,
2831653363992091308880573627558515686245403755586311978724025292003353336665,
],
[
2052859812632218952608271535089179639890275494426396974475479657192657094698,
1670756178709659908159049531058853320846231785448204274277900022176591811072,
3538757242013734574731807289786598937548399719866320954894004830207085723125,
],
[
710549042741321081781917034337800036872214466705318638023070812391485261299,
2345013122330545298606028187653996682275206910242635100920038943391319595180,
3528369671971445493932880023233332035122954362711876290904323783426765912206,
],
[
1167120829038120978297497195837406760848728897181138760506162680655977700764,
3073243357129146594530765548901087443775563058893907738967898816092270628884,
378514724418106317738164464176041649567501099164061863402473942795977719726,
],
[
333391138410406330127594722511180398159664250722328578952158227406762627796,
1727570175639917398410201375510924114487348765559913502662122372848626931905,
968312190621809249603425066974405725769739606059422769908547372904403793174,
],
[
360659316299446405855194688051178331671817370423873014757323462844775818348,
1386580151907705298970465943238806620109618995410132218037375811184684929291,
3604888328937389309031638299660239238400230206645344173700074923133890528967,
],
[
2496185632263372962152518155651824899299616724241852816983268163379540137546,
486538168871046887467737983064272608432052269868418721234810979756540672990,
1558415498960552213241704009433360128041672577274390114589014204605400783336,
],
[
3512058327686147326577190314835092911156317204978509183234511559551181053926,
2235429387083113882635494090887463486491842634403047716936833563914243946191,
1290896777143878193192832813769470418518651727840187056683408155503813799882,
],
[
1143310336918357319571079551779316654556781203013096026972411429993634080835,
3235435208525081966062419599803346573407862428113723170955762956243193422118,
1293239921425673430660897025143433077974838969258268884994339615096356996604,
],
[
236252269127612784685426260840574970698541177557674806964960352572864382971,
1733907592497266237374827232200506798207318263912423249709509725341212026275,
302004309771755665128395814807589350526779835595021835389022325987048089868,
],
[
3018926838139221755384801385583867283206879023218491758435446265703006270945,
39701437664873825906031098349904330565195980985885489447836580931425171297,
908381723021746969965674308809436059628307487140174335882627549095646509778,
],
[
219062858908229855064136253265968615354041842047384625689776811853821594358,
1283129863776453589317845316917890202859466483456216900835390291449830275503,
418512623547417594896140369190919231877873410935689672661226540908900544012,
],
[
1792181590047131972851015200157890246436013346535432437041535789841136268632,
370546432987510607338044736824316856592558876687225326692366316978098770516,
3323437805230586112013581113386626899534419826098235300155664022709435756946,
],
[
910076621742039763058481476739499965761942516177975130656340375573185415877,
1762188042455633427137702520675816545396284185254002959309669405982213803405,
2186362253913140345102191078329764107619534641234549431429008219905315900520,
],
[
2230647725927681765419218738218528849146504088716182944327179019215826045083,
1069243907556644434301190076451112491469636357133398376850435321160857761825,
2695241469149243992683268025359863087303400907336026926662328156934068747593,
],
[
1361519681544413849831669554199151294308350560528931040264950307931824877035,
1339116632207878730171031743761550901312154740800549632983325427035029084904,
790593524918851401449292693473498591068920069246127392274811084156907468875,
],
[
2723400368331924254840192318398326090089058735091724263333980290765736363637,
3457180265095920471443772463283225391927927225993685928066766687141729456030,
1483675376954327086153452545475557749815683871577400883707749788555424847954,
],
[
2926303836265506736227240325795090239680154099205721426928300056982414025239,
543969119775473768170832347411484329362572550684421616624136244239799475526,
237401230683847084256617415614300816373730178313253487575312839074042461932,
],
[
844568412840391587862072008674263874021460074878949862892685736454654414423,
151922054871708336050647150237534498235916969120198637893731715254687336644,
1299332034710622815055321547569101119597030148120309411086203580212105652312,
],
[
487046922649899823989594814663418784068895385009696501386459462815688122993,
1104883249092599185744249485896585912845784382683240114120846423960548576851,
1458388705536282069567179348797334876446380557083422364875248475157495514484,
],
[
850248109622750774031817200193861444623975329881731864752464222442574976566,
2885843173858536690032695698009109793537724845140477446409245651176355435722,
3027068551635372249579348422266406787688980506275086097330568993357835463816,
],
[
3231892723647447539926175383213338123506134054432701323145045438168976970994,
1719080830641935421242626784132692936776388194122314954558418655725251172826,
1172253756541066126131022537343350498482225068791630219494878195815226839450,
],
[
1619232269633026603732619978083169293258272967781186544174521481891163985093,
3495680684841853175973173610562400042003100419811771341346135531754869014567,
1576161515913099892951745452471618612307857113799539794680346855318958552758,
],
[
2618326122974253423403350731396350223238201817594761152626832144510903048529,
2696245132758436974032479782852265185094623165224532063951287925001108567649,
930116505665110070247395429730201844026054810856263733273443066419816003444,
],
[
2786389174502246248523918824488629229455088716707062764363111940462137404076,
1555260846425735320214671887347115247546042526197895180675436886484523605116,
2306241912153325247392671742757902161446877415586158295423293240351799505917,
],
[
411529621724849932999694270803131456243889635467661223241617477462914950626,
1542495485262286701469125140275904136434075186064076910329015697714211835205,
1853045663799041100600825096887578544265580718909350942241802897995488264551,
],
[
2963055259497271220202739837493041799968576111953080503132045092194513937286,
2303806870349915764285872605046527036748108533406243381676768310692344456050,
2622104986201990620910286730213140904984256464479840856728424375142929278875,
],
[
2369987021925266811581727383184031736927816625797282287927222602539037105864,
285070227712021899602056480426671736057274017903028992288878116056674401781,
3034087076179360957800568733595959058628497428787907887933697691951454610691,
],
[
469095854351700119980323115747590868855368701825706298740201488006320881056,
360001976264385426746283365024817520563236378289230404095383746911725100012,
3438709327109021347267562000879503009590697221730578667498351600602230296178,
],
[
63573904800572228121671659287593650438456772568903228287754075619928214969,
3470881855042989871434874691030920672110111605547839662680968354703074556970,
724559311507950497340993415408274803001166693839947519425501269424891465492,
],
[
880409284677518997550768549487344416321062350742831373397603704465823658986,
6876255662475867703077362872097208259197756317287339941435193538565586230,
2701916445133770775447884812906226786217969545216086200932273680400909154638,
],
[
425152119158711585559310064242720816611629181537672850898056934507216982586,
1475552998258917706756737045704649573088377604240716286977690565239187213744,
2413772448122400684309006716414417978370152271397082147158000439863002593561,
],
[
392160855822256520519339260245328807036619920858503984710539815951012864164,
1075036996503791536261050742318169965707018400307026402939804424927087093987,
2176439430328703902070742432016450246365760303014562857296722712989275658921,
],
[
1413865976587623331051814207977382826721471106513581745229680113383908569693,
4879283427490523253696177116563427032332223531862961281430108575019551814,
3392583297537374046875199552977614390492290683707960975137418536812266544902,
],
[
3600854486849487646325182927019642276644093512133907046667282144129939150983,
2779924664161372134024229593301361846129279572186444474616319283535189797834,
2722699960903170449291146429799738181514821447014433304730310678334403972040,
],
[
819109815049226540285781191874507704729062681836086010078910930707209464699,
3046121243742768013822760785918001632929744274211027071381357122228091333823,
1339019590803056172509793134119156250729668216522001157582155155947567682278,
],
[
1933279639657506214789316403763326578443023901555983256955812717638093967201,
2138221547112520744699126051903811860205771600821672121643894708182292213541,
2694713515543641924097704224170357995809887124438248292930846280951601597065,
],
[
2471734202930133750093618989223585244499567111661178960753938272334153710615,
504903761112092757611047718215309856203214372330635774577409639907729993533,
1943979703748281357156510253941035712048221353507135074336243405478613241290,
],
[
684525210957572142559049112233609445802004614280157992196913315652663518936,
1705585400798782397786453706717059483604368413512485532079242223503960814508,
192429517716023021556170942988476050278432319516032402725586427701913624665,
],
[
1586493702243128040549584165333371192888583026298039652930372758731750166765,
686072673323546915014972146032384917012218151266600268450347114036285993377,
3464340397998075738891129996710075228740496767934137465519455338004332839215,
],
[
2805249176617071054530589390406083958753103601524808155663551392362371834663,
667746464250968521164727418691487653339733392025160477655836902744186489526,
1131527712905109997177270289411406385352032457456054589588342450404257139778,
],
[
1908969485750011212309284349900149072003218505891252313183123635318886241171,
1025257076985551890132050019084873267454083056307650830147063480409707787695,
2153175291918371429502545470578981828372846236838301412119329786849737957977,
],
[
3410257749736714576487217882785226905621212230027780855361670645857085424384,
3442969106887588154491488961893254739289120695377621434680934888062399029952,
3029953900235731770255937704976720759948880815387104275525268727341390470237,
],
[
85453456084781138713939104192561924536933417707871501802199311333127894466,
2730629666577257820220329078741301754580009106438115341296453318350676425129,
178242450661072967256438102630920745430303027840919213764087927763335940415,
],
[
2844589222514708695700541363167856718216388819406388706818431442998498677557,
3547876269219141094308889387292091231377253967587961309624916269569559952944,
2525005406762984211707203144785482908331876505006839217175334833739957826850,
],
[
3096397013555211396701910432830904669391580557191845136003938801598654871345,
574424067119200181933992948252007230348512600107123873197603373898923821490,
1714030696055067278349157346067719307863507310709155690164546226450579547098,
],
[
2339895272202694698739231405357972261413383527237194045718815176814132612501,
3562501318971895161271663840954705079797767042115717360959659475564651685069,
69069358687197963617161747606993436483967992689488259107924379545671193749,
],
[
2614502738369008850475068874731531583863538486212691941619835266611116051561,
655247349763023251625727726218660142895322325659927266813592114640858573566,
2305235672527595714255517865498269719545193172975330668070873705108690670678,
],
[
926416070297755413261159098243058134401665060349723804040714357642180531931,
866523735635840246543516964237513287099659681479228450791071595433217821460,
2284334068466681424919271582037156124891004191915573957556691163266198707693,
],
[
1812588309302477291425732810913354633465435706480768615104211305579383928792,
2836899808619013605432050476764608707770404125005720004551836441247917488507,
2989087789022865112405242078196235025698647423649950459911546051695688370523,
],
[
68056284404189102136488263779598243992465747932368669388126367131855404486,
505425339250887519581119854377342241317528319745596963584548343662758204398,
2118963546856545068961709089296976921067035227488975882615462246481055679215,
],
[
2253872596319969096156004495313034590996995209785432485705134570745135149681,
1625090409149943603241183848936692198923183279116014478406452426158572703264,
179139838844452470348634657368199622305888473747024389514258107503778442495,
],
[
1567067018147735642071130442904093290030432522257811793540290101391210410341,
2737301854006865242314806979738760349397411136469975337509958305470398783585,
3002738216460904473515791428798860225499078134627026021350799206894618186256,
],
[
374029488099466837453096950537275565120689146401077127482884887409712315162,
973403256517481077805460710540468856199855789930951602150773500862180885363,
2691967457038172130555117632010860984519926022632800605713473799739632878867,
],
[
3515906794910381201365530594248181418811879320679684239326734893975752012109,
148057579455448384062325089530558091463206199724854022070244924642222283388,
1541588700238272710315890873051237741033408846596322948443180470429851502842,
],
[
147013865879011936545137344076637170977925826031496203944786839068852795297,
2630278389304735265620281704608245039972003761509102213752997636382302839857,
1359048670759642844930007747955701205155822111403150159614453244477853867621,
],
[
2438984569205812336319229336885480537793786558293523767186829418969842616677,
2137792255841525507649318539501906353254503076308308692873313199435029594138,
2262318076430740712267739371170174514379142884859595360065535117601097652755,
],
[
2792703718581084537295613508201818489836796608902614779596544185252826291584,
2294173715793292812015960640392421991604150133581218254866878921346561546149,
2770011224727997178743274791849308200493823127651418989170761007078565678171,
],
]

View File

@@ -0,0 +1,559 @@
Rate = 3
Capacity = 1
FullRounds = 8
PartialRounds = 84
MDS = [[2, 1, 1, 1], [1, 1, 1, 1], [1, 1, 0, 1], [1, 1, 1, -1]]
RoundKeys = [
[
2950795762459345168613727575620414179244544320470208355568817838579231751791,
1587446564224215276866294500450702039420286416111469274423465069420553242820,
1645965921169490687904413452218868659025437693527479459426157555728339600137,
2782373324549879794752287702905278018819686065818504085638398966973694145741,
],
[
3409172630025222641379726933524480516420204828329395644967085131392375707302,
2379053116496905638239090788901387719228422033660130943198035907032739387135,
2570819397480941104144008784293466051718826502582588529995520356691856497111,
3546220846133880637977653625763703334841539452343273304410918449202580719746,
],
[
2720682389492889709700489490056111332164748138023159726590726667539759963454,
1899653471897224903834726250400246354200311275092866725547887381599836519005,
2369443697923857319844855392163763375394720104106200469525915896159690979559,
2354174693689535854311272135513626412848402744119855553970180659094265527996,
],
[
2404084503073127963385083467393598147276436640877011103379112521338973185443,
950320777137731763811524327595514151340412860090489448295239456547370725376,
2121140748740143694053732746913428481442990369183417228688865837805149503386,
2372065044800422557577242066480215868569521938346032514014152523102053709709,
],
[
2618497439310693947058545060953893433487994458443568169824149550389484489896,
3518297267402065742048564133910509847197496119850246255805075095266319996916,
340529752683340505065238931581518232901634742162506851191464448040657139775,
1954876811294863748406056845662382214841467408616109501720437541211031966538,
],
[
813813157354633930267029888722341725864333883175521358739311868164460385261,
71901595776070443337150458310956362034911936706490730914901986556638720031,
2789761472166115462625363403490399263810962093264318361008954888847594113421,
2628791615374802560074754031104384456692791616314774034906110098358135152410,
],
[
3617032588734559635167557152518265808024917503198278888820567553943986939719,
2624012360209966117322788103333497793082705816015202046036057821340914061980,
149101987103211771991327927827692640556911620408176100290586418839323044234,
1039927963829140138166373450440320262590862908847727961488297105916489431045,
],
[
2213946951050724449162431068646025833746639391992751674082854766704900195669,
2792724903541814965769131737117981991997031078369482697195201969174353468597,
3212031629728871219804596347439383805499808476303618848198208101593976279441,
3343514080098703935339621028041191631325798327656683100151836206557453199613,
],
[
614054702436541219556958850933730254992710988573177298270089989048553060199,
148148081026449726283933484730968827750202042869875329032965774667206931170,
1158283532103191908366672518396366136968613180867652172211392033571980848414,
1032400527342371389481069504520755916075559110755235773196747439146396688513,
],
[
806900704622005851310078578853499250941978435851598088619290797134710613736,
462498083559902778091095573017508352472262817904991134671058825705968404510,
1003580119810278869589347418043095667699674425582646347949349245557449452503,
619074932220101074089137133998298830285661916867732916607601635248249357793,
],
[
2635090520059500019661864086615522409798872905401305311748231832709078452746,
978252636251682252755279071140187792306115352460774007308726210405257135181,
1766912167973123409669091967764158892111310474906691336473559256218048677083,
1663265127259512472182980890707014969235283233442916350121860684522654120381,
],
[
3532407621206959585000336211742670185380751515636605428496206887841428074250,
2507023127157093845256722098502856938353143387711652912931112668310034975446,
3321152907858462102434883844787153373036767230808678981306827073335525034593,
3039253036806065280643845548147711477270022154459620569428286684179698125661,
],
[
103480338868480851881924519768416587261556021758163719199282794248762465380,
2394049781357087698434751577708655768465803975478348134669006211289636928495,
2660531560345476340796109810821127229446538730404600368347902087220064379579,
3603166934034556203649050570865466556260359798872408576857928196141785055563,
],
[
1553799760191949768532188139643704561532896296986025007089826672890485412324,
2744284717053657689091306578463476341218866418732695211367062598446038965164,
320745764922149897598257794663594419839885234101078803811049904310835548856,
979382242100682161589753881721708883681034024104145498709287731138044566302,
],
[
1860426855810549882740147175136418997351054138609396651615467358416651354991,
336173081054369235994909356892506146234495707857220254489443629387613956145,
1632470326779699229772327605759783482411227247311431865655466227711078175883,
921958250077481394074960433988881176409497663777043304881055317463712938502,
],
[
3034358982193370602048539901033542101022185309652879937418114324899281842797,
25626282149517463867572353922222474817434101087272320606729439087234878607,
3002662261401575565838149305485737102400501329139562227180277188790091853682,
2939684373453383817196521641512509179310654199629514917426341354023324109367,
],
[
1076484609897998179434851570277297233169621096172424141759873688902355505136,
2575095284833160494841112025725243274091830284746697961080467506739203605049,
3565075264617591783581665711620369529657840830498005563542124551465195621851,
2197016502533303822395077038351174326125210255869204501838837289716363437993,
],
[
331415322883530754594261416546036195982886300052707474899691116664327869405,
1935011233711290003793244296594669823169522055520303479680359990463281661839,
3495901467168087413996941216661589517270845976538454329511167073314577412322,
954195417117133246453562983448451025087661597543338750600301835944144520375,
],
[
1271840477709992894995746871435810599280944810893784031132923384456797925777,
2565310762274337662754531859505158700827688964841878141121196528015826671847,
3365022288251637014588279139038152521653896670895105540140002607272936852513,
1660592021628965529963974299647026602622092163312666588591285654477111176051,
],
[
970104372286014048279296575474974982288801187216974504035759997141059513421,
2617024574317953753849168721871770134225690844968986289121504184985993971227,
999899815343607746071464113462778273556695659506865124478430189024755832262,
2228536129413411161615629030408828764980855956560026807518714080003644769896,
],
[
2701953891198001564547196795777701119629537795442025393867364730330476403227,
837078355588159388741598313782044128527494922918203556465116291436461597853,
2121749601840466143704862369657561429793951309962582099604848281796392359214,
771812260179247428733132708063116523892339056677915387749121983038690154755,
],
[
3317336423132806446086732225036532603224267214833263122557471741829060578219,
481570067997721834712647566896657604857788523050900222145547508314620762046,
242195042559343964206291740270858862066153636168162642380846129622127460192,
2855462178889999218204481481614105202770810647859867354506557827319138379686,
],
[
3525521107148375040131784770413887305850308357895464453970651672160034885202,
1320839531502392535964065058804908871811967681250362364246430459003920305799,
2514191518588387125173345107242226637171897291221681115249521904869763202419,
2798335750958827619666318316247381695117827718387653874070218127140615157902,
],
[
2808467767967035643407948058486565877867906577474361783201337540214875566395,
3551834385992706206273955480294669176699286104229279436819137165202231595747,
1219439673853113792340300173186247996249367102884530407862469123523013083971,
761519904537984520554247997444508040636526566551719396202550009393012691157,
],
[
3355402549169351700500518865338783382387571349497391475317206324155237401353,
199541098009731541347317515995192175813554789571447733944970283654592727138,
192100490643078165121235261796864975568292640203635147901612231594408079071,
1187019357602953326192019968809486933768550466167033084944727938441427050581,
],
[
189525349641911362389041124808934468936759383310282010671081989585219065700,
2831653363992091308880573627558515686245403755586311978724025292003353336665,
2052859812632218952608271535089179639890275494426396974475479657192657094698,
1670756178709659908159049531058853320846231785448204274277900022176591811072,
],
[
3538757242013734574731807289786598937548399719866320954894004830207085723125,
710549042741321081781917034337800036872214466705318638023070812391485261299,
2345013122330545298606028187653996682275206910242635100920038943391319595180,
3528369671971445493932880023233332035122954362711876290904323783426765912206,
],
[
1167120829038120978297497195837406760848728897181138760506162680655977700764,
3073243357129146594530765548901087443775563058893907738967898816092270628884,
378514724418106317738164464176041649567501099164061863402473942795977719726,
333391138410406330127594722511180398159664250722328578952158227406762627796,
],
[
1727570175639917398410201375510924114487348765559913502662122372848626931905,
968312190621809249603425066974405725769739606059422769908547372904403793174,
360659316299446405855194688051178331671817370423873014757323462844775818348,
1386580151907705298970465943238806620109618995410132218037375811184684929291,
],
[
3604888328937389309031638299660239238400230206645344173700074923133890528967,
2496185632263372962152518155651824899299616724241852816983268163379540137546,
486538168871046887467737983064272608432052269868418721234810979756540672990,
1558415498960552213241704009433360128041672577274390114589014204605400783336,
],
[
3512058327686147326577190314835092911156317204978509183234511559551181053926,
2235429387083113882635494090887463486491842634403047716936833563914243946191,
1290896777143878193192832813769470418518651727840187056683408155503813799882,
1143310336918357319571079551779316654556781203013096026972411429993634080835,
],
[
3235435208525081966062419599803346573407862428113723170955762956243193422118,
1293239921425673430660897025143433077974838969258268884994339615096356996604,
236252269127612784685426260840574970698541177557674806964960352572864382971,
1733907592497266237374827232200506798207318263912423249709509725341212026275,
],
[
302004309771755665128395814807589350526779835595021835389022325987048089868,
3018926838139221755384801385583867283206879023218491758435446265703006270945,
39701437664873825906031098349904330565195980985885489447836580931425171297,
908381723021746969965674308809436059628307487140174335882627549095646509778,
],
[
219062858908229855064136253265968615354041842047384625689776811853821594358,
1283129863776453589317845316917890202859466483456216900835390291449830275503,
418512623547417594896140369190919231877873410935689672661226540908900544012,
1792181590047131972851015200157890246436013346535432437041535789841136268632,
],
[
370546432987510607338044736824316856592558876687225326692366316978098770516,
3323437805230586112013581113386626899534419826098235300155664022709435756946,
910076621742039763058481476739499965761942516177975130656340375573185415877,
1762188042455633427137702520675816545396284185254002959309669405982213803405,
],
[
2186362253913140345102191078329764107619534641234549431429008219905315900520,
2230647725927681765419218738218528849146504088716182944327179019215826045083,
1069243907556644434301190076451112491469636357133398376850435321160857761825,
2695241469149243992683268025359863087303400907336026926662328156934068747593,
],
[
1361519681544413849831669554199151294308350560528931040264950307931824877035,
1339116632207878730171031743761550901312154740800549632983325427035029084904,
790593524918851401449292693473498591068920069246127392274811084156907468875,
2723400368331924254840192318398326090089058735091724263333980290765736363637,
],
[
3457180265095920471443772463283225391927927225993685928066766687141729456030,
1483675376954327086153452545475557749815683871577400883707749788555424847954,
2926303836265506736227240325795090239680154099205721426928300056982414025239,
543969119775473768170832347411484329362572550684421616624136244239799475526,
],
[
237401230683847084256617415614300816373730178313253487575312839074042461932,
844568412840391587862072008674263874021460074878949862892685736454654414423,
151922054871708336050647150237534498235916969120198637893731715254687336644,
1299332034710622815055321547569101119597030148120309411086203580212105652312,
],
[
487046922649899823989594814663418784068895385009696501386459462815688122993,
1104883249092599185744249485896585912845784382683240114120846423960548576851,
1458388705536282069567179348797334876446380557083422364875248475157495514484,
850248109622750774031817200193861444623975329881731864752464222442574976566,
],
[
2885843173858536690032695698009109793537724845140477446409245651176355435722,
3027068551635372249579348422266406787688980506275086097330568993357835463816,
3231892723647447539926175383213338123506134054432701323145045438168976970994,
1719080830641935421242626784132692936776388194122314954558418655725251172826,
],
[
1172253756541066126131022537343350498482225068791630219494878195815226839450,
1619232269633026603732619978083169293258272967781186544174521481891163985093,
3495680684841853175973173610562400042003100419811771341346135531754869014567,
1576161515913099892951745452471618612307857113799539794680346855318958552758,
],
[
2618326122974253423403350731396350223238201817594761152626832144510903048529,
2696245132758436974032479782852265185094623165224532063951287925001108567649,
930116505665110070247395429730201844026054810856263733273443066419816003444,
2786389174502246248523918824488629229455088716707062764363111940462137404076,
],
[
1555260846425735320214671887347115247546042526197895180675436886484523605116,
2306241912153325247392671742757902161446877415586158295423293240351799505917,
411529621724849932999694270803131456243889635467661223241617477462914950626,
1542495485262286701469125140275904136434075186064076910329015697714211835205,
],
[
1853045663799041100600825096887578544265580718909350942241802897995488264551,
2963055259497271220202739837493041799968576111953080503132045092194513937286,
2303806870349915764285872605046527036748108533406243381676768310692344456050,
2622104986201990620910286730213140904984256464479840856728424375142929278875,
],
[
2369987021925266811581727383184031736927816625797282287927222602539037105864,
285070227712021899602056480426671736057274017903028992288878116056674401781,
3034087076179360957800568733595959058628497428787907887933697691951454610691,
469095854351700119980323115747590868855368701825706298740201488006320881056,
],
[
360001976264385426746283365024817520563236378289230404095383746911725100012,
3438709327109021347267562000879503009590697221730578667498351600602230296178,
63573904800572228121671659287593650438456772568903228287754075619928214969,
3470881855042989871434874691030920672110111605547839662680968354703074556970,
],
[
724559311507950497340993415408274803001166693839947519425501269424891465492,
880409284677518997550768549487344416321062350742831373397603704465823658986,
6876255662475867703077362872097208259197756317287339941435193538565586230,
2701916445133770775447884812906226786217969545216086200932273680400909154638,
],
[
425152119158711585559310064242720816611629181537672850898056934507216982586,
1475552998258917706756737045704649573088377604240716286977690565239187213744,
2413772448122400684309006716414417978370152271397082147158000439863002593561,
392160855822256520519339260245328807036619920858503984710539815951012864164,
],
[
1075036996503791536261050742318169965707018400307026402939804424927087093987,
2176439430328703902070742432016450246365760303014562857296722712989275658921,
1413865976587623331051814207977382826721471106513581745229680113383908569693,
4879283427490523253696177116563427032332223531862961281430108575019551814,
],
[
3392583297537374046875199552977614390492290683707960975137418536812266544902,
3600854486849487646325182927019642276644093512133907046667282144129939150983,
2779924664161372134024229593301361846129279572186444474616319283535189797834,
2722699960903170449291146429799738181514821447014433304730310678334403972040,
],
[
819109815049226540285781191874507704729062681836086010078910930707209464699,
3046121243742768013822760785918001632929744274211027071381357122228091333823,
1339019590803056172509793134119156250729668216522001157582155155947567682278,
1933279639657506214789316403763326578443023901555983256955812717638093967201,
],
[
2138221547112520744699126051903811860205771600821672121643894708182292213541,
2694713515543641924097704224170357995809887124438248292930846280951601597065,
2471734202930133750093618989223585244499567111661178960753938272334153710615,
504903761112092757611047718215309856203214372330635774577409639907729993533,
],
[
1943979703748281357156510253941035712048221353507135074336243405478613241290,
684525210957572142559049112233609445802004614280157992196913315652663518936,
1705585400798782397786453706717059483604368413512485532079242223503960814508,
192429517716023021556170942988476050278432319516032402725586427701913624665,
],
[
1586493702243128040549584165333371192888583026298039652930372758731750166765,
686072673323546915014972146032384917012218151266600268450347114036285993377,
3464340397998075738891129996710075228740496767934137465519455338004332839215,
2805249176617071054530589390406083958753103601524808155663551392362371834663,
],
[
667746464250968521164727418691487653339733392025160477655836902744186489526,
1131527712905109997177270289411406385352032457456054589588342450404257139778,
1908969485750011212309284349900149072003218505891252313183123635318886241171,
1025257076985551890132050019084873267454083056307650830147063480409707787695,
],
[
2153175291918371429502545470578981828372846236838301412119329786849737957977,
3410257749736714576487217882785226905621212230027780855361670645857085424384,
3442969106887588154491488961893254739289120695377621434680934888062399029952,
3029953900235731770255937704976720759948880815387104275525268727341390470237,
],
[
85453456084781138713939104192561924536933417707871501802199311333127894466,
2730629666577257820220329078741301754580009106438115341296453318350676425129,
178242450661072967256438102630920745430303027840919213764087927763335940415,
2844589222514708695700541363167856718216388819406388706818431442998498677557,
],
[
3547876269219141094308889387292091231377253967587961309624916269569559952944,
2525005406762984211707203144785482908331876505006839217175334833739957826850,
3096397013555211396701910432830904669391580557191845136003938801598654871345,
574424067119200181933992948252007230348512600107123873197603373898923821490,
],
[
1714030696055067278349157346067719307863507310709155690164546226450579547098,
2339895272202694698739231405357972261413383527237194045718815176814132612501,
3562501318971895161271663840954705079797767042115717360959659475564651685069,
69069358687197963617161747606993436483967992689488259107924379545671193749,
],
[
2614502738369008850475068874731531583863538486212691941619835266611116051561,
655247349763023251625727726218660142895322325659927266813592114640858573566,
2305235672527595714255517865498269719545193172975330668070873705108690670678,
926416070297755413261159098243058134401665060349723804040714357642180531931,
],
[
866523735635840246543516964237513287099659681479228450791071595433217821460,
2284334068466681424919271582037156124891004191915573957556691163266198707693,
1812588309302477291425732810913354633465435706480768615104211305579383928792,
2836899808619013605432050476764608707770404125005720004551836441247917488507,
],
[
2989087789022865112405242078196235025698647423649950459911546051695688370523,
68056284404189102136488263779598243992465747932368669388126367131855404486,
505425339250887519581119854377342241317528319745596963584548343662758204398,
2118963546856545068961709089296976921067035227488975882615462246481055679215,
],
[
2253872596319969096156004495313034590996995209785432485705134570745135149681,
1625090409149943603241183848936692198923183279116014478406452426158572703264,
179139838844452470348634657368199622305888473747024389514258107503778442495,
1567067018147735642071130442904093290030432522257811793540290101391210410341,
],
[
2737301854006865242314806979738760349397411136469975337509958305470398783585,
3002738216460904473515791428798860225499078134627026021350799206894618186256,
374029488099466837453096950537275565120689146401077127482884887409712315162,
973403256517481077805460710540468856199855789930951602150773500862180885363,
],
[
2691967457038172130555117632010860984519926022632800605713473799739632878867,
3515906794910381201365530594248181418811879320679684239326734893975752012109,
148057579455448384062325089530558091463206199724854022070244924642222283388,
1541588700238272710315890873051237741033408846596322948443180470429851502842,
],
[
147013865879011936545137344076637170977925826031496203944786839068852795297,
2630278389304735265620281704608245039972003761509102213752997636382302839857,
1359048670759642844930007747955701205155822111403150159614453244477853867621,
2438984569205812336319229336885480537793786558293523767186829418969842616677,
],
[
2137792255841525507649318539501906353254503076308308692873313199435029594138,
2262318076430740712267739371170174514379142884859595360065535117601097652755,
2792703718581084537295613508201818489836796608902614779596544185252826291584,
2294173715793292812015960640392421991604150133581218254866878921346561546149,
],
[
2770011224727997178743274791849308200493823127651418989170761007078565678171,
3321642244537785916275181932172303118112488081726311374164578600576901819844,
3522708517589950573320671158134804505970724681591943826922697952040487655044,
3417974441436557992524691506735790206623600049454586729879955931972546347402,
],
[
175039333145381316571259690443853067809784261609912638686739799135919647022,
1930713062131033273316869231148248962041053029742760224583505092759642967464,
2971452932574554603554350185069538580257636405419430340233233400633251319042,
2774781903758215341037075610938953949868289195845367046186588750871862784919,
],
[
666516874137869653699423537799457099346460194092311952417454613224504932738,
1900462225013533249140457703727169176351786259991305560412832202759625668041,
2665631186082687279121709429531834469477679375137509769347092380798929714377,
837840745988147279235494664091280091563355097569199320366973125128366540061,
],
[
3391544118305848781823721719916289805455110924839794510205940718821197620955,
2888553035909938253628892138500390690221493345071933642853222968481016605919,
3386241569867597612447901482685846444743718781330869478721963580925825915450,
1205126220630896984850042596877918177217334376800874965105642874206963597698,
],
[
3590072615491710252422997155203204584659171612188004116415640739580250394190,
692469013329617220154003334549812915100479873898898958632988703738125356983,
1623178235190707102808841905143937367808788834203621005714003335195182126335,
1972826180775011489122426045504602288576507493792470102803637471568052321297,
],
[
3415141329098504418158191749675997877417539760075593313736376750580696083073,
587811537889727046473915684463981273175495137461951211739526104349163747811,
2523982964351069134084525951849317400231659428055762640605248929856135518199,
2686176526711834950207666047281383173339057216783586039351834948812568447629,
],
[
983144446441739425577690449774542566745526459152966545642451764143532586964,
171558252019175695567663688494555626159399786667979998273792882504784080805,
332337623010057542760158225837623039780806442976079546879646069338600179518,
1264669683963885571544813806669118319675288608634733888843804451222546848295,
],
[
2426165115815723668018318268486497504249785449504758403912155206515511627681,
11387399609384288947733630450855186629703576293221897150193655994854764608,
2541728569046079092074077754414781968906176513081761690404588216304985421091,
47685947554980329431290582269851186106577733250761848107645535357326439312,
],
[
472176388418187405374813530639596064799362505024895746833751173199773896628,
2764298116617383397920343358525617195195060562266243809245480210157942112738,
486863835068754002670800862273365477867695879270721744227071001883208334054,
2973492686137102577527656941792991264994301121122130295965761350095846874635,
],
[
178385615141132702906181473263873416748818415607305319148067639744074654009,
533624640096756667052211553746016402543259206286603356120804827761339634127,
819406716720171922688026098737835227857400444543748198788964759773510472096,
531851793767260921861217458033110066464894334064526987603936107947006031387,
],
[
3269709072483585277009748181134917746036523619604017812342933951952104134829,
838191718603413598040249006803464503100808192944407407147899973659013630611,
1574561296941310904780257598780779812250055948216417844567262310524022037406,
551394354289003977607664358739006072556227894953233419144430578080352094737,
],
[
445076790942318675726839050057337819004979443030540904213920669247413907302,
1963946696292687224902912968478695543164747600779913024040878700455222386521,
484284614181963381509745298932402076252103342403432879800905151752488144767,
2240507606126946994415203252302826782042951346966859379502140796364876543253,
],
[
3237135638753992982179886898758938279897590886053928839613434762582576319619,
2334333034701915027889533058426879447140084891006486138782876488162658230991,
14411091399844539897439754491034751977136685514851444574462584316609631592,
1264480371592407258420308876448697804787923638319277097663041109464608464284,
],
[
671929312763821646360589403212798993954209530574443543917757335777610372144,
2513909805455654095962542944994577107405216428214873444765576238504714067396,
870121102846043786263357605823753628974859886859187558617096145653709171231,
399132620893316356411986266679786708905730068946836982293484206366500277754,
],
[
2855046250836680633532995284655778407402587437073106249445470889390454667586,
2063679741125384345396981490971605710211281905716315529671473143278849561151,
1433753212258929925682201698758056443128516570551146995210728194816988328337,
3334984763425011856632257855270507440816274246647423607159847074739331865077,
],
[
337911293622078184850923533628334646725451591671907148383867096651211846605,
559587005295238702015018022040357402231957131094636365177008701077975941644,
885963059604819264377490633589388189646118257469490919900554134369512794660,
1957748763518471091057032383332840331641373304981058387824598000170709016333,
],
[
3175295982155056798972302481564899381103533409383494814704562889625572018450,
498987160612401618114584726510347771865331516606886613019084323862447372555,
947374835104260364630171441676101001841507588423166778786886198914150312958,
906933977754491302438795274167251538820934378773708095543613756654712689280,
],
[
2170116291766863179909957030577284618726490893598499117272497866180009722894,
1801335399574515889082584621772588704763181408217893911806726119813067220453,
1942500232535842474530840356353427989892065499159260166135596750084681859966,
62936080219825306823124060587235998278756755377419521154040408253893795176,
],
[
3091993939935137795359769774909373279950941171574748645375255810204590357753,
1283528386884634267663661033944552098742115012555712906773586466375284501324,
1581820717639229420476069802992937438655873471854930764425841549067913106065,
2301986095388751633126546121528329200085681648876910655269533407603441046514,
],
[
2850003828037698751961753862613545302539465803982364898225617297398939302949,
48024691078494936445046366770271288984930221238071705874025261821606393528,
1482336297033144958942154923925185950152551534403871620222916667536030875354,
3081177564717719643771186007689458633949181485535169123213511264603782950049,
],
[
3315701127039521853279746297714590495201061397709680410650043502532250578075,
3514407611000441301995070394422463400067690470546731164089622325748803106020,
368970178199930154322724953487299516224498421233447528815195701420122548537,
584353160413525267849669053228533951552602295860601556035386665117717227391,
],
[
752038702160385294706011538400822066722189014251268673051846350397729870418,
3594041683498798688197194521326299097635429790757880308152971477196489335154,
1367902435756906062215608264424138718742854099315395230911274560900857414183,
1828549068951502746189364466794037234789986878381694857475972053743463890779,
],
[
488172495141237210878388657234137733008417573114482400652274985829148564248,
962906242461930394022372340919543491337923491322497419797555620396501785566,
2275418085010046236619290386129138234541669589549771944697082317642065048898,
1966395064658902622886154686288219600816893261614483533899715888994623208964,
],
[
3496095878293416917311185659829821476802828534554531050412634978086916288609,
3368478822390537245916137403277928093536087427951052230723275731232142463388,
3397410259276620127103231993277518800970669191016277541098821699302368873803,
2662600899665871010006649609856695263727220473364611552472965243032255906029,
],
]

View File

@@ -0,0 +1,651 @@
Rate = 4
Capacity = 1
FullRounds = 8
PartialRounds = 84
MDS = [[3, 1, 1, 1, 1], [1, 2, 1, 1, 1], [1, 1, 1, 1, 1], [1, 1, 1, -1, 1], [1, 1, 1, 1, -2]]
RoundKeys = [
[
2950795762459345168613727575620414179244544320470208355568817838579231751791,
1587446564224215276866294500450702039420286416111469274423465069420553242820,
1645965921169490687904413452218868659025437693527479459426157555728339600137,
2782373324549879794752287702905278018819686065818504085638398966973694145741,
3409172630025222641379726933524480516420204828329395644967085131392375707302,
],
[
2379053116496905638239090788901387719228422033660130943198035907032739387135,
2570819397480941104144008784293466051718826502582588529995520356691856497111,
3546220846133880637977653625763703334841539452343273304410918449202580719746,
2720682389492889709700489490056111332164748138023159726590726667539759963454,
1899653471897224903834726250400246354200311275092866725547887381599836519005,
],
[
2369443697923857319844855392163763375394720104106200469525915896159690979559,
2354174693689535854311272135513626412848402744119855553970180659094265527996,
2404084503073127963385083467393598147276436640877011103379112521338973185443,
950320777137731763811524327595514151340412860090489448295239456547370725376,
2121140748740143694053732746913428481442990369183417228688865837805149503386,
],
[
2372065044800422557577242066480215868569521938346032514014152523102053709709,
2618497439310693947058545060953893433487994458443568169824149550389484489896,
3518297267402065742048564133910509847197496119850246255805075095266319996916,
340529752683340505065238931581518232901634742162506851191464448040657139775,
1954876811294863748406056845662382214841467408616109501720437541211031966538,
],
[
813813157354633930267029888722341725864333883175521358739311868164460385261,
71901595776070443337150458310956362034911936706490730914901986556638720031,
2789761472166115462625363403490399263810962093264318361008954888847594113421,
2628791615374802560074754031104384456692791616314774034906110098358135152410,
3617032588734559635167557152518265808024917503198278888820567553943986939719,
],
[
2624012360209966117322788103333497793082705816015202046036057821340914061980,
149101987103211771991327927827692640556911620408176100290586418839323044234,
1039927963829140138166373450440320262590862908847727961488297105916489431045,
2213946951050724449162431068646025833746639391992751674082854766704900195669,
2792724903541814965769131737117981991997031078369482697195201969174353468597,
],
[
3212031629728871219804596347439383805499808476303618848198208101593976279441,
3343514080098703935339621028041191631325798327656683100151836206557453199613,
614054702436541219556958850933730254992710988573177298270089989048553060199,
148148081026449726283933484730968827750202042869875329032965774667206931170,
1158283532103191908366672518396366136968613180867652172211392033571980848414,
],
[
1032400527342371389481069504520755916075559110755235773196747439146396688513,
806900704622005851310078578853499250941978435851598088619290797134710613736,
462498083559902778091095573017508352472262817904991134671058825705968404510,
1003580119810278869589347418043095667699674425582646347949349245557449452503,
619074932220101074089137133998298830285661916867732916607601635248249357793,
],
[
2635090520059500019661864086615522409798872905401305311748231832709078452746,
978252636251682252755279071140187792306115352460774007308726210405257135181,
1766912167973123409669091967764158892111310474906691336473559256218048677083,
1663265127259512472182980890707014969235283233442916350121860684522654120381,
3532407621206959585000336211742670185380751515636605428496206887841428074250,
],
[
2507023127157093845256722098502856938353143387711652912931112668310034975446,
3321152907858462102434883844787153373036767230808678981306827073335525034593,
3039253036806065280643845548147711477270022154459620569428286684179698125661,
103480338868480851881924519768416587261556021758163719199282794248762465380,
2394049781357087698434751577708655768465803975478348134669006211289636928495,
],
[
2660531560345476340796109810821127229446538730404600368347902087220064379579,
3603166934034556203649050570865466556260359798872408576857928196141785055563,
1553799760191949768532188139643704561532896296986025007089826672890485412324,
2744284717053657689091306578463476341218866418732695211367062598446038965164,
320745764922149897598257794663594419839885234101078803811049904310835548856,
],
[
979382242100682161589753881721708883681034024104145498709287731138044566302,
1860426855810549882740147175136418997351054138609396651615467358416651354991,
336173081054369235994909356892506146234495707857220254489443629387613956145,
1632470326779699229772327605759783482411227247311431865655466227711078175883,
921958250077481394074960433988881176409497663777043304881055317463712938502,
],
[
3034358982193370602048539901033542101022185309652879937418114324899281842797,
25626282149517463867572353922222474817434101087272320606729439087234878607,
3002662261401575565838149305485737102400501329139562227180277188790091853682,
2939684373453383817196521641512509179310654199629514917426341354023324109367,
1076484609897998179434851570277297233169621096172424141759873688902355505136,
],
[
2575095284833160494841112025725243274091830284746697961080467506739203605049,
3565075264617591783581665711620369529657840830498005563542124551465195621851,
2197016502533303822395077038351174326125210255869204501838837289716363437993,
331415322883530754594261416546036195982886300052707474899691116664327869405,
1935011233711290003793244296594669823169522055520303479680359990463281661839,
],
[
3495901467168087413996941216661589517270845976538454329511167073314577412322,
954195417117133246453562983448451025087661597543338750600301835944144520375,
1271840477709992894995746871435810599280944810893784031132923384456797925777,
2565310762274337662754531859505158700827688964841878141121196528015826671847,
3365022288251637014588279139038152521653896670895105540140002607272936852513,
],
[
1660592021628965529963974299647026602622092163312666588591285654477111176051,
970104372286014048279296575474974982288801187216974504035759997141059513421,
2617024574317953753849168721871770134225690844968986289121504184985993971227,
999899815343607746071464113462778273556695659506865124478430189024755832262,
2228536129413411161615629030408828764980855956560026807518714080003644769896,
],
[
2701953891198001564547196795777701119629537795442025393867364730330476403227,
837078355588159388741598313782044128527494922918203556465116291436461597853,
2121749601840466143704862369657561429793951309962582099604848281796392359214,
771812260179247428733132708063116523892339056677915387749121983038690154755,
3317336423132806446086732225036532603224267214833263122557471741829060578219,
],
[
481570067997721834712647566896657604857788523050900222145547508314620762046,
242195042559343964206291740270858862066153636168162642380846129622127460192,
2855462178889999218204481481614105202770810647859867354506557827319138379686,
3525521107148375040131784770413887305850308357895464453970651672160034885202,
1320839531502392535964065058804908871811967681250362364246430459003920305799,
],
[
2514191518588387125173345107242226637171897291221681115249521904869763202419,
2798335750958827619666318316247381695117827718387653874070218127140615157902,
2808467767967035643407948058486565877867906577474361783201337540214875566395,
3551834385992706206273955480294669176699286104229279436819137165202231595747,
1219439673853113792340300173186247996249367102884530407862469123523013083971,
],
[
761519904537984520554247997444508040636526566551719396202550009393012691157,
3355402549169351700500518865338783382387571349497391475317206324155237401353,
199541098009731541347317515995192175813554789571447733944970283654592727138,
192100490643078165121235261796864975568292640203635147901612231594408079071,
1187019357602953326192019968809486933768550466167033084944727938441427050581,
],
[
189525349641911362389041124808934468936759383310282010671081989585219065700,
2831653363992091308880573627558515686245403755586311978724025292003353336665,
2052859812632218952608271535089179639890275494426396974475479657192657094698,
1670756178709659908159049531058853320846231785448204274277900022176591811072,
3538757242013734574731807289786598937548399719866320954894004830207085723125,
],
[
710549042741321081781917034337800036872214466705318638023070812391485261299,
2345013122330545298606028187653996682275206910242635100920038943391319595180,
3528369671971445493932880023233332035122954362711876290904323783426765912206,
1167120829038120978297497195837406760848728897181138760506162680655977700764,
3073243357129146594530765548901087443775563058893907738967898816092270628884,
],
[
378514724418106317738164464176041649567501099164061863402473942795977719726,
333391138410406330127594722511180398159664250722328578952158227406762627796,
1727570175639917398410201375510924114487348765559913502662122372848626931905,
968312190621809249603425066974405725769739606059422769908547372904403793174,
360659316299446405855194688051178331671817370423873014757323462844775818348,
],
[
1386580151907705298970465943238806620109618995410132218037375811184684929291,
3604888328937389309031638299660239238400230206645344173700074923133890528967,
2496185632263372962152518155651824899299616724241852816983268163379540137546,
486538168871046887467737983064272608432052269868418721234810979756540672990,
1558415498960552213241704009433360128041672577274390114589014204605400783336,
],
[
3512058327686147326577190314835092911156317204978509183234511559551181053926,
2235429387083113882635494090887463486491842634403047716936833563914243946191,
1290896777143878193192832813769470418518651727840187056683408155503813799882,
1143310336918357319571079551779316654556781203013096026972411429993634080835,
3235435208525081966062419599803346573407862428113723170955762956243193422118,
],
[
1293239921425673430660897025143433077974838969258268884994339615096356996604,
236252269127612784685426260840574970698541177557674806964960352572864382971,
1733907592497266237374827232200506798207318263912423249709509725341212026275,
302004309771755665128395814807589350526779835595021835389022325987048089868,
3018926838139221755384801385583867283206879023218491758435446265703006270945,
],
[
39701437664873825906031098349904330565195980985885489447836580931425171297,
908381723021746969965674308809436059628307487140174335882627549095646509778,
219062858908229855064136253265968615354041842047384625689776811853821594358,
1283129863776453589317845316917890202859466483456216900835390291449830275503,
418512623547417594896140369190919231877873410935689672661226540908900544012,
],
[
1792181590047131972851015200157890246436013346535432437041535789841136268632,
370546432987510607338044736824316856592558876687225326692366316978098770516,
3323437805230586112013581113386626899534419826098235300155664022709435756946,
910076621742039763058481476739499965761942516177975130656340375573185415877,
1762188042455633427137702520675816545396284185254002959309669405982213803405,
],
[
2186362253913140345102191078329764107619534641234549431429008219905315900520,
2230647725927681765419218738218528849146504088716182944327179019215826045083,
1069243907556644434301190076451112491469636357133398376850435321160857761825,
2695241469149243992683268025359863087303400907336026926662328156934068747593,
1361519681544413849831669554199151294308350560528931040264950307931824877035,
],
[
1339116632207878730171031743761550901312154740800549632983325427035029084904,
790593524918851401449292693473498591068920069246127392274811084156907468875,
2723400368331924254840192318398326090089058735091724263333980290765736363637,
3457180265095920471443772463283225391927927225993685928066766687141729456030,
1483675376954327086153452545475557749815683871577400883707749788555424847954,
],
[
2926303836265506736227240325795090239680154099205721426928300056982414025239,
543969119775473768170832347411484329362572550684421616624136244239799475526,
237401230683847084256617415614300816373730178313253487575312839074042461932,
844568412840391587862072008674263874021460074878949862892685736454654414423,
151922054871708336050647150237534498235916969120198637893731715254687336644,
],
[
1299332034710622815055321547569101119597030148120309411086203580212105652312,
487046922649899823989594814663418784068895385009696501386459462815688122993,
1104883249092599185744249485896585912845784382683240114120846423960548576851,
1458388705536282069567179348797334876446380557083422364875248475157495514484,
850248109622750774031817200193861444623975329881731864752464222442574976566,
],
[
2885843173858536690032695698009109793537724845140477446409245651176355435722,
3027068551635372249579348422266406787688980506275086097330568993357835463816,
3231892723647447539926175383213338123506134054432701323145045438168976970994,
1719080830641935421242626784132692936776388194122314954558418655725251172826,
1172253756541066126131022537343350498482225068791630219494878195815226839450,
],
[
1619232269633026603732619978083169293258272967781186544174521481891163985093,
3495680684841853175973173610562400042003100419811771341346135531754869014567,
1576161515913099892951745452471618612307857113799539794680346855318958552758,
2618326122974253423403350731396350223238201817594761152626832144510903048529,
2696245132758436974032479782852265185094623165224532063951287925001108567649,
],
[
930116505665110070247395429730201844026054810856263733273443066419816003444,
2786389174502246248523918824488629229455088716707062764363111940462137404076,
1555260846425735320214671887347115247546042526197895180675436886484523605116,
2306241912153325247392671742757902161446877415586158295423293240351799505917,
411529621724849932999694270803131456243889635467661223241617477462914950626,
],
[
1542495485262286701469125140275904136434075186064076910329015697714211835205,
1853045663799041100600825096887578544265580718909350942241802897995488264551,
2963055259497271220202739837493041799968576111953080503132045092194513937286,
2303806870349915764285872605046527036748108533406243381676768310692344456050,
2622104986201990620910286730213140904984256464479840856728424375142929278875,
],
[
2369987021925266811581727383184031736927816625797282287927222602539037105864,
285070227712021899602056480426671736057274017903028992288878116056674401781,
3034087076179360957800568733595959058628497428787907887933697691951454610691,
469095854351700119980323115747590868855368701825706298740201488006320881056,
360001976264385426746283365024817520563236378289230404095383746911725100012,
],
[
3438709327109021347267562000879503009590697221730578667498351600602230296178,
63573904800572228121671659287593650438456772568903228287754075619928214969,
3470881855042989871434874691030920672110111605547839662680968354703074556970,
724559311507950497340993415408274803001166693839947519425501269424891465492,
880409284677518997550768549487344416321062350742831373397603704465823658986,
],
[
6876255662475867703077362872097208259197756317287339941435193538565586230,
2701916445133770775447884812906226786217969545216086200932273680400909154638,
425152119158711585559310064242720816611629181537672850898056934507216982586,
1475552998258917706756737045704649573088377604240716286977690565239187213744,
2413772448122400684309006716414417978370152271397082147158000439863002593561,
],
[
392160855822256520519339260245328807036619920858503984710539815951012864164,
1075036996503791536261050742318169965707018400307026402939804424927087093987,
2176439430328703902070742432016450246365760303014562857296722712989275658921,
1413865976587623331051814207977382826721471106513581745229680113383908569693,
4879283427490523253696177116563427032332223531862961281430108575019551814,
],
[
3392583297537374046875199552977614390492290683707960975137418536812266544902,
3600854486849487646325182927019642276644093512133907046667282144129939150983,
2779924664161372134024229593301361846129279572186444474616319283535189797834,
2722699960903170449291146429799738181514821447014433304730310678334403972040,
819109815049226540285781191874507704729062681836086010078910930707209464699,
],
[
3046121243742768013822760785918001632929744274211027071381357122228091333823,
1339019590803056172509793134119156250729668216522001157582155155947567682278,
1933279639657506214789316403763326578443023901555983256955812717638093967201,
2138221547112520744699126051903811860205771600821672121643894708182292213541,
2694713515543641924097704224170357995809887124438248292930846280951601597065,
],
[
2471734202930133750093618989223585244499567111661178960753938272334153710615,
504903761112092757611047718215309856203214372330635774577409639907729993533,
1943979703748281357156510253941035712048221353507135074336243405478613241290,
684525210957572142559049112233609445802004614280157992196913315652663518936,
1705585400798782397786453706717059483604368413512485532079242223503960814508,
],
[
192429517716023021556170942988476050278432319516032402725586427701913624665,
1586493702243128040549584165333371192888583026298039652930372758731750166765,
686072673323546915014972146032384917012218151266600268450347114036285993377,
3464340397998075738891129996710075228740496767934137465519455338004332839215,
2805249176617071054530589390406083958753103601524808155663551392362371834663,
],
[
667746464250968521164727418691487653339733392025160477655836902744186489526,
1131527712905109997177270289411406385352032457456054589588342450404257139778,
1908969485750011212309284349900149072003218505891252313183123635318886241171,
1025257076985551890132050019084873267454083056307650830147063480409707787695,
2153175291918371429502545470578981828372846236838301412119329786849737957977,
],
[
3410257749736714576487217882785226905621212230027780855361670645857085424384,
3442969106887588154491488961893254739289120695377621434680934888062399029952,
3029953900235731770255937704976720759948880815387104275525268727341390470237,
85453456084781138713939104192561924536933417707871501802199311333127894466,
2730629666577257820220329078741301754580009106438115341296453318350676425129,
],
[
178242450661072967256438102630920745430303027840919213764087927763335940415,
2844589222514708695700541363167856718216388819406388706818431442998498677557,
3547876269219141094308889387292091231377253967587961309624916269569559952944,
2525005406762984211707203144785482908331876505006839217175334833739957826850,
3096397013555211396701910432830904669391580557191845136003938801598654871345,
],
[
574424067119200181933992948252007230348512600107123873197603373898923821490,
1714030696055067278349157346067719307863507310709155690164546226450579547098,
2339895272202694698739231405357972261413383527237194045718815176814132612501,
3562501318971895161271663840954705079797767042115717360959659475564651685069,
69069358687197963617161747606993436483967992689488259107924379545671193749,
],
[
2614502738369008850475068874731531583863538486212691941619835266611116051561,
655247349763023251625727726218660142895322325659927266813592114640858573566,
2305235672527595714255517865498269719545193172975330668070873705108690670678,
926416070297755413261159098243058134401665060349723804040714357642180531931,
866523735635840246543516964237513287099659681479228450791071595433217821460,
],
[
2284334068466681424919271582037156124891004191915573957556691163266198707693,
1812588309302477291425732810913354633465435706480768615104211305579383928792,
2836899808619013605432050476764608707770404125005720004551836441247917488507,
2989087789022865112405242078196235025698647423649950459911546051695688370523,
68056284404189102136488263779598243992465747932368669388126367131855404486,
],
[
505425339250887519581119854377342241317528319745596963584548343662758204398,
2118963546856545068961709089296976921067035227488975882615462246481055679215,
2253872596319969096156004495313034590996995209785432485705134570745135149681,
1625090409149943603241183848936692198923183279116014478406452426158572703264,
179139838844452470348634657368199622305888473747024389514258107503778442495,
],
[
1567067018147735642071130442904093290030432522257811793540290101391210410341,
2737301854006865242314806979738760349397411136469975337509958305470398783585,
3002738216460904473515791428798860225499078134627026021350799206894618186256,
374029488099466837453096950537275565120689146401077127482884887409712315162,
973403256517481077805460710540468856199855789930951602150773500862180885363,
],
[
2691967457038172130555117632010860984519926022632800605713473799739632878867,
3515906794910381201365530594248181418811879320679684239326734893975752012109,
148057579455448384062325089530558091463206199724854022070244924642222283388,
1541588700238272710315890873051237741033408846596322948443180470429851502842,
147013865879011936545137344076637170977925826031496203944786839068852795297,
],
[
2630278389304735265620281704608245039972003761509102213752997636382302839857,
1359048670759642844930007747955701205155822111403150159614453244477853867621,
2438984569205812336319229336885480537793786558293523767186829418969842616677,
2137792255841525507649318539501906353254503076308308692873313199435029594138,
2262318076430740712267739371170174514379142884859595360065535117601097652755,
],
[
2792703718581084537295613508201818489836796608902614779596544185252826291584,
2294173715793292812015960640392421991604150133581218254866878921346561546149,
2770011224727997178743274791849308200493823127651418989170761007078565678171,
3321642244537785916275181932172303118112488081726311374164578600576901819844,
3522708517589950573320671158134804505970724681591943826922697952040487655044,
],
[
3417974441436557992524691506735790206623600049454586729879955931972546347402,
175039333145381316571259690443853067809784261609912638686739799135919647022,
1930713062131033273316869231148248962041053029742760224583505092759642967464,
2971452932574554603554350185069538580257636405419430340233233400633251319042,
2774781903758215341037075610938953949868289195845367046186588750871862784919,
],
[
666516874137869653699423537799457099346460194092311952417454613224504932738,
1900462225013533249140457703727169176351786259991305560412832202759625668041,
2665631186082687279121709429531834469477679375137509769347092380798929714377,
837840745988147279235494664091280091563355097569199320366973125128366540061,
3391544118305848781823721719916289805455110924839794510205940718821197620955,
],
[
2888553035909938253628892138500390690221493345071933642853222968481016605919,
3386241569867597612447901482685846444743718781330869478721963580925825915450,
1205126220630896984850042596877918177217334376800874965105642874206963597698,
3590072615491710252422997155203204584659171612188004116415640739580250394190,
692469013329617220154003334549812915100479873898898958632988703738125356983,
],
[
1623178235190707102808841905143937367808788834203621005714003335195182126335,
1972826180775011489122426045504602288576507493792470102803637471568052321297,
3415141329098504418158191749675997877417539760075593313736376750580696083073,
587811537889727046473915684463981273175495137461951211739526104349163747811,
2523982964351069134084525951849317400231659428055762640605248929856135518199,
],
[
2686176526711834950207666047281383173339057216783586039351834948812568447629,
983144446441739425577690449774542566745526459152966545642451764143532586964,
171558252019175695567663688494555626159399786667979998273792882504784080805,
332337623010057542760158225837623039780806442976079546879646069338600179518,
1264669683963885571544813806669118319675288608634733888843804451222546848295,
],
[
2426165115815723668018318268486497504249785449504758403912155206515511627681,
11387399609384288947733630450855186629703576293221897150193655994854764608,
2541728569046079092074077754414781968906176513081761690404588216304985421091,
47685947554980329431290582269851186106577733250761848107645535357326439312,
472176388418187405374813530639596064799362505024895746833751173199773896628,
],
[
2764298116617383397920343358525617195195060562266243809245480210157942112738,
486863835068754002670800862273365477867695879270721744227071001883208334054,
2973492686137102577527656941792991264994301121122130295965761350095846874635,
178385615141132702906181473263873416748818415607305319148067639744074654009,
533624640096756667052211553746016402543259206286603356120804827761339634127,
],
[
819406716720171922688026098737835227857400444543748198788964759773510472096,
531851793767260921861217458033110066464894334064526987603936107947006031387,
3269709072483585277009748181134917746036523619604017812342933951952104134829,
838191718603413598040249006803464503100808192944407407147899973659013630611,
1574561296941310904780257598780779812250055948216417844567262310524022037406,
],
[
551394354289003977607664358739006072556227894953233419144430578080352094737,
445076790942318675726839050057337819004979443030540904213920669247413907302,
1963946696292687224902912968478695543164747600779913024040878700455222386521,
484284614181963381509745298932402076252103342403432879800905151752488144767,
2240507606126946994415203252302826782042951346966859379502140796364876543253,
],
[
3237135638753992982179886898758938279897590886053928839613434762582576319619,
2334333034701915027889533058426879447140084891006486138782876488162658230991,
14411091399844539897439754491034751977136685514851444574462584316609631592,
1264480371592407258420308876448697804787923638319277097663041109464608464284,
671929312763821646360589403212798993954209530574443543917757335777610372144,
],
[
2513909805455654095962542944994577107405216428214873444765576238504714067396,
870121102846043786263357605823753628974859886859187558617096145653709171231,
399132620893316356411986266679786708905730068946836982293484206366500277754,
2855046250836680633532995284655778407402587437073106249445470889390454667586,
2063679741125384345396981490971605710211281905716315529671473143278849561151,
],
[
1433753212258929925682201698758056443128516570551146995210728194816988328337,
3334984763425011856632257855270507440816274246647423607159847074739331865077,
337911293622078184850923533628334646725451591671907148383867096651211846605,
559587005295238702015018022040357402231957131094636365177008701077975941644,
885963059604819264377490633589388189646118257469490919900554134369512794660,
],
[
1957748763518471091057032383332840331641373304981058387824598000170709016333,
3175295982155056798972302481564899381103533409383494814704562889625572018450,
498987160612401618114584726510347771865331516606886613019084323862447372555,
947374835104260364630171441676101001841507588423166778786886198914150312958,
906933977754491302438795274167251538820934378773708095543613756654712689280,
],
[
2170116291766863179909957030577284618726490893598499117272497866180009722894,
1801335399574515889082584621772588704763181408217893911806726119813067220453,
1942500232535842474530840356353427989892065499159260166135596750084681859966,
62936080219825306823124060587235998278756755377419521154040408253893795176,
3091993939935137795359769774909373279950941171574748645375255810204590357753,
],
[
1283528386884634267663661033944552098742115012555712906773586466375284501324,
1581820717639229420476069802992937438655873471854930764425841549067913106065,
2301986095388751633126546121528329200085681648876910655269533407603441046514,
2850003828037698751961753862613545302539465803982364898225617297398939302949,
48024691078494936445046366770271288984930221238071705874025261821606393528,
],
[
1482336297033144958942154923925185950152551534403871620222916667536030875354,
3081177564717719643771186007689458633949181485535169123213511264603782950049,
3315701127039521853279746297714590495201061397709680410650043502532250578075,
3514407611000441301995070394422463400067690470546731164089622325748803106020,
368970178199930154322724953487299516224498421233447528815195701420122548537,
],
[
584353160413525267849669053228533951552602295860601556035386665117717227391,
752038702160385294706011538400822066722189014251268673051846350397729870418,
3594041683498798688197194521326299097635429790757880308152971477196489335154,
1367902435756906062215608264424138718742854099315395230911274560900857414183,
1828549068951502746189364466794037234789986878381694857475972053743463890779,
],
[
488172495141237210878388657234137733008417573114482400652274985829148564248,
962906242461930394022372340919543491337923491322497419797555620396501785566,
2275418085010046236619290386129138234541669589549771944697082317642065048898,
1966395064658902622886154686288219600816893261614483533899715888994623208964,
3496095878293416917311185659829821476802828534554531050412634978086916288609,
],
[
3368478822390537245916137403277928093536087427951052230723275731232142463388,
3397410259276620127103231993277518800970669191016277541098821699302368873803,
2662600899665871010006649609856695263727220473364611552472965243032255906029,
2854831720595596992200155718152374313555878203864206470581502555480894633975,
2417859092561967752135741161218626374900182454089059862468108240576782064037,
],
[
1064506915903089299531724594973601253341866933071158266140674053459433520889,
243845138053687262800349059300355289745206315347524675450796070948867090098,
1952653154963756062322124110012629666160000286707762177032475477295929736283,
2760979128531476595658428672038276216079708408852493051222686009638650156041,
3341178930260137001230946104398194306290005446746057811731360203227371301716,
],
[
1033242545866274439991875444609632860132556714736615395036273942261573810479,
3567973410830779135148598005871071456943945697865168835204985462698751038238,
23014034649293369426970379738102323014738017168969687350330825050016457105,
1146720508452451012445869043641390200263192255569203352823376998708972325392,
2553707028642376593497768606567528232999203496079990242456254686325586089356,
],
[
269729857648436699208023125596593246149228245518586029792966091405383426269,
276912682886955358118649215147238115764108757952690361549816619060658800027,
2367180947887796341722261610916728725977893583923967218630363334645641817362,
2398694802751362950028137620758033447242325333923222365760836442417755445092,
984868389243025029364428136317275892280780834039611841422502834917752411391,
],
[
861353329558771468244040268521983016756775808329676883407171471251365927595,
2498672969617384807617108262141800974986393948110233099680635130601163654234,
1336236634145657673540555267430353130305889434115514586892320600753700983325,
980337801407886250576371882962628290239239581416378379141354256717803603922,
2308558359523317875952657835109605515063994805873180719205156915762120497245,
],
[
2116737905426837141304542819940293184404010538896700217242374222514653607487,
2143995283326808680518644927890182524580312777400009071739277407358043120199,
3038758768133404431511594054950351369492648883179154555267474054094234927849,
981824005865625678985009911415023115269386212492064371040001594972137748141,
2427990511715778580869565219059895697855813782250850855111162965998948386792,
],
[
1987498156785173719076522405088076990979859292718600184358583152317049836167,
1633834915134208237423144264187482951766302060112099587851513525797020813799,
2895454976388515752029424688351979030650325184941524820409482023485820781526,
941019661238578826272324221721825852217063629464317974190162904813488515671,
2529926057929249454763690180607677568685011502604470585585763159431333258299,
],
[
2604831509257756199338105380847564711923112853239827243306562341166492672823,
2300475954087415591738767759767032267163723345312082546282694920273655145455,
1954000528502201000509342111010021527425422549437946241062907964768089317082,
1179936151696782249912570883839105595634344582873818018332922940963046083567,
3077707030301573630126144767923697288658782137457660869231140049571827937228,
],
[
1062324397142900251844488719868780667589966366756786302007970554437994421840,
353718609497993885193404630053532608155520921625518104461520254335222009911,
770557645309607171206012551080400276506165720184677119001983749356594531977,
3043628430985247363392058521341757139056029350680498644930013342982472853636,
1694968537785457252742656255724723357998402478572600479401200420305593921487,
],
[
539865665379093791531434211889371819368504193082947002067781562776138072582,
3473466148775696692731190426971123680342615414200262605154732883324298196699,
482783534456196983135936103604928650836406142744767857356485953118411089098,
2389101033971236780034779577432189630800997581132154923233144722790749715251,
845264223568475649981141803833883014312596504303895519674002924871878791033,
],
[
3027004059915270231142566724881373969831662022738947178800901294120992473905,
2169574859350740480088697859610203373582027214052754592019828328614087431593,
3515527080764222354309565181793838292349410992793070639041305826153436624160,
1817926918350512904327755405973355211358017834277255662858654992240629698587,
1999148133619270973098477176176178514394558202995832714883251820350860287223,
],
[
1203131300029280096510929599113528018338088236684405405384757591977164161039,
336815403657101171302040383579077521911288747438919304948637997306314852594,
986661060847815533035934253464295060766339947679669645818832311132001095573,
2291116974939980228917916563988261327966840303336559854772343651559589512651,
3421243089992476528970346847858594146122972226790673723411896208702859892637,
],
[
1015505198663386486420800821559060487156096175034250154764824837183581949724,
1165880582987807286271819576391581724550686829511475839624601920297855380101,
904232961143172831178860280790910264843503022179578981166030973682571903458,
261322216292849827900157598748641385787016033372999683866859675894253115357,
3060676319159217735181388708455879854358158161989877552543698103915296690395,
],
[
1175560144527845912984609340783959238735643215413930887771084560168082442967,
2813871258576082360085006002528268796351819524936446195552260262614692343332,
1841341101531851399935829271555098629075809587212843292354556374386667658235,
3076135575511709688509914361447080149794919016880133063891720256749999834767,
753111801049754117414662684453226478940731922961768343984187479992842213733,
],
[
1405657437118503342762241742745888533114216548278983907019917904938403345580,
3111186124713876864436867307979940633543281080828379725576742174555539054855,
3404463650394703220454952017098727360005393139199301323890695570346564876407,
2024087816190101179456573591359233695334184711688920998987373624570170649371,
2770035625774572095496575568588054654502991645588385802705097377675051032967,
],
[
437058215235292632621847481185406671372191763951486300610124033096831557414,
1345792773780982398809956395232061067669190682958320579442454533085407626029,
925357273912625669941681596445839316566672314870287993638671283923476231904,
3288133122086768300615066039539687885053110015077924175836976549020438910830,
666190075990703867784232802074474372379358766701681865975596503982238839889,
],
[
2664898601165892062970298960258838238925231697327906221693001926762280012052,
2075648691532387787722427044464731934171216054855867223374228487601569118337,
3173725544188532489243684991828985285646224157242834030308807120745121062293,
1517474443612606408422643323550409253700128234157734252330869178582583531320,
1593950878945144789965609248470060076911813704207225832606804796819386297511,
],
[
141195541167651298813588829225208004611326987855926870823948793274702167509,
2990187949585642302497822222637786229364740008175968941859105979392907839776,
2893807105405820282316438050347503569385510241526138409321358916388308586443,
1379719211597875648759619903854862028510320482486109668868067715175935658353,
2702780364788282233075255946852944970202849869091427738791947810055591218061,
],
[
1825815734419326277729273926504439575157952821379179501821641713286627304656,
1481344458867016048625916723816339719872443766684158199301690902395849166360,
2014084774259125722186109781197998076881266739680534358898592778318128968629,
2612744185006548312909661512508122065214170543806989291921289897662387203493,
2486291022451231582267428921150634472835925206862678364689227838329114330247,
],
]

File diff suppressed because it is too large Load Diff

View File

@@ -1,49 +1,51 @@
import { deepStrictEqual, throws } from 'assert'; import { deepStrictEqual, throws } from 'assert';
import { should } from 'micro-should'; import { describe, should } from 'micro-should';
import * as starknet from '../../lib/esm/stark.js'; import * as starknet from '../../lib/esm/stark.js';
import * as fc from 'fast-check'; import * as fc from 'fast-check';
const FC_BIGINT = fc.bigInt(1n + 1n, starknet.CURVE.n - 1n); const FC_BIGINT = fc.bigInt(1n + 1n, starknet.CURVE.n - 1n);
should('Point#toHex() roundtrip', () => { describe('starknet property', () => {
fc.assert( should('Point#toHex() roundtrip', () => {
fc.property(FC_BIGINT, (x) => { fc.assert(
const point1 = starknet.Point.fromPrivateKey(x); fc.property(FC_BIGINT, (x) => {
const hex = point1.toHex(true); const point1 = starknet.ProjectivePoint.fromPrivateKey(x);
deepStrictEqual(starknet.Point.fromHex(hex).toHex(true), hex); const hex = point1.toHex(true);
}) deepStrictEqual(starknet.ProjectivePoint.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.property(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);
})
)
); );
}); });
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. // ESM is broken.
import url from 'url'; import url from 'url';
if (import.meta.url === url.pathToFileURL(process.argv[1]).href) { if (import.meta.url === url.pathToFileURL(process.argv[1]).href) {

View File

@@ -1,5 +1,5 @@
import { deepStrictEqual, throws } from 'assert'; import { deepStrictEqual, throws } from 'assert';
import { should } from 'micro-should'; import { describe, should } from 'micro-should';
import { hex, utf8 } from '@scure/base'; import { hex, utf8 } from '@scure/base';
import * as bip32 from '@scure/bip32'; import * as bip32 from '@scure/bip32';
import * as bip39 from '@scure/bip39'; import * as bip39 from '@scure/bip39';
@@ -7,277 +7,279 @@ 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 sigVec } from './fixtures/rfc6979_signature_test_vector.json' assert { type: 'json' };
import { default as precomputedKeys } from './fixtures/keys_precomputed.json' assert { type: 'json' }; import { default as precomputedKeys } from './fixtures/keys_precomputed.json' assert { type: 'json' };
should('Starknet keccak', () => { describe('starknet', () => {
const value = starknet.keccak(utf8.decode('hello')); should('custom keccak', () => {
deepStrictEqual(value, 0x8aff950685c2ed4bc3174f3472287b56d9517b9c948127319a09a7a36deac8n); const value = starknet.keccak(utf8.decode('hello'));
deepStrictEqual(value < 2n ** 250n, true); deepStrictEqual(value, 0x8aff950685c2ed4bc3174f3472287b56d9517b9c948127319a09a7a36deac8n);
}); deepStrictEqual(value < 2n ** 250n, true);
});
should('RFC6979', () => { should('RFC6979', () => {
for (const msg of sigVec.messages) { for (const msg of sigVec.messages) {
const { r, s } = starknet.sign(msg.hash, sigVec.private_key); const { r, s } = starknet.sign(msg.hash, sigVec.private_key);
// const { r, s } = starknet.Signature.fromDER(sig); // const { r, s } = starknet.Signature.fromDER(sig);
deepStrictEqual(r.toString(10), msg.r); deepStrictEqual(r.toString(10), msg.r);
deepStrictEqual(s.toString(10), msg.s); deepStrictEqual(s.toString(10), msg.s);
} }
}); });
should('Signatures', () => { should('Signatures', () => {
const vectors = [ const vectors = [
{ {
// Message hash of length 61. // Message hash of length 61.
msg: 'c465dd6b1bbffdb05442eb17f5ca38ad1aa78a6f56bf4415bdee219114a47', msg: 'c465dd6b1bbffdb05442eb17f5ca38ad1aa78a6f56bf4415bdee219114a47',
r: '5f496f6f210b5810b2711c74c15c05244dad43d18ecbbdbe6ed55584bc3b0a2', r: '5f496f6f210b5810b2711c74c15c05244dad43d18ecbbdbe6ed55584bc3b0a2',
s: '4e8657b153787f741a67c0666bad6426c3741b478c8eaa3155196fc571416f3', s: '4e8657b153787f741a67c0666bad6426c3741b478c8eaa3155196fc571416f3',
}, },
{ {
// Message hash of length 61, with leading zeros. // Message hash of length 61, with leading zeros.
msg: '00c465dd6b1bbffdb05442eb17f5ca38ad1aa78a6f56bf4415bdee219114a47', msg: '00c465dd6b1bbffdb05442eb17f5ca38ad1aa78a6f56bf4415bdee219114a47',
r: '5f496f6f210b5810b2711c74c15c05244dad43d18ecbbdbe6ed55584bc3b0a2', r: '5f496f6f210b5810b2711c74c15c05244dad43d18ecbbdbe6ed55584bc3b0a2',
s: '4e8657b153787f741a67c0666bad6426c3741b478c8eaa3155196fc571416f3', s: '4e8657b153787f741a67c0666bad6426c3741b478c8eaa3155196fc571416f3',
}, },
{ {
// Message hash of length 62. // Message hash of length 62.
msg: 'c465dd6b1bbffdb05442eb17f5ca38ad1aa78a6f56bf4415bdee219114a47a', msg: 'c465dd6b1bbffdb05442eb17f5ca38ad1aa78a6f56bf4415bdee219114a47a',
r: '233b88c4578f0807b4a7480c8076eca5cfefa29980dd8e2af3c46a253490e9c', r: '233b88c4578f0807b4a7480c8076eca5cfefa29980dd8e2af3c46a253490e9c',
s: '28b055e825bc507349edfb944740a35c6f22d377443c34742c04e0d82278cf1', s: '28b055e825bc507349edfb944740a35c6f22d377443c34742c04e0d82278cf1',
}, },
{ {
// Message hash of length 63. // Message hash of length 63.
msg: '7465dd6b1bbffdb05442eb17f5ca38ad1aa78a6f56bf4415bdee219114a47a1', msg: '7465dd6b1bbffdb05442eb17f5ca38ad1aa78a6f56bf4415bdee219114a47a1',
r: 'b6bee8010f96a723f6de06b5fa06e820418712439c93850dd4e9bde43ddf', r: 'b6bee8010f96a723f6de06b5fa06e820418712439c93850dd4e9bde43ddf',
s: '1a3d2bc954ed77e22986f507d68d18115fa543d1901f5b4620db98e2f6efd80', s: '1a3d2bc954ed77e22986f507d68d18115fa543d1901f5b4620db98e2f6efd80',
}, },
]; ];
const privateKey = '2dccce1da22003777062ee0870e9881b460a8b7eca276870f57c601f182136c'; const privateKey = '2dccce1da22003777062ee0870e9881b460a8b7eca276870f57c601f182136c';
const publicKey = starknet.getPublicKey(privateKey); const publicKey = starknet.getPublicKey(privateKey);
for (const v of vectors) { for (const v of vectors) {
const sig = starknet.sign(v.msg, privateKey); 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; 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', () => { deepStrictEqual(
/* r.toString(),
'1427981024487605678086498726488552139932400435436186597196374630267616399345'
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)); deepStrictEqual(
const msgSignature = starkwareCrypto.sign(keyPair, msgHash); s.toString(),
'1853664302719670721837677288395394946745467311923401353018029119631574115563'
// Test invalid public key.
const invalidKeyPairPub = starkwareCrypto.ec.keyFromPublic(
{x: keyPairPub.pub.getX().add(oneBn), y: keyPairPub.pub.getY()},
'BN'
); );
expect( const hashMsg2 = starknet.pedersen(
starkwareCrypto.verify( '0x33f45f07e1bd1a51b45fc24ec8c8c9908db9e42191be9e169bfcac0c0d99745',
invalidKeyPairPub, '1'
msgHash.toString(16), );
msgSignature deepStrictEqual(hashMsg2, '0x2b0d4d43acce8ff68416f667f92ec7eab2b96f1d2224abd4d9d4d1e7fa4bb00');
) const pubKey =
).to.be.false; '04033f45f07e1bd1a51b45fc24ec8c8c9908db9e42191be9e169bfcac0c0d997450319d0f53f6ca077c4fa5207819144a2a4165daef6ee47a7c1d06c0dcaa3e456';
// Test invalid message. const sig2 = new starknet.Signature(
expect( 558858382392827003930138586379728730695763862039474863361948210004201119180n,
starkwareCrypto.verify( 2440689354481625417078677634625227600823892606910345662891037256374285369343n
keyPair, );
msgHash.add(oneBn).toString(16), deepStrictEqual(starknet.verify(sig2.toDERHex(), hashMsg2, pubKey), true);
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. // ESM is broken.
import url from 'url'; import url from 'url';

1080
test/vectors/poseidon.json Normal file

File diff suppressed because it is too large Load Diff