forked from tornado-packages/noble-curves
Split curves. Improve speed. Better tests
This commit is contained in:
parent
6b0d9611a5
commit
5b305abe85
@ -1,8 +1,9 @@
|
||||
import * as bench from 'micro-bmark';
|
||||
const { run, mark } = bench; // or bench.mark
|
||||
// Curves
|
||||
import * as nist from '../lib/nist.js';
|
||||
import * as ed from '../lib/ed.js';
|
||||
import { secp256k1 } from '../lib/secp256k1.js';
|
||||
import { ed25519 } from '../lib/ed25519.js';
|
||||
import { ed448 } from '../lib/ed448.js';
|
||||
|
||||
// Others
|
||||
import { hmac } from '@noble/hashes/hmac';
|
||||
@ -24,10 +25,9 @@ noble_secp256k1.utils.hmacSha256Sync = (key, ...msgs) =>
|
||||
.digest();
|
||||
import * as noble_ed25519 from '@noble/ed25519';
|
||||
|
||||
|
||||
nist.secp256k1.utils.precompute(8); // Not enabled by default?
|
||||
ed.ed25519.utils.precompute(8);
|
||||
ed.ed448.utils.precompute(8);
|
||||
secp256k1.utils.precompute(8); // Not enabled by default?
|
||||
ed25519.utils.precompute(8);
|
||||
ed448.utils.precompute(8);
|
||||
|
||||
noble_ed25519.utils.sha512Sync = (...m) => sha512(concatBytes(...m));
|
||||
noble_secp256k1.utils.precompute(8);
|
||||
@ -48,19 +48,18 @@ export const CURVES = {
|
||||
},
|
||||
getPublicKey: {
|
||||
samples: 10000,
|
||||
old: () =>
|
||||
noble_secp256k1.getPublicKey(noble_secp256k1.utils.randomPrivateKey()),
|
||||
noble: () => nist.secp256k1.getPublicKey(nist.secp256k1.utils.randomPrivateKey()),
|
||||
old: () => noble_secp256k1.getPublicKey(noble_secp256k1.utils.randomPrivateKey()),
|
||||
noble: () => secp256k1.getPublicKey(secp256k1.utils.randomPrivateKey()),
|
||||
},
|
||||
sign: {
|
||||
samples: 5000,
|
||||
old: ({ msg, priv }) => noble_secp256k1.signSync(msg, priv),
|
||||
noble: ({ msg, priv }) => nist.secp256k1.sign(msg, priv),
|
||||
noble: ({ msg, priv }) => secp256k1.sign(msg, priv),
|
||||
},
|
||||
getSharedSecret: {
|
||||
samples: 1000,
|
||||
old: ({ pub, priv }) => noble_secp256k1.getSharedSecret(priv, pub),
|
||||
noble: ({ pub, priv }) => nist.secp256k1.getSharedSecret(priv, pub),
|
||||
noble: ({ pub, priv }) => secp256k1.getSharedSecret(priv, pub),
|
||||
},
|
||||
},
|
||||
ed25519: {
|
||||
@ -77,21 +76,20 @@ export const CURVES = {
|
||||
},
|
||||
getPublicKey: {
|
||||
samples: 10000,
|
||||
old: () =>
|
||||
noble_ed25519.sync.getPublicKey(noble_ed25519.utils.randomPrivateKey()),
|
||||
noble: () => ed.ed25519.getPublicKey(ed.ed25519.utils.randomPrivateKey()),
|
||||
'ed448': () => ed.ed448.getPublicKey(ed.ed448.utils.randomPrivateKey()),
|
||||
old: () => noble_ed25519.sync.getPublicKey(noble_ed25519.utils.randomPrivateKey()),
|
||||
noble: () => ed25519.getPublicKey(ed25519.utils.randomPrivateKey()),
|
||||
ed448: () => ed448.getPublicKey(ed448.utils.randomPrivateKey()),
|
||||
},
|
||||
sign: {
|
||||
samples: 5000,
|
||||
old: ({ msg, priv }) => noble_ed25519.sync.sign(msg, priv),
|
||||
noble: ({ msg, priv }) => ed.ed25519.sign(msg, priv),
|
||||
'ed448': () => ed.ed448.sign(ed.ed448.utils.randomPrivateKey(), ed.ed448.utils.randomPrivateKey()),
|
||||
noble: ({ msg, priv }) => ed25519.sign(msg, priv),
|
||||
ed448: () => ed448.sign(ed448.utils.randomPrivateKey(), ed448.utils.randomPrivateKey()),
|
||||
},
|
||||
verify: {
|
||||
samples: 1000,
|
||||
old: ({ msg, pub, sig }) => noble_ed25519.sync.verify(sig, msg, pub),
|
||||
noble: ({ msg, pub, sig }) => ed.ed25519.verify(sig, msg, pub),
|
||||
noble: ({ msg, pub, sig }) => ed25519.verify(sig, msg, pub),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
17
curve-definitions/src/_shortw_utils.ts
Normal file
17
curve-definitions/src/_shortw_utils.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import { hmac } from '@noble/hashes/hmac';
|
||||
import { concatBytes, randomBytes } from '@noble/hashes/utils';
|
||||
import { weierstrass, CurveType, CHash } from '@noble/curves/weierstrass';
|
||||
|
||||
export function getHash(hash: CHash) {
|
||||
return {
|
||||
hash,
|
||||
hmac: (key: Uint8Array, ...msgs: Uint8Array[]) => hmac(hash, key, concatBytes(...msgs)),
|
||||
randomBytes,
|
||||
};
|
||||
}
|
||||
// Same API as @noble/hashes, with ability to create curve with custom hash
|
||||
type CurveDef = Readonly<Omit<CurveType, 'hash' | 'hmac' | 'randomBytes'>>;
|
||||
export function createCurve(curveDef: CurveDef, defHash: CHash) {
|
||||
const create = (hash: CHash) => weierstrass({ ...curveDef, ...getHash(hash) });
|
||||
return Object.freeze({ ...create(defHash), create });
|
||||
}
|
@ -1,19 +1,11 @@
|
||||
/*! @noble/curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
|
||||
import { weierstrass, CHash } from '@noble/curves/weierstrass';
|
||||
import { concatBytes, randomBytes } from '@noble/hashes/utils';
|
||||
import { hmac } from '@noble/hashes/hmac';
|
||||
import { weierstrass } from '@noble/curves/weierstrass';
|
||||
import { sha256 } from '@noble/hashes/sha256';
|
||||
|
||||
function getHash(hash: CHash) {
|
||||
return {
|
||||
hash,
|
||||
hmac: (key: Uint8Array, ...msgs: Uint8Array[]) => hmac(hash, key, concatBytes(...msgs)),
|
||||
randomBytes,
|
||||
};
|
||||
}
|
||||
import { getHash } from './_shortw_utils.js';
|
||||
|
||||
// Was known as alt_bn128 when it had 128-bit security. Now that it's much lower, the naming
|
||||
// has been changed to its prime bit count.
|
||||
// https://neuromancer.sk/std/bn/bn254
|
||||
export const bn254 = weierstrass({
|
||||
a: 0n,
|
||||
b: 3n,
|
||||
@ -21,5 +13,6 @@ export const bn254 = weierstrass({
|
||||
n: 0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001n,
|
||||
Gx: 1n,
|
||||
Gy: 2n,
|
||||
h: BigInt(1),
|
||||
...getHash(sha256),
|
||||
});
|
||||
|
@ -1,3 +1,4 @@
|
||||
/*! @noble/curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
|
||||
import { sha512 } from '@noble/hashes/sha512';
|
||||
import { concatBytes, randomBytes, utf8ToBytes } from '@noble/hashes/utils';
|
||||
import { twistedEdwards } from '@noble/curves/edwards';
|
||||
|
@ -1,3 +1,4 @@
|
||||
/*! @noble/curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
|
||||
import { shake256 } from '@noble/hashes/sha3';
|
||||
import { concatBytes, randomBytes, utf8ToBytes, wrapConstructor } from '@noble/hashes/utils';
|
||||
import { PointType, twistedEdwards } from '@noble/curves/edwards';
|
||||
@ -43,7 +44,8 @@ function adjustScalarBytes(bytes: Uint8Array): Uint8Array {
|
||||
bytes[56] = 0; // Byte outside of group (456 buts vs 448 bits)
|
||||
return bytes;
|
||||
}
|
||||
|
||||
// Edwards448 from RFC 8032 (https://www.rfc-editor.org/rfc/rfc8032.html#section-5.2).
|
||||
// NOTE: Ed448-Goldilocks is different curve
|
||||
const ED448_DEF = {
|
||||
// Param: a
|
||||
a: BigInt(1),
|
||||
|
52
curve-definitions/src/jubjub.ts
Normal file
52
curve-definitions/src/jubjub.ts
Normal file
@ -0,0 +1,52 @@
|
||||
/*! @noble/curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
|
||||
import { sha256 } from '@noble/hashes/sha256';
|
||||
import { concatBytes, randomBytes, utf8ToBytes } from '@noble/hashes/utils';
|
||||
import { twistedEdwards } from '@noble/curves/edwards';
|
||||
import { blake2s } from '@noble/hashes/blake2s';
|
||||
|
||||
// https://neuromancer.sk/std/other/JubJub
|
||||
export const jubjub = twistedEdwards({
|
||||
// Params: a, d
|
||||
a: BigInt('0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000000'),
|
||||
d: BigInt('0x2a9318e74bfa2b48f5fd9207e6bd7fd4292d7f6d37579d2601065fd6d6343eb1'),
|
||||
// Finite field 𝔽p over which we'll do calculations
|
||||
P: BigInt('0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001'),
|
||||
// Subgroup order: how many points ed25519 has
|
||||
// 2n ** 252n + 27742317777372353535851937790883648493n;
|
||||
n: BigInt('0xe7db4ea6533afa906673b0101343b00a6682093ccc81082d0970e5ed6f72cb7'),
|
||||
// Cofactor
|
||||
h: BigInt(8),
|
||||
// Base point (x, y) aka generator point
|
||||
Gx: BigInt('0x11dafe5d23e1218086a365b99fbf3d3be72f6afd7d1f72623e6b071492d1122b'),
|
||||
Gy: BigInt('0x1d523cf1ddab1a1793132e78c866c0c33e26ba5cc220fed7cc3f870e59d292aa'),
|
||||
hash: sha256,
|
||||
randomBytes,
|
||||
} as const);
|
||||
|
||||
const GH_FIRST_BLOCK = utf8ToBytes(
|
||||
'096b36a5804bfacef1691e173c366a47ff5ba84a44f26ddd7e8d9f79d5b42df0'
|
||||
);
|
||||
|
||||
// Returns point at JubJub curve which is prime order and not zero
|
||||
export function groupHash(tag: Uint8Array, personalization: Uint8Array) {
|
||||
const h = blake2s.create({ personalization, dkLen: 32 });
|
||||
h.update(GH_FIRST_BLOCK);
|
||||
h.update(tag);
|
||||
// NOTE: returns ExtendedPoint, in case it will be multiplied later
|
||||
let p = jubjub.ExtendedPoint.fromAffine(jubjub.Point.fromHex(h.digest()));
|
||||
// NOTE: cannot replace with isSmallOrder, returns Point*8
|
||||
p = p.multiply(jubjub.CURVE.h);
|
||||
if (p.equals(jubjub.ExtendedPoint.ZERO)) throw new Error('Point has small order');
|
||||
return p;
|
||||
}
|
||||
|
||||
export function findGroupHash(m: Uint8Array, personalization: Uint8Array) {
|
||||
const tag = concatBytes(m, new Uint8Array([0]));
|
||||
for (let i = 0; i < 256; i++) {
|
||||
tag[tag.length - 1] = i;
|
||||
try {
|
||||
return groupHash(tag, personalization);
|
||||
} catch (e) {}
|
||||
}
|
||||
throw new Error('findGroupHash tag overflow');
|
||||
}
|
@ -1,212 +0,0 @@
|
||||
/*! @noble/curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
|
||||
import { hmac } from '@noble/hashes/hmac';
|
||||
import { sha256 } from '@noble/hashes/sha256';
|
||||
import { sha384, sha512 } from '@noble/hashes/sha512';
|
||||
import { concatBytes, randomBytes } from '@noble/hashes/utils';
|
||||
import { weierstrass, CurveType, CHash } from '@noble/curves/weierstrass';
|
||||
import { mod, pow2 } from '@noble/curves/modular';
|
||||
|
||||
function getHash(hash: CHash) {
|
||||
return {
|
||||
hash,
|
||||
hmac: (key: Uint8Array, ...msgs: Uint8Array[]) => hmac(hash, key, concatBytes(...msgs)),
|
||||
randomBytes,
|
||||
};
|
||||
}
|
||||
// Same API as @noble/hashes, with ability to create curve with custom hash
|
||||
type CurveDef = Readonly<Omit<CurveType, 'hash' | 'hmac' | 'randomBytes'>>;
|
||||
function createCurve(curveDef: CurveDef, defHash: CHash) {
|
||||
const create = (hash: CHash) => weierstrass({ ...curveDef, ...getHash(hash) });
|
||||
return Object.freeze({ ...create(defHash), create });
|
||||
}
|
||||
|
||||
// https://www.secg.org/sec2-v2.pdf
|
||||
// https://neuromancer.sk/std/secg/secp192r1
|
||||
export const P192 = createCurve(
|
||||
{
|
||||
// Params: a, b
|
||||
a: BigInt('0xfffffffffffffffffffffffffffffffefffffffffffffffc'),
|
||||
b: BigInt('0x64210519e59c80e70fa7e9ab72243049feb8deecc146b9b1'),
|
||||
// Field over which we'll do calculations. Verify with: 2n ** 192n - 2n ** 64n - 1n
|
||||
P: BigInt('0xfffffffffffffffffffffffffffffffeffffffffffffffff'),
|
||||
// Curve order, total count of valid points in the field. Verify with:
|
||||
n: BigInt('0xffffffffffffffffffffffff99def836146bc9b1b4d22831'),
|
||||
// Base point (x, y) aka generator point
|
||||
Gx: BigInt('0x188da80eb03090f67cbf20eb43a18800f4ff0afd82ff1012'),
|
||||
Gy: BigInt('0x07192b95ffc8da78631011ed6b24cdd573f977a11e794811'),
|
||||
lowS: false,
|
||||
} as const,
|
||||
sha256
|
||||
);
|
||||
export const secp192r1 = P192;
|
||||
// https://neuromancer.sk/std/nist/P-224
|
||||
export const P224 = createCurve(
|
||||
{
|
||||
// Params: a, b
|
||||
a: BigInt('0xfffffffffffffffffffffffffffffffefffffffffffffffffffffffe'),
|
||||
b: BigInt('0xb4050a850c04b3abf54132565044b0b7d7bfd8ba270b39432355ffb4'),
|
||||
// Field over which we'll do calculations. Verify with:
|
||||
P: 2n ** 224n - 2n ** 96n + 1n,
|
||||
// Curve order, total count of valid points in the field. Verify with:
|
||||
n: BigInt('0xffffffffffffffffffffffffffff16a2e0b8f03e13dd29455c5c2a3d'),
|
||||
// Base point (x, y) aka generator point
|
||||
Gx: BigInt('0xb70e0cbd6bb4bf7f321390b94a03c1d356c21122343280d6115c1d21'),
|
||||
Gy: BigInt('0xbd376388b5f723fb4c22dfe6cd4375a05a07476444d5819985007e34'),
|
||||
lowS: false,
|
||||
} as const,
|
||||
sha256 // TODO: replace with sha224 when new @noble/hashes released
|
||||
);
|
||||
export const secp224r1 = P224;
|
||||
// https://neuromancer.sk/std/nist/P-256
|
||||
export const P256 = createCurve(
|
||||
{
|
||||
// Params: a, b
|
||||
a: BigInt('0xffffffff00000001000000000000000000000000fffffffffffffffffffffffc'),
|
||||
b: BigInt('0x5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b'),
|
||||
// Field over which we'll do calculations. Verify with:
|
||||
// 2n ** 224n * (2n ** 32n - 1n) + 2n ** 192n + 2n ** 96n - 1n,
|
||||
P: BigInt('0xffffffff00000001000000000000000000000000ffffffffffffffffffffffff'),
|
||||
// Curve order, total count of valid points in the field. Verify with:
|
||||
n: BigInt('0xffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551'),
|
||||
// Base point (x, y) aka generator point
|
||||
Gx: BigInt('0x6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296'),
|
||||
Gy: BigInt('0x4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5'),
|
||||
lowS: false,
|
||||
} as const,
|
||||
sha256
|
||||
);
|
||||
export const secp256r1 = P256;
|
||||
// https://neuromancer.sk/std/nist/P-384
|
||||
// prettier-ignore
|
||||
export const P384 = createCurve(
|
||||
{
|
||||
// Params: a, b
|
||||
a: BigInt(
|
||||
'0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffff0000000000000000fffffffc'
|
||||
),
|
||||
b: BigInt(
|
||||
'0xb3312fa7e23ee7e4988e056be3f82d19181d9c6efe8141120314088f5013875ac656398d8a2ed19d2a85c8edd3ec2aef'
|
||||
),
|
||||
// Field over which we'll do calculations. Verify with:
|
||||
// 2n ** 384n - 2n ** 128n - 2n ** 96n + 2n ** 32n - 1n
|
||||
P: BigInt(
|
||||
'0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffff0000000000000000ffffffff'
|
||||
),
|
||||
// Curve order, total count of valid points in the field. Verify with:
|
||||
n: BigInt(
|
||||
'0xffffffffffffffffffffffffffffffffffffffffffffffffc7634d81f4372ddf581a0db248b0a77aecec196accc52973'
|
||||
),
|
||||
// Base point (x, y) aka generator point
|
||||
Gx: BigInt(
|
||||
'0xaa87ca22be8b05378eb1c71ef320ad746e1d3b628ba79b9859f741e082542a385502f25dbf55296c3a545e3872760ab7'
|
||||
),
|
||||
Gy: BigInt(
|
||||
'0x3617de4a96262c6f5d9e98bf9292dc29f8f41dbd289a147ce9da3113b5f0b8c00a60b1ce1d7e819d7a431d7c90ea0e5f'
|
||||
),
|
||||
lowS: false,
|
||||
} as const,
|
||||
sha384
|
||||
);
|
||||
export const secp384r1 = P384;
|
||||
// https://neuromancer.sk/std/nist/P-521
|
||||
// prettier-ignore
|
||||
export const P521 = createCurve({
|
||||
// Params: a, b
|
||||
a: BigInt('0x01fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc'),
|
||||
b: BigInt('0x0051953eb9618e1c9a1f929a21a0b68540eea2da725b99b315f3b8b489918ef109e156193951ec7e937b1652c0bd3bb1bf073573df883d2c34f1ef451fd46b503f00'),
|
||||
// Field over which we'll do calculations. Verify with:
|
||||
// 2n ** 521n - 1n,
|
||||
P: BigInt('0x1ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff'),
|
||||
// Curve order, total count of valid points in the field. Verify with:
|
||||
n: BigInt('0x01fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa51868783bf2f966b7fcc0148f709a5d03bb5c9b8899c47aebb6fb71e91386409'),
|
||||
// Base point (x, y) aka generator point
|
||||
Gx: BigInt('0x00c6858e06b70404e9cd9e3ecb662395b4429c648139053fb521f828af606b4d3dbaa14b5e77efe75928fe1dc127a2ffa8de3348b3c1856a429bf97e7e31c2e5bd66'),
|
||||
Gy: BigInt('0x011839296a789a3bc0045c8a5fb42c7d1bd998f54449579b446817afbd17273e662c97ee72995ef42640c550b9013fad0761353c7086a272c24088be94769fd16650'),
|
||||
lowS: false,
|
||||
} as const, sha512);
|
||||
export const secp521r1 = P521;
|
||||
|
||||
/**
|
||||
* secp256k1 definition with efficient square root and endomorphism.
|
||||
* Endomorphism works only for Koblitz curves with a == 0.
|
||||
* It improves efficiency:
|
||||
* Uses 2x less RAM, speeds up precomputation by 2x and ECDH / sign key recovery by 20%.
|
||||
* Should always be used for Jacobian's double-and-add multiplication.
|
||||
* For affines cached multiplication, it trades off 1/2 init time & 1/3 ram for 20% perf hit.
|
||||
* https://gist.github.com/paulmillr/eb670806793e84df628a7c434a873066
|
||||
*/
|
||||
const secp256k1P = BigInt('0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f');
|
||||
const secp256k1N = BigInt('0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141');
|
||||
const _1n = BigInt(1);
|
||||
const _2n = BigInt(2);
|
||||
const divNearest = (a: bigint, b: bigint) => (a + b / _2n) / b;
|
||||
export const secp256k1 = createCurve(
|
||||
{
|
||||
a: 0n,
|
||||
b: 7n,
|
||||
// Field over which we'll do calculations. Verify with:
|
||||
// 2n ** 256n - 2n ** 32n - 2n ** 9n - 2n ** 8n - 2n ** 7n - 2n ** 6n - 2n ** 4n - 1n
|
||||
P: secp256k1P,
|
||||
// Curve order, total count of valid points in the field. Verify with:
|
||||
n: secp256k1N,
|
||||
// Base point (x, y) aka generator point
|
||||
Gx: BigInt('55066263022277343669578718895168534326250603453777594175500187360389116729240'),
|
||||
Gy: BigInt('32670510020758816978083085130507043184471273380659243275938904335757337482424'),
|
||||
// noble-secp256k1 compat
|
||||
lowS: true,
|
||||
// Used to calculate y - the square root of y².
|
||||
// Exponentiates it to very big number (P+1)/4.
|
||||
// We are unwrapping the loop because it's 2x faster.
|
||||
// (P+1n/4n).toString(2) would produce bits [223x 1, 0, 22x 1, 4x 0, 11, 00]
|
||||
// We are multiplying it bit-by-bit
|
||||
sqrtMod: (x: bigint): bigint => {
|
||||
const P = secp256k1P;
|
||||
const _3n = BigInt(3);
|
||||
const _6n = BigInt(6);
|
||||
const _11n = BigInt(11);
|
||||
const _22n = BigInt(22);
|
||||
const _23n = BigInt(23);
|
||||
const _44n = BigInt(44);
|
||||
const _88n = BigInt(88);
|
||||
const b2 = (x * x * x) % P; // x^3, 11
|
||||
const b3 = (b2 * b2 * x) % P; // x^7
|
||||
const b6 = (pow2(b3, _3n, P) * b3) % P;
|
||||
const b9 = (pow2(b6, _3n, P) * b3) % P;
|
||||
const b11 = (pow2(b9, _2n, P) * b2) % P;
|
||||
const b22 = (pow2(b11, _11n, P) * b11) % P;
|
||||
const b44 = (pow2(b22, _22n, P) * b22) % P;
|
||||
const b88 = (pow2(b44, _44n, P) * b44) % P;
|
||||
const b176 = (pow2(b88, _88n, P) * b88) % P;
|
||||
const b220 = (pow2(b176, _44n, P) * b44) % P;
|
||||
const b223 = (pow2(b220, _3n, P) * b3) % P;
|
||||
const t1 = (pow2(b223, _23n, P) * b22) % P;
|
||||
const t2 = (pow2(t1, _6n, P) * b2) % P;
|
||||
return pow2(t2, _2n, P);
|
||||
},
|
||||
endo: {
|
||||
beta: BigInt('0x7ae96a2b657c07106e64479eac3434e99cf0497512f58995c1396c28719501ee'),
|
||||
splitScalar: (k: bigint) => {
|
||||
const n = secp256k1N;
|
||||
const a1 = BigInt('0x3086d221a7d46bcde86c90e49284eb15');
|
||||
const b1 = -_1n * BigInt('0xe4437ed6010e88286f547fa90abfe4c3');
|
||||
const a2 = BigInt('0x114ca50f7a8e2f3f657c1108d9d44cfd8');
|
||||
const b2 = a1;
|
||||
const POW_2_128 = BigInt('0x100000000000000000000000000000000');
|
||||
|
||||
const c1 = divNearest(b2 * k, n);
|
||||
const c2 = divNearest(-b1 * k, n);
|
||||
let k1 = mod(k - c1 * a1 - c2 * a2, n);
|
||||
let k2 = mod(-c1 * b1 - c2 * b2, n);
|
||||
const k1neg = k1 > POW_2_128;
|
||||
const k2neg = k2 > POW_2_128;
|
||||
if (k1neg) k1 = n - k1;
|
||||
if (k2neg) k2 = n - k2;
|
||||
if (k1 > POW_2_128 || k2 > POW_2_128) {
|
||||
throw new Error('splitScalar: Endomorphism failed, k=' + k);
|
||||
}
|
||||
return { k1neg, k1, k2neg, k2 };
|
||||
},
|
||||
},
|
||||
},
|
||||
sha256
|
||||
);
|
24
curve-definitions/src/p192.ts
Normal file
24
curve-definitions/src/p192.ts
Normal file
@ -0,0 +1,24 @@
|
||||
/*! @noble/curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
|
||||
import { createCurve } from './_shortw_utils.js';
|
||||
import { sha256 } from '@noble/hashes/sha256';
|
||||
|
||||
// https://www.secg.org/sec2-v2.pdf
|
||||
// https://neuromancer.sk/std/secg/secp192r1
|
||||
export const P192 = createCurve(
|
||||
{
|
||||
// Params: a, b
|
||||
a: BigInt('0xfffffffffffffffffffffffffffffffefffffffffffffffc'),
|
||||
b: BigInt('0x64210519e59c80e70fa7e9ab72243049feb8deecc146b9b1'),
|
||||
// Field over which we'll do calculations. Verify with: 2n ** 192n - 2n ** 64n - 1n
|
||||
P: BigInt('0xfffffffffffffffffffffffffffffffeffffffffffffffff'),
|
||||
// Curve order, total count of valid points in the field. Verify with:
|
||||
n: BigInt('0xffffffffffffffffffffffff99def836146bc9b1b4d22831'),
|
||||
// Base point (x, y) aka generator point
|
||||
Gx: BigInt('0x188da80eb03090f67cbf20eb43a18800f4ff0afd82ff1012'),
|
||||
Gy: BigInt('0x07192b95ffc8da78631011ed6b24cdd573f977a11e794811'),
|
||||
h: BigInt(1),
|
||||
lowS: false,
|
||||
} as const,
|
||||
sha256
|
||||
);
|
||||
export const secp192r1 = P192;
|
24
curve-definitions/src/p224.ts
Normal file
24
curve-definitions/src/p224.ts
Normal file
@ -0,0 +1,24 @@
|
||||
/*! @noble/curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
|
||||
import { createCurve } from './_shortw_utils.js';
|
||||
import { sha256 } from '@noble/hashes/sha256';
|
||||
|
||||
// https://www.secg.org/sec2-v2.pdf
|
||||
// https://neuromancer.sk/std/nist/P-224
|
||||
export const P224 = createCurve(
|
||||
{
|
||||
// Params: a, b
|
||||
a: BigInt('0xfffffffffffffffffffffffffffffffefffffffffffffffffffffffe'),
|
||||
b: BigInt('0xb4050a850c04b3abf54132565044b0b7d7bfd8ba270b39432355ffb4'),
|
||||
// Field over which we'll do calculations. Verify with:
|
||||
P: 2n ** 224n - 2n ** 96n + 1n,
|
||||
// Curve order, total count of valid points in the field. Verify with:
|
||||
n: BigInt('0xffffffffffffffffffffffffffff16a2e0b8f03e13dd29455c5c2a3d'),
|
||||
// Base point (x, y) aka generator point
|
||||
Gx: BigInt('0xb70e0cbd6bb4bf7f321390b94a03c1d356c21122343280d6115c1d21'),
|
||||
Gy: BigInt('0xbd376388b5f723fb4c22dfe6cd4375a05a07476444d5819985007e34'),
|
||||
h: BigInt(1),
|
||||
lowS: false,
|
||||
} as const,
|
||||
sha256 // TODO: replace with sha224 when new @noble/hashes released
|
||||
);
|
||||
export const secp224r1 = P224;
|
25
curve-definitions/src/p256.ts
Normal file
25
curve-definitions/src/p256.ts
Normal file
@ -0,0 +1,25 @@
|
||||
/*! @noble/curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
|
||||
import { createCurve } from './_shortw_utils.js';
|
||||
import { sha256 } from '@noble/hashes/sha256';
|
||||
|
||||
// https://www.secg.org/sec2-v2.pdf
|
||||
// https://neuromancer.sk/std/nist/P-256
|
||||
export const P256 = createCurve(
|
||||
{
|
||||
// Params: a, b
|
||||
a: BigInt('0xffffffff00000001000000000000000000000000fffffffffffffffffffffffc'),
|
||||
b: BigInt('0x5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b'),
|
||||
// Field over which we'll do calculations. Verify with:
|
||||
// 2n ** 224n * (2n ** 32n - 1n) + 2n ** 192n + 2n ** 96n - 1n,
|
||||
P: BigInt('0xffffffff00000001000000000000000000000000ffffffffffffffffffffffff'),
|
||||
// Curve order, total count of valid points in the field. Verify with:
|
||||
n: BigInt('0xffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551'),
|
||||
// Base point (x, y) aka generator point
|
||||
Gx: BigInt('0x6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296'),
|
||||
Gy: BigInt('0x4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5'),
|
||||
h: BigInt(1),
|
||||
lowS: false,
|
||||
} as const,
|
||||
sha256
|
||||
);
|
||||
export const secp256r1 = P256;
|
23
curve-definitions/src/p384.ts
Normal file
23
curve-definitions/src/p384.ts
Normal file
@ -0,0 +1,23 @@
|
||||
/*! @noble/curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
|
||||
import { createCurve } from './_shortw_utils.js';
|
||||
import { sha384 } from '@noble/hashes/sha512';
|
||||
|
||||
// https://www.secg.org/sec2-v2.pdf
|
||||
// https://neuromancer.sk/std/nist/P-384
|
||||
// prettier-ignore
|
||||
export const P384 = createCurve({
|
||||
// Params: a, b
|
||||
a: BigInt('0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffff0000000000000000fffffffc'),
|
||||
b: BigInt('0xb3312fa7e23ee7e4988e056be3f82d19181d9c6efe8141120314088f5013875ac656398d8a2ed19d2a85c8edd3ec2aef'),
|
||||
// Field over which we'll do calculations. Verify with:
|
||||
// 2n ** 384n - 2n ** 128n - 2n ** 96n + 2n ** 32n - 1n
|
||||
P: BigInt('0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffff0000000000000000ffffffff'),
|
||||
// Curve order, total count of valid points in the field. Verify with:
|
||||
n: BigInt('0xffffffffffffffffffffffffffffffffffffffffffffffffc7634d81f4372ddf581a0db248b0a77aecec196accc52973'),
|
||||
// Base point (x, y) aka generator point
|
||||
Gx: BigInt('0xaa87ca22be8b05378eb1c71ef320ad746e1d3b628ba79b9859f741e082542a385502f25dbf55296c3a545e3872760ab7'),
|
||||
Gy: BigInt('0x3617de4a96262c6f5d9e98bf9292dc29f8f41dbd289a147ce9da3113b5f0b8c00a60b1ce1d7e819d7a431d7c90ea0e5f'),
|
||||
h: BigInt(1),
|
||||
lowS: false,
|
||||
} as const, sha384);
|
||||
export const secp384r1 = P384;
|
23
curve-definitions/src/p521.ts
Normal file
23
curve-definitions/src/p521.ts
Normal file
@ -0,0 +1,23 @@
|
||||
/*! @noble/curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
|
||||
import { createCurve } from './_shortw_utils.js';
|
||||
import { sha512 } from '@noble/hashes/sha512';
|
||||
|
||||
// https://www.secg.org/sec2-v2.pdf
|
||||
// https://neuromancer.sk/std/nist/P-521
|
||||
// prettier-ignore
|
||||
export const P521 = createCurve({
|
||||
// Params: a, b
|
||||
a: BigInt('0x01fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc'),
|
||||
b: BigInt('0x0051953eb9618e1c9a1f929a21a0b68540eea2da725b99b315f3b8b489918ef109e156193951ec7e937b1652c0bd3bb1bf073573df883d2c34f1ef451fd46b503f00'),
|
||||
// Field over which we'll do calculations. Verify with:
|
||||
// 2n ** 521n - 1n,
|
||||
P: BigInt('0x1ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff'),
|
||||
// Curve order, total count of valid points in the field. Verify with:
|
||||
n: BigInt('0x01fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa51868783bf2f966b7fcc0148f709a5d03bb5c9b8899c47aebb6fb71e91386409'),
|
||||
// Base point (x, y) aka generator point
|
||||
Gx: BigInt('0x00c6858e06b70404e9cd9e3ecb662395b4429c648139053fb521f828af606b4d3dbaa14b5e77efe75928fe1dc127a2ffa8de3348b3c1856a429bf97e7e31c2e5bd66'),
|
||||
Gy: BigInt('0x011839296a789a3bc0045c8a5fb42c7d1bd998f54449579b446817afbd17273e662c97ee72995ef42640c550b9013fad0761353c7086a272c24088be94769fd16650'),
|
||||
h: BigInt(1),
|
||||
lowS: false,
|
||||
} as const, sha512);
|
||||
export const secp521r1 = P521;
|
@ -1,35 +1,31 @@
|
||||
/*! @noble/curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
|
||||
import { hmac } from '@noble/hashes/hmac';
|
||||
import { sha256 } from '@noble/hashes/sha256';
|
||||
import { concatBytes, randomBytes } from '@noble/hashes/utils';
|
||||
import { weierstrass, CHash } from '@noble/curves/weierstrass';
|
||||
import { weierstrass } from '@noble/curves/weierstrass';
|
||||
import { getHash } from './_shortw_utils.js';
|
||||
import * as mod from '@noble/curves/modular';
|
||||
|
||||
function getHash(hash: CHash) {
|
||||
return {
|
||||
hash,
|
||||
hmac: (key: Uint8Array, ...msgs: Uint8Array[]) => hmac(hash, key, concatBytes(...msgs)),
|
||||
randomBytes,
|
||||
};
|
||||
}
|
||||
const p = BigInt('0x40000000000000000000000000000000224698fc094cf91b992d30ed00000001');
|
||||
const q = BigInt('0x40000000000000000000000000000000224698fc0994a8dd8c46eb2100000001');
|
||||
|
||||
// https://neuromancer.sk/std/other/Pallas
|
||||
export const pallas = weierstrass({
|
||||
a: BigInt(0),
|
||||
b: BigInt(5),
|
||||
P: p,
|
||||
n: q,
|
||||
Gx: BigInt(-1),
|
||||
Gx: mod.mod(BigInt(-1), p),
|
||||
Gy: BigInt(2),
|
||||
h: BigInt(1),
|
||||
...getHash(sha256),
|
||||
});
|
||||
|
||||
// https://neuromancer.sk/std/other/Vesta
|
||||
export const vesta = weierstrass({
|
||||
a: BigInt(0),
|
||||
b: BigInt(5),
|
||||
P: q,
|
||||
n: p,
|
||||
Gx: BigInt(-1),
|
||||
Gx: mod.mod(BigInt(-1), q),
|
||||
Gy: BigInt(2),
|
||||
h: BigInt(1),
|
||||
...getHash(sha256),
|
||||
});
|
||||
|
90
curve-definitions/src/secp256k1.ts
Normal file
90
curve-definitions/src/secp256k1.ts
Normal file
@ -0,0 +1,90 @@
|
||||
/*! @noble/curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
|
||||
import { sha256 } from '@noble/hashes/sha256';
|
||||
import { mod, pow2 } from '@noble/curves/modular';
|
||||
import { createCurve } from './_shortw_utils.js';
|
||||
|
||||
/**
|
||||
* secp256k1 definition with efficient square root and endomorphism.
|
||||
* Endomorphism works only for Koblitz curves with a == 0.
|
||||
* It improves efficiency:
|
||||
* Uses 2x less RAM, speeds up precomputation by 2x and ECDH / sign key recovery by 20%.
|
||||
* Should always be used for Jacobian's double-and-add multiplication.
|
||||
* For affines cached multiplication, it trades off 1/2 init time & 1/3 ram for 20% perf hit.
|
||||
* https://gist.github.com/paulmillr/eb670806793e84df628a7c434a873066
|
||||
*/
|
||||
const secp256k1P = BigInt('0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f');
|
||||
const secp256k1N = BigInt('0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141');
|
||||
const _1n = BigInt(1);
|
||||
const _2n = BigInt(2);
|
||||
const divNearest = (a: bigint, b: bigint) => (a + b / _2n) / b;
|
||||
export const secp256k1 = createCurve(
|
||||
{
|
||||
a: 0n,
|
||||
b: 7n,
|
||||
// Field over which we'll do calculations. Verify with:
|
||||
// 2n ** 256n - 2n ** 32n - 2n ** 9n - 2n ** 8n - 2n ** 7n - 2n ** 6n - 2n ** 4n - 1n
|
||||
P: secp256k1P,
|
||||
// Curve order, total count of valid points in the field. Verify with:
|
||||
n: secp256k1N,
|
||||
// Base point (x, y) aka generator point
|
||||
Gx: BigInt('55066263022277343669578718895168534326250603453777594175500187360389116729240'),
|
||||
Gy: BigInt('32670510020758816978083085130507043184471273380659243275938904335757337482424'),
|
||||
h: BigInt(1),
|
||||
// noble-secp256k1 compat
|
||||
lowS: true,
|
||||
// Used to calculate y - the square root of y².
|
||||
// Exponentiates it to very big number (P+1)/4.
|
||||
// We are unwrapping the loop because it's 2x faster.
|
||||
// (P+1n/4n).toString(2) would produce bits [223x 1, 0, 22x 1, 4x 0, 11, 00]
|
||||
// We are multiplying it bit-by-bit
|
||||
sqrtMod: (x: bigint): bigint => {
|
||||
const P = secp256k1P;
|
||||
const _3n = BigInt(3);
|
||||
const _6n = BigInt(6);
|
||||
const _11n = BigInt(11);
|
||||
const _22n = BigInt(22);
|
||||
const _23n = BigInt(23);
|
||||
const _44n = BigInt(44);
|
||||
const _88n = BigInt(88);
|
||||
const b2 = (x * x * x) % P; // x^3, 11
|
||||
const b3 = (b2 * b2 * x) % P; // x^7
|
||||
const b6 = (pow2(b3, _3n, P) * b3) % P;
|
||||
const b9 = (pow2(b6, _3n, P) * b3) % P;
|
||||
const b11 = (pow2(b9, _2n, P) * b2) % P;
|
||||
const b22 = (pow2(b11, _11n, P) * b11) % P;
|
||||
const b44 = (pow2(b22, _22n, P) * b22) % P;
|
||||
const b88 = (pow2(b44, _44n, P) * b44) % P;
|
||||
const b176 = (pow2(b88, _88n, P) * b88) % P;
|
||||
const b220 = (pow2(b176, _44n, P) * b44) % P;
|
||||
const b223 = (pow2(b220, _3n, P) * b3) % P;
|
||||
const t1 = (pow2(b223, _23n, P) * b22) % P;
|
||||
const t2 = (pow2(t1, _6n, P) * b2) % P;
|
||||
return pow2(t2, _2n, P);
|
||||
},
|
||||
endo: {
|
||||
beta: BigInt('0x7ae96a2b657c07106e64479eac3434e99cf0497512f58995c1396c28719501ee'),
|
||||
splitScalar: (k: bigint) => {
|
||||
const n = secp256k1N;
|
||||
const a1 = BigInt('0x3086d221a7d46bcde86c90e49284eb15');
|
||||
const b1 = -_1n * BigInt('0xe4437ed6010e88286f547fa90abfe4c3');
|
||||
const a2 = BigInt('0x114ca50f7a8e2f3f657c1108d9d44cfd8');
|
||||
const b2 = a1;
|
||||
const POW_2_128 = BigInt('0x100000000000000000000000000000000');
|
||||
|
||||
const c1 = divNearest(b2 * k, n);
|
||||
const c2 = divNearest(-b1 * k, n);
|
||||
let k1 = mod(k - c1 * a1 - c2 * a2, n);
|
||||
let k2 = mod(-c1 * b1 - c2 * b2, n);
|
||||
const k1neg = k1 > POW_2_128;
|
||||
const k2neg = k2 > POW_2_128;
|
||||
if (k1neg) k1 = n - k1;
|
||||
if (k2neg) k2 = n - k2;
|
||||
if (k1 > POW_2_128 || k2 > POW_2_128) {
|
||||
throw new Error('splitScalar: Endomorphism failed, k=' + k);
|
||||
}
|
||||
return { k1neg, k1, k2neg, k2 };
|
||||
},
|
||||
},
|
||||
},
|
||||
sha256
|
||||
);
|
@ -30,6 +30,7 @@ export const starkCurve = weierstrass({
|
||||
// Base point (x, y) aka generator point
|
||||
Gx: 874739451078007766457464989774322083649278607533249481151382481072868806602n,
|
||||
Gy: 152666792071518830868575557812948353041420400780739481342941381225525861407n,
|
||||
h: BigInt(1),
|
||||
// Default options
|
||||
lowS: false,
|
||||
...getHash(sha256),
|
@ -1,341 +1,312 @@
|
||||
import { deepStrictEqual, throws } from 'assert';
|
||||
import { should } from 'micro-should';
|
||||
import * as nist from '../lib/nist.js';
|
||||
import { hexToBytes, bytesToHex } from '@noble/curves/utils';
|
||||
import { default as ecdsa } from './wycheproof/ecdsa_test.json' assert { type: 'json' };
|
||||
import { default as ecdh } from './wycheproof/ecdh_test.json' assert { type: 'json' };
|
||||
import * as fc from 'fast-check';
|
||||
import * as mod from '@noble/curves/modular';
|
||||
import { randomBytes } from '@noble/hashes/utils';
|
||||
// Generic tests for all curves in package
|
||||
import { secp192r1 } from '../lib/p192.js';
|
||||
import { secp224r1 } from '../lib/p224.js';
|
||||
import { secp256r1 } from '../lib/p256.js';
|
||||
import { secp384r1 } from '../lib/p384.js';
|
||||
import { secp521r1 } from '../lib/p521.js';
|
||||
import { secp256k1 } from '../lib/secp256k1.js';
|
||||
import { ed25519, ed25519ctx, ed25519ph } from '../lib/ed25519.js';
|
||||
import { ed448, ed448ph } from '../lib/ed448.js';
|
||||
import { starkCurve } from '../lib/stark.js';
|
||||
import { pallas, vesta } from '../lib/pasta.js';
|
||||
import { bn254 } from '../lib/bn.js';
|
||||
import { jubjub } from '../lib/jubjub.js';
|
||||
|
||||
const hex = bytesToHex;
|
||||
|
||||
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.P, vectors[n]);
|
||||
});
|
||||
|
||||
should('wychenproof ECDSA vectors', () => {
|
||||
for (const group of ecdsa.testGroups) {
|
||||
// Tested in secp256k1.test.js
|
||||
if (group.key.curve === 'secp256k1') continue;
|
||||
// We don't have SHA-224
|
||||
if (group.key.curve === 'secp224r1' && group.sha === 'SHA-224') continue;
|
||||
const CURVE = nist[group.key.curve];
|
||||
if (!CURVE) continue;
|
||||
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_secp256r1_test } from './wycheproof/ecdh_secp256r1_test.json' assert { type: 'json' };
|
||||
import { default as ecdh_secp256k1_test } from './wycheproof/ecdh_secp256k1_test.json' assert { type: 'json' };
|
||||
import { default as ecdh_secp384r1_test } from './wycheproof/ecdh_secp384r1_test.json' assert { type: 'json' };
|
||||
import { default as ecdh_secp521r1_test } from './wycheproof/ecdh_secp521r1_test.json' assert { type: 'json' };
|
||||
|
||||
// More per curve tests
|
||||
const WYCHEPROOF_ECDH = {
|
||||
P224: {
|
||||
curve: nist.P224,
|
||||
tests: [ecdh_secp224r1_test],
|
||||
},
|
||||
P256: {
|
||||
curve: nist.P256,
|
||||
tests: [ecdh_secp256r1_test],
|
||||
},
|
||||
secp256k1: {
|
||||
curve: nist.secp256k1,
|
||||
tests: [ecdh_secp256k1_test],
|
||||
},
|
||||
P384: {
|
||||
curve: nist.P384,
|
||||
tests: [ecdh_secp384r1_test],
|
||||
},
|
||||
P521: {
|
||||
curve: nist.P521,
|
||||
tests: [ecdh_secp521r1_test],
|
||||
},
|
||||
// prettier-ignore
|
||||
const CURVES = {
|
||||
secp192r1, secp224r1, secp256k1, secp256r1, secp384r1, secp521r1,
|
||||
ed25519, ed25519ctx, ed25519ph,
|
||||
ed448, ed448ph,
|
||||
starkCurve,
|
||||
pallas, vesta,
|
||||
bn254,
|
||||
jubjub,
|
||||
};
|
||||
|
||||
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');
|
||||
const NUM_RUNS = 5;
|
||||
const getXY = (p) => ({ x: p.x, y: p.y });
|
||||
|
||||
function equal(a, b, comment) {
|
||||
deepStrictEqual(a.equals(b), true, `eq(${comment})`);
|
||||
if (a.toAffine && b.toAffine) {
|
||||
deepStrictEqual(getXY(a.toAffine()), getXY(b.toAffine()), `eqToAffine(${comment})`);
|
||||
} else if (!a.toAffine && !b.toAffine) {
|
||||
// Already affine
|
||||
deepStrictEqual(getXY(a), getXY(b), `eqAffine(${comment})`);
|
||||
} else throw new Error('Different point types');
|
||||
}
|
||||
|
||||
for (const name in CURVES) {
|
||||
const C = CURVES[name];
|
||||
const CURVE_ORDER = C.CURVE.n;
|
||||
const FC_BIGINT = fc.bigInt(1n + 1n, CURVE_ORDER - 1n);
|
||||
|
||||
const POINTS = { Point: C.Point, JacobianPoint: C.JacobianPoint, ExtendedPoint: C.ExtendedPoint };
|
||||
// Check that curve doesn't accept points from other curves
|
||||
const O = name === 'secp256k1' ? secp256r1 : secp256k1;
|
||||
const OTHER_POINTS = {
|
||||
Point: O.Point,
|
||||
JacobianPoint: O.JacobianPoint,
|
||||
ExtendedPoint: O.ExtendedPoint,
|
||||
};
|
||||
for (const pointName in POINTS) {
|
||||
const p = POINTS[pointName];
|
||||
const o = OTHER_POINTS[pointName];
|
||||
if (!p) continue;
|
||||
|
||||
const G = [p.ZERO, p.BASE];
|
||||
for (let i = 2; i < 10; i++) G.push(G[1].multiply(i));
|
||||
// Here we check basic group laws, to verify that points works as group
|
||||
should(`${name}/${pointName}/Basic group laws (zero)`, () => {
|
||||
equal(G[0].double(), G[0], '(0*G).double() = 0');
|
||||
equal(G[0].add(G[0]), G[0], '0*G + 0*G = 0');
|
||||
equal(G[0].subtract(G[0]), G[0], '0*G - 0*G = 0');
|
||||
equal(G[0].negate(), G[0], '-0 = 0');
|
||||
for (let i = 0; i < G.length; i++) {
|
||||
const p = G[i];
|
||||
equal(p, p.add(G[0]), `${i}*G + 0 = ${i}*G`);
|
||||
equal(G[0].multiply(i + 1), G[0], `${i + 1}*0 = 0`);
|
||||
}
|
||||
});
|
||||
should(`${name}/${pointName}/Basic group laws (one)`, () => {
|
||||
equal(G[1].double(), G[2], '(1*G).double() = 2*G');
|
||||
equal(G[1].subtract(G[1]), G[0], '1*G - 1*G = 0');
|
||||
equal(G[1].add(G[1]), G[2], '1*G + 1*G = 2*G');
|
||||
});
|
||||
should(`${name}/${pointName}/Basic group laws (sanity tests)`, () => {
|
||||
equal(G[2].double(), G[4], `(2*G).double() = 4*G`);
|
||||
equal(G[2].add(G[2]), G[4], `2*G + 2*G = 4*G`);
|
||||
equal(G[7].add(G[3].negate()), G[4], `7*G - 3*G = 4*G`);
|
||||
});
|
||||
should(`${name}/${pointName}/Basic group laws (addition commutativity)`, () => {
|
||||
equal(G[4].add(G[3]), G[3].add(G[4]), `4*G + 3*G = 3*G + 4*G`);
|
||||
equal(G[4].add(G[3]), G[3].add(G[2]).add(G[2]), `4*G + 3*G = 3*G + 2*G + 2*G`);
|
||||
});
|
||||
should(`${name}/${pointName}/Basic group laws (double)`, () => {
|
||||
equal(G[3].double(), G[6], '(3*G).double() = 6*G');
|
||||
});
|
||||
should(`${name}/${pointName}/Basic group laws (multiply)`, () => {
|
||||
equal(G[2].multiply(3), G[6], '(2*G).multiply(3) = 6*G');
|
||||
});
|
||||
should(`${name}/${pointName}/Basic group laws (same point addition)`, () => {
|
||||
equal(G[3].add(G[3]), G[6], `3*G + 3*G = 6*G`);
|
||||
});
|
||||
should(`${name}/${pointName}/Basic group laws (same point (negative) addition)`, () => {
|
||||
equal(G[3].add(G[3].negate()), G[0], '3*G + (- 3*G) = 0*G');
|
||||
equal(G[3].subtract(G[3]), G[0], '3*G - 3*G = 0*G');
|
||||
});
|
||||
should(`${name}/${pointName}/Basic group laws (curve order)`, () => {
|
||||
equal(G[1].multiply(CURVE_ORDER - 1n).add(G[1]), G[0], '(N-1)*G + G = 0');
|
||||
equal(G[1].multiply(CURVE_ORDER - 1n).add(G[2]), G[1], '(N-1)*G + 2*G = 1*G');
|
||||
equal(G[1].multiply(CURVE_ORDER - 2n).add(G[2]), G[0], '(N-2)*G + 2*G = 0');
|
||||
const half = CURVE_ORDER / 2n;
|
||||
const carry = CURVE_ORDER % 2n === 1n ? G[1] : G[0];
|
||||
equal(G[1].multiply(half).double().add(carry), G[0], '((N/2) * G).double() = 0');
|
||||
});
|
||||
should(`${name}/${pointName}/Basic group laws (inversion)`, () => {
|
||||
const a = 1234n;
|
||||
const b = 5678n;
|
||||
const c = a * b;
|
||||
equal(G[1].multiply(a).multiply(b), G[1].multiply(c), 'a*b*G = c*G');
|
||||
const inv = mod.invert(b, CURVE_ORDER);
|
||||
equal(G[1].multiply(c).multiply(inv), G[1].multiply(a), 'c*G * (1/b)*G = a*G');
|
||||
});
|
||||
should(`${name}/${pointName}/Basic group laws (multiply, rand)`, () =>
|
||||
fc.assert(
|
||||
fc.property(FC_BIGINT, FC_BIGINT, (a, b) => {
|
||||
const c = mod.mod(a + b, CURVE_ORDER);
|
||||
const pA = G[1].multiply(a);
|
||||
const pB = G[1].multiply(b);
|
||||
const pC = G[1].multiply(c);
|
||||
equal(pA.add(pB), pB.add(pA), `pA + pB = pB + pA`);
|
||||
equal(pA.add(pB), pC, `pA + pB = pC`);
|
||||
}),
|
||||
{ numRuns: NUM_RUNS }
|
||||
)
|
||||
);
|
||||
should(`${name}/${pointName}/Basic group laws (multiply2, rand)`, () =>
|
||||
fc.assert(
|
||||
fc.property(FC_BIGINT, FC_BIGINT, (a, b) => {
|
||||
const c = mod.mod(a * b, CURVE_ORDER);
|
||||
const pA = G[1].multiply(a);
|
||||
const pB = G[1].multiply(b);
|
||||
equal(pA.multiply(b), pB.multiply(a), `b*pA = a*pB`);
|
||||
equal(pA.multiply(b), G[1].multiply(c), `b*pA = c*G`);
|
||||
}),
|
||||
{ numRuns: NUM_RUNS }
|
||||
)
|
||||
);
|
||||
|
||||
for (const op of ['add', 'subtract']) {
|
||||
should(`${name}/${pointName}/${op} type check`, () => {
|
||||
throws(() => G[1][op](0), '0');
|
||||
throws(() => G[1][op](0n), '0n');
|
||||
G[1][op](G[2]);
|
||||
throws(() => G[1][op](CURVE_ORDER), 'CURVE_ORDER');
|
||||
throws(() => G[1][op](123.456), '123.456');
|
||||
throws(() => G[1][op](true), 'true');
|
||||
throws(() => G[1][op]('1'), "'1'");
|
||||
throws(() => G[1][op]({ x: 1n, y: 1n, z: 1n, t: 1n }), '{ x: 1n, y: 1n, z: 1n, t: 1n }');
|
||||
throws(() => G[1][op](new Uint8Array([])), 'ui8a([])');
|
||||
throws(() => G[1][op](new Uint8Array([0])), 'ui8a([0])');
|
||||
throws(() => G[1][op](new Uint8Array([1])), 'ui8a([1])');
|
||||
throws(() => G[1][op](new Uint8Array(4096).fill(1)), 'ui8a(4096*[1])');
|
||||
if (G[1].toAffine) throws(() => G[1][op](C.Point.BASE), `Point ${op} ${pointName}`);
|
||||
throws(() => G[1][op](O.BASE), `${op}/other curve point`);
|
||||
});
|
||||
}
|
||||
|
||||
should(`${name}/${pointName}/equals type check`, () => {
|
||||
throws(() => G[1].equals(0), '0');
|
||||
throws(() => G[1].equals(0n), '0n');
|
||||
deepStrictEqual(G[1].equals(G[2]), false, '1*G != 2*G');
|
||||
deepStrictEqual(G[1].equals(G[1]), true, '1*G == 1*G');
|
||||
deepStrictEqual(G[2].equals(G[2]), true, '2*G == 2*G');
|
||||
throws(() => G[1].equals(CURVE_ORDER), 'CURVE_ORDER');
|
||||
throws(() => G[1].equals(123.456), '123.456');
|
||||
throws(() => G[1].equals(true), 'true');
|
||||
throws(() => G[1].equals('1'), "'1'");
|
||||
throws(() => G[1].equals({ x: 1n, y: 1n, z: 1n, t: 1n }), '{ x: 1n, y: 1n, z: 1n, t: 1n }');
|
||||
throws(() => G[1].equals(new Uint8Array([])), 'ui8a([])');
|
||||
throws(() => G[1].equals(new Uint8Array([0])), 'ui8a([0])');
|
||||
throws(() => G[1].equals(new Uint8Array([1])), 'ui8a([1])');
|
||||
throws(() => G[1].equals(new Uint8Array(4096).fill(1)), 'ui8a(4096*[1])');
|
||||
if (G[1].toAffine) throws(() => G[1].equals(C.Point.BASE), `Point.equals(${pointName})`);
|
||||
throws(() => G[1].equals(O.BASE), 'other curve point');
|
||||
});
|
||||
|
||||
for (const op of ['multiply', 'multiplyUnsafe']) {
|
||||
if (!p.BASE[op]) continue;
|
||||
should(`${name}/${pointName}/${op} type check`, () => {
|
||||
if (op !== 'multiplyUnsafe') {
|
||||
throws(() => G[1][op](0), '0');
|
||||
throws(() => G[1][op](0n), '0n');
|
||||
}
|
||||
G[1][op](1n);
|
||||
G[1][op](CURVE_ORDER - 1n);
|
||||
throws(() => G[1][op](G[2]), 'G[2]');
|
||||
throws(() => G[1][op](CURVE_ORDER), 'CURVE_ORDER');
|
||||
throws(() => G[1][op](CURVE_ORDER + 1n), 'CURVE_ORDER+1');
|
||||
throws(() => G[1][op](123.456), '123.456');
|
||||
throws(() => G[1][op](true), 'true');
|
||||
throws(() => G[1][op]('1'), '1');
|
||||
throws(() => G[1][op](new Uint8Array([])), 'ui8a([])');
|
||||
throws(() => G[1][op](new Uint8Array([0])), 'ui8a([0])');
|
||||
throws(() => G[1][op](new Uint8Array([1])), 'ui8a([1])');
|
||||
throws(() => G[1][op](new Uint8Array(4096).fill(1)), 'ui8a(4096*[1])');
|
||||
throws(() => G[1][op](O.BASE), 'other curve point');
|
||||
});
|
||||
}
|
||||
// Complex point (Extended/Jacobian/Projective?)
|
||||
if (p.BASE.toAffine) {
|
||||
should(`${name}/${pointName}/toAffine()`, () => {
|
||||
equal(p.ZERO.toAffine(), C.Point.ZERO, `0 = 0`);
|
||||
equal(p.BASE.toAffine(), C.Point.BASE, `1 = 1`);
|
||||
});
|
||||
}
|
||||
if (p.fromAffine) {
|
||||
should(`${name}/${pointName}/fromAffine()`, () => {
|
||||
equal(p.ZERO, p.fromAffine(C.Point.ZERO), `0 = 0`);
|
||||
equal(p.BASE, p.fromAffine(C.Point.BASE), `1 = 1`);
|
||||
});
|
||||
}
|
||||
// toHex/fromHex (if available)
|
||||
if (p.fromHex && p.BASE.toHex) {
|
||||
should(`${name}/${pointName}/fromHex(toHex()) roundtrip`, () => {
|
||||
fc.assert(
|
||||
fc.property(FC_BIGINT, (x) => {
|
||||
const hex = p.BASE.multiply(x).toHex();
|
||||
deepStrictEqual(p.fromHex(hex).toHex(), hex);
|
||||
})
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
// Generic complex things (getPublicKey/sign/verify/getSharedSecret)
|
||||
should(`${name}/getPublicKey type check`, () => {
|
||||
throws(() => C.getPublicKey(0), '0');
|
||||
throws(() => C.getPublicKey(0n), '0n');
|
||||
throws(() => C.getPublicKey(false), 'false');
|
||||
throws(() => C.getPublicKey(123.456), '123.456');
|
||||
throws(() => C.getPublicKey(true), 'true');
|
||||
throws(() => C.getPublicKey(''), "''");
|
||||
// NOTE: passes because of disabled hex padding checks for starknet, maybe enable?
|
||||
//throws(() => C.getPublicKey('1'), "'1'");
|
||||
throws(() => C.getPublicKey('key'), "'key'");
|
||||
throws(() => C.getPublicKey(new Uint8Array([])));
|
||||
throws(() => C.getPublicKey(new Uint8Array([0])));
|
||||
throws(() => C.getPublicKey(new Uint8Array([1])));
|
||||
throws(() => C.getPublicKey(new Uint8Array(4096).fill(1)));
|
||||
});
|
||||
should(`${name}.verify()/should verify random signatures`, () =>
|
||||
fc.assert(
|
||||
fc.property(fc.hexaString({ minLength: 64, maxLength: 64 }), (msg) => {
|
||||
const priv = C.utils.randomPrivateKey();
|
||||
const pub = C.getPublicKey(priv);
|
||||
const sig = C.sign(msg, priv);
|
||||
deepStrictEqual(C.verify(sig, msg, pub), true);
|
||||
}),
|
||||
{ numRuns: NUM_RUNS }
|
||||
)
|
||||
);
|
||||
should(`${name}.sign()/edge cases`, () => {
|
||||
throws(() => C.sign());
|
||||
throws(() => C.sign(''));
|
||||
});
|
||||
|
||||
// Tests with custom hashes
|
||||
import { default as secp224r1_sha224_test } from './wycheproof/ecdsa_secp224r1_sha224_test.json' assert { type: 'json' };
|
||||
import { default as secp224r1_sha256_test } from './wycheproof/ecdsa_secp224r1_sha256_test.json' assert { type: 'json' };
|
||||
import { default as secp224r1_sha3_224_test } from './wycheproof/ecdsa_secp224r1_sha3_224_test.json' assert { type: 'json' };
|
||||
import { default as secp224r1_sha3_256_test } from './wycheproof/ecdsa_secp224r1_sha3_256_test.json' assert { type: 'json' };
|
||||
import { default as secp224r1_sha3_512_test } from './wycheproof/ecdsa_secp224r1_sha3_512_test.json' assert { type: 'json' };
|
||||
import { default as secp224r1_sha512_test } from './wycheproof/ecdsa_secp224r1_sha512_test.json' assert { type: 'json' };
|
||||
should(`${name}.verify()/should not verify signature with wrong hash`, () => {
|
||||
const MSG = '01'.repeat(32);
|
||||
const PRIV_KEY = 0x2n;
|
||||
const WRONG_MSG = '11'.repeat(32);
|
||||
const signature = C.sign(MSG, PRIV_KEY);
|
||||
const publicKey = C.getPublicKey(PRIV_KEY);
|
||||
deepStrictEqual(C.verify(signature, WRONG_MSG, publicKey), false);
|
||||
});
|
||||
// NOTE: fails for ed, because of empty message. Since we convert it to scalar,
|
||||
// need to check what other implementations do. Empty message != new Uint8Array([0]), but what scalar should be in that case?
|
||||
// should(`${name}/should not verify signature with wrong message`, () => {
|
||||
// fc.assert(
|
||||
// fc.property(
|
||||
// fc.array(fc.integer({ min: 0x00, max: 0xff })),
|
||||
// fc.array(fc.integer({ min: 0x00, max: 0xff })),
|
||||
// (bytes, wrongBytes) => {
|
||||
// const privKey = C.utils.randomPrivateKey();
|
||||
// const message = new Uint8Array(bytes);
|
||||
// const wrongMessage = new Uint8Array(wrongBytes);
|
||||
// const publicKey = C.getPublicKey(privKey);
|
||||
// const signature = C.sign(message, privKey);
|
||||
// deepStrictEqual(
|
||||
// C.verify(signature, wrongMessage, publicKey),
|
||||
// bytes.toString() === wrongBytes.toString()
|
||||
// );
|
||||
// }
|
||||
// ),
|
||||
// { numRuns: NUM_RUNS }
|
||||
// );
|
||||
// });
|
||||
|
||||
import { default as secp256k1_sha256_test } from './wycheproof/ecdsa_secp256k1_sha256_test.json' assert { type: 'json' };
|
||||
import { default as secp256k1_sha3_256_test } from './wycheproof/ecdsa_secp256k1_sha3_256_test.json' assert { type: 'json' };
|
||||
import { default as secp256k1_sha3_512_test } from './wycheproof/ecdsa_secp256k1_sha3_512_test.json' assert { type: 'json' };
|
||||
import { default as secp256k1_sha512_test } from './wycheproof/ecdsa_secp256k1_sha512_test.json' assert { type: 'json' };
|
||||
|
||||
import { default as secp256r1_sha256_test } from './wycheproof/ecdsa_secp256r1_sha256_test.json' assert { type: 'json' };
|
||||
import { default as secp256r1_sha3_256_test } from './wycheproof/ecdsa_secp256r1_sha3_256_test.json' assert { type: 'json' };
|
||||
import { default as secp256r1_sha3_512_test } from './wycheproof/ecdsa_secp256r1_sha3_512_test.json' assert { type: 'json' };
|
||||
import { default as secp256r1_sha512_test } from './wycheproof/ecdsa_secp256r1_sha512_test.json' assert { type: 'json' };
|
||||
|
||||
import { default as secp384r1_sha384_test } from './wycheproof/ecdsa_secp384r1_sha384_test.json' assert { type: 'json' };
|
||||
import { default as secp384r1_sha3_384_test } from './wycheproof/ecdsa_secp384r1_sha3_384_test.json' assert { type: 'json' };
|
||||
import { default as secp384r1_sha3_512_test } from './wycheproof/ecdsa_secp384r1_sha3_512_test.json' assert { type: 'json' };
|
||||
import { default as secp384r1_sha512_test } from './wycheproof/ecdsa_secp384r1_sha512_test.json' assert { type: 'json' };
|
||||
|
||||
import { default as secp521r1_sha3_512_test } from './wycheproof/ecdsa_secp521r1_sha3_512_test.json' assert { type: 'json' };
|
||||
import { default as secp521r1_sha512_test } from './wycheproof/ecdsa_secp521r1_sha512_test.json' assert { type: 'json' };
|
||||
|
||||
import { sha3_224, sha3_256, sha3_384, sha3_512 } from '@noble/hashes/sha3';
|
||||
import { sha512, sha384 } from '@noble/hashes/sha512';
|
||||
import { sha256 } from '@noble/hashes/sha256';
|
||||
|
||||
const WYCHEPROOF_ECDSA = {
|
||||
P224: {
|
||||
curve: nist.P224,
|
||||
hashes: {
|
||||
// sha224 not released yet
|
||||
// sha224: {
|
||||
// hash: sha224,
|
||||
// tests: [secp224r1_sha224_test],
|
||||
// },
|
||||
sha256: {
|
||||
hash: sha256,
|
||||
tests: [secp224r1_sha256_test],
|
||||
},
|
||||
sha3_224: {
|
||||
hash: sha3_224,
|
||||
tests: [secp224r1_sha3_224_test],
|
||||
},
|
||||
sha3_256: {
|
||||
hash: sha3_256,
|
||||
tests: [secp224r1_sha3_256_test],
|
||||
},
|
||||
sha3_512: {
|
||||
hash: sha3_512,
|
||||
tests: [secp224r1_sha3_512_test],
|
||||
},
|
||||
sha512: {
|
||||
hash: sha512,
|
||||
tests: [secp224r1_sha512_test],
|
||||
},
|
||||
},
|
||||
},
|
||||
secp256k1: {
|
||||
curve: nist.secp256k1,
|
||||
hashes: {
|
||||
// TODO: debug why fails, can be bug
|
||||
// sha256: {
|
||||
// hash: sha256,
|
||||
// tests: [secp256k1_sha256_test],
|
||||
// },
|
||||
// sha3_256: {
|
||||
// hash: sha3_256,
|
||||
// tests: [secp256k1_sha3_256_test],
|
||||
// },
|
||||
// sha3_512: {
|
||||
// hash: sha3_512,
|
||||
// tests: [secp256k1_sha3_512_test],
|
||||
// },
|
||||
// sha512: {
|
||||
// hash: sha512,
|
||||
// tests: [secp256k1_sha512_test],
|
||||
// },
|
||||
},
|
||||
},
|
||||
P256: {
|
||||
curve: nist.P256,
|
||||
hashes: {
|
||||
sha256: {
|
||||
hash: sha256,
|
||||
tests: [secp256r1_sha256_test],
|
||||
},
|
||||
sha3_256: {
|
||||
hash: sha3_256,
|
||||
tests: [secp256r1_sha3_256_test],
|
||||
},
|
||||
sha3_512: {
|
||||
hash: sha3_512,
|
||||
tests: [secp256r1_sha3_512_test],
|
||||
},
|
||||
sha512: {
|
||||
hash: sha512,
|
||||
tests: [secp256r1_sha512_test],
|
||||
},
|
||||
},
|
||||
},
|
||||
P384: {
|
||||
curve: nist.P384,
|
||||
hashes: {
|
||||
sha384: {
|
||||
hash: sha384,
|
||||
tests: [secp384r1_sha384_test],
|
||||
},
|
||||
sha3_384: {
|
||||
hash: sha3_384,
|
||||
tests: [secp384r1_sha3_384_test],
|
||||
},
|
||||
sha3_512: {
|
||||
hash: sha3_512,
|
||||
tests: [secp384r1_sha3_512_test],
|
||||
},
|
||||
sha512: {
|
||||
hash: sha512,
|
||||
tests: [secp384r1_sha512_test],
|
||||
},
|
||||
},
|
||||
},
|
||||
P521: {
|
||||
curve: nist.P521,
|
||||
hashes: {
|
||||
sha3_512: {
|
||||
hash: sha3_512,
|
||||
tests: [secp521r1_sha3_512_test],
|
||||
},
|
||||
sha512: {
|
||||
hash: sha512,
|
||||
tests: [secp521r1_sha512_test],
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
for (const name in WYCHEPROOF_ECDSA) {
|
||||
const { curve, hashes } = WYCHEPROOF_ECDSA[name];
|
||||
for (const hName in hashes) {
|
||||
const { hash, tests } = hashes[hName];
|
||||
const CURVE = curve.create(hash);
|
||||
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/WYCHEPROOF_ECDSA ${name}/${hName} (${i}/${j})`, () => {
|
||||
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');
|
||||
}
|
||||
});
|
||||
if (C.getSharedSecret) {
|
||||
should(`${name}/getSharedSecret() should be commutative`, () => {
|
||||
for (let i = 0; i < NUM_RUNS; i++) {
|
||||
const asec = C.utils.randomPrivateKey();
|
||||
const apub = C.getPublicKey(asec);
|
||||
const bsec = C.utils.randomPrivateKey();
|
||||
const bpub = C.getPublicKey(bsec);
|
||||
try {
|
||||
deepStrictEqual(C.getSharedSecret(asec, bpub), C.getSharedSecret(bsec, apub));
|
||||
} catch (error) {
|
||||
console.error('not commutative', { asec, apub, bsec, bpub });
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// ESM is broken.
|
||||
import url from 'url';
|
||||
if (import.meta.url === url.pathToFileURL(process.argv[1]).href) {
|
||||
|
@ -5,6 +5,7 @@ import { ed25519, ed25519ctx, ed25519ph, x25519 } from '../lib/ed25519.js';
|
||||
import { readFileSync } from 'fs';
|
||||
import { default as zip215 } from './ed25519/zip215.json' assert { type: 'json' };
|
||||
import { hexToBytes, bytesToHex, randomBytes } from '@noble/hashes/utils';
|
||||
import { numberToBytesLE } from '@noble/curves/utils';
|
||||
import { default as ed25519vectors } from './wycheproof/eddsa_test.json' assert { type: 'json' };
|
||||
import { default as x25519vectors } from './wycheproof/x25519_test.json' assert { type: 'json' };
|
||||
|
||||
@ -434,7 +435,6 @@ should('ZIP-215 compliance tests/should pass all of them', () => {
|
||||
});
|
||||
should('ZIP-215 compliance tests/disallows sig.s >= CURVE.n', () => {
|
||||
const sig = new ed.Signature(ed.Point.BASE, 1n);
|
||||
// @ts-ignore
|
||||
sig.s = ed.CURVE.n + 1n;
|
||||
throws(() => ed.verify(sig, 'deadbeef', ed.Point.BASE));
|
||||
});
|
||||
@ -646,6 +646,12 @@ for (let i = 0; i < VECTORS_RFC8032_PH.length; i++) {
|
||||
});
|
||||
}
|
||||
|
||||
should('X25519 base point', () => {
|
||||
const { y } = ed25519.Point.BASE;
|
||||
const u = ed25519.utils.mod((y + 1n) * ed25519.utils.invert(1n - y, ed25519.CURVE.P));
|
||||
deepStrictEqual(hex(numberToBytesLE(u, 32)), x25519.Gu);
|
||||
});
|
||||
|
||||
// ESM is broken.
|
||||
import url from 'url';
|
||||
if (import.meta.url === url.pathToFileURL(process.argv[1]).href) {
|
||||
|
@ -3,6 +3,7 @@ import { should } from 'micro-should';
|
||||
import * as fc from 'fast-check';
|
||||
import { ed448, ed448ph, x448 } from '../lib/ed448.js';
|
||||
import { hexToBytes, bytesToHex, randomBytes } from '@noble/hashes/utils';
|
||||
import { numberToBytesLE } from '@noble/curves/utils';
|
||||
import { default as ed448vectors } from './wycheproof/ed448_test.json' assert { type: 'json' };
|
||||
import { default as x448vectors } from './wycheproof/x448_test.json' assert { type: 'json' };
|
||||
|
||||
@ -646,6 +647,14 @@ for (let i = 0; i < VECTORS_RFC8032_PH.length; i++) {
|
||||
});
|
||||
}
|
||||
|
||||
should('X448 base point', () => {
|
||||
const { x, y } = ed448.Point.BASE;
|
||||
const { P } = ed448.CURVE;
|
||||
const invX = ed448.utils.invert(x * x, P); // x^2
|
||||
const u = ed448.utils.mod(y * y * invX, P); // (y^2/x^2)
|
||||
deepStrictEqual(hex(numberToBytesLE(u, 56)), x448.Gu);
|
||||
});
|
||||
|
||||
// ESM is broken.
|
||||
import url from 'url';
|
||||
if (import.meta.url === url.pathToFileURL(process.argv[1]).href) {
|
||||
|
@ -1,58 +0,0 @@
|
||||
import { deepStrictEqual, throws } from 'assert';
|
||||
import { should } from 'micro-should';
|
||||
import * as fc from 'fast-check';
|
||||
// Generic tests for all curves in package
|
||||
import { secp192r1, secp224r1, secp256k1, secp256r1, secp384r1, secp521r1 } from '../lib/nist.js';
|
||||
import { ed25519, ed25519ctx, ed25519ph, ed448, ed448ph } from '../lib/ed.js';
|
||||
import { starkCurve } from '../lib/starknet.js';
|
||||
import { pallas, vesta } from '../lib/pasta.js';
|
||||
import { bn254 } from '../lib/bn.js';
|
||||
|
||||
// prettier-ignore
|
||||
const CURVES = {
|
||||
secp192r1, secp224r1, secp256k1, secp256r1, secp384r1, secp521r1,
|
||||
ed25519, ed25519ctx, ed25519ph, ed448, ed448ph,
|
||||
starkCurve,
|
||||
pallas, vesta,
|
||||
bn254,
|
||||
};
|
||||
|
||||
for (const name in CURVES) {
|
||||
const C = CURVES[name];
|
||||
// Generic sanity tests:
|
||||
// - group laws:
|
||||
// G*(CURVE.n-1) + 1 = Point.ZERO
|
||||
// G*(CURVE.n-2) + 2 = Point.ZERO
|
||||
// G*(CURVE.n/2).double() = Point.ZERO or Point.BASE?
|
||||
// rand*G + rand2*G = G*(rand+rand mod N)
|
||||
// - double works
|
||||
// ZERO.double() == zero
|
||||
// - adding zero point works
|
||||
// - add(samePoint) works
|
||||
// - add(-samePoint) works
|
||||
// - 2+2 = 2.double() = 7-5 (should have different Z coordinates, but it is same point)
|
||||
// ToAffine: Point.BASE = Extended/Jacobian.toAffine()
|
||||
// Property tests:
|
||||
// signatures, getSharedKey/etc
|
||||
//const FC_BIGINT = fc.bigInt(1n + 1n, C.n - 1n);
|
||||
|
||||
should(`${name}/Basic`, () => {});
|
||||
const POINTS = { Point: C.Point, JacobianPoint: C.JacobianPoint, ExtendedPoint: C.ExtendedPoint };
|
||||
for (const pointName in POINTS) {
|
||||
const p = POINTS[pointName];
|
||||
if (!p) continue;
|
||||
|
||||
const G = [p.ZERO, p.BASE];
|
||||
for (let i = 2; i < 10; i++) G.push(G[1].multiply(i));
|
||||
should(`${name}/${pointName}/Basic`, () => {
|
||||
// ... And we dont have it
|
||||
//deepStrictEqual(G[2].double().equals(G[4]), true);
|
||||
//console.log('Z', G);
|
||||
});
|
||||
}
|
||||
}
|
||||
// ESM is broken.
|
||||
import url from 'url';
|
||||
if (import.meta.url === url.pathToFileURL(process.argv[1]).href) {
|
||||
should.run();
|
||||
}
|
@ -1,10 +1,12 @@
|
||||
import { should } from 'micro-should';
|
||||
|
||||
// Should be first to catch obvious things
|
||||
import './basic.test.js';
|
||||
import './rfc6979.test.js';
|
||||
import './nist.test.js';
|
||||
import './ed448.test.js';
|
||||
import './ed25519.test.js';
|
||||
import './secp256k1.test.js';
|
||||
import './starknet/starknet.test.js';
|
||||
import './stark/stark.test.js';
|
||||
import './jubjub.test.js';
|
||||
|
||||
should.run();
|
||||
|
74
curve-definitions/test/jubjub.test.js
Normal file
74
curve-definitions/test/jubjub.test.js
Normal file
@ -0,0 +1,74 @@
|
||||
import { jubjub, findGroupHash } from '../lib/jubjub.js';
|
||||
import { should } from 'micro-should';
|
||||
import { deepStrictEqual, throws } from 'assert';
|
||||
import { hexToBytes, bytesToHex } from '@noble/hashes/utils';
|
||||
|
||||
const G_SPEND = new jubjub.ExtendedPoint(
|
||||
0x055f1f24f0f0512287e51c3c5a0a6903fc0baf8711de9eafd7c0e66f69d8d2dbn,
|
||||
0x566178b2505fdd52132a5007d80a04652842e78ffb376897588f406278214ed7n,
|
||||
0x0141fafa1f11088a3b2007c14d652375888f3b37838ba6bdffae096741ceddfen,
|
||||
0x12eada93c0b7d595f5f04f5ebfb4b7d033ef2884136475cab5e41ce17db5be9cn
|
||||
);
|
||||
const G_PROOF = new jubjub.ExtendedPoint(
|
||||
0x0174d54ce9fad258a2f8a86a1deabf15c7a2b51106b0fbcd9d29020f78936f71n,
|
||||
0x16871d6d877dcd222e4ec3bccb3f37cb1865a2d37dd3a5dcbc032a69b62b4445n,
|
||||
0x57a3cd31e496d82bd4aa78bd5ecd751cfb76d54a5d3f4560866379f9fc11c9b3n,
|
||||
0x42cc53f6b519d1f4f52c47ff1256463a616c2c2f49ffe77765481eca04c72081n
|
||||
);
|
||||
|
||||
const getXY = (p) => ({ x: p.x, y: p.y });
|
||||
|
||||
should('toHex/fromHex', () => {
|
||||
// More than field
|
||||
throws(() =>
|
||||
jubjub.Point.fromHex(
|
||||
new Uint8Array([
|
||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||
])
|
||||
)
|
||||
);
|
||||
// Multiplicative generator (sqrt == null), not on curve.
|
||||
throws(() =>
|
||||
jubjub.Point.fromHex(
|
||||
new Uint8Array([
|
||||
7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0,
|
||||
])
|
||||
)
|
||||
);
|
||||
const tmp = jubjub.Point.fromHex(
|
||||
new Uint8Array([
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0,
|
||||
])
|
||||
);
|
||||
deepStrictEqual(tmp.x, 0x8d51ccce760304d0ec030002760300000001000000000000n);
|
||||
deepStrictEqual(tmp.y, 0n);
|
||||
|
||||
const S = G_SPEND.toAffine().toRawBytes();
|
||||
const S2 = G_SPEND.double().toAffine().toRawBytes();
|
||||
const P = G_PROOF.toAffine().toRawBytes();
|
||||
const P2 = G_PROOF.double().toAffine().toRawBytes();
|
||||
const S_exp = jubjub.Point.fromHex(S);
|
||||
const S2_exp = jubjub.Point.fromHex(S2);
|
||||
const P_exp = jubjub.Point.fromHex(P);
|
||||
const P2_exp = jubjub.Point.fromHex(P2);
|
||||
deepStrictEqual(getXY(G_SPEND.toAffine()), getXY(S_exp));
|
||||
deepStrictEqual(getXY(G_SPEND.double().toAffine()), getXY(S2_exp));
|
||||
deepStrictEqual(getXY(G_PROOF.toAffine()), getXY(P_exp));
|
||||
deepStrictEqual(getXY(G_PROOF.double().toAffine()), getXY(P2_exp));
|
||||
});
|
||||
|
||||
should('Find generators', () => {
|
||||
const spend = findGroupHash(new Uint8Array(), new Uint8Array([90, 99, 97, 115, 104, 95, 71, 95]));
|
||||
const proof = findGroupHash(new Uint8Array(), new Uint8Array([90, 99, 97, 115, 104, 95, 72, 95]));
|
||||
deepStrictEqual(getXY(spend.toAffine()), getXY(G_SPEND.toAffine()));
|
||||
deepStrictEqual(getXY(proof.toAffine()), getXY(G_PROOF.toAffine()));
|
||||
});
|
||||
|
||||
// ESM is broken.
|
||||
import url from 'url';
|
||||
if (import.meta.url === url.pathToFileURL(process.argv[1]).href) {
|
||||
should.run();
|
||||
}
|
381
curve-definitions/test/nist.test.js
Normal file
381
curve-definitions/test/nist.test.js
Normal file
@ -0,0 +1,381 @@
|
||||
import { deepStrictEqual, throws } from 'assert';
|
||||
import { should } from 'micro-should';
|
||||
import { secp192r1, P192 } from '../lib/p192.js';
|
||||
import { secp224r1, P224 } from '../lib/p224.js';
|
||||
import { secp256r1, P256 } from '../lib/p256.js';
|
||||
import { secp384r1, P384 } from '../lib/p384.js';
|
||||
import { secp521r1, P521 } from '../lib/p521.js';
|
||||
import { secp256k1 } from '../lib/secp256k1.js';
|
||||
import { hexToBytes, bytesToHex } from '@noble/curves/utils';
|
||||
import { default as ecdsa } from './wycheproof/ecdsa_test.json' assert { type: 'json' };
|
||||
import { default as ecdh } from './wycheproof/ecdh_test.json' assert { type: 'json' };
|
||||
import { default as rfc6979 } from './fixtures/rfc6979.json' assert { type: 'json' };
|
||||
|
||||
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.P, vectors[n]);
|
||||
});
|
||||
|
||||
should('wychenproof ECDSA vectors', () => {
|
||||
for (const group of ecdsa.testGroups) {
|
||||
// Tested in secp256k1.test.js
|
||||
if (group.key.curve === 'secp256k1') continue;
|
||||
// We don't have SHA-224
|
||||
if (group.key.curve === 'secp224r1' && group.sha === 'SHA-224') continue;
|
||||
const CURVE = NIST[group.key.curve];
|
||||
if (!CURVE) continue;
|
||||
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_secp256r1_test } from './wycheproof/ecdh_secp256r1_test.json' assert { type: 'json' };
|
||||
import { default as ecdh_secp256k1_test } from './wycheproof/ecdh_secp256k1_test.json' assert { type: 'json' };
|
||||
import { default as ecdh_secp384r1_test } from './wycheproof/ecdh_secp384r1_test.json' assert { type: 'json' };
|
||||
import { default as ecdh_secp521r1_test } from './wycheproof/ecdh_secp521r1_test.json' assert { type: 'json' };
|
||||
|
||||
// More per curve tests
|
||||
const WYCHEPROOF_ECDH = {
|
||||
P224: {
|
||||
curve: P224,
|
||||
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
|
||||
import { default as secp224r1_sha224_test } from './wycheproof/ecdsa_secp224r1_sha224_test.json' assert { type: 'json' };
|
||||
import { default as secp224r1_sha256_test } from './wycheproof/ecdsa_secp224r1_sha256_test.json' assert { type: 'json' };
|
||||
import { default as secp224r1_sha3_224_test } from './wycheproof/ecdsa_secp224r1_sha3_224_test.json' assert { type: 'json' };
|
||||
import { default as secp224r1_sha3_256_test } from './wycheproof/ecdsa_secp224r1_sha3_256_test.json' assert { type: 'json' };
|
||||
import { default as secp224r1_sha3_512_test } from './wycheproof/ecdsa_secp224r1_sha3_512_test.json' assert { type: 'json' };
|
||||
import { default as secp224r1_sha512_test } from './wycheproof/ecdsa_secp224r1_sha512_test.json' assert { type: 'json' };
|
||||
|
||||
import { default as secp256k1_sha256_test } from './wycheproof/ecdsa_secp256k1_sha256_test.json' assert { type: 'json' };
|
||||
import { default as secp256k1_sha3_256_test } from './wycheproof/ecdsa_secp256k1_sha3_256_test.json' assert { type: 'json' };
|
||||
import { default as secp256k1_sha3_512_test } from './wycheproof/ecdsa_secp256k1_sha3_512_test.json' assert { type: 'json' };
|
||||
import { default as secp256k1_sha512_test } from './wycheproof/ecdsa_secp256k1_sha512_test.json' assert { type: 'json' };
|
||||
|
||||
import { default as secp256r1_sha256_test } from './wycheproof/ecdsa_secp256r1_sha256_test.json' assert { type: 'json' };
|
||||
import { default as secp256r1_sha3_256_test } from './wycheproof/ecdsa_secp256r1_sha3_256_test.json' assert { type: 'json' };
|
||||
import { default as secp256r1_sha3_512_test } from './wycheproof/ecdsa_secp256r1_sha3_512_test.json' assert { type: 'json' };
|
||||
import { default as secp256r1_sha512_test } from './wycheproof/ecdsa_secp256r1_sha512_test.json' assert { type: 'json' };
|
||||
|
||||
import { default as secp384r1_sha384_test } from './wycheproof/ecdsa_secp384r1_sha384_test.json' assert { type: 'json' };
|
||||
import { default as secp384r1_sha3_384_test } from './wycheproof/ecdsa_secp384r1_sha3_384_test.json' assert { type: 'json' };
|
||||
import { default as secp384r1_sha3_512_test } from './wycheproof/ecdsa_secp384r1_sha3_512_test.json' assert { type: 'json' };
|
||||
import { default as secp384r1_sha512_test } from './wycheproof/ecdsa_secp384r1_sha512_test.json' assert { type: 'json' };
|
||||
|
||||
import { default as secp521r1_sha3_512_test } from './wycheproof/ecdsa_secp521r1_sha3_512_test.json' assert { type: 'json' };
|
||||
import { default as secp521r1_sha512_test } from './wycheproof/ecdsa_secp521r1_sha512_test.json' assert { type: 'json' };
|
||||
|
||||
import { sha3_224, sha3_256, sha3_384, sha3_512 } from '@noble/hashes/sha3';
|
||||
import { sha512, sha384 } from '@noble/hashes/sha512';
|
||||
import { sha256 } from '@noble/hashes/sha256';
|
||||
|
||||
const WYCHEPROOF_ECDSA = {
|
||||
P224: {
|
||||
curve: P224,
|
||||
hashes: {
|
||||
// sha224 not released yet
|
||||
// sha224: {
|
||||
// hash: sha224,
|
||||
// tests: [secp224r1_sha224_test],
|
||||
// },
|
||||
sha256: {
|
||||
hash: sha256,
|
||||
tests: [secp224r1_sha256_test],
|
||||
},
|
||||
sha3_224: {
|
||||
hash: sha3_224,
|
||||
tests: [secp224r1_sha3_224_test],
|
||||
},
|
||||
sha3_256: {
|
||||
hash: sha3_256,
|
||||
tests: [secp224r1_sha3_256_test],
|
||||
},
|
||||
sha3_512: {
|
||||
hash: sha3_512,
|
||||
tests: [secp224r1_sha3_512_test],
|
||||
},
|
||||
sha512: {
|
||||
hash: sha512,
|
||||
tests: [secp224r1_sha512_test],
|
||||
},
|
||||
},
|
||||
},
|
||||
secp256k1: {
|
||||
curve: secp256k1,
|
||||
hashes: {
|
||||
// TODO: debug why fails, can be bug
|
||||
sha256: {
|
||||
hash: sha256,
|
||||
tests: [secp256k1_sha256_test],
|
||||
},
|
||||
sha3_256: {
|
||||
hash: sha3_256,
|
||||
tests: [secp256k1_sha3_256_test],
|
||||
},
|
||||
sha3_512: {
|
||||
hash: sha3_512,
|
||||
tests: [secp256k1_sha3_512_test],
|
||||
},
|
||||
sha512: {
|
||||
hash: sha512,
|
||||
tests: [secp256k1_sha512_test],
|
||||
},
|
||||
},
|
||||
},
|
||||
P256: {
|
||||
curve: P256,
|
||||
hashes: {
|
||||
sha256: {
|
||||
hash: sha256,
|
||||
tests: [secp256r1_sha256_test],
|
||||
},
|
||||
sha3_256: {
|
||||
hash: sha3_256,
|
||||
tests: [secp256r1_sha3_256_test],
|
||||
},
|
||||
sha3_512: {
|
||||
hash: sha3_512,
|
||||
tests: [secp256r1_sha3_512_test],
|
||||
},
|
||||
sha512: {
|
||||
hash: sha512,
|
||||
tests: [secp256r1_sha512_test],
|
||||
},
|
||||
},
|
||||
},
|
||||
P384: {
|
||||
curve: P384,
|
||||
hashes: {
|
||||
sha384: {
|
||||
hash: sha384,
|
||||
tests: [secp384r1_sha384_test],
|
||||
},
|
||||
sha3_384: {
|
||||
hash: sha3_384,
|
||||
tests: [secp384r1_sha3_384_test],
|
||||
},
|
||||
sha3_512: {
|
||||
hash: sha3_512,
|
||||
tests: [secp384r1_sha3_512_test],
|
||||
},
|
||||
sha512: {
|
||||
hash: sha512,
|
||||
tests: [secp384r1_sha512_test],
|
||||
},
|
||||
},
|
||||
},
|
||||
P521: {
|
||||
curve: P521,
|
||||
hashes: {
|
||||
sha3_512: {
|
||||
hash: sha3_512,
|
||||
tests: [secp521r1_sha3_512_test],
|
||||
},
|
||||
sha512: {
|
||||
hash: sha512,
|
||||
tests: [secp521r1_sha512_test],
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
for (const name in WYCHEPROOF_ECDSA) {
|
||||
const { curve, hashes } = WYCHEPROOF_ECDSA[name];
|
||||
for (const hName in hashes) {
|
||||
const { hash, tests } = hashes[hName];
|
||||
const CURVE = curve.create(hash);
|
||||
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/WYCHEPROOF_ECDSA ${name}/${hName} (${i}/${j})`, () => {
|
||||
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) {
|
||||
const m = CURVE.CURVE.hash(hexToBytes(test.msg));
|
||||
if (test.result === 'valid' || test.result === 'acceptable') {
|
||||
try {
|
||||
CURVE.Signature.fromDER(test.sig);
|
||||
} catch (e) {
|
||||
// Some tests has invalid signature which we don't accept
|
||||
if (e.message.includes('Invalid signature: incorrect length')) continue;
|
||||
throw e;
|
||||
}
|
||||
const verified = CURVE.verify(test.sig, m, pubKey);
|
||||
if (name === 'secp256k1') {
|
||||
// lowS: true for secp256k1
|
||||
deepStrictEqual(verified, !CURVE.Signature.fromDER(test.sig).hasHighS());
|
||||
} else {
|
||||
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');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const hexToBigint = (hex) => BigInt(`0x${hex}`);
|
||||
should('RFC6979', () => {
|
||||
for (const v of rfc6979) {
|
||||
const curve = NIST[v.curve];
|
||||
deepStrictEqual(curve.CURVE.n, hexToBigint(v.q));
|
||||
const pubKey = curve.getPublicKey(v.private);
|
||||
const pubPoint = curve.Point.fromHex(pubKey);
|
||||
deepStrictEqual(pubPoint.x, hexToBigint(v.Ux));
|
||||
deepStrictEqual(pubPoint.y, hexToBigint(v.Uy));
|
||||
for (const c of v.cases) {
|
||||
const h = curve.CURVE.hash(c.message);
|
||||
const sigObj = curve.sign(h, v.private);
|
||||
deepStrictEqual(sigObj.r, hexToBigint(c.r), 'R');
|
||||
deepStrictEqual(sigObj.s, hexToBigint(c.s), 'S');
|
||||
deepStrictEqual(curve.verify(sigObj.toDERRawBytes(), h, pubKey), true, 'verify(1)');
|
||||
deepStrictEqual(curve.verify(sigObj, h, pubKey), true, 'verify(2)');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// ESM is broken.
|
||||
import url from 'url';
|
||||
if (import.meta.url === url.pathToFileURL(process.argv[1]).href) {
|
||||
should.run();
|
||||
}
|
@ -1,32 +0,0 @@
|
||||
import { deepStrictEqual } from 'assert';
|
||||
import { should } from 'micro-should';
|
||||
import * as nist from '../lib/nist.js';
|
||||
import { default as rfc6979 } from './fixtures/rfc6979.json' assert { type: 'json' };
|
||||
function hexToBigint(hex) {
|
||||
return BigInt('0x' + hex)
|
||||
}
|
||||
|
||||
should('RFC6979', () => {
|
||||
for (const v of rfc6979) {
|
||||
const curve = nist[v.curve];
|
||||
deepStrictEqual(curve.CURVE.n, hexToBigint(v.q));
|
||||
const pubKey = curve.getPublicKey(v.private);
|
||||
const pubPoint = curve.Point.fromHex(pubKey);
|
||||
deepStrictEqual(pubPoint.x, hexToBigint(v.Ux));
|
||||
deepStrictEqual(pubPoint.y, hexToBigint(v.Uy));
|
||||
for (const c of v.cases) {
|
||||
const h = curve.CURVE.hash(c.message);
|
||||
const sigObj = curve.sign(h, v.private);
|
||||
deepStrictEqual(sigObj.r, hexToBigint(c.r), 'R');
|
||||
deepStrictEqual(sigObj.s, hexToBigint(c.s), 'S');
|
||||
deepStrictEqual(curve.verify(sigObj.toDERRawBytes(), h, pubKey), true, 'verify(1)');
|
||||
deepStrictEqual(curve.verify(sigObj, h, pubKey), true, 'verify(2)');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// ESM is broken.
|
||||
import url from 'url';
|
||||
if (import.meta.url === url.pathToFileURL(process.argv[1]).href) {
|
||||
should.run();
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
import * as fc from 'fast-check';
|
||||
import * as nist from '../lib/nist.js';
|
||||
import { secp256k1 } from '../lib/secp256k1.js';
|
||||
import { readFileSync } from 'fs';
|
||||
import { default as ecdsa } from './vectors/ecdsa.json' assert { type: 'json' };
|
||||
import { default as ecdh } from './vectors/ecdh.json' assert { type: 'json' };
|
||||
@ -7,11 +7,11 @@ import { default as privates } from './vectors/privates.json' assert { type: 'js
|
||||
import { default as points } from './vectors/points.json' assert { type: 'json' };
|
||||
import { default as wp } from './vectors/wychenproof.json' assert { type: 'json' };
|
||||
import { should } from 'micro-should';
|
||||
import { deepStrictEqual, throws, rejects } from 'assert';
|
||||
import { deepStrictEqual, throws } from 'assert';
|
||||
import { hexToBytes, bytesToHex } from '@noble/hashes/utils';
|
||||
|
||||
const hex = bytesToHex;
|
||||
const secp = nist.secp256k1;
|
||||
const secp = secp256k1;
|
||||
const privatesTxt = readFileSync('./test/vectors/privates-2.txt', 'utf-8');
|
||||
const schCsv = readFileSync('./test/vectors/schnorr.csv', 'utf-8');
|
||||
|
||||
@ -192,9 +192,9 @@ should('secp256k1.Signature.fromDERHex() roundtrip', () => {
|
||||
);
|
||||
});
|
||||
|
||||
should('secp256k1.sign()/should create deterministic signatures with RFC 6979', async () => {
|
||||
should('secp256k1.sign()/should create deterministic signatures with RFC 6979', () => {
|
||||
for (const vector of ecdsa.valid) {
|
||||
let usig = await secp.sign(vector.m, vector.d);
|
||||
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));
|
||||
@ -202,25 +202,18 @@ should('secp256k1.sign()/should create deterministic signatures with RFC 6979',
|
||||
}
|
||||
});
|
||||
|
||||
should(
|
||||
'secp256k1.sign()/should not create invalid deterministic signatures with RFC 6979',
|
||||
async () => {
|
||||
for (const vector of ecdsa.invalid.sign) {
|
||||
throws(() => {
|
||||
return secp.sign(vector.m, vector.d);
|
||||
});
|
||||
}
|
||||
should('secp256k1.sign()/should not create invalid deterministic signatures with RFC 6979', () => {
|
||||
for (const vector of ecdsa.invalid.sign) {
|
||||
throws(() => secp.sign(vector.m, vector.d));
|
||||
}
|
||||
);
|
||||
|
||||
should('secp256k1.sign()/edge cases', () => {
|
||||
// @ts-ignore
|
||||
rejects(async () => await secp.sign());
|
||||
// @ts-ignore
|
||||
rejects(async () => await secp.sign(''));
|
||||
});
|
||||
|
||||
should('secp256k1.sign()/should create correct DER encoding against libsecp256k1', async () => {
|
||||
should('secp256k1.sign()/edge cases', () => {
|
||||
throws(() => secp.sign());
|
||||
throws(() => secp.sign(''));
|
||||
});
|
||||
|
||||
should('secp256k1.sign()/should create correct DER encoding against libsecp256k1', () => {
|
||||
const CASES = [
|
||||
[
|
||||
'd1a9dc8ed4e46a6a3e5e594615ca351d7d7ef44df1e4c94c1802f3592183794b',
|
||||
@ -236,14 +229,14 @@ should('secp256k1.sign()/should create correct DER encoding against libsecp256k1
|
||||
],
|
||||
];
|
||||
const privKey = hexToBytes('0101010101010101010101010101010101010101010101010101010101010101');
|
||||
for (let [msg, exp] of CASES) {
|
||||
const res = await secp.sign(msg, privKey, { extraEntropy: undefined });
|
||||
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('secp256k1.sign()/sign ecdsa extraData', async () => {
|
||||
should('secp256k1.sign()/sign ecdsa extraData', () => {
|
||||
const ent1 = '0000000000000000000000000000000000000000000000000000000000000000';
|
||||
const ent2 = '0000000000000000000000000000000000000000000000000000000000000001';
|
||||
const ent3 = '6e723d3fd94ed5d2b6bdd4f123364b0f3ca52af829988a63f8afe91d29db1c33';
|
||||
@ -251,56 +244,52 @@ should('secp256k1.sign()/sign ecdsa extraData', async () => {
|
||||
const ent5 = 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff';
|
||||
|
||||
for (const e of ecdsa.extraEntropy) {
|
||||
const sign = async (extraEntropy) => {
|
||||
const sign = (extraEntropy) => {
|
||||
const s = secp.sign(e.m, e.d, { extraEntropy }).toCompactHex();
|
||||
return s;
|
||||
};
|
||||
deepStrictEqual(await sign(), e.signature);
|
||||
deepStrictEqual(await sign(ent1), e.extraEntropy0);
|
||||
deepStrictEqual(await sign(ent2), e.extraEntropy1);
|
||||
deepStrictEqual(await sign(ent3), e.extraEntropyRand);
|
||||
deepStrictEqual(await sign(ent4), e.extraEntropyN);
|
||||
deepStrictEqual(await sign(ent5), e.extraEntropyMax);
|
||||
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('secp256k1.verify()/should verify signature', async () => {
|
||||
should('secp256k1.verify()/should verify signature', () => {
|
||||
const MSG = '01'.repeat(32);
|
||||
const PRIV_KEY = 0x2n;
|
||||
const signature = await secp.sign(MSG, PRIV_KEY);
|
||||
const signature = secp.sign(MSG, PRIV_KEY);
|
||||
const publicKey = secp.getPublicKey(PRIV_KEY);
|
||||
deepStrictEqual(publicKey.length, 65);
|
||||
deepStrictEqual(secp.verify(signature, MSG, publicKey), true);
|
||||
});
|
||||
should('secp256k1.verify()/should not verify signature with wrong public key', async () => {
|
||||
should('secp256k1.verify()/should not verify signature with wrong public key', () => {
|
||||
const MSG = '01'.repeat(32);
|
||||
const PRIV_KEY = 0x2n;
|
||||
const WRONG_PRIV_KEY = 0x22n;
|
||||
const signature = await secp.sign(MSG, PRIV_KEY);
|
||||
const signature = secp.sign(MSG, PRIV_KEY);
|
||||
const publicKey = secp.Point.fromPrivateKey(WRONG_PRIV_KEY).toHex();
|
||||
deepStrictEqual(publicKey.length, 130);
|
||||
deepStrictEqual(secp.verify(signature, MSG, publicKey), false);
|
||||
});
|
||||
should('secp256k1.verify()/should not verify signature with wrong hash', async () => {
|
||||
should('secp256k1.verify()/should not verify signature with wrong hash', () => {
|
||||
const MSG = '01'.repeat(32);
|
||||
const PRIV_KEY = 0x2n;
|
||||
const WRONG_MSG = '11'.repeat(32);
|
||||
const signature = await secp.sign(MSG, PRIV_KEY);
|
||||
const signature = secp.sign(MSG, PRIV_KEY);
|
||||
const publicKey = secp.getPublicKey(PRIV_KEY);
|
||||
deepStrictEqual(publicKey.length, 65);
|
||||
deepStrictEqual(secp.verify(signature, WRONG_MSG, publicKey), false);
|
||||
});
|
||||
should('secp256k1.verify()/should verify random signatures', async () =>
|
||||
should('secp256k1.verify()/should verify random signatures', () =>
|
||||
fc.assert(
|
||||
fc.asyncProperty(
|
||||
FC_BIGINT,
|
||||
fc.hexaString({ minLength: 64, maxLength: 64 }),
|
||||
async (privKey, msg) => {
|
||||
const pub = secp.getPublicKey(privKey);
|
||||
const sig = await secp.sign(msg, privKey);
|
||||
deepStrictEqual(secp.verify(sig, msg, pub), true);
|
||||
}
|
||||
)
|
||||
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('secp256k1.verify()/should not verify signature with invalid r/s', () => {
|
||||
@ -315,16 +304,14 @@ should('secp256k1.verify()/should not verify signature with invalid r/s', () =>
|
||||
|
||||
const pub = new secp.Point(x, y);
|
||||
const signature = new secp.Signature(2n, 2n);
|
||||
// @ts-ignore
|
||||
signature.r = r;
|
||||
// @ts-ignore
|
||||
signature.s = s;
|
||||
|
||||
const verified = secp.verify(signature, msg, pub);
|
||||
// Verifies, but it shouldn't, because signature S > curve order
|
||||
deepStrictEqual(verified, false);
|
||||
});
|
||||
should('secp256k1.verify()/should not verify msg = curve order', async () => {
|
||||
should('secp256k1.verify()/should not verify msg = curve order', () => {
|
||||
const msg = 'fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141';
|
||||
const x = 55066263022277343669578718895168534326250603453777594175500187360389116729240n;
|
||||
const y = 32670510020758816978083085130507043184471273380659243275938904335757337482424n;
|
||||
@ -334,7 +321,7 @@ should('secp256k1.verify()/should not verify msg = curve order', async () => {
|
||||
const sig = new secp.Signature(r, s);
|
||||
deepStrictEqual(secp.verify(sig, msg, pub), false);
|
||||
});
|
||||
should('secp256k1.verify()/should verify non-strict msg bb5a...', async () => {
|
||||
should('secp256k1.verify()/should verify non-strict msg bb5a...', () => {
|
||||
const msg = 'bb5a52f42f9c9261ed4361f59422a1e30036e7c32b270c8807a419feca605023';
|
||||
const x = 3252872872578928810725465493269682203671229454553002637820453004368632726370n;
|
||||
const y = 17482644437196207387910659778872952193236850502325156318830589868678978890912n;
|
||||
@ -386,11 +373,11 @@ should(
|
||||
// }
|
||||
// });
|
||||
|
||||
should('secp256k1.recoverPublicKey()/should recover public key from recovery bit', async () => {
|
||||
should('secp256k1.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 = await secp.sign(message, privateKey);
|
||||
const sig = secp.sign(message, privateKey);
|
||||
const recoveredPubkey = sig.recoverPublicKey(message);
|
||||
// const recoveredPubkey = secp.recoverPublicKey(message, signature, recovery);
|
||||
deepStrictEqual(recoveredPubkey !== null, true);
|
||||
@ -404,15 +391,15 @@ should('secp256k1.recoverPublicKey()/should not recover zero points', () => {
|
||||
const recovery = 0;
|
||||
throws(() => secp.recoverPublicKey(msgHash, sig, recovery));
|
||||
});
|
||||
should('secp256k1.recoverPublicKey()/should handle all-zeros msghash', async () => {
|
||||
should('secp256k1.recoverPublicKey()/should handle all-zeros msghash', () => {
|
||||
const privKey = secp.utils.randomPrivateKey();
|
||||
const pub = secp.getPublicKey(privKey);
|
||||
const zeros = '0000000000000000000000000000000000000000000000000000000000000000';
|
||||
const sig = await secp.sign(zeros, privKey, { recovered: true });
|
||||
const sig = secp.sign(zeros, privKey, { recovered: true });
|
||||
const recoveredKey = sig.recoverPublicKey(zeros);
|
||||
deepStrictEqual(recoveredKey.toRawBytes(), pub);
|
||||
});
|
||||
should('secp256k1.recoverPublicKey()/should handle RFC 6979 vectors', async () => {
|
||||
should('secp256k1.recoverPublicKey()/should handle RFC 6979 vectors', () => {
|
||||
for (const vector of ecdsa.valid) {
|
||||
if (secp.utils.mod(hexToNumber(vector.m), secp.CURVE.n) === 0n) continue;
|
||||
let usig = secp.sign(vector.m, vector.d);
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { deepStrictEqual, throws } from 'assert';
|
||||
import { should } from 'micro-should';
|
||||
import * as starknet from '../../lib/starknet.js';
|
||||
import * as starknet from '../../lib/stark.js';
|
||||
import { default as issue2 } from './fixtures/issue2.json' assert { type: 'json' };
|
||||
|
||||
should('Basic elliptic sanity check', () => {
|
0
curve-definitions/test/starknet/benchmark/index.js → curve-definitions/test/stark/benchmark/index.js
0
curve-definitions/test/starknet/benchmark/index.js → curve-definitions/test/stark/benchmark/index.js
@ -1,5 +1,3 @@
|
||||
|
||||
import './basic.test.js';
|
||||
import './starknet.test.js';
|
||||
import './stark.test.js';
|
||||
import './property.test.js';
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { deepStrictEqual, throws } from 'assert';
|
||||
import { should } from 'micro-should';
|
||||
import * as starknet from '../../lib/starknet.js';
|
||||
import * as starknet from '../../lib/stark.js';
|
||||
import * as fc from 'fast-check';
|
||||
|
||||
const FC_BIGINT = fc.bigInt(1n + 1n, starknet.CURVE.n - 1n);
|
@ -3,7 +3,7 @@ import { should } from 'micro-should';
|
||||
import { hex, utf8 } from '@scure/base';
|
||||
import * as bip32 from '@scure/bip32';
|
||||
import * as bip39 from '@scure/bip39';
|
||||
import * as starknet from '../../lib/starknet.js';
|
||||
import * as starknet from '../../lib/stark.js';
|
||||
import { default as sigVec } from './fixtures/rfc6979_signature_test_vector.json' assert { type: 'json' };
|
||||
import { default as precomputedKeys } from './fixtures/keys_precomputed.json' assert { type: 'json' };
|
||||
|
109
src/edwards.ts
109
src/edwards.ts
@ -1,10 +1,9 @@
|
||||
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
|
||||
// Implementation of Twisted Edwards curve. The formula is: ax² + y² = 1 + dx²y²
|
||||
|
||||
// Differences with @noble/ed25519:
|
||||
// 1. EDDSA & ECDH have different field element lengths (for ed448/x448 only)
|
||||
// https://www.rfc-editor.org/rfc/rfc8032.html says bitLength is 456 (57 bytes)
|
||||
// https://www.rfc-editor.org/rfc/rfc7748 says bitLength is 448 (56 bytes)
|
||||
// Differences from @noble/ed25519 1.7:
|
||||
// 1. EDDSA & ECDH have different field element lengths (for ed448/x448 only):
|
||||
// RFC8032 bitLength is 456 bits (57 bytes), RFC7748 bitLength is 448 (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 too (unexpected), now using generalized formula
|
||||
@ -17,10 +16,11 @@ import {
|
||||
ensureBytes,
|
||||
numberToBytesLE,
|
||||
bytesToNumberLE,
|
||||
nLength,
|
||||
hashToPrivateScalar,
|
||||
} from './utils.js';
|
||||
import { wNAF } from './group.js';
|
||||
BasicCurve,
|
||||
validateOpts as utilOpts,
|
||||
} from './utils.js'; // TODO: import * as u from './utils.js'?
|
||||
import { Group, GroupConstructor, wNAF } from './group.js';
|
||||
|
||||
// Be friendly to bad ECMAScript parsers by not using bigint literals like 123n
|
||||
const _0n = BigInt(0);
|
||||
@ -35,21 +35,10 @@ export type CHash = {
|
||||
create(): any;
|
||||
};
|
||||
|
||||
export type CurveType = {
|
||||
export type CurveType = BasicCurve & {
|
||||
// Params: a, d
|
||||
a: bigint;
|
||||
d: bigint;
|
||||
// Field over which we'll do calculations. Verify with:
|
||||
P: bigint;
|
||||
// Subgroup order: how many points ed25519 has
|
||||
n: bigint; // in rfc8032 called l
|
||||
// Cofactor
|
||||
h: bigint;
|
||||
nBitLength?: number;
|
||||
nByteLength?: number;
|
||||
// Base point (x, y) aka generator point
|
||||
Gx: bigint;
|
||||
Gy: bigint;
|
||||
// Hashes
|
||||
hash: CHash; // Because we need outputLen for DRBG
|
||||
randomBytes: (bytesLength?: number) => Uint8Array;
|
||||
@ -66,26 +55,22 @@ type PrivKey = Hex | bigint | number;
|
||||
|
||||
// Should be separate from overrides, since overrides can use information about curve (for example nBits)
|
||||
function validateOpts(curve: CurveType) {
|
||||
if (typeof curve.hash !== 'function' || !Number.isSafeInteger(curve.hash.outputLen))
|
||||
const opts = utilOpts(curve);
|
||||
if (typeof opts.hash !== 'function' || !Number.isSafeInteger(opts.hash.outputLen))
|
||||
throw new Error('Invalid hash function');
|
||||
for (const i of ['a', 'd', 'P', 'n', 'h', 'Gx', 'Gy'] as const) {
|
||||
if (typeof curve[i] !== 'bigint')
|
||||
throw new Error(`Invalid curve param ${i}=${curve[i]} (${typeof curve[i]})`);
|
||||
}
|
||||
for (const i of ['nBitLength', 'nByteLength'] as const) {
|
||||
if (curve[i] === undefined) continue; // Optional
|
||||
if (!Number.isSafeInteger(curve[i]))
|
||||
throw new Error(`Invalid curve param ${i}=${curve[i]} (${typeof curve[i]})`);
|
||||
for (const i of ['a', 'd'] as const) {
|
||||
if (typeof opts[i] !== 'bigint')
|
||||
throw new Error(`Invalid curve param ${i}=${opts[i]} (${typeof opts[i]})`);
|
||||
}
|
||||
for (const fn of ['randomBytes'] as const) {
|
||||
if (typeof curve[fn] !== 'function') throw new Error(`Invalid ${fn} function`);
|
||||
if (typeof opts[fn] !== 'function') throw new Error(`Invalid ${fn} function`);
|
||||
}
|
||||
for (const fn of ['adjustScalarBytes', 'domain', 'uvRatio'] as const) {
|
||||
if (curve[fn] === undefined) continue; // Optional
|
||||
if (typeof curve[fn] !== 'function') throw new Error(`Invalid ${fn} function`);
|
||||
if (opts[fn] === undefined) continue; // Optional
|
||||
if (typeof opts[fn] !== 'function') throw new Error(`Invalid ${fn} function`);
|
||||
}
|
||||
// Set defaults
|
||||
return Object.freeze({ ...nLength(curve.n, curve.nBitLength), ...curve } as const);
|
||||
return Object.freeze({ ...opts } as const);
|
||||
}
|
||||
|
||||
// Instance
|
||||
@ -103,16 +88,11 @@ export type SignatureConstructor = {
|
||||
};
|
||||
|
||||
// Instance
|
||||
export interface ExtendedPointType {
|
||||
export interface ExtendedPointType extends Group<ExtendedPointType> {
|
||||
readonly x: bigint;
|
||||
readonly y: bigint;
|
||||
readonly z: bigint;
|
||||
readonly t: bigint;
|
||||
equals(other: ExtendedPointType): boolean;
|
||||
negate(): ExtendedPointType;
|
||||
double(): ExtendedPointType;
|
||||
add(other: ExtendedPointType): ExtendedPointType;
|
||||
subtract(other: ExtendedPointType): ExtendedPointType;
|
||||
multiply(scalar: number | bigint, affinePoint?: PointType): ExtendedPointType;
|
||||
multiplyUnsafe(scalar: number | bigint): ExtendedPointType;
|
||||
isSmallOrder(): boolean;
|
||||
@ -120,37 +100,28 @@ export interface ExtendedPointType {
|
||||
toAffine(invZ?: bigint): PointType;
|
||||
}
|
||||
// Static methods
|
||||
export type ExtendedPointConstructor = {
|
||||
export interface ExtendedPointConstructor extends GroupConstructor<ExtendedPointType> {
|
||||
new (x: bigint, y: bigint, z: bigint, t: bigint): ExtendedPointType;
|
||||
BASE: ExtendedPointType;
|
||||
ZERO: ExtendedPointType;
|
||||
fromAffine(p: PointType): ExtendedPointType;
|
||||
toAffineBatch(points: ExtendedPointType[]): PointType[];
|
||||
normalizeZ(points: ExtendedPointType[]): ExtendedPointType[];
|
||||
};
|
||||
}
|
||||
|
||||
// Instance
|
||||
export interface PointType {
|
||||
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;
|
||||
equals(other: PointType): boolean;
|
||||
negate(): PointType;
|
||||
add(other: PointType): PointType;
|
||||
subtract(other: PointType): PointType;
|
||||
multiply(scalar: number | bigint): PointType;
|
||||
}
|
||||
// Static methods
|
||||
export type PointConstructor = {
|
||||
BASE: PointType;
|
||||
ZERO: PointType;
|
||||
export interface PointConstructor extends GroupConstructor<PointType> {
|
||||
new (x: bigint, y: bigint): PointType;
|
||||
fromHex(hex: Hex): PointType;
|
||||
fromPrivateKey(privateKey: PrivKey): PointType;
|
||||
};
|
||||
}
|
||||
|
||||
export type PubKey = Hex | PointType;
|
||||
export type SigType = Hex | SignatureType;
|
||||
@ -289,6 +260,27 @@ export function twistedEdwards(curveDef: CurveType): CurveFn {
|
||||
const { a, d } = CURVE;
|
||||
const { x: X1, y: Y1, z: Z1, t: T1 } = this;
|
||||
const { x: X2, y: Y2, z: Z2, t: T2 } = other;
|
||||
|
||||
// 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
|
||||
// Cost: 8M + 8add + 2*2.
|
||||
// Note: It does not check whether the `other` point is valid.
|
||||
if (a === BigInt(-1)) {
|
||||
const A = modP((Y1 - X1) * (Y2 + X2));
|
||||
const B = modP((Y1 + X1) * (Y2 - X2));
|
||||
const F = modP(B - A);
|
||||
if (F === _0n) return this.double(); // Same point.
|
||||
const C = modP(Z1 * _2n * T2);
|
||||
const D = modP(T1 * _2n * Z2);
|
||||
const E = D + C;
|
||||
const G = B + A;
|
||||
const H = D - C;
|
||||
const X3 = modP(E * F);
|
||||
const Y3 = modP(G * H);
|
||||
const T3 = modP(E * H);
|
||||
const Z3 = modP(F * G);
|
||||
return new ExtendedPoint(X3, Y3, Z3, T3);
|
||||
}
|
||||
const A = modP(X1 * X2); // A = X1*X2
|
||||
const B = modP(Y1 * Y2); // B = Y1*Y2
|
||||
const C = modP(T1 * d * T2); // C = T1*d*T2
|
||||
@ -466,13 +458,18 @@ export function twistedEdwards(curveDef: CurveType): CurveFn {
|
||||
}
|
||||
|
||||
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() {
|
||||
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();
|
||||
}
|
||||
@ -523,12 +520,6 @@ export function twistedEdwards(curveDef: CurveType): CurveFn {
|
||||
}
|
||||
}
|
||||
|
||||
// Little Endian
|
||||
// function bytesToNumberLE(uint8a: Uint8Array): bigint {
|
||||
// if (!(uint8a instanceof Uint8Array)) throw new Error('Expected Uint8Array');
|
||||
// return BigInt('0x' + bytesToHex(Uint8Array.from(uint8a).reverse()));
|
||||
// }
|
||||
|
||||
// -------------------------
|
||||
// Little-endian SHA512 with modulo n
|
||||
function modlLE(hash: Uint8Array): bigint {
|
||||
|
@ -5,8 +5,11 @@ const _1n = BigInt(1);
|
||||
|
||||
export interface Group<T extends Group<T>> {
|
||||
double(): T;
|
||||
add(other: T): T;
|
||||
negate(): T;
|
||||
add(other: T): T;
|
||||
subtract(other: T): T;
|
||||
equals(other: T): boolean;
|
||||
multiply(scalar: number | bigint): T;
|
||||
}
|
||||
|
||||
export type GroupConstructor<T> = {
|
||||
|
@ -53,6 +53,8 @@ function validateOpts(curve: CurveType) {
|
||||
return Object.freeze({ ...curve } as const);
|
||||
}
|
||||
|
||||
// NOTE: not really montgomery curve, just bunch of very specific methods for X25519/X448 (RFC 7748, https://www.rfc-editor.org/rfc/rfc7748)
|
||||
// Uses only one coordinate instead of two
|
||||
export function montgomery(curveDef: CurveType): CurveFn {
|
||||
const CURVE = validateOpts(curveDef);
|
||||
const { P } = CURVE;
|
||||
@ -83,6 +85,16 @@ export function montgomery(curveDef: CurveType): CurveFn {
|
||||
}
|
||||
|
||||
// cswap from RFC7748
|
||||
// NOTE: cswap is not from RFC7748!
|
||||
/*
|
||||
cswap(swap, x_2, x_3):
|
||||
dummy = mask(swap) AND (x_2 XOR x_3)
|
||||
x_2 = x_2 XOR dummy
|
||||
x_3 = x_3 XOR dummy
|
||||
Return (x_2, x_3)
|
||||
Where mask(swap) is the all-1 or all-0 word of the same length as x_2
|
||||
and x_3, computed, e.g., as mask(swap) = 0 - swap.
|
||||
*/
|
||||
function cswap(swap: bigint, x_2: bigint, x_3: bigint): [bigint, bigint] {
|
||||
const dummy = modP(swap * (x_2 - x_3));
|
||||
x_2 = modP(x_2 - dummy);
|
||||
|
42
src/utils.ts
42
src/utils.ts
@ -1,8 +1,38 @@
|
||||
/*! @noble/curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
|
||||
// Convert between types
|
||||
// ---------------------
|
||||
export type Hex = 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 = {
|
||||
// Field over which we'll do calculations (Fp)
|
||||
P: bigint;
|
||||
// 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;
|
||||
// Base point (x, y) aka generator point
|
||||
Gx: bigint;
|
||||
Gy: bigint;
|
||||
};
|
||||
|
||||
export function validateOpts<T extends BasicCurve>(curve: T) {
|
||||
for (const i of ['P', 'n', 'h', 'Gx', 'Gy'] as const) {
|
||||
if (typeof curve[i] !== 'bigint')
|
||||
throw new Error(`Invalid curve param ${i}=${curve[i]} (${typeof curve[i]})`);
|
||||
}
|
||||
for (const i of ['nBitLength', 'nByteLength'] as const) {
|
||||
if (curve[i] === undefined) continue; // Optional
|
||||
if (!Number.isSafeInteger(curve[i]))
|
||||
throw new Error(`Invalid curve param ${i}=${curve[i]} (${typeof curve[i]})`);
|
||||
}
|
||||
// Set defaults
|
||||
return Object.freeze({ ...nLength(curve.n, curve.nBitLength), ...curve } as const);
|
||||
}
|
||||
|
||||
type Hex = string | Uint8Array;
|
||||
import * as mod from './modular.js';
|
||||
|
||||
const hexes = Array.from({ length: 256 }, (v, i) => i.toString(16).padStart(2, '0'));
|
||||
@ -100,8 +130,10 @@ export function nLength(n: bigint, nBitLength?: number) {
|
||||
const _1n = BigInt(1);
|
||||
export function hashToPrivateScalar(hash: Hex, CURVE_ORDER: bigint, isLE = false): bigint {
|
||||
hash = ensureBytes(hash);
|
||||
if (hash.length < 40 || hash.length > 1024)
|
||||
throw new Error('Expected 40-1024 bytes of private key as per FIPS 186');
|
||||
const orderLen = nLength(CURVE_ORDER).nByteLength;
|
||||
const minLen = orderLen + 8;
|
||||
if (orderLen < 16 || hash.length < minLen || hash.length > 1024)
|
||||
throw new Error('Expected valid bytes of private key as per FIPS 186');
|
||||
const num = isLE ? bytesToNumberLE(hash) : bytesToNumberBE(hash);
|
||||
return mod.mod(num, CURVE_ORDER - _1n) + _1n;
|
||||
}
|
||||
|
@ -3,7 +3,7 @@
|
||||
|
||||
// TODO: sync vs async naming
|
||||
// TODO: default randomBytes
|
||||
// Differences from noble/secp256k1:
|
||||
// Differences from @noble/secp256k1 1.7:
|
||||
// 1. Different double() formula (but same addition)
|
||||
// 2. Different sqrt() function
|
||||
// 3. truncateHash() truncateOnly mode
|
||||
@ -18,10 +18,11 @@ import {
|
||||
hexToBytes,
|
||||
hexToNumber,
|
||||
numberToHexUnpadded,
|
||||
nLength,
|
||||
hashToPrivateScalar,
|
||||
BasicCurve,
|
||||
validateOpts as utilOpts,
|
||||
} from './utils.js';
|
||||
import { wNAF } from './group.js';
|
||||
import { Group, GroupConstructor, wNAF } from './group.js';
|
||||
|
||||
export type CHash = {
|
||||
(message: Uint8Array | string): Uint8Array;
|
||||
@ -35,32 +36,19 @@ type EndomorphismOpts = {
|
||||
splitScalar: (k: bigint) => { k1neg: boolean; k1: bigint; k2neg: boolean; k2: bigint };
|
||||
};
|
||||
|
||||
export type CurveType = {
|
||||
export type CurveType = BasicCurve & {
|
||||
// Params: a, b
|
||||
a: bigint;
|
||||
b: bigint;
|
||||
// Field over which we'll do calculations. Verify with:
|
||||
P: bigint;
|
||||
// Curve order, total count of valid points in the field. Verify with:
|
||||
n: bigint;
|
||||
nBitLength?: number;
|
||||
nByteLength?: number;
|
||||
// Base point (x, y) aka generator point
|
||||
Gx: bigint;
|
||||
Gy: bigint;
|
||||
|
||||
// Default options
|
||||
lowS?: boolean;
|
||||
|
||||
// Hashes
|
||||
hash: CHash; // Because we need outputLen for DRBG
|
||||
hmac: HmacFnSync;
|
||||
randomBytes: (bytesLength?: number) => Uint8Array;
|
||||
|
||||
truncateHash?: (hash: Uint8Array, truncateOnly?: boolean) => bigint;
|
||||
// Some fields can have specialized fast case
|
||||
sqrtMod?: (n: bigint) => bigint;
|
||||
|
||||
// TODO: move into options?
|
||||
// Endomorphism options for Koblitz curves
|
||||
endo?: EndomorphismOpts;
|
||||
@ -73,23 +61,19 @@ type PrivKey = Hex | bigint | number;
|
||||
|
||||
// Should be separate from overrides, since overrides can use information about curve (for example nBits)
|
||||
function validateOpts(curve: CurveType) {
|
||||
if (typeof curve.hash !== 'function' || !Number.isSafeInteger(curve.hash.outputLen))
|
||||
const opts = utilOpts(curve);
|
||||
if (typeof opts.hash !== 'function' || !Number.isSafeInteger(opts.hash.outputLen))
|
||||
throw new Error('Invalid hash function');
|
||||
if (typeof curve.hmac !== 'function') throw new Error('Invalid hmac function');
|
||||
if (typeof curve.randomBytes !== 'function') throw new Error('Invalid randomBytes function');
|
||||
if (typeof opts.hmac !== 'function') throw new Error('Invalid hmac function');
|
||||
if (typeof opts.randomBytes !== 'function') throw new Error('Invalid randomBytes function');
|
||||
|
||||
for (const i of ['a', 'b', 'P', 'n', 'Gx', 'Gy'] as const) {
|
||||
if (typeof curve[i] !== 'bigint')
|
||||
throw new Error(`Invalid curve param ${i}=${curve[i]} (${typeof curve[i]})`);
|
||||
for (const i of ['a', 'b'] as const) {
|
||||
if (typeof opts[i] !== 'bigint')
|
||||
throw new Error(`Invalid curve param ${i}=${opts[i]} (${typeof opts[i]})`);
|
||||
}
|
||||
for (const i of ['nBitLength', 'nByteLength'] as const) {
|
||||
if (curve[i] === undefined) continue; // Optional
|
||||
if (!Number.isSafeInteger(curve[i]))
|
||||
throw new Error(`Invalid curve param ${i}=${curve[i]} (${typeof curve[i]})`);
|
||||
}
|
||||
const endo = curve.endo;
|
||||
const endo = opts.endo;
|
||||
if (endo) {
|
||||
if (curve.a !== _0n) {
|
||||
if (opts.a !== _0n) {
|
||||
throw new Error('Endomorphism can only be defined for Koblitz curves that have a=0');
|
||||
}
|
||||
if (
|
||||
@ -101,7 +85,7 @@ function validateOpts(curve: CurveType) {
|
||||
}
|
||||
}
|
||||
// Set defaults
|
||||
return Object.freeze({ lowS: true, ...nLength(curve.n, curve.nBitLength), ...curve } as const);
|
||||
return Object.freeze({ lowS: true, ...opts } as const);
|
||||
}
|
||||
|
||||
// TODO: convert bits to bytes aligned to 32 bits? (224 for example)
|
||||
@ -206,30 +190,23 @@ export type SignatureConstructor = {
|
||||
};
|
||||
|
||||
// Instance
|
||||
export interface JacobianPointType {
|
||||
export interface JacobianPointType extends Group<JacobianPointType> {
|
||||
readonly x: bigint;
|
||||
readonly y: bigint;
|
||||
readonly z: bigint;
|
||||
equals(other: JacobianPointType): boolean;
|
||||
negate(): JacobianPointType;
|
||||
double(): JacobianPointType;
|
||||
add(other: JacobianPointType): JacobianPointType;
|
||||
subtract(other: JacobianPointType): JacobianPointType;
|
||||
multiply(scalar: number | bigint, affinePoint?: PointType): JacobianPointType;
|
||||
multiplyUnsafe(scalar: bigint): JacobianPointType;
|
||||
toAffine(invZ?: bigint): PointType;
|
||||
}
|
||||
// Static methods
|
||||
export type JacobianPointConstructor = {
|
||||
export interface JacobianPointConstructor extends GroupConstructor<JacobianPointType> {
|
||||
new (x: bigint, y: bigint, z: bigint): JacobianPointType;
|
||||
BASE: JacobianPointType;
|
||||
ZERO: JacobianPointType;
|
||||
fromAffine(p: PointType): JacobianPointType;
|
||||
toAffineBatch(points: JacobianPointType[]): PointType[];
|
||||
normalizeZ(points: JacobianPointType[]): JacobianPointType[];
|
||||
};
|
||||
}
|
||||
// Instance
|
||||
export interface PointType {
|
||||
export interface PointType extends Group<PointType> {
|
||||
readonly x: bigint;
|
||||
readonly y: bigint;
|
||||
_setWindowSize(windowSize: number): void;
|
||||
@ -237,22 +214,14 @@ export interface PointType {
|
||||
toRawBytes(isCompressed?: boolean): Uint8Array;
|
||||
toHex(isCompressed?: boolean): string;
|
||||
assertValidity(): void;
|
||||
equals(other: PointType): boolean;
|
||||
negate(): PointType;
|
||||
double(): PointType;
|
||||
add(other: PointType): PointType;
|
||||
subtract(other: PointType): PointType;
|
||||
multiply(scalar: number | bigint): PointType;
|
||||
multiplyAndAddUnsafe(Q: PointType, a: bigint, b: bigint): PointType | undefined;
|
||||
}
|
||||
// Static methods
|
||||
export type PointConstructor = {
|
||||
BASE: PointType;
|
||||
ZERO: PointType;
|
||||
export interface PointConstructor extends GroupConstructor<PointType> {
|
||||
new (x: bigint, y: bigint): PointType;
|
||||
fromHex(hex: Hex): PointType;
|
||||
fromPrivateKey(privateKey: PrivKey): PointType;
|
||||
};
|
||||
}
|
||||
|
||||
export type PubKey = Hex | PointType;
|
||||
|
||||
@ -510,9 +479,9 @@ export function weierstrass(curveDef: CurveType): CurveFn {
|
||||
const { x: X1, y: Y1, z: Z1 } = this;
|
||||
const { a } = CURVE;
|
||||
|
||||
// // Faster algorithm: when a=0
|
||||
// // From: https://hyperelliptic.org/EFD/g1p/auto-shortw-jacobian-0.html#doubling-dbl-2009-l
|
||||
// // Cost: 2M + 5S + 6add + 3*2 + 1*3 + 1*8.
|
||||
// Faster algorithm: when a=0
|
||||
// From: https://hyperelliptic.org/EFD/g1p/auto-shortw-jacobian-0.html#doubling-dbl-2009-l
|
||||
// Cost: 2M + 5S + 6add + 3*2 + 1*3 + 1*8.
|
||||
if (a === _0n) {
|
||||
const A = modP(X1 * X1);
|
||||
const B = modP(Y1 * Y1);
|
||||
@ -789,6 +758,7 @@ export function weierstrass(curveDef: CurveType): CurveFn {
|
||||
}
|
||||
|
||||
equals(other: Point): boolean {
|
||||
if (!(other instanceof Point)) throw new TypeError('Point#equals: expected Point');
|
||||
return this.x === other.x && this.y === other.y;
|
||||
}
|
||||
|
||||
@ -965,8 +935,8 @@ export function weierstrass(curveDef: CurveType): CurveFn {
|
||||
*/
|
||||
hashToPrivateKey: (hash: Hex): Uint8Array => numToField(hashToPrivateScalar(hash, CURVE_ORDER)),
|
||||
|
||||
// Takes curve order + 64 bits from CSPRNG
|
||||
// so that modulo bias is neglible, matches FIPS 186 B.4.1.
|
||||
// Takes curve order + 64 bits from CSPRNG so that modulo bias is neglible,
|
||||
// matches FIPS 186 B.4.1.
|
||||
randomPrivateKey: (): Uint8Array => utils.hashToPrivateKey(CURVE.randomBytes(fieldLen + 8)),
|
||||
|
||||
/**
|
||||
|
Loading…
Reference in New Issue
Block a user