Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b9482bb17d | ||
|
|
74475dca68 | ||
|
|
f4cf21b9c8 | ||
|
|
5312d92b2c | ||
|
|
d1770c0ac7 | ||
|
|
2d37edf7d1 | ||
|
|
36998fede8 | ||
|
|
83960d445d | ||
|
|
23cc2aa5d1 | ||
|
|
e45d7c2d25 | ||
|
|
bfe929aac3 | ||
|
|
069452dbe7 | ||
|
|
2e81f31d2e | ||
|
|
9f7df0f13b | ||
|
|
5600629bca |
@@ -201,8 +201,6 @@ export type CurveFn = {
|
|||||||
ExtendedPoint: ExtendedPointConstructor;
|
ExtendedPoint: ExtendedPointConstructor;
|
||||||
Signature: SignatureConstructor;
|
Signature: SignatureConstructor;
|
||||||
utils: {
|
utils: {
|
||||||
mod: (a: bigint, b?: bigint) => bigint;
|
|
||||||
invert: (number: bigint, modulo?: bigint) => bigint;
|
|
||||||
randomPrivateKey: () => Uint8Array;
|
randomPrivateKey: () => Uint8Array;
|
||||||
getExtendedPublicKey: (key: PrivKey) => {
|
getExtendedPublicKey: (key: PrivKey) => {
|
||||||
head: Uint8Array;
|
head: Uint8Array;
|
||||||
@@ -306,6 +304,7 @@ export type CurveFn = {
|
|||||||
getPublicKey: (privateKey: PrivKey, isCompressed?: boolean) => Uint8Array;
|
getPublicKey: (privateKey: PrivKey, isCompressed?: boolean) => Uint8Array;
|
||||||
getSharedSecret: (privateA: PrivKey, publicB: PubKey, isCompressed?: boolean) => Uint8Array;
|
getSharedSecret: (privateA: PrivKey, publicB: PubKey, isCompressed?: boolean) => Uint8Array;
|
||||||
sign: (msgHash: Hex, privKey: PrivKey, opts?: SignOpts) => SignatureType;
|
sign: (msgHash: Hex, privKey: PrivKey, opts?: SignOpts) => SignatureType;
|
||||||
|
signUnhashed: (msg: Uint8Array, privKey: PrivKey, opts?: SignOpts) => SignatureType;
|
||||||
verify: (
|
verify: (
|
||||||
signature: Hex | SignatureType,
|
signature: Hex | SignatureType,
|
||||||
msgHash: Hex,
|
msgHash: Hex,
|
||||||
@@ -316,8 +315,6 @@ export type CurveFn = {
|
|||||||
ProjectivePoint: ProjectivePointConstructor;
|
ProjectivePoint: ProjectivePointConstructor;
|
||||||
Signature: SignatureConstructor;
|
Signature: SignatureConstructor;
|
||||||
utils: {
|
utils: {
|
||||||
mod: (a: bigint) => bigint;
|
|
||||||
invert: (number: bigint) => bigint;
|
|
||||||
isValidPrivateKey(privateKey: PrivKey): boolean;
|
isValidPrivateKey(privateKey: PrivKey): boolean;
|
||||||
hashToPrivateKey: (hash: Hex) => Uint8Array;
|
hashToPrivateKey: (hash: Hex) => Uint8Array;
|
||||||
randomPrivateKey: () => Uint8Array;
|
randomPrivateKey: () => Uint8Array;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@noble/curves",
|
"name": "@noble/curves",
|
||||||
"version": "0.5.1",
|
"version": "0.5.2",
|
||||||
"description": "Minimal, auditable JS implementation of elliptic curve cryptography",
|
"description": "Minimal, auditable JS implementation of elliptic curve cryptography",
|
||||||
"files": [
|
"files": [
|
||||||
"lib"
|
"lib"
|
||||||
@@ -31,7 +31,7 @@
|
|||||||
"@types/node": "18.11.3",
|
"@types/node": "18.11.3",
|
||||||
"fast-check": "3.0.0",
|
"fast-check": "3.0.0",
|
||||||
"micro-bmark": "0.2.0",
|
"micro-bmark": "0.2.0",
|
||||||
"micro-should": "0.2.0",
|
"micro-should": "0.3.0",
|
||||||
"prettier": "2.6.2",
|
"prettier": "2.6.2",
|
||||||
"rollup": "2.75.5",
|
"rollup": "2.75.5",
|
||||||
"typescript": "4.7.3"
|
"typescript": "4.7.3"
|
||||||
|
|||||||
@@ -1,13 +1,26 @@
|
|||||||
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
|
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
|
||||||
// Barreto-Lynn-Scott Curves. A family of pairing friendly curves, with embedding degree = 12 or 24
|
/**
|
||||||
// NOTE: only 12 supported for now
|
* BLS (Barreto-Lynn-Scott) family of pairing-friendly curves.
|
||||||
// Constructed from pair of weierstrass curves, based pairing logic
|
* Implements BLS (Boneh-Lynn-Shacham) signatures.
|
||||||
|
* Consists of two curves: G1 and G2:
|
||||||
|
* - G1 is a subgroup of (x, y) E(Fq) over y² = x³ + 4.
|
||||||
|
* - G2 is a subgroup of ((x₁, x₂+i), (y₁, y₂+i)) E(Fq²) over y² = x³ + 4(1 + i) where i is √-1
|
||||||
|
* - Gt, created by bilinear (ate) pairing e(G1, G2), consists of p-th roots of unity in
|
||||||
|
* Fq^k where k is embedding degree. Only degree 12 is currently supported, 24 is not.
|
||||||
|
* Pairing is used to aggregate and verify signatures.
|
||||||
|
* We are using Fp for private keys (shorter) and Fp₂ for signatures (longer).
|
||||||
|
* Some projects may prefer to swap this relation, it is not supported for now.
|
||||||
|
*/
|
||||||
import * as mod from './modular.js';
|
import * as mod from './modular.js';
|
||||||
import { ensureBytes, numberToBytesBE, bytesToNumberBE, bitLen, bitGet } from './utils.js';
|
import * as ut from './utils.js';
|
||||||
import * as utils from './utils.js';
|
// Types require separate import
|
||||||
// Types
|
import { Hex, PrivKey } from './utils.js';
|
||||||
import { hexToBytes, bytesToHex, Hex, PrivKey } from './utils.js';
|
import {
|
||||||
import { htfOpts, stringToBytes, hash_to_field, expand_message_xmd } from './hash-to-curve.js';
|
htfOpts,
|
||||||
|
stringToBytes,
|
||||||
|
hash_to_field as hashToField,
|
||||||
|
expand_message_xmd as expandMessageXMD,
|
||||||
|
} from './hash-to-curve.js';
|
||||||
import { CurvePointsType, PointType, CurvePointsRes, weierstrassPoints } from './weierstrass.js';
|
import { CurvePointsType, PointType, CurvePointsRes, weierstrassPoints } from './weierstrass.js';
|
||||||
|
|
||||||
type Fp = bigint; // Can be different field?
|
type Fp = bigint; // Can be different field?
|
||||||
@@ -39,7 +52,7 @@ export type CurveType<Fp, Fp2, Fp6, Fp12> = {
|
|||||||
finalExponentiate(num: Fp12): Fp12;
|
finalExponentiate(num: Fp12): Fp12;
|
||||||
};
|
};
|
||||||
htfDefaults: htfOpts;
|
htfDefaults: htfOpts;
|
||||||
hash: utils.CHash; // Because we need outputLen for DRBG
|
hash: ut.CHash; // Because we need outputLen for DRBG
|
||||||
randomBytes: (bytesLength?: number) => Uint8Array;
|
randomBytes: (bytesLength?: number) => Uint8Array;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -80,12 +93,9 @@ export type CurveFn<Fp, Fp2, Fp6, Fp12> = {
|
|||||||
publicKeys: (Hex | PointType<Fp>)[]
|
publicKeys: (Hex | PointType<Fp>)[]
|
||||||
) => boolean;
|
) => boolean;
|
||||||
utils: {
|
utils: {
|
||||||
bytesToHex: typeof utils.bytesToHex;
|
|
||||||
hexToBytes: typeof utils.hexToBytes;
|
|
||||||
stringToBytes: typeof stringToBytes;
|
stringToBytes: typeof stringToBytes;
|
||||||
hashToField: typeof hash_to_field;
|
hashToField: typeof hashToField;
|
||||||
expandMessageXMD: typeof expand_message_xmd;
|
expandMessageXMD: typeof expandMessageXMD;
|
||||||
mod: typeof mod.mod;
|
|
||||||
getDSTLabel: () => string;
|
getDSTLabel: () => string;
|
||||||
setDSTLabel(newLabel: string): void;
|
setDSTLabel(newLabel: string): void;
|
||||||
};
|
};
|
||||||
@@ -95,12 +105,9 @@ export function bls<Fp2, Fp6, Fp12>(
|
|||||||
CURVE: CurveType<Fp, Fp2, Fp6, Fp12>
|
CURVE: CurveType<Fp, Fp2, Fp6, Fp12>
|
||||||
): CurveFn<Fp, Fp2, Fp6, Fp12> {
|
): CurveFn<Fp, Fp2, Fp6, Fp12> {
|
||||||
// Fields looks pretty specific for curve, so for now we need to pass them with options
|
// Fields looks pretty specific for curve, so for now we need to pass them with options
|
||||||
const Fp = CURVE.Fp;
|
const { Fp, Fr, Fp2, Fp6, Fp12 } = CURVE;
|
||||||
const Fr = CURVE.Fr;
|
const BLS_X_LEN = ut.bitLen(CURVE.x);
|
||||||
const Fp2 = CURVE.Fp2;
|
const groupLen = 32; // TODO: calculate; hardcoded for now
|
||||||
const Fp6 = CURVE.Fp6;
|
|
||||||
const Fp12 = CURVE.Fp12;
|
|
||||||
const BLS_X_LEN = bitLen(CURVE.x);
|
|
||||||
|
|
||||||
// Pre-compute coefficients for sparse multiplication
|
// Pre-compute coefficients for sparse multiplication
|
||||||
// Point addition and point double calculations is reused for coefficients
|
// Point addition and point double calculations is reused for coefficients
|
||||||
@@ -125,7 +132,7 @@ export function bls<Fp2, Fp6, Fp12>(
|
|||||||
Rx = Fp2.div(Fp2.mul(Fp2.mul(Fp2.sub(t0, t3), Rx), Ry), 2n); // ((T0 - T3) * Rx * Ry) / 2
|
Rx = Fp2.div(Fp2.mul(Fp2.mul(Fp2.sub(t0, t3), Rx), Ry), 2n); // ((T0 - T3) * Rx * Ry) / 2
|
||||||
Ry = Fp2.sub(Fp2.square(Fp2.div(Fp2.add(t0, t3), 2n)), Fp2.mul(Fp2.square(t2), 3n)); // ((T0 + T3) / 2)² - 3 * T2²
|
Ry = Fp2.sub(Fp2.square(Fp2.div(Fp2.add(t0, t3), 2n)), Fp2.mul(Fp2.square(t2), 3n)); // ((T0 + T3) / 2)² - 3 * T2²
|
||||||
Rz = Fp2.mul(t0, t4); // T0 * T4
|
Rz = Fp2.mul(t0, t4); // T0 * T4
|
||||||
if (bitGet(CURVE.x, i)) {
|
if (ut.bitGet(CURVE.x, i)) {
|
||||||
// Addition
|
// Addition
|
||||||
let t0 = Fp2.sub(Ry, Fp2.mul(Qy, Rz)); // Ry - Qy * Rz
|
let t0 = Fp2.sub(Ry, Fp2.mul(Qy, Rz)); // Ry - Qy * Rz
|
||||||
let t1 = Fp2.sub(Rx, Fp2.mul(Qx, Rz)); // Rx - Qx * Rz
|
let t1 = Fp2.sub(Rx, Fp2.mul(Qx, Rz)); // Rx - Qx * Rz
|
||||||
@@ -147,13 +154,14 @@ export function bls<Fp2, Fp6, Fp12>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
function millerLoop(ell: [Fp2, Fp2, Fp2][], g1: [Fp, Fp]): Fp12 {
|
function millerLoop(ell: [Fp2, Fp2, Fp2][], g1: [Fp, Fp]): Fp12 {
|
||||||
|
const { x } = CURVE;
|
||||||
const Px = g1[0];
|
const Px = g1[0];
|
||||||
const Py = g1[1];
|
const Py = g1[1];
|
||||||
let f12 = Fp12.ONE;
|
let f12 = Fp12.ONE;
|
||||||
for (let j = 0, i = BLS_X_LEN - 2; i >= 0; i--, j++) {
|
for (let j = 0, i = BLS_X_LEN - 2; i >= 0; i--, j++) {
|
||||||
const E = ell[j];
|
const E = ell[j];
|
||||||
f12 = Fp12.multiplyBy014(f12, E[0], Fp2.mul(E[1], Px), Fp2.mul(E[2], Py));
|
f12 = Fp12.multiplyBy014(f12, E[0], Fp2.mul(E[1], Px), Fp2.mul(E[2], Py));
|
||||||
if (bitGet(CURVE.x, i)) {
|
if (ut.bitGet(x, i)) {
|
||||||
j += 1;
|
j += 1;
|
||||||
const F = ell[j];
|
const F = ell[j];
|
||||||
f12 = Fp12.multiplyBy014(f12, F[0], Fp2.mul(F[1], Px), Fp2.mul(F[2], Py));
|
f12 = Fp12.multiplyBy014(f12, F[0], Fp2.mul(F[1], Px), Fp2.mul(F[2], Py));
|
||||||
@@ -163,81 +171,31 @@ export function bls<Fp2, Fp6, Fp12>(
|
|||||||
return Fp12.conjugate(f12);
|
return Fp12.conjugate(f12);
|
||||||
}
|
}
|
||||||
|
|
||||||
// bls12-381 is a construction of two curves:
|
|
||||||
// 1. Fp: (x, y)
|
|
||||||
// 2. Fp₂: ((x₁, x₂+i), (y₁, y₂+i)) - (complex numbers)
|
|
||||||
//
|
|
||||||
// Bilinear Pairing (ate pairing) is used to combine both elements into a paired one:
|
|
||||||
// Fp₁₂ = e(Fp, Fp2)
|
|
||||||
// where Fp₁₂ = 12-degree polynomial
|
|
||||||
// Pairing is used to verify signatures.
|
|
||||||
//
|
|
||||||
// We are using Fp for private keys (shorter) and Fp2 for signatures (longer).
|
|
||||||
// Some projects may prefer to swap this relation, it is not supported for now.
|
|
||||||
|
|
||||||
const htfDefaults = { ...CURVE.htfDefaults };
|
|
||||||
|
|
||||||
function isWithinCurveOrder(num: bigint): boolean {
|
|
||||||
return 0 < num && num < CURVE.r;
|
|
||||||
}
|
|
||||||
|
|
||||||
const utils = {
|
const utils = {
|
||||||
hexToBytes: hexToBytes,
|
hexToBytes: ut.hexToBytes,
|
||||||
bytesToHex: bytesToHex,
|
bytesToHex: ut.bytesToHex,
|
||||||
mod: mod.mod,
|
stringToBytes: stringToBytes,
|
||||||
stringToBytes,
|
|
||||||
// TODO: do we need to export it here?
|
// TODO: do we need to export it here?
|
||||||
hashToField: (msg: Uint8Array, count: number, options: Partial<typeof htfDefaults> = {}) =>
|
hashToField: (
|
||||||
hash_to_field(msg, count, { ...CURVE.htfDefaults, ...options }),
|
msg: Uint8Array,
|
||||||
|
count: number,
|
||||||
|
options: Partial<typeof CURVE.htfDefaults> = {}
|
||||||
|
) => hashToField(msg, count, { ...CURVE.htfDefaults, ...options }),
|
||||||
expandMessageXMD: (msg: Uint8Array, DST: Uint8Array, lenInBytes: number, H = CURVE.hash) =>
|
expandMessageXMD: (msg: Uint8Array, DST: Uint8Array, lenInBytes: number, H = CURVE.hash) =>
|
||||||
expand_message_xmd(msg, DST, lenInBytes, H),
|
expandMessageXMD(msg, DST, lenInBytes, H),
|
||||||
|
hashToPrivateKey: (hash: Hex): Uint8Array => Fr.toBytes(ut.hashToPrivateScalar(hash, CURVE.r)),
|
||||||
/**
|
randomBytes: (bytesLength: number = groupLen): Uint8Array => CURVE.randomBytes(bytesLength),
|
||||||
* Can take 40 or more bytes of uniform input e.g. from CSPRNG or KDF
|
randomPrivateKey: (): Uint8Array => utils.hashToPrivateKey(utils.randomBytes(groupLen + 8)),
|
||||||
* and convert them into private key, with the modulo bias being negligible.
|
getDSTLabel: () => CURVE.htfDefaults.DST,
|
||||||
* As per FIPS 186 B.1.1.
|
|
||||||
* https://research.kudelskisecurity.com/2020/07/28/the-definitive-guide-to-modulo-bias-and-how-to-avoid-it/
|
|
||||||
* @param hash hash output from sha512, or a similar function
|
|
||||||
* @returns valid private key
|
|
||||||
*/
|
|
||||||
hashToPrivateKey: (hash: Hex): Uint8Array => {
|
|
||||||
hash = ensureBytes(hash);
|
|
||||||
if (hash.length < 40 || hash.length > 1024)
|
|
||||||
throw new Error('Expected 40-1024 bytes of private key as per FIPS 186');
|
|
||||||
// hashToPrivateScalar(hash, CURVE.r)
|
|
||||||
// NOTE: doesn't add +/-1
|
|
||||||
const num = mod.mod(bytesToNumberBE(hash), CURVE.r);
|
|
||||||
// This should never happen
|
|
||||||
if (num === 0n || num === 1n) throw new Error('Invalid private key');
|
|
||||||
return numberToBytesBE(num, 32);
|
|
||||||
},
|
|
||||||
|
|
||||||
randomBytes: (bytesLength: number = 32): Uint8Array => CURVE.randomBytes(bytesLength),
|
|
||||||
// NIST SP 800-56A rev 3, section 5.6.1.2.2
|
|
||||||
// https://research.kudelskisecurity.com/2020/07/28/the-definitive-guide-to-modulo-bias-and-how-to-avoid-it/
|
|
||||||
randomPrivateKey: (): Uint8Array => utils.hashToPrivateKey(utils.randomBytes(40)),
|
|
||||||
getDSTLabel: () => htfDefaults.DST,
|
|
||||||
setDSTLabel(newLabel: string) {
|
setDSTLabel(newLabel: string) {
|
||||||
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-3.1
|
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-3.1
|
||||||
if (typeof newLabel !== 'string' || newLabel.length > 2048 || newLabel.length === 0) {
|
if (typeof newLabel !== 'string' || newLabel.length > 2048 || newLabel.length === 0) {
|
||||||
throw new TypeError('Invalid DST');
|
throw new TypeError('Invalid DST');
|
||||||
}
|
}
|
||||||
htfDefaults.DST = newLabel;
|
CURVE.htfDefaults.DST = newLabel;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
function normalizePrivKey(key: PrivKey): bigint {
|
|
||||||
let int: bigint;
|
|
||||||
if (key instanceof Uint8Array && key.length === 32) int = bytesToNumberBE(key);
|
|
||||||
else if (typeof key === 'string' && key.length === 64) int = BigInt(`0x${key}`);
|
|
||||||
else if (typeof key === 'number' && key > 0 && Number.isSafeInteger(key)) int = BigInt(key);
|
|
||||||
else if (typeof key === 'bigint' && key > 0n) int = key;
|
|
||||||
else throw new TypeError('Expected valid private key');
|
|
||||||
int = mod.mod(int, CURVE.r);
|
|
||||||
if (!isWithinCurveOrder(int)) throw new Error('Private key must be 0 < key < CURVE.r');
|
|
||||||
return int;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Point on G1 curve: (x, y)
|
// Point on G1 curve: (x, y)
|
||||||
const G1 = weierstrassPoints({
|
const G1 = weierstrassPoints({
|
||||||
n: Fr.ORDER,
|
n: Fr.ORDER,
|
||||||
@@ -309,7 +267,7 @@ export function bls<Fp2, Fp6, Fp12>(
|
|||||||
function sign(message: G2Hex, privateKey: PrivKey): Uint8Array | G2 {
|
function sign(message: G2Hex, privateKey: PrivKey): Uint8Array | G2 {
|
||||||
const msgPoint = normP2Hash(message);
|
const msgPoint = normP2Hash(message);
|
||||||
msgPoint.assertValidity();
|
msgPoint.assertValidity();
|
||||||
const sigPoint = msgPoint.multiply(normalizePrivKey(privateKey));
|
const sigPoint = msgPoint.multiply(G1.normalizePrivateKey(privateKey));
|
||||||
if (message instanceof G2.Point) return sigPoint;
|
if (message instanceof G2.Point) return sigPoint;
|
||||||
return Signature.encode(sigPoint);
|
return Signature.encode(sigPoint);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,28 +2,18 @@
|
|||||||
// Twisted Edwards curve. The formula is: ax² + y² = 1 + dx²y²
|
// Twisted Edwards curve. The formula is: ax² + y² = 1 + dx²y²
|
||||||
|
|
||||||
// Differences from @noble/ed25519 1.7:
|
// Differences from @noble/ed25519 1.7:
|
||||||
// 1. Different field element lengths in ed448:
|
// 1. Variable field element lengths between EDDSA/ECDH:
|
||||||
// EDDSA (RFC8032) is 456 bits / 57 bytes, ECDH (RFC7748) is 448 bits / 56 bytes
|
// EDDSA (RFC8032) is 456 bits / 57 bytes, ECDH (RFC7748) is 448 bits / 56 bytes
|
||||||
// 2. Different addition formula (doubling is same)
|
// 2. Different addition formula (doubling is same)
|
||||||
// 3. uvRatio differs between curves (half-expected, not only pow fn changes)
|
// 3. uvRatio differs between curves (half-expected, not only pow fn changes)
|
||||||
// 4. Point decompression code is different too (unexpected), now using generalized formula
|
// 4. Point decompression code is different (unexpected), now using generalized formula
|
||||||
// 5. Domain function was no-op for ed25519, but adds some data even with empty context for ed448
|
// 5. Domain function was no-op for ed25519, but adds some data even with empty context for ed448
|
||||||
|
|
||||||
import * as mod from './modular.js';
|
import * as mod from './modular.js';
|
||||||
import {
|
import * as ut from './utils.js';
|
||||||
bytesToHex,
|
import { ensureBytes, Hex, PrivKey } from './utils.js';
|
||||||
concatBytes,
|
|
||||||
ensureBytes,
|
|
||||||
numberToBytesLE,
|
|
||||||
bytesToNumberLE,
|
|
||||||
hashToPrivateScalar,
|
|
||||||
BasicCurve,
|
|
||||||
validateOpts as utilOpts,
|
|
||||||
Hex,
|
|
||||||
PrivKey,
|
|
||||||
} from './utils.js'; // TODO: import * as u from './utils.js'?
|
|
||||||
import { Group, GroupConstructor, wNAF } from './group.js';
|
import { Group, GroupConstructor, wNAF } from './group.js';
|
||||||
import { hash_to_field, htfOpts, validateHTFOpts } from './hash-to-curve.js';
|
import { hash_to_field as hashToField, htfOpts, validateHTFOpts } from './hash-to-curve.js';
|
||||||
|
|
||||||
// Be friendly to bad ECMAScript parsers by not using bigint literals like 123n
|
// Be friendly to bad ECMAScript parsers by not using bigint literals like 123n
|
||||||
const _0n = BigInt(0);
|
const _0n = BigInt(0);
|
||||||
@@ -31,49 +21,41 @@ const _1n = BigInt(1);
|
|||||||
const _2n = BigInt(2);
|
const _2n = BigInt(2);
|
||||||
const _8n = BigInt(8);
|
const _8n = BigInt(8);
|
||||||
|
|
||||||
export type CHash = {
|
// Edwards curves must declare params a & d.
|
||||||
(message: Uint8Array | string): Uint8Array;
|
export type CurveType = ut.BasicCurve<bigint> & {
|
||||||
blockLen: number;
|
|
||||||
outputLen: number;
|
|
||||||
create(): any;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type CurveType = BasicCurve<bigint> & {
|
|
||||||
// Params: a, d
|
// Params: a, d
|
||||||
a: bigint;
|
a: bigint;
|
||||||
d: bigint;
|
d: bigint;
|
||||||
// Hashes
|
// Hashes
|
||||||
hash: CHash; // Because we need outputLen for DRBG
|
// The interface, because we need outputLen for DRBG
|
||||||
|
hash: ut.CHash;
|
||||||
|
// CSPRNG
|
||||||
randomBytes: (bytesLength?: number) => Uint8Array;
|
randomBytes: (bytesLength?: number) => Uint8Array;
|
||||||
|
// Probably clears bits in a byte array to produce a valid field element
|
||||||
adjustScalarBytes?: (bytes: Uint8Array) => Uint8Array;
|
adjustScalarBytes?: (bytes: Uint8Array) => Uint8Array;
|
||||||
|
// Used during hashing
|
||||||
domain?: (data: Uint8Array, ctx: Uint8Array, phflag: boolean) => Uint8Array;
|
domain?: (data: Uint8Array, ctx: Uint8Array, phflag: boolean) => Uint8Array;
|
||||||
|
// Ratio √(u/v)
|
||||||
uvRatio?: (u: bigint, v: bigint) => { isValid: boolean; value: bigint };
|
uvRatio?: (u: bigint, v: bigint) => { isValid: boolean; value: bigint };
|
||||||
preHash?: CHash;
|
// RFC 8032 pre-hashing of messages to sign() / verify()
|
||||||
clearCofactor?: (c: ExtendedPointConstructor, point: ExtendedPointType) => ExtendedPointType;
|
preHash?: ut.CHash;
|
||||||
// Hash to field opts
|
// Hash to field options
|
||||||
htfDefaults?: htfOpts;
|
htfDefaults?: htfOpts;
|
||||||
mapToCurve?: (scalar: bigint[]) => { x: bigint; y: bigint };
|
mapToCurve?: (scalar: bigint[]) => { x: bigint; y: bigint };
|
||||||
};
|
};
|
||||||
|
|
||||||
// Should be separate from overrides, since overrides can use information about curve (for example nBits)
|
|
||||||
function validateOpts(curve: CurveType) {
|
function validateOpts(curve: CurveType) {
|
||||||
const opts = utilOpts(curve);
|
const opts = ut.validateOpts(curve);
|
||||||
if (typeof opts.hash !== 'function' || !Number.isSafeInteger(opts.hash.outputLen))
|
if (typeof opts.hash !== 'function' || !ut.isPositiveInt(opts.hash.outputLen))
|
||||||
throw new Error('Invalid hash function');
|
throw new Error('Invalid hash function');
|
||||||
for (const i of ['a', 'd'] as const) {
|
for (const i of ['a', 'd'] as const) {
|
||||||
if (typeof opts[i] !== 'bigint')
|
const val = opts[i];
|
||||||
throw new Error(`Invalid curve param ${i}=${opts[i]} (${typeof opts[i]})`);
|
if (typeof val !== 'bigint') throw new Error(`Invalid curve param ${i}=${val} (${typeof val})`);
|
||||||
}
|
}
|
||||||
for (const fn of ['randomBytes'] as const) {
|
for (const fn of ['randomBytes'] as const) {
|
||||||
if (typeof opts[fn] !== 'function') throw new Error(`Invalid ${fn} function`);
|
if (typeof opts[fn] !== 'function') throw new Error(`Invalid ${fn} function`);
|
||||||
}
|
}
|
||||||
for (const fn of [
|
for (const fn of ['adjustScalarBytes', 'domain', 'uvRatio', 'mapToCurve'] as const) {
|
||||||
'adjustScalarBytes',
|
|
||||||
'domain',
|
|
||||||
'uvRatio',
|
|
||||||
'mapToCurve',
|
|
||||||
'clearCofactor',
|
|
||||||
] as const) {
|
|
||||||
if (opts[fn] === undefined) continue; // Optional
|
if (opts[fn] === undefined) continue; // Optional
|
||||||
if (typeof opts[fn] !== 'function') throw new Error(`Invalid ${fn} function`);
|
if (typeof opts[fn] !== 'function') throw new Error(`Invalid ${fn} function`);
|
||||||
}
|
}
|
||||||
@@ -96,7 +78,7 @@ export type SignatureConstructor = {
|
|||||||
fromHex(hex: Hex): SignatureType;
|
fromHex(hex: Hex): SignatureType;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Instance
|
// Instance of Extended Point with coordinates in X, Y, Z, T
|
||||||
export interface ExtendedPointType extends Group<ExtendedPointType> {
|
export interface ExtendedPointType extends Group<ExtendedPointType> {
|
||||||
readonly x: bigint;
|
readonly x: bigint;
|
||||||
readonly y: bigint;
|
readonly y: bigint;
|
||||||
@@ -109,7 +91,7 @@ export interface ExtendedPointType extends Group<ExtendedPointType> {
|
|||||||
toAffine(invZ?: bigint): PointType;
|
toAffine(invZ?: bigint): PointType;
|
||||||
clearCofactor(): ExtendedPointType;
|
clearCofactor(): ExtendedPointType;
|
||||||
}
|
}
|
||||||
// Static methods
|
// Static methods of Extended Point with coordinates in X, Y, Z, T
|
||||||
export interface ExtendedPointConstructor extends GroupConstructor<ExtendedPointType> {
|
export interface ExtendedPointConstructor extends GroupConstructor<ExtendedPointType> {
|
||||||
new (x: bigint, y: bigint, z: bigint, t: bigint): ExtendedPointType;
|
new (x: bigint, y: bigint, z: bigint, t: bigint): ExtendedPointType;
|
||||||
fromAffine(p: PointType): ExtendedPointType;
|
fromAffine(p: PointType): ExtendedPointType;
|
||||||
@@ -117,7 +99,7 @@ export interface ExtendedPointConstructor extends GroupConstructor<ExtendedPoint
|
|||||||
normalizeZ(points: ExtendedPointType[]): ExtendedPointType[];
|
normalizeZ(points: ExtendedPointType[]): ExtendedPointType[];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Instance
|
// Instance of Affine Point with coordinates in X, Y
|
||||||
export interface PointType extends Group<PointType> {
|
export interface PointType extends Group<PointType> {
|
||||||
readonly x: bigint;
|
readonly x: bigint;
|
||||||
readonly y: bigint;
|
readonly y: bigint;
|
||||||
@@ -127,7 +109,7 @@ export interface PointType extends Group<PointType> {
|
|||||||
isTorsionFree(): boolean;
|
isTorsionFree(): boolean;
|
||||||
clearCofactor(): PointType;
|
clearCofactor(): PointType;
|
||||||
}
|
}
|
||||||
// Static methods
|
// Static methods of Affine Point with coordinates in X, Y
|
||||||
export interface PointConstructor extends GroupConstructor<PointType> {
|
export interface PointConstructor extends GroupConstructor<PointType> {
|
||||||
new (x: bigint, y: bigint): PointType;
|
new (x: bigint, y: bigint): PointType;
|
||||||
fromHex(hex: Hex): PointType;
|
fromHex(hex: Hex): PointType;
|
||||||
@@ -148,8 +130,6 @@ export type CurveFn = {
|
|||||||
ExtendedPoint: ExtendedPointConstructor;
|
ExtendedPoint: ExtendedPointConstructor;
|
||||||
Signature: SignatureConstructor;
|
Signature: SignatureConstructor;
|
||||||
utils: {
|
utils: {
|
||||||
mod: (a: bigint) => bigint;
|
|
||||||
invert: (number: bigint) => bigint;
|
|
||||||
randomPrivateKey: () => Uint8Array;
|
randomPrivateKey: () => Uint8Array;
|
||||||
getExtendedPublicKey: (key: PrivKey) => {
|
getExtendedPublicKey: (key: PrivKey) => {
|
||||||
head: Uint8Array;
|
head: Uint8Array;
|
||||||
@@ -164,36 +144,31 @@ export type CurveFn = {
|
|||||||
// NOTE: it is not generic twisted curve for now, but ed25519/ed448 generic implementation
|
// NOTE: it is not generic twisted curve for now, but ed25519/ed448 generic implementation
|
||||||
export function twistedEdwards(curveDef: CurveType): CurveFn {
|
export function twistedEdwards(curveDef: CurveType): CurveFn {
|
||||||
const CURVE = validateOpts(curveDef) as ReturnType<typeof validateOpts>;
|
const CURVE = validateOpts(curveDef) as ReturnType<typeof validateOpts>;
|
||||||
const Fp = CURVE.Fp as mod.Field<bigint>;
|
const Fp = CURVE.Fp;
|
||||||
const CURVE_ORDER = CURVE.n;
|
const CURVE_ORDER = CURVE.n;
|
||||||
const fieldLen = Fp.BYTES; // 32 (length of one field element)
|
const maxGroupElement = _2n ** BigInt(CURVE.nByteLength * 8);
|
||||||
if (fieldLen > 2048) throw new Error('Field lengths over 2048 are not supported');
|
|
||||||
const groupLen = CURVE.nByteLength;
|
|
||||||
// (2n ** 256n).toString(16);
|
|
||||||
const maxGroupElement = _2n ** BigInt(groupLen * 8); // previous POW_2_256
|
|
||||||
|
|
||||||
// Function overrides
|
// Function overrides
|
||||||
const { randomBytes } = CURVE;
|
const { randomBytes } = CURVE;
|
||||||
const modP = Fp.create;
|
const modP = Fp.create;
|
||||||
|
|
||||||
// sqrt(u/v)
|
// sqrt(u/v)
|
||||||
function _uvRatio(u: bigint, v: bigint) {
|
const uvRatio =
|
||||||
try {
|
CURVE.uvRatio ||
|
||||||
const value = Fp.sqrt(u * Fp.invert(v));
|
((u: bigint, v: bigint) => {
|
||||||
return { isValid: true, value };
|
try {
|
||||||
} catch (e) {
|
return { isValid: true, value: Fp.sqrt(u * Fp.invert(v)) };
|
||||||
return { isValid: false, value: _0n };
|
} catch (e) {
|
||||||
}
|
return { isValid: false, value: _0n };
|
||||||
}
|
}
|
||||||
const uvRatio = CURVE.uvRatio || _uvRatio;
|
});
|
||||||
|
const adjustScalarBytes = CURVE.adjustScalarBytes || ((bytes: Uint8Array) => bytes); // NOOP
|
||||||
const _adjustScalarBytes = (bytes: Uint8Array) => bytes; // NOOP
|
const domain =
|
||||||
const adjustScalarBytes = CURVE.adjustScalarBytes || _adjustScalarBytes;
|
CURVE.domain ||
|
||||||
function _domain(data: Uint8Array, ctx: Uint8Array, phflag: boolean) {
|
((data: Uint8Array, ctx: Uint8Array, phflag: boolean) => {
|
||||||
if (ctx.length || phflag) throw new Error('Contexts/pre-hash are not supported');
|
if (ctx.length || phflag) throw new Error('Contexts/pre-hash are not supported');
|
||||||
return data;
|
return data;
|
||||||
}
|
}); // NOOP
|
||||||
const domain = CURVE.domain || _domain; // NOOP
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extended Point works in extended coordinates: (x, y, z, t) ∋ (x=x/z, y=y/z, t=xy).
|
* Extended Point works in extended coordinates: (x, y, z, t) ∋ (x=x/z, y=y/z, t=xy).
|
||||||
@@ -336,25 +311,27 @@ export function twistedEdwards(curveDef: CurveType): CurveFn {
|
|||||||
// Non-constant-time multiplication. Uses double-and-add algorithm.
|
// Non-constant-time multiplication. Uses double-and-add algorithm.
|
||||||
// It's faster, but should only be used when you don't care about
|
// It's faster, but should only be used when you don't care about
|
||||||
// an exposed private key e.g. sig verification.
|
// an exposed private key e.g. sig verification.
|
||||||
// Allows scalar bigger than curve order, but less than 2^256
|
|
||||||
multiplyUnsafe(scalar: number | bigint): ExtendedPoint {
|
multiplyUnsafe(scalar: number | bigint): ExtendedPoint {
|
||||||
let n = normalizeScalar(scalar, CURVE_ORDER, false);
|
let n = normalizeScalar(scalar, CURVE_ORDER, false);
|
||||||
const G = ExtendedPoint.BASE;
|
|
||||||
const P0 = ExtendedPoint.ZERO;
|
const P0 = ExtendedPoint.ZERO;
|
||||||
if (n === _0n) return P0;
|
if (n === _0n) return P0;
|
||||||
if (this.equals(P0) || n === _1n) return this;
|
if (this.equals(P0) || n === _1n) return this;
|
||||||
if (this.equals(G)) return this.wNAF(n);
|
if (this.equals(ExtendedPoint.BASE)) return this.wNAF(n);
|
||||||
return wnaf.unsafeLadder(this, n);
|
return wnaf.unsafeLadder(this, n);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Checks if point is of small order.
|
||||||
|
// If you add something to small order point, you will have "dirty"
|
||||||
|
// point with torsion component.
|
||||||
// Multiplies point by cofactor and checks if the result is 0.
|
// Multiplies point by cofactor and checks if the result is 0.
|
||||||
isSmallOrder(): boolean {
|
isSmallOrder(): boolean {
|
||||||
return this.multiplyUnsafe(CURVE.h).equals(ExtendedPoint.ZERO);
|
return this.multiplyUnsafe(CURVE.h).equals(ExtendedPoint.ZERO);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Multiplies point by a very big scalar n and checks if the result is 0.
|
// Multiplies point by curve order (very big scalar CURVE.n) and checks if the result is 0.
|
||||||
|
// Returns `false` is the point is dirty.
|
||||||
isTorsionFree(): boolean {
|
isTorsionFree(): boolean {
|
||||||
return this.multiplyUnsafe(CURVE_ORDER).equals(ExtendedPoint.ZERO);
|
return wnaf.unsafeLadder(this, CURVE_ORDER).equals(ExtendedPoint.ZERO);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Converts Extended point to default (x, y) coordinates.
|
// Converts Extended point to default (x, y) coordinates.
|
||||||
@@ -371,14 +348,12 @@ export function twistedEdwards(curveDef: CurveType): CurveFn {
|
|||||||
return new Point(ax, ay);
|
return new Point(ax, ay);
|
||||||
}
|
}
|
||||||
clearCofactor(): ExtendedPoint {
|
clearCofactor(): ExtendedPoint {
|
||||||
if (CURVE.h === _1n) return this; // Fast-path
|
const { h: cofactor } = CURVE;
|
||||||
// clear_cofactor(P) := h_eff * P
|
if (cofactor === _1n) return this;
|
||||||
// hEff = h for ed25519/ed448. Maybe worth moving to params?
|
return this.multiplyUnsafe(cofactor);
|
||||||
if (CURVE.clearCofactor) return CURVE.clearCofactor(ExtendedPoint, this) as ExtendedPoint;
|
|
||||||
return this.multiplyUnsafe(CURVE.h);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const wnaf = wNAF(ExtendedPoint, groupLen * 8);
|
const wnaf = wNAF(ExtendedPoint, CURVE.nByteLength * 8);
|
||||||
|
|
||||||
function assertExtPoint(other: unknown) {
|
function assertExtPoint(other: unknown) {
|
||||||
if (!(other instanceof ExtendedPoint)) throw new TypeError('ExtendedPoint expected');
|
if (!(other instanceof ExtendedPoint)) throw new TypeError('ExtendedPoint expected');
|
||||||
@@ -413,19 +388,20 @@ export function twistedEdwards(curveDef: CurveType): CurveFn {
|
|||||||
// Uses algo from RFC8032 5.1.3.
|
// Uses algo from RFC8032 5.1.3.
|
||||||
static fromHex(hex: Hex, strict = true) {
|
static fromHex(hex: Hex, strict = true) {
|
||||||
const { d, a } = CURVE;
|
const { d, a } = CURVE;
|
||||||
hex = ensureBytes(hex, fieldLen);
|
const len = Fp.BYTES;
|
||||||
|
hex = ensureBytes(hex, len);
|
||||||
// 1. First, interpret the string as an integer in little-endian
|
// 1. First, interpret the string as an integer in little-endian
|
||||||
// representation. Bit 255 of this number is the least significant
|
// representation. Bit 255 of this number is the least significant
|
||||||
// bit of the x-coordinate and denote this value x_0. The
|
// bit of the x-coordinate and denote this value x_0. The
|
||||||
// y-coordinate is recovered simply by clearing this bit. If the
|
// y-coordinate is recovered simply by clearing this bit. If the
|
||||||
// resulting value is >= p, decoding fails.
|
// resulting value is >= p, decoding fails.
|
||||||
const normed = hex.slice();
|
const normed = hex.slice();
|
||||||
const lastByte = hex[fieldLen - 1];
|
const lastByte = hex[len - 1];
|
||||||
normed[fieldLen - 1] = lastByte & ~0x80;
|
normed[len - 1] = lastByte & ~0x80;
|
||||||
const y = bytesToNumberLE(normed);
|
const y = ut.bytesToNumberLE(normed);
|
||||||
|
|
||||||
if (strict && y >= Fp.ORDER) throw new Error('Expected 0 < hex < P');
|
if (strict && y >= Fp.ORDER) throw new Error('Expected 0 < hex < P');
|
||||||
if (!strict && y >= maxGroupElement) throw new Error('Expected 0 < hex < 2**256');
|
if (!strict && y >= maxGroupElement) throw new Error('Expected 0 < hex < CURVE.n');
|
||||||
|
|
||||||
// 2. To recover the x-coordinate, the curve equation implies
|
// 2. To recover the x-coordinate, the curve equation implies
|
||||||
// Ed25519: x² = (y² - 1) / (d y² + 1) (mod p).
|
// Ed25519: x² = (y² - 1) / (d y² + 1) (mod p).
|
||||||
@@ -459,16 +435,18 @@ export function twistedEdwards(curveDef: CurveType): CurveFn {
|
|||||||
// When compressing point, it's enough to only store its y coordinate
|
// When compressing point, it's enough to only store its y coordinate
|
||||||
// and use the last byte to encode sign of x.
|
// and use the last byte to encode sign of x.
|
||||||
toRawBytes(): Uint8Array {
|
toRawBytes(): Uint8Array {
|
||||||
const bytes = numberToBytesLE(this.y, fieldLen);
|
const bytes = ut.numberToBytesLE(this.y, Fp.BYTES);
|
||||||
bytes[fieldLen - 1] |= this.x & _1n ? 0x80 : 0;
|
bytes[Fp.BYTES - 1] |= this.x & _1n ? 0x80 : 0;
|
||||||
return bytes;
|
return bytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Same as toRawBytes, but returns string.
|
// Same as toRawBytes, but returns string.
|
||||||
toHex(): string {
|
toHex(): string {
|
||||||
return bytesToHex(this.toRawBytes());
|
return ut.bytesToHex(this.toRawBytes());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Determines if point is in prime-order subgroup.
|
||||||
|
// Returns `false` is the point is dirty.
|
||||||
isTorsionFree(): boolean {
|
isTorsionFree(): boolean {
|
||||||
return ExtendedPoint.fromAffine(this).isTorsionFree();
|
return ExtendedPoint.fromAffine(this).isTorsionFree();
|
||||||
}
|
}
|
||||||
@@ -509,20 +487,20 @@ export function twistedEdwards(curveDef: CurveType): CurveFn {
|
|||||||
// Encodes byte string to elliptic curve
|
// Encodes byte string to elliptic curve
|
||||||
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-3
|
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-3
|
||||||
static hashToCurve(msg: Hex, options?: Partial<htfOpts>) {
|
static hashToCurve(msg: Hex, options?: Partial<htfOpts>) {
|
||||||
if (!CURVE.mapToCurve) throw new Error('No mapToCurve defined for curve');
|
const { mapToCurve, htfDefaults } = CURVE;
|
||||||
msg = ensureBytes(msg);
|
if (!mapToCurve) throw new Error('No mapToCurve defined for curve');
|
||||||
const u = hash_to_field(msg, 2, { ...CURVE.htfDefaults, ...options } as htfOpts);
|
const u = hashToField(ensureBytes(msg), 2, { ...htfDefaults, ...options } as htfOpts);
|
||||||
const { x: x0, y: y0 } = CURVE.mapToCurve(u[0]);
|
const { x: x0, y: y0 } = mapToCurve(u[0]);
|
||||||
const { x: x1, y: y1 } = CURVE.mapToCurve(u[1]);
|
const { x: x1, y: y1 } = mapToCurve(u[1]);
|
||||||
const p = new Point(x0, y0).add(new Point(x1, y1)).clearCofactor();
|
const p = new Point(x0, y0).add(new Point(x1, y1)).clearCofactor();
|
||||||
return p;
|
return p;
|
||||||
}
|
}
|
||||||
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-16#section-3
|
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-16#section-3
|
||||||
static encodeToCurve(msg: Hex, options?: Partial<htfOpts>) {
|
static encodeToCurve(msg: Hex, options?: Partial<htfOpts>) {
|
||||||
if (!CURVE.mapToCurve) throw new Error('No mapToCurve defined for curve');
|
const { mapToCurve, htfDefaults } = CURVE;
|
||||||
msg = ensureBytes(msg);
|
if (!mapToCurve) throw new Error('No mapToCurve defined for curve');
|
||||||
const u = hash_to_field(msg, 1, { ...CURVE.htfDefaults, ...options } as htfOpts);
|
const u = hashToField(ensureBytes(msg), 1, { ...htfDefaults, ...options } as htfOpts);
|
||||||
const { x, y } = CURVE.mapToCurve(u[0]);
|
const { x, y } = mapToCurve(u[0]);
|
||||||
return new Point(x, y).clearCofactor();
|
return new Point(x, y).clearCofactor();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -536,9 +514,10 @@ export function twistedEdwards(curveDef: CurveType): CurveFn {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static fromHex(hex: Hex) {
|
static fromHex(hex: Hex) {
|
||||||
const bytes = ensureBytes(hex, 2 * fieldLen);
|
const len = Fp.BYTES;
|
||||||
const r = Point.fromHex(bytes.slice(0, fieldLen), false);
|
const bytes = ensureBytes(hex, 2 * len);
|
||||||
const s = bytesToNumberLE(bytes.slice(fieldLen, 2 * fieldLen));
|
const r = Point.fromHex(bytes.slice(0, len), false);
|
||||||
|
const s = ut.bytesToNumberLE(bytes.slice(len, 2 * len));
|
||||||
return new Signature(r, s);
|
return new Signature(r, s);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -551,17 +530,17 @@ export function twistedEdwards(curveDef: CurveType): CurveFn {
|
|||||||
}
|
}
|
||||||
|
|
||||||
toRawBytes() {
|
toRawBytes() {
|
||||||
return concatBytes(this.r.toRawBytes(), numberToBytesLE(this.s, fieldLen));
|
return ut.concatBytes(this.r.toRawBytes(), ut.numberToBytesLE(this.s, Fp.BYTES));
|
||||||
}
|
}
|
||||||
|
|
||||||
toHex() {
|
toHex() {
|
||||||
return bytesToHex(this.toRawBytes());
|
return ut.bytesToHex(this.toRawBytes());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Little-endian SHA512 with modulo n
|
// Little-endian SHA512 with modulo n
|
||||||
function modlLE(hash: Uint8Array): bigint {
|
function modnLE(hash: Uint8Array): bigint {
|
||||||
return mod.mod(bytesToNumberLE(hash), CURVE_ORDER);
|
return mod.mod(ut.bytesToNumberLE(hash), CURVE_ORDER);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -572,7 +551,7 @@ export function twistedEdwards(curveDef: CurveType): CurveFn {
|
|||||||
*/
|
*/
|
||||||
function normalizeScalar(num: number | bigint, max: bigint, strict = true): bigint {
|
function normalizeScalar(num: number | bigint, max: bigint, strict = true): bigint {
|
||||||
if (!max) throw new TypeError('Specify max value');
|
if (!max) throw new TypeError('Specify max value');
|
||||||
if (typeof num === 'number' && Number.isSafeInteger(num)) num = BigInt(num);
|
if (ut.isPositiveInt(num)) num = BigInt(num);
|
||||||
if (typeof num === 'bigint' && num < max) {
|
if (typeof num === 'bigint' && num < max) {
|
||||||
if (strict) {
|
if (strict) {
|
||||||
if (_0n < num) return num;
|
if (_0n < num) return num;
|
||||||
@@ -580,37 +559,32 @@ export function twistedEdwards(curveDef: CurveType): CurveFn {
|
|||||||
if (_0n <= num) return num;
|
if (_0n <= num) return num;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
throw new TypeError('Expected valid scalar: 0 < scalar < max');
|
throw new TypeError(`Expected valid scalar: 0 < scalar < ${max}`);
|
||||||
}
|
|
||||||
|
|
||||||
function checkPrivateKey(key: PrivKey) {
|
|
||||||
// Normalize bigint / number / string to Uint8Array
|
|
||||||
key =
|
|
||||||
typeof key === 'bigint' || typeof key === 'number'
|
|
||||||
? numberToBytesLE(normalizeScalar(key, maxGroupElement), groupLen)
|
|
||||||
: ensureBytes(key);
|
|
||||||
if (key.length !== groupLen) throw new Error(`Expected ${groupLen} bytes, got ${key.length}`);
|
|
||||||
return key;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Takes 64 bytes
|
|
||||||
function getKeyFromHash(hashed: Uint8Array) {
|
|
||||||
// First 32 bytes of 64b uniformingly random input are taken,
|
|
||||||
// clears 3 bits of it to produce a random field element.
|
|
||||||
const head = adjustScalarBytes(hashed.slice(0, groupLen));
|
|
||||||
// Second 32 bytes is called key prefix (5.1.6)
|
|
||||||
const prefix = hashed.slice(groupLen, 2 * groupLen);
|
|
||||||
// The actual private scalar
|
|
||||||
const scalar = modlLE(head);
|
|
||||||
// Point on Edwards curve aka public key
|
|
||||||
const point = Point.BASE.multiply(scalar);
|
|
||||||
const pointBytes = point.toRawBytes();
|
|
||||||
return { head, prefix, scalar, point, pointBytes };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Convenience method that creates public key and other stuff. RFC8032 5.1.5 */
|
/** Convenience method that creates public key and other stuff. RFC8032 5.1.5 */
|
||||||
function getExtendedPublicKey(key: PrivKey) {
|
function getExtendedPublicKey(key: PrivKey) {
|
||||||
return getKeyFromHash(CURVE.hash(checkPrivateKey(key)));
|
const groupLen = CURVE.nByteLength;
|
||||||
|
// Normalize bigint / number / string to Uint8Array
|
||||||
|
const keyb =
|
||||||
|
typeof key === 'bigint' || typeof key === 'number'
|
||||||
|
? ut.numberToBytesLE(normalizeScalar(key, maxGroupElement), groupLen)
|
||||||
|
: key;
|
||||||
|
// Hash private key with curve's hash function to produce uniformingly random input
|
||||||
|
// We check byte lengths e.g.: ensureBytes(64, hash(ensureBytes(32, key)))
|
||||||
|
const hashed = ensureBytes(CURVE.hash(ensureBytes(keyb, groupLen)), 2 * groupLen);
|
||||||
|
|
||||||
|
// First half's bits are cleared to produce a random field element.
|
||||||
|
const head = adjustScalarBytes(hashed.slice(0, groupLen));
|
||||||
|
// Second half is called key prefix (5.1.6)
|
||||||
|
const prefix = hashed.slice(groupLen, 2 * groupLen);
|
||||||
|
// The actual private scalar
|
||||||
|
const scalar = modnLE(head);
|
||||||
|
// Point on Edwards curve aka public key
|
||||||
|
const point = Point.BASE.multiply(scalar);
|
||||||
|
// Uint8Array representation
|
||||||
|
const pointBytes = point.toRawBytes();
|
||||||
|
return { head, prefix, scalar, point, pointBytes };
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -625,7 +599,7 @@ export function twistedEdwards(curveDef: CurveType): CurveFn {
|
|||||||
const EMPTY = new Uint8Array();
|
const EMPTY = new Uint8Array();
|
||||||
function hashDomainToScalar(message: Uint8Array, context: Hex = EMPTY) {
|
function hashDomainToScalar(message: Uint8Array, context: Hex = EMPTY) {
|
||||||
context = ensureBytes(context);
|
context = ensureBytes(context);
|
||||||
return modlLE(CURVE.hash(domain(message, context, !!CURVE.preHash)));
|
return modnLE(CURVE.hash(domain(message, context, !!CURVE.preHash)));
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Signs message with privateKey. RFC8032 5.1.6 */
|
/** Signs message with privateKey. RFC8032 5.1.6 */
|
||||||
@@ -633,9 +607,9 @@ export function twistedEdwards(curveDef: CurveType): CurveFn {
|
|||||||
message = ensureBytes(message);
|
message = ensureBytes(message);
|
||||||
if (CURVE.preHash) message = CURVE.preHash(message);
|
if (CURVE.preHash) message = CURVE.preHash(message);
|
||||||
const { prefix, scalar, pointBytes } = getExtendedPublicKey(privateKey);
|
const { prefix, scalar, pointBytes } = getExtendedPublicKey(privateKey);
|
||||||
const r = hashDomainToScalar(concatBytes(prefix, message), context);
|
const r = hashDomainToScalar(ut.concatBytes(prefix, message), context);
|
||||||
const R = Point.BASE.multiply(r); // R = rG
|
const R = Point.BASE.multiply(r); // R = rG
|
||||||
const k = hashDomainToScalar(concatBytes(R.toRawBytes(), pointBytes, message), context); // k = hash(R+P+msg)
|
const k = hashDomainToScalar(ut.concatBytes(R.toRawBytes(), pointBytes, message), context); // k = hash(R+P+msg)
|
||||||
const s = mod.mod(r + k * scalar, CURVE_ORDER); // s = r + kp
|
const s = mod.mod(r + k * scalar, CURVE_ORDER); // s = r + kp
|
||||||
return new Signature(R, s).toRawBytes();
|
return new Signature(R, s).toRawBytes();
|
||||||
}
|
}
|
||||||
@@ -672,13 +646,13 @@ export function twistedEdwards(curveDef: CurveType): CurveFn {
|
|||||||
const { r, s } = sig;
|
const { r, s } = sig;
|
||||||
const SB = ExtendedPoint.BASE.multiplyUnsafe(s);
|
const SB = ExtendedPoint.BASE.multiplyUnsafe(s);
|
||||||
const k = hashDomainToScalar(
|
const k = hashDomainToScalar(
|
||||||
concatBytes(r.toRawBytes(), publicKey.toRawBytes(), message),
|
ut.concatBytes(r.toRawBytes(), publicKey.toRawBytes(), message),
|
||||||
context
|
context
|
||||||
);
|
);
|
||||||
const kA = ExtendedPoint.fromAffine(publicKey).multiplyUnsafe(k);
|
const kA = ExtendedPoint.fromAffine(publicKey).multiplyUnsafe(k);
|
||||||
const RkA = ExtendedPoint.fromAffine(r).add(kA);
|
const RkA = ExtendedPoint.fromAffine(r).add(kA);
|
||||||
// [8][S]B = [8]R + [8][k]A'
|
// [8][S]B = [8]R + [8][k]A'
|
||||||
return RkA.subtract(SB).multiplyUnsafe(CURVE.h).equals(ExtendedPoint.ZERO);
|
return RkA.subtract(SB).clearCofactor().equals(ExtendedPoint.ZERO);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Enable precomputes. Slows down first publicKey computation by 20ms.
|
// Enable precomputes. Slows down first publicKey computation by 20ms.
|
||||||
@@ -686,19 +660,16 @@ export function twistedEdwards(curveDef: CurveType): CurveFn {
|
|||||||
|
|
||||||
const utils = {
|
const utils = {
|
||||||
getExtendedPublicKey,
|
getExtendedPublicKey,
|
||||||
mod: modP,
|
|
||||||
invert: Fp.invert,
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Not needed for ed25519 private keys. Needed if you use scalars directly (rare).
|
* Not needed for ed25519 private keys. Needed if you use scalars directly (rare).
|
||||||
*/
|
*/
|
||||||
hashToPrivateScalar: (hash: Hex): bigint => hashToPrivateScalar(hash, CURVE_ORDER, true),
|
hashToPrivateScalar: (hash: Hex): bigint => ut.hashToPrivateScalar(hash, CURVE_ORDER, true),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ed25519 private keys are uniform 32-bit strings. We do not need to check for
|
* ed25519 private keys are uniform 32-bit strings. We do not need to check for
|
||||||
* modulo bias like we do in secp256k1 randomPrivateKey()
|
* modulo bias like we do in secp256k1 randomPrivateKey()
|
||||||
*/
|
*/
|
||||||
randomPrivateKey: (): Uint8Array => randomBytes(fieldLen),
|
randomPrivateKey: (): Uint8Array => randomBytes(Fp.BYTES),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* We're doing scalar multiplication (used in getPublicKey etc) with precomputed BASE_POINT
|
* We're doing scalar multiplication (used in getPublicKey etc) with precomputed BASE_POINT
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ export function invert(number: bigint, modulo: bigint): bigint {
|
|||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
let x = _0n, y = _1n, u = _1n, v = _0n;
|
let x = _0n, y = _1n, u = _1n, v = _0n;
|
||||||
while (a !== _0n) {
|
while (a !== _0n) {
|
||||||
|
// JIT applies optimization if those two lines follow each other
|
||||||
const q = b / a;
|
const q = b / a;
|
||||||
const r = b % a;
|
const r = b % a;
|
||||||
const m = x - u * q;
|
const m = x - u * q;
|
||||||
@@ -68,7 +69,8 @@ export function invert(number: bigint, modulo: bigint): bigint {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Tonelli-Shanks algorithm
|
// Tonelli-Shanks algorithm
|
||||||
// https://eprint.iacr.org/2012/685.pdf (page 12)
|
// Paper 1: https://eprint.iacr.org/2012/685.pdf (page 12)
|
||||||
|
// Paper 2: Square Roots from 1; 24, 51, 10 to Dan Shanks
|
||||||
export function tonelliShanks(P: bigint) {
|
export function tonelliShanks(P: bigint) {
|
||||||
// Legendre constant: used to calculate Legendre symbol (a | p),
|
// Legendre constant: used to calculate Legendre symbol (a | p),
|
||||||
// which denotes the value of a^((p-1)/2) (mod p).
|
// which denotes the value of a^((p-1)/2) (mod p).
|
||||||
@@ -79,7 +81,7 @@ export function tonelliShanks(P: bigint) {
|
|||||||
|
|
||||||
let Q: bigint, S: number, Z: bigint;
|
let Q: bigint, S: number, Z: bigint;
|
||||||
// Step 1: By factoring out powers of 2 from p - 1,
|
// Step 1: By factoring out powers of 2 from p - 1,
|
||||||
// find q and s such that p - 1 = q2s with q odd
|
// find q and s such that p - 1 = q*(2^s) with q odd
|
||||||
for (Q = P - _1n, S = 0; Q % _2n === _0n; Q /= _2n, S++);
|
for (Q = P - _1n, S = 0; Q % _2n === _0n; Q /= _2n, S++);
|
||||||
|
|
||||||
// Step 2: Select a non-square z such that (z | p) ≡ -1 and set c ≡ zq
|
// Step 2: Select a non-square z such that (z | p) ≡ -1 and set c ≡ zq
|
||||||
@@ -98,31 +100,30 @@ export function tonelliShanks(P: bigint) {
|
|||||||
// Slow-path
|
// Slow-path
|
||||||
const Q1div2 = (Q + _1n) / _2n;
|
const Q1div2 = (Q + _1n) / _2n;
|
||||||
return function tonelliSlow<T>(Fp: Field<T>, n: T): T {
|
return function tonelliSlow<T>(Fp: Field<T>, n: T): T {
|
||||||
// Step 0: Check that n is indeed a square: (n | p) must be ≡ 1
|
// Step 0: Check that n is indeed a square: (n | p) should not be ≡ -1
|
||||||
if (Fp.pow(n, legendreC) !== Fp.ONE) throw new Error('Cannot find square root');
|
if (Fp.pow(n, legendreC) === Fp.negate(Fp.ONE)) throw new Error('Cannot find square root');
|
||||||
let s = S;
|
let r = S;
|
||||||
|
// TODO: will fail at Fp2/etc
|
||||||
|
let g = Fp.pow(Fp.mul(Fp.ONE, Z), Q); // will update both x and b
|
||||||
|
let x = Fp.pow(n, Q1div2); // first guess at the square root
|
||||||
|
let b = Fp.pow(n, Q); // first guess at the fudge factor
|
||||||
|
|
||||||
let c = pow(Z, Q, P);
|
while (!Fp.equals(b, Fp.ONE)) {
|
||||||
let r = Fp.pow(n, Q1div2);
|
if (Fp.equals(b, Fp.ZERO)) return Fp.ZERO; // https://en.wikipedia.org/wiki/Tonelli%E2%80%93Shanks_algorithm (4. If t = 0, return r = 0)
|
||||||
let t = Fp.pow(n, Q);
|
// Find m such b^(2^m)==1
|
||||||
|
let m = 1;
|
||||||
let t2 = Fp.ZERO;
|
for (let t2 = Fp.square(b); m < r; m++) {
|
||||||
while (!Fp.equals(Fp.sub(t, Fp.ONE), Fp.ZERO)) {
|
if (Fp.equals(t2, Fp.ONE)) break;
|
||||||
t2 = Fp.square(t);
|
t2 = Fp.square(t2); // t2 *= t2
|
||||||
let i;
|
|
||||||
for (i = 1; i < s; i++) {
|
|
||||||
// stop if t2-1 == 0
|
|
||||||
if (Fp.equals(Fp.sub(t2, Fp.ONE), Fp.ZERO)) break;
|
|
||||||
// t2 *= t2
|
|
||||||
t2 = Fp.square(t2);
|
|
||||||
}
|
}
|
||||||
let b = pow(c, BigInt(1 << (s - i - 1)), P);
|
// NOTE: r-m-1 can be bigger than 32, need to convert to bigint before shift, otherwise there will be overflow
|
||||||
r = Fp.mul(r, b);
|
const ge = Fp.pow(g, _1n << BigInt(r - m - 1)); // ge = 2^(r-m-1)
|
||||||
c = mod(b * b, P);
|
g = Fp.square(ge); // g = ge * ge
|
||||||
t = Fp.mul(t, c);
|
x = Fp.mul(x, ge); // x *= ge
|
||||||
s = i;
|
b = Fp.mul(b, g); // b *= g
|
||||||
|
r = m;
|
||||||
}
|
}
|
||||||
return r;
|
return x;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,6 @@
|
|||||||
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
|
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
|
||||||
import * as mod from './modular.js';
|
import * as mod from './modular.js';
|
||||||
import {
|
import { ensureBytes, numberToBytesLE, bytesToNumberLE, isPositiveInt } from './utils.js';
|
||||||
ensureBytes,
|
|
||||||
numberToBytesLE,
|
|
||||||
bytesToNumberLE,
|
|
||||||
// nLength,
|
|
||||||
} from './utils.js';
|
|
||||||
|
|
||||||
const _0n = BigInt(0);
|
const _0n = BigInt(0);
|
||||||
const _1n = BigInt(1);
|
const _1n = BigInt(1);
|
||||||
@@ -38,7 +33,7 @@ function validateOpts(curve: CurveType) {
|
|||||||
}
|
}
|
||||||
for (const i of ['montgomeryBits', 'nByteLength'] as const) {
|
for (const i of ['montgomeryBits', 'nByteLength'] as const) {
|
||||||
if (curve[i] === undefined) continue; // Optional
|
if (curve[i] === undefined) continue; // Optional
|
||||||
if (!Number.isSafeInteger(curve[i]))
|
if (!isPositiveInt(curve[i]))
|
||||||
throw new Error(`Invalid curve param ${i}=${curve[i]} (${typeof curve[i]})`);
|
throw new Error(`Invalid curve param ${i}=${curve[i]} (${typeof curve[i]})`);
|
||||||
}
|
}
|
||||||
for (const fn of ['adjustScalarBytes', 'domain', 'powPminus2'] as const) {
|
for (const fn of ['adjustScalarBytes', 'domain', 'powPminus2'] as const) {
|
||||||
|
|||||||
@@ -40,19 +40,24 @@ export type BasicCurve<T> = {
|
|||||||
allowInfinityPoint?: boolean;
|
allowInfinityPoint?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Bans floats and integers above 2^53-1
|
||||||
|
export function isPositiveInt(num: any): num is number {
|
||||||
|
return typeof num === 'number' && Number.isSafeInteger(num) && num > 0;
|
||||||
|
}
|
||||||
|
|
||||||
export function validateOpts<FP, T>(curve: BasicCurve<FP> & T) {
|
export function validateOpts<FP, T>(curve: BasicCurve<FP> & T) {
|
||||||
mod.validateField(curve.Fp);
|
mod.validateField(curve.Fp);
|
||||||
for (const i of ['n', 'h'] as const) {
|
for (const i of ['n', 'h'] as const) {
|
||||||
if (typeof curve[i] !== 'bigint')
|
const val = curve[i];
|
||||||
throw new Error(`Invalid curve param ${i}=${curve[i]} (${typeof curve[i]})`);
|
if (typeof val !== 'bigint') throw new Error(`Invalid curve param ${i}=${val} (${typeof val})`);
|
||||||
}
|
}
|
||||||
if (!curve.Fp.isValid(curve.Gx)) throw new Error('Invalid generator X coordinate Fp element');
|
if (!curve.Fp.isValid(curve.Gx)) throw new Error('Invalid generator X coordinate Fp element');
|
||||||
if (!curve.Fp.isValid(curve.Gy)) throw new Error('Invalid generator Y coordinate Fp element');
|
if (!curve.Fp.isValid(curve.Gy)) throw new Error('Invalid generator Y coordinate Fp element');
|
||||||
|
|
||||||
for (const i of ['nBitLength', 'nByteLength'] as const) {
|
for (const i of ['nBitLength', 'nByteLength'] as const) {
|
||||||
if (curve[i] === undefined) continue; // Optional
|
const val = curve[i];
|
||||||
if (!Number.isSafeInteger(curve[i]))
|
if (val === undefined) continue; // Optional
|
||||||
throw new Error(`Invalid curve param ${i}=${curve[i]} (${typeof curve[i]})`);
|
if (!isPositiveInt(val)) throw new Error(`Invalid curve param ${i}=${val} (${typeof val})`);
|
||||||
}
|
}
|
||||||
// Set defaults
|
// Set defaults
|
||||||
return Object.freeze({ ...nLength(curve.n, curve.nBitLength), ...curve } as const);
|
return Object.freeze({ ...nLength(curve.n, curve.nBitLength), ...curve } as const);
|
||||||
@@ -144,21 +149,22 @@ export function nLength(n: bigint, nBitLength?: number) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* FIPS 186 B.4.1-compliant "constant-time" private key generation utility.
|
||||||
* Can take (n+8) or more bytes of uniform input e.g. from CSPRNG or KDF
|
* Can take (n+8) or more bytes of uniform input e.g. from CSPRNG or KDF
|
||||||
* and convert them into private scalar, with the modulo bias being neglible.
|
* and convert them into private scalar, with the modulo bias being neglible.
|
||||||
* As per FIPS 186 B.4.1.
|
* Needs at least 40 bytes of input for 32-byte private key.
|
||||||
* https://research.kudelskisecurity.com/2020/07/28/the-definitive-guide-to-modulo-bias-and-how-to-avoid-it/
|
* https://research.kudelskisecurity.com/2020/07/28/the-definitive-guide-to-modulo-bias-and-how-to-avoid-it/
|
||||||
* @param hash hash output from sha512, or a similar function
|
* @param hash hash output from SHA3 or a similar function
|
||||||
* @returns valid private scalar
|
* @returns valid private scalar
|
||||||
*/
|
*/
|
||||||
export function hashToPrivateScalar(hash: Hex, CURVE_ORDER: bigint, isLE = false): bigint {
|
export function hashToPrivateScalar(hash: Hex, groupOrder: bigint, isLE = false): bigint {
|
||||||
hash = ensureBytes(hash);
|
hash = ensureBytes(hash);
|
||||||
const orderLen = nLength(CURVE_ORDER).nByteLength;
|
const hashLen = hash.length;
|
||||||
const minLen = orderLen + 8;
|
const minLen = nLength(groupOrder).nByteLength + 8;
|
||||||
if (orderLen < 16 || hash.length < minLen || hash.length > 1024)
|
if (minLen < 24 || hashLen < minLen || hashLen > 1024)
|
||||||
throw new Error('Expected valid bytes of private key as per FIPS 186');
|
throw new Error(`hashToPrivateScalar: expected ${minLen}-1024 bytes of input, got ${hashLen}`);
|
||||||
const num = isLE ? bytesToNumberLE(hash) : bytesToNumberBE(hash);
|
const num = isLE ? bytesToNumberLE(hash) : bytesToNumberBE(hash);
|
||||||
return mod.mod(num, CURVE_ORDER - _1n) + _1n;
|
return mod.mod(num, groupOrder - _1n) + _1n;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function equalBytes(b1: Uint8Array, b2: Uint8Array) {
|
export function equalBytes(b1: Uint8Array, b2: Uint8Array) {
|
||||||
|
|||||||
@@ -1,28 +1,16 @@
|
|||||||
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
|
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
|
||||||
// Short Weierstrass curve. The formula is: y² = x³ + ax + b
|
// Short Weierstrass curve. The formula is: y² = x³ + ax + b
|
||||||
|
|
||||||
// TODO: sync vs async naming
|
|
||||||
// TODO: default randomBytes
|
|
||||||
// Differences from @noble/secp256k1 1.7:
|
// Differences from @noble/secp256k1 1.7:
|
||||||
// 1. Different double() formula (but same addition)
|
// 1. Different double() formula (but same addition)
|
||||||
// 2. Different sqrt() function
|
// 2. Different sqrt() function
|
||||||
// 3. truncateHash() truncateOnly mode
|
// 3. truncateHash() truncateOnly mode
|
||||||
// 4. DRBG supports outputLen bigger than outputLen of hmac
|
// 4. DRBG supports outputLen bigger than outputLen of hmac
|
||||||
|
// 5. Support for different hash functions
|
||||||
|
|
||||||
import * as mod from './modular.js';
|
import * as mod from './modular.js';
|
||||||
import {
|
import * as ut from './utils.js';
|
||||||
bytesToHex,
|
import { bytesToHex, Hex, PrivKey } from './utils.js';
|
||||||
bytesToNumberBE,
|
|
||||||
concatBytes,
|
|
||||||
ensureBytes,
|
|
||||||
hexToBytes,
|
|
||||||
hexToNumber,
|
|
||||||
numberToHexUnpadded,
|
|
||||||
hashToPrivateScalar,
|
|
||||||
Hex,
|
|
||||||
PrivKey,
|
|
||||||
} from './utils.js';
|
|
||||||
import * as utils from './utils.js';
|
|
||||||
import { hash_to_field, htfOpts, validateHTFOpts } from './hash-to-curve.js';
|
import { hash_to_field, htfOpts, validateHTFOpts } from './hash-to-curve.js';
|
||||||
import { Group, GroupConstructor, wNAF } from './group.js';
|
import { Group, GroupConstructor, wNAF } from './group.js';
|
||||||
|
|
||||||
@@ -31,76 +19,77 @@ type EndomorphismOpts = {
|
|||||||
beta: bigint;
|
beta: bigint;
|
||||||
splitScalar: (k: bigint) => { k1neg: boolean; k1: bigint; k2neg: boolean; k2: bigint };
|
splitScalar: (k: bigint) => { k1neg: boolean; k1: bigint; k2neg: boolean; k2: bigint };
|
||||||
};
|
};
|
||||||
export type BasicCurve<T> = utils.BasicCurve<T> & {
|
export type BasicCurve<T> = ut.BasicCurve<T> & {
|
||||||
// Params: a, b
|
// Params: a, b
|
||||||
a: T;
|
a: T;
|
||||||
b: T;
|
b: T;
|
||||||
// TODO: move into options?
|
|
||||||
|
|
||||||
|
// Optional params
|
||||||
|
// Executed before privkey validation. Useful for P521 with var-length priv key
|
||||||
normalizePrivateKey?: (key: PrivKey) => PrivKey;
|
normalizePrivateKey?: (key: PrivKey) => PrivKey;
|
||||||
|
// Whether to execute modular division on a private key, useful for bls curves with cofactor > 1
|
||||||
|
wrapPrivateKey?: boolean;
|
||||||
// Endomorphism options for Koblitz curves
|
// Endomorphism options for Koblitz curves
|
||||||
endo?: EndomorphismOpts;
|
endo?: EndomorphismOpts;
|
||||||
// Torsions, can be optimized via endomorphisms
|
// When a cofactor != 1, there can be an effective methods to:
|
||||||
|
// 1. Determine whether a point is torsion-free
|
||||||
isTorsionFree?: (c: ProjectiveConstructor<T>, point: ProjectivePointType<T>) => boolean;
|
isTorsionFree?: (c: ProjectiveConstructor<T>, point: ProjectivePointType<T>) => boolean;
|
||||||
|
// 2. Clear torsion component
|
||||||
clearCofactor?: (
|
clearCofactor?: (
|
||||||
c: ProjectiveConstructor<T>,
|
c: ProjectiveConstructor<T>,
|
||||||
point: ProjectivePointType<T>
|
point: ProjectivePointType<T>
|
||||||
) => ProjectivePointType<T>;
|
) => ProjectivePointType<T>;
|
||||||
// Hash to field opts
|
// Hash to field options
|
||||||
htfDefaults?: htfOpts;
|
htfDefaults?: htfOpts;
|
||||||
mapToCurve?: (scalar: bigint[]) => { x: T; y: T };
|
mapToCurve?: (scalar: bigint[]) => { x: T; y: T };
|
||||||
};
|
};
|
||||||
// DER encoding utilities
|
|
||||||
|
// ASN.1 DER encoding utilities
|
||||||
class DERError extends Error {
|
class DERError extends Error {
|
||||||
constructor(message: string) {
|
constructor(message: string) {
|
||||||
super(message);
|
super(message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function sliceDER(s: string): string {
|
const DER = {
|
||||||
// Proof: any([(i>=0x80) == (int(hex(i).replace('0x', '').zfill(2)[0], 16)>=8) for i in range(0, 256)])
|
slice(s: string): string {
|
||||||
// Padding done by numberToHex
|
// Proof: any([(i>=0x80) == (int(hex(i).replace('0x', '').zfill(2)[0], 16)>=8) for i in range(0, 256)])
|
||||||
return Number.parseInt(s[0], 16) >= 8 ? '00' + s : s;
|
// Padding done by numberToHex
|
||||||
}
|
return Number.parseInt(s[0], 16) >= 8 ? '00' + s : s;
|
||||||
|
},
|
||||||
function parseDERInt(data: Uint8Array) {
|
parseInt(data: Uint8Array): { data: bigint; left: Uint8Array } {
|
||||||
if (data.length < 2 || data[0] !== 0x02) {
|
if (data.length < 2 || data[0] !== 0x02) {
|
||||||
throw new DERError(`Invalid signature integer tag: ${bytesToHex(data)}`);
|
throw new DERError(`Invalid signature integer tag: ${bytesToHex(data)}`);
|
||||||
}
|
}
|
||||||
const len = data[1];
|
const len = data[1];
|
||||||
const res = data.subarray(2, len + 2);
|
const res = data.subarray(2, len + 2);
|
||||||
if (!len || res.length !== len) {
|
if (!len || res.length !== len) {
|
||||||
throw new DERError(`Invalid signature integer: wrong length`);
|
throw new DERError(`Invalid signature integer: wrong length`);
|
||||||
}
|
}
|
||||||
// Strange condition, its not about length, but about first bytes of number.
|
// Strange condition, its not about length, but about first bytes of number.
|
||||||
if (res[0] === 0x00 && res[1] <= 0x7f) {
|
if (res[0] === 0x00 && res[1] <= 0x7f) {
|
||||||
throw new DERError('Invalid signature integer: trailing length');
|
throw new DERError('Invalid signature integer: trailing length');
|
||||||
}
|
}
|
||||||
return { data: bytesToNumberBE(res), left: data.subarray(len + 2) };
|
return { data: ut.bytesToNumberBE(res), left: data.subarray(len + 2) };
|
||||||
}
|
},
|
||||||
|
parseSig(data: Uint8Array): { r: bigint; s: bigint } {
|
||||||
function parseDERSignature(data: Uint8Array) {
|
if (data.length < 2 || data[0] != 0x30) {
|
||||||
if (data.length < 2 || data[0] != 0x30) {
|
throw new DERError(`Invalid signature tag: ${bytesToHex(data)}`);
|
||||||
throw new DERError(`Invalid signature tag: ${bytesToHex(data)}`);
|
}
|
||||||
}
|
if (data[1] !== data.length - 2) {
|
||||||
if (data[1] !== data.length - 2) {
|
throw new DERError('Invalid signature: incorrect length');
|
||||||
throw new DERError('Invalid signature: incorrect length');
|
}
|
||||||
}
|
const { data: r, left: sBytes } = DER.parseInt(data.subarray(2));
|
||||||
const { data: r, left: sBytes } = parseDERInt(data.subarray(2));
|
const { data: s, left: rBytesLeft } = DER.parseInt(sBytes);
|
||||||
const { data: s, left: rBytesLeft } = parseDERInt(sBytes);
|
if (rBytesLeft.length) {
|
||||||
if (rBytesLeft.length) {
|
throw new DERError(`Invalid signature: left bytes after parsing: ${bytesToHex(rBytesLeft)}`);
|
||||||
throw new DERError(`Invalid signature: left bytes after parsing: ${bytesToHex(rBytesLeft)}`);
|
}
|
||||||
}
|
return { r, s };
|
||||||
return { r, s };
|
},
|
||||||
}
|
};
|
||||||
|
|
||||||
// Be friendly to bad ECMAScript parsers by not using bigint literals like 123n
|
|
||||||
const _0n = BigInt(0);
|
|
||||||
const _1n = BigInt(1);
|
|
||||||
const _3n = BigInt(3);
|
|
||||||
|
|
||||||
type Entropy = Hex | true;
|
type Entropy = Hex | true;
|
||||||
type SignOpts = { lowS?: boolean; extraEntropy?: Entropy };
|
export type SignOpts = { lowS?: boolean; extraEntropy?: Entropy };
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ### Design rationale for types
|
* ### Design rationale for types
|
||||||
@@ -124,7 +113,7 @@ type SignOpts = { lowS?: boolean; extraEntropy?: Entropy };
|
|||||||
* TODO: https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-7.html#unique-symbol
|
* TODO: https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-7.html#unique-symbol
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// Instance
|
// Instance for 3d XYZ points
|
||||||
export interface ProjectivePointType<T> extends Group<ProjectivePointType<T>> {
|
export interface ProjectivePointType<T> extends Group<ProjectivePointType<T>> {
|
||||||
readonly x: T;
|
readonly x: T;
|
||||||
readonly y: T;
|
readonly y: T;
|
||||||
@@ -133,14 +122,14 @@ export interface ProjectivePointType<T> extends Group<ProjectivePointType<T>> {
|
|||||||
multiplyUnsafe(scalar: bigint): ProjectivePointType<T>;
|
multiplyUnsafe(scalar: bigint): ProjectivePointType<T>;
|
||||||
toAffine(invZ?: T): PointType<T>;
|
toAffine(invZ?: T): PointType<T>;
|
||||||
}
|
}
|
||||||
// Static methods
|
// Static methods for 3d XYZ points
|
||||||
export interface ProjectiveConstructor<T> extends GroupConstructor<ProjectivePointType<T>> {
|
export interface ProjectiveConstructor<T> extends GroupConstructor<ProjectivePointType<T>> {
|
||||||
new (x: T, y: T, z: T): ProjectivePointType<T>;
|
new (x: T, y: T, z: T): ProjectivePointType<T>;
|
||||||
fromAffine(p: PointType<T>): ProjectivePointType<T>;
|
fromAffine(p: PointType<T>): ProjectivePointType<T>;
|
||||||
toAffineBatch(points: ProjectivePointType<T>[]): PointType<T>[];
|
toAffineBatch(points: ProjectivePointType<T>[]): PointType<T>[];
|
||||||
normalizeZ(points: ProjectivePointType<T>[]): ProjectivePointType<T>[];
|
normalizeZ(points: ProjectivePointType<T>[]): ProjectivePointType<T>[];
|
||||||
}
|
}
|
||||||
// Instance
|
// Instance for 2d XY points
|
||||||
export interface PointType<T> extends Group<PointType<T>> {
|
export interface PointType<T> extends Group<PointType<T>> {
|
||||||
readonly x: T;
|
readonly x: T;
|
||||||
readonly y: T;
|
readonly y: T;
|
||||||
@@ -151,7 +140,7 @@ export interface PointType<T> extends Group<PointType<T>> {
|
|||||||
assertValidity(): void;
|
assertValidity(): void;
|
||||||
multiplyAndAddUnsafe(Q: PointType<T>, a: bigint, b: bigint): PointType<T> | undefined;
|
multiplyAndAddUnsafe(Q: PointType<T>, a: bigint, b: bigint): PointType<T> | undefined;
|
||||||
}
|
}
|
||||||
// Static methods
|
// Static methods for 2d XY points
|
||||||
export interface PointConstructor<T> extends GroupConstructor<PointType<T>> {
|
export interface PointConstructor<T> extends GroupConstructor<PointType<T>> {
|
||||||
new (x: T, y: T): PointType<T>;
|
new (x: T, y: T): PointType<T>;
|
||||||
fromHex(hex: Hex): PointType<T>;
|
fromHex(hex: Hex): PointType<T>;
|
||||||
@@ -167,7 +156,7 @@ export type CurvePointsType<T> = BasicCurve<T> & {
|
|||||||
};
|
};
|
||||||
|
|
||||||
function validatePointOpts<T>(curve: CurvePointsType<T>) {
|
function validatePointOpts<T>(curve: CurvePointsType<T>) {
|
||||||
const opts = utils.validateOpts(curve);
|
const opts = ut.validateOpts(curve);
|
||||||
const Fp = opts.Fp;
|
const Fp = opts.Fp;
|
||||||
for (const i of ['a', 'b'] as const) {
|
for (const i of ['a', 'b'] as const) {
|
||||||
if (!Fp.isValid(curve[i]))
|
if (!Fp.isValid(curve[i]))
|
||||||
@@ -206,22 +195,14 @@ export type CurvePointsRes<T> = {
|
|||||||
isWithinCurveOrder: (num: bigint) => boolean;
|
isWithinCurveOrder: (num: bigint) => boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Be friendly to bad ECMAScript parsers by not using bigint literals like 123n
|
||||||
|
const _0n = BigInt(0);
|
||||||
|
const _1n = BigInt(1);
|
||||||
|
const _3n = BigInt(3);
|
||||||
|
|
||||||
export function weierstrassPoints<T>(opts: CurvePointsType<T>) {
|
export function weierstrassPoints<T>(opts: CurvePointsType<T>) {
|
||||||
const CURVE = validatePointOpts(opts);
|
const CURVE = validatePointOpts(opts);
|
||||||
const Fp = CURVE.Fp;
|
const { Fp } = CURVE; // All curves has same field / group length as for now, but they can differ
|
||||||
// Lengths
|
|
||||||
// All curves has same field / group length as for now, but it can be different for other curves
|
|
||||||
const { nByteLength, nBitLength } = CURVE;
|
|
||||||
const groupLen = nByteLength;
|
|
||||||
|
|
||||||
// Not using ** operator with bigints for old engines.
|
|
||||||
// 2n ** (8n * 32n) == 2n << (8n * 32n - 1n)
|
|
||||||
//const FIELD_MASK = _2n << (_8n * BigInt(fieldLen) - _1n);
|
|
||||||
// function numToFieldStr(num: bigint): string {
|
|
||||||
// if (typeof num !== 'bigint') throw new Error('Expected bigint');
|
|
||||||
// if (!(_0n <= num && num < FIELD_MASK)) throw new Error(`Expected number < 2^${fieldLen * 8}`);
|
|
||||||
// return num.toString(16).padStart(2 * fieldLen, '0');
|
|
||||||
// }
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* y² = x³ + ax + b: Short weierstrass curve formula
|
* y² = x³ + ax + b: Short weierstrass curve formula
|
||||||
@@ -234,35 +215,48 @@ export function weierstrassPoints<T>(opts: CurvePointsType<T>) {
|
|||||||
return Fp.add(Fp.add(x3, Fp.mul(x, a)), b); // x3 + a * x + b
|
return Fp.add(Fp.add(x3, Fp.mul(x, a)), b); // x3 + a * x + b
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Valid group elements reside in range 1..n-1
|
||||||
function isWithinCurveOrder(num: bigint): boolean {
|
function isWithinCurveOrder(num: bigint): boolean {
|
||||||
return _0n < num && num < CURVE.n;
|
return _0n < num && num < CURVE.n;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates if a private key is valid and converts it to bigint form.
|
||||||
|
* Supports two options, that are passed when CURVE is initialized:
|
||||||
|
* - `normalizePrivateKey()` executed before all checks
|
||||||
|
* - `wrapPrivateKey` when true, executed after most checks, but before `0 < key < n`
|
||||||
|
*/
|
||||||
function normalizePrivateKey(key: PrivKey): bigint {
|
function normalizePrivateKey(key: PrivKey): bigint {
|
||||||
if (typeof CURVE.normalizePrivateKey === 'function') {
|
const { normalizePrivateKey: custom, nByteLength: groupLen, wrapPrivateKey, n: order } = CURVE;
|
||||||
key = CURVE.normalizePrivateKey(key);
|
if (typeof custom === 'function') key = custom(key);
|
||||||
}
|
|
||||||
let num: bigint;
|
let num: bigint;
|
||||||
if (typeof key === 'bigint') {
|
if (typeof key === 'bigint') {
|
||||||
|
// Curve order check is done below
|
||||||
num = key;
|
num = key;
|
||||||
} else if (typeof key === 'number' && Number.isSafeInteger(key) && key > 0) {
|
} else if (ut.isPositiveInt(key)) {
|
||||||
num = BigInt(key);
|
num = BigInt(key);
|
||||||
} else if (typeof key === 'string') {
|
} else if (typeof key === 'string') {
|
||||||
if (key.length !== 2 * groupLen) throw new Error(`Expected ${groupLen} bytes of private key`);
|
if (key.length !== 2 * groupLen) throw new Error(`Expected ${groupLen} bytes of private key`);
|
||||||
num = hexToNumber(key);
|
// Validates individual octets
|
||||||
|
num = ut.hexToNumber(key);
|
||||||
} else if (key instanceof Uint8Array) {
|
} else if (key instanceof Uint8Array) {
|
||||||
if (key.length !== groupLen) throw new Error(`Expected ${groupLen} bytes of private key`);
|
if (key.length !== groupLen) throw new Error(`Expected ${groupLen} bytes of private key`);
|
||||||
num = bytesToNumberBE(key);
|
num = ut.bytesToNumberBE(key);
|
||||||
} else {
|
} else {
|
||||||
throw new TypeError('Expected valid private key');
|
throw new TypeError('Expected valid private key');
|
||||||
}
|
}
|
||||||
if (CURVE.wrapPrivateKey) num = mod.mod(num, CURVE.n);
|
// Useful for curves with cofactor != 1
|
||||||
|
if (wrapPrivateKey) num = mod.mod(num, order);
|
||||||
if (!isWithinCurveOrder(num)) throw new Error('Expected private key: 0 < key < n');
|
if (!isWithinCurveOrder(num)) throw new Error('Expected private key: 0 < key < n');
|
||||||
return num;
|
return num;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates if a scalar ("private number") is valid.
|
||||||
|
* Scalars are valid only if they are less than curve order.
|
||||||
|
*/
|
||||||
function normalizeScalar(num: number | bigint): bigint {
|
function normalizeScalar(num: number | bigint): bigint {
|
||||||
if (typeof num === 'number' && Number.isSafeInteger(num) && num > 0) return BigInt(num);
|
if (ut.isPositiveInt(num)) return BigInt(num);
|
||||||
if (typeof num === 'bigint' && isWithinCurveOrder(num)) return num;
|
if (typeof num === 'bigint' && isWithinCurveOrder(num)) return num;
|
||||||
throw new TypeError('Expected valid private scalar: 0 < scalar < curve.n');
|
throw new TypeError('Expected valid private scalar: 0 < scalar < curve.n');
|
||||||
}
|
}
|
||||||
@@ -289,7 +283,7 @@ export function weierstrassPoints<T>(opts: CurvePointsType<T>) {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Takes a bunch of Projective Points but executes only one
|
* Takes a bunch of Projective Points but executes only one
|
||||||
* invert on all of them. invert is very slow operation,
|
* inversion on all of them. Inversion is very slow operation,
|
||||||
* so this improves performance massively.
|
* so this improves performance massively.
|
||||||
*/
|
*/
|
||||||
static toAffineBatch(points: ProjectivePoint[]): Point[] {
|
static toAffineBatch(points: ProjectivePoint[]): Point[] {
|
||||||
@@ -297,6 +291,9 @@ export function weierstrassPoints<T>(opts: CurvePointsType<T>) {
|
|||||||
return points.map((p, i) => p.toAffine(toInv[i]));
|
return points.map((p, i) => p.toAffine(toInv[i]));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Optimization: converts a list of projective points to a list of identical points with Z=1.
|
||||||
|
*/
|
||||||
static normalizeZ(points: ProjectivePoint[]): ProjectivePoint[] {
|
static normalizeZ(points: ProjectivePoint[]): ProjectivePoint[] {
|
||||||
return ProjectivePoint.toAffineBatch(points).map(ProjectivePoint.fromAffine);
|
return ProjectivePoint.toAffineBatch(points).map(ProjectivePoint.fromAffine);
|
||||||
}
|
}
|
||||||
@@ -320,10 +317,6 @@ export function weierstrassPoints<T>(opts: CurvePointsType<T>) {
|
|||||||
return new ProjectivePoint(this.x, Fp.negate(this.y), this.z);
|
return new ProjectivePoint(this.x, Fp.negate(this.y), this.z);
|
||||||
}
|
}
|
||||||
|
|
||||||
doubleAdd(): ProjectivePoint {
|
|
||||||
return this.add(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Renes-Costello-Batina exception-free doubling formula.
|
// Renes-Costello-Batina exception-free doubling formula.
|
||||||
// There is 30% faster Jacobian formula, but it is not complete.
|
// There is 30% faster Jacobian formula, but it is not complete.
|
||||||
// https://eprint.iacr.org/2015/1060, algorithm 3
|
// https://eprint.iacr.org/2015/1060, algorithm 3
|
||||||
@@ -525,24 +518,25 @@ export function weierstrassPoints<T>(opts: CurvePointsType<T>) {
|
|||||||
return new Point(ax, ay);
|
return new Point(ax, ay);
|
||||||
}
|
}
|
||||||
isTorsionFree(): boolean {
|
isTorsionFree(): boolean {
|
||||||
if (CURVE.h === _1n) return true; // No subgroups, always torsion fee
|
const { h: cofactor, isTorsionFree } = CURVE;
|
||||||
if (CURVE.isTorsionFree) return CURVE.isTorsionFree(ProjectivePoint, this);
|
if (cofactor === _1n) return true; // No subgroups, always torsion-free
|
||||||
// is multiplyUnsafe(CURVE.n) is always ok, same as for edwards?
|
if (isTorsionFree) return isTorsionFree(ProjectivePoint, this);
|
||||||
throw new Error('Unsupported!');
|
throw new Error('isTorsionFree() has not been declared for the elliptic curve');
|
||||||
}
|
}
|
||||||
// Clear cofactor of G1
|
|
||||||
// https://eprint.iacr.org/2019/403
|
|
||||||
clearCofactor(): ProjectivePoint {
|
clearCofactor(): ProjectivePoint {
|
||||||
if (CURVE.h === _1n) return this; // Fast-path
|
const { h: cofactor, clearCofactor } = CURVE;
|
||||||
if (CURVE.clearCofactor) return CURVE.clearCofactor(ProjectivePoint, this) as ProjectivePoint;
|
if (cofactor === _1n) return this; // Fast-path
|
||||||
|
if (clearCofactor) return clearCofactor(ProjectivePoint, this) as ProjectivePoint;
|
||||||
return this.multiplyUnsafe(CURVE.h);
|
return this.multiplyUnsafe(CURVE.h);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const wnaf = wNAF(ProjectivePoint, CURVE.endo ? nBitLength / 2 : nBitLength);
|
const _bits = CURVE.nBitLength;
|
||||||
|
const wnaf = wNAF(ProjectivePoint, CURVE.endo ? Math.ceil(_bits / 2) : _bits);
|
||||||
|
|
||||||
function assertPrjPoint(other: unknown) {
|
function assertPrjPoint(other: unknown) {
|
||||||
if (!(other instanceof ProjectivePoint)) throw new TypeError('ProjectivePoint expected');
|
if (!(other instanceof ProjectivePoint)) throw new TypeError('ProjectivePoint expected');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stores precomputed values for points.
|
// Stores precomputed values for points.
|
||||||
const pointPrecomputes = new WeakMap<Point, ProjectivePoint[]>();
|
const pointPrecomputes = new WeakMap<Point, ProjectivePoint[]>();
|
||||||
|
|
||||||
@@ -551,11 +545,11 @@ export function weierstrassPoints<T>(opts: CurvePointsType<T>) {
|
|||||||
*/
|
*/
|
||||||
class Point implements PointType<T> {
|
class Point implements PointType<T> {
|
||||||
/**
|
/**
|
||||||
* Base point aka generator. public_key = Point.BASE * private_key
|
* Base point aka generator. Any public_key = Point.BASE * private_key
|
||||||
*/
|
*/
|
||||||
static BASE: Point = new Point(CURVE.Gx, CURVE.Gy);
|
static BASE: Point = new Point(CURVE.Gx, CURVE.Gy);
|
||||||
/**
|
/**
|
||||||
* Identity point aka point at infinity. point = point + zero_point
|
* Identity point aka point at infinity. p - p = zero_p; p + zero_p = p
|
||||||
*/
|
*/
|
||||||
static ZERO: Point = new Point(Fp.ZERO, Fp.ZERO);
|
static ZERO: Point = new Point(Fp.ZERO, Fp.ZERO);
|
||||||
|
|
||||||
@@ -583,7 +577,7 @@ export function weierstrassPoints<T>(opts: CurvePointsType<T>) {
|
|||||||
* @param hex short/long ECDSA hex
|
* @param hex short/long ECDSA hex
|
||||||
*/
|
*/
|
||||||
static fromHex(hex: Hex): Point {
|
static fromHex(hex: Hex): Point {
|
||||||
const { x, y } = CURVE.fromBytes(ensureBytes(hex));
|
const { x, y } = CURVE.fromBytes(ut.ensureBytes(hex));
|
||||||
const point = new Point(x, y);
|
const point = new Point(x, y);
|
||||||
point.assertValidity();
|
point.assertValidity();
|
||||||
return point;
|
return point;
|
||||||
@@ -607,16 +601,16 @@ export function weierstrassPoints<T>(opts: CurvePointsType<T>) {
|
|||||||
// Zero is valid point too!
|
// Zero is valid point too!
|
||||||
if (this.equals(Point.ZERO)) {
|
if (this.equals(Point.ZERO)) {
|
||||||
if (CURVE.allowInfinityPoint) return;
|
if (CURVE.allowInfinityPoint) return;
|
||||||
throw new Error('Point is infinity');
|
throw new Error('Point at infinity');
|
||||||
}
|
}
|
||||||
// Some 3rd-party test vectors require different wording between here & `fromCompressedHex`
|
// Some 3rd-party test vectors require different wording between here & `fromCompressedHex`
|
||||||
const msg = 'Point is not on elliptic curve';
|
const msg = 'Point is not on elliptic curve';
|
||||||
const { x, y } = this;
|
const { x, y } = this;
|
||||||
|
// Check if x, y are valid field elements
|
||||||
if (!Fp.isValid(x) || !Fp.isValid(y)) throw new Error(msg);
|
if (!Fp.isValid(x) || !Fp.isValid(y)) throw new Error(msg);
|
||||||
const left = Fp.square(y);
|
const left = Fp.square(y); // y²
|
||||||
const right = weierstrassEquation(x);
|
const right = weierstrassEquation(x); // x³ + ax + b
|
||||||
if (!Fp.equals(left, right)) throw new Error(msg);
|
if (!Fp.equals(left, right)) throw new Error(msg);
|
||||||
// TODO: flag to disable this?
|
|
||||||
if (!this.isTorsionFree()) throw new Error('Point must be of prime-order subgroup');
|
if (!this.isTorsionFree()) throw new Error('Point must be of prime-order subgroup');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -630,34 +624,37 @@ export function weierstrassPoints<T>(opts: CurvePointsType<T>) {
|
|||||||
return new Point(this.x, Fp.negate(this.y));
|
return new Point(this.x, Fp.negate(this.y));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected toProj() {
|
||||||
|
return ProjectivePoint.fromAffine(this);
|
||||||
|
}
|
||||||
|
|
||||||
// Adds point to itself
|
// Adds point to itself
|
||||||
double() {
|
double() {
|
||||||
return ProjectivePoint.fromAffine(this).double().toAffine();
|
return this.toProj().double().toAffine();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Adds point to other point
|
|
||||||
add(other: Point) {
|
add(other: Point) {
|
||||||
return ProjectivePoint.fromAffine(this).add(ProjectivePoint.fromAffine(other)).toAffine();
|
return this.toProj().add(ProjectivePoint.fromAffine(other)).toAffine();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Subtracts other point from the point
|
|
||||||
subtract(other: Point) {
|
subtract(other: Point) {
|
||||||
return this.add(other.negate());
|
return this.add(other.negate());
|
||||||
}
|
}
|
||||||
|
|
||||||
multiply(scalar: number | bigint) {
|
multiply(scalar: number | bigint) {
|
||||||
return ProjectivePoint.fromAffine(this).multiply(scalar, this).toAffine();
|
return this.toProj().multiply(scalar, this).toAffine();
|
||||||
}
|
}
|
||||||
|
|
||||||
multiplyUnsafe(scalar: bigint) {
|
multiplyUnsafe(scalar: bigint) {
|
||||||
return ProjectivePoint.fromAffine(this).multiplyUnsafe(scalar).toAffine();
|
return this.toProj().multiplyUnsafe(scalar).toAffine();
|
||||||
}
|
}
|
||||||
|
|
||||||
clearCofactor() {
|
clearCofactor() {
|
||||||
return ProjectivePoint.fromAffine(this).clearCofactor().toAffine();
|
return this.toProj().clearCofactor().toAffine();
|
||||||
}
|
}
|
||||||
|
|
||||||
isTorsionFree(): boolean {
|
isTorsionFree(): boolean {
|
||||||
return ProjectivePoint.fromAffine(this).isTorsionFree();
|
return this.toProj().isTorsionFree();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -667,7 +664,7 @@ export function weierstrassPoints<T>(opts: CurvePointsType<T>) {
|
|||||||
* @returns non-zero affine point
|
* @returns non-zero affine point
|
||||||
*/
|
*/
|
||||||
multiplyAndAddUnsafe(Q: Point, a: bigint, b: bigint): Point | undefined {
|
multiplyAndAddUnsafe(Q: Point, a: bigint, b: bigint): Point | undefined {
|
||||||
const P = ProjectivePoint.fromAffine(this);
|
const P = this.toProj();
|
||||||
const aP =
|
const aP =
|
||||||
a === _0n || a === _1n || this !== Point.BASE ? P.multiplyUnsafe(a) : P.multiply(a);
|
a === _0n || a === _1n || this !== Point.BASE ? P.multiplyUnsafe(a) : P.multiply(a);
|
||||||
const bQ = ProjectivePoint.fromAffine(Q).multiplyUnsafe(b);
|
const bQ = ProjectivePoint.fromAffine(Q).multiplyUnsafe(b);
|
||||||
@@ -678,23 +675,26 @@ export function weierstrassPoints<T>(opts: CurvePointsType<T>) {
|
|||||||
// Encodes byte string to elliptic curve
|
// Encodes byte string to elliptic curve
|
||||||
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-3
|
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-3
|
||||||
static hashToCurve(msg: Hex, options?: Partial<htfOpts>) {
|
static hashToCurve(msg: Hex, options?: Partial<htfOpts>) {
|
||||||
if (!CURVE.mapToCurve) throw new Error('No mapToCurve defined for curve');
|
const { mapToCurve } = CURVE;
|
||||||
msg = ensureBytes(msg);
|
if (!mapToCurve) throw new Error('CURVE.mapToCurve() has not been defined');
|
||||||
|
msg = ut.ensureBytes(msg);
|
||||||
const u = hash_to_field(msg, 2, { ...CURVE.htfDefaults, ...options } as htfOpts);
|
const u = hash_to_field(msg, 2, { ...CURVE.htfDefaults, ...options } as htfOpts);
|
||||||
const { x: x0, y: y0 } = CURVE.mapToCurve(u[0]);
|
const { x: x0, y: y0 } = mapToCurve(u[0]);
|
||||||
const { x: x1, y: y1 } = CURVE.mapToCurve(u[1]);
|
const { x: x1, y: y1 } = mapToCurve(u[1]);
|
||||||
const p = new Point(x0, y0).add(new Point(x1, y1)).clearCofactor();
|
return new Point(x0, y0).add(new Point(x1, y1)).clearCofactor();
|
||||||
return p;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-16#section-3
|
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-16#section-3
|
||||||
static encodeToCurve(msg: Hex, options?: Partial<htfOpts>) {
|
static encodeToCurve(msg: Hex, options?: Partial<htfOpts>) {
|
||||||
if (!CURVE.mapToCurve) throw new Error('No mapToCurve defined for curve');
|
const { mapToCurve } = CURVE;
|
||||||
msg = ensureBytes(msg);
|
if (!mapToCurve) throw new Error('CURVE.mapToCurve() has not been defined');
|
||||||
|
msg = ut.ensureBytes(msg);
|
||||||
const u = hash_to_field(msg, 1, { ...CURVE.htfDefaults, ...options } as htfOpts);
|
const u = hash_to_field(msg, 1, { ...CURVE.htfDefaults, ...options } as htfOpts);
|
||||||
const { x, y } = CURVE.mapToCurve(u[0]);
|
const { x, y } = mapToCurve(u[0]);
|
||||||
return new Point(x, y).clearCofactor();
|
return new Point(x, y).clearCofactor();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
Point: Point as PointConstructor<T>,
|
Point: Point as PointConstructor<T>,
|
||||||
ProjectivePoint: ProjectivePoint as ProjectiveConstructor<T>,
|
ProjectivePoint: ProjectivePoint as ProjectiveConstructor<T>,
|
||||||
@@ -733,15 +733,15 @@ export type CurveType = BasicCurve<bigint> & {
|
|||||||
// Default options
|
// Default options
|
||||||
lowS?: boolean;
|
lowS?: boolean;
|
||||||
// Hashes
|
// Hashes
|
||||||
hash: utils.CHash; // Because we need outputLen for DRBG
|
hash: ut.CHash; // Because we need outputLen for DRBG
|
||||||
hmac: HmacFnSync;
|
hmac: HmacFnSync;
|
||||||
randomBytes: (bytesLength?: number) => Uint8Array;
|
randomBytes: (bytesLength?: number) => Uint8Array;
|
||||||
truncateHash?: (hash: Uint8Array, truncateOnly?: boolean) => bigint;
|
truncateHash?: (hash: Uint8Array, truncateOnly?: boolean) => bigint;
|
||||||
};
|
};
|
||||||
|
|
||||||
function validateOpts(curve: CurveType) {
|
function validateOpts(curve: CurveType) {
|
||||||
const opts = utils.validateOpts(curve);
|
const opts = ut.validateOpts(curve);
|
||||||
if (typeof opts.hash !== 'function' || !Number.isSafeInteger(opts.hash.outputLen))
|
if (typeof opts.hash !== 'function' || !ut.isPositiveInt(opts.hash.outputLen))
|
||||||
throw new Error('Invalid hash function');
|
throw new Error('Invalid hash function');
|
||||||
if (typeof opts.hmac !== 'function') throw new Error('Invalid hmac function');
|
if (typeof opts.hmac !== 'function') throw new Error('Invalid hmac function');
|
||||||
if (typeof opts.randomBytes !== 'function') throw new Error('Invalid randomBytes function');
|
if (typeof opts.randomBytes !== 'function') throw new Error('Invalid randomBytes function');
|
||||||
@@ -754,6 +754,7 @@ export type CurveFn = {
|
|||||||
getPublicKey: (privateKey: PrivKey, isCompressed?: boolean) => Uint8Array;
|
getPublicKey: (privateKey: PrivKey, isCompressed?: boolean) => Uint8Array;
|
||||||
getSharedSecret: (privateA: PrivKey, publicB: PubKey, isCompressed?: boolean) => Uint8Array;
|
getSharedSecret: (privateA: PrivKey, publicB: PubKey, isCompressed?: boolean) => Uint8Array;
|
||||||
sign: (msgHash: Hex, privKey: PrivKey, opts?: SignOpts) => SignatureType;
|
sign: (msgHash: Hex, privKey: PrivKey, opts?: SignOpts) => SignatureType;
|
||||||
|
signUnhashed: (msg: Uint8Array, privKey: PrivKey, opts?: SignOpts) => SignatureType;
|
||||||
verify: (
|
verify: (
|
||||||
signature: Hex | SignatureType,
|
signature: Hex | SignatureType,
|
||||||
msgHash: Hex,
|
msgHash: Hex,
|
||||||
@@ -766,8 +767,6 @@ export type CurveFn = {
|
|||||||
ProjectivePoint: ProjectiveConstructor<bigint>;
|
ProjectivePoint: ProjectiveConstructor<bigint>;
|
||||||
Signature: SignatureConstructor;
|
Signature: SignatureConstructor;
|
||||||
utils: {
|
utils: {
|
||||||
mod: (a: bigint, b?: bigint) => bigint;
|
|
||||||
invert: (number: bigint, modulo?: bigint) => bigint;
|
|
||||||
_bigintToBytes: (num: bigint) => Uint8Array;
|
_bigintToBytes: (num: bigint) => Uint8Array;
|
||||||
_bigintToString: (num: bigint) => string;
|
_bigintToString: (num: bigint) => string;
|
||||||
_normalizePrivateKey: (key: PrivKey) => bigint;
|
_normalizePrivateKey: (key: PrivKey) => bigint;
|
||||||
@@ -824,19 +823,17 @@ class HmacDrbg {
|
|||||||
out.push(sl);
|
out.push(sl);
|
||||||
len += this.v.length;
|
len += this.v.length;
|
||||||
}
|
}
|
||||||
return concatBytes(...out);
|
return ut.concatBytes(...out);
|
||||||
}
|
}
|
||||||
// There is no need in clean() method
|
// There are no guarantees with JS GC whether bigints are removed even if you clean Uint8Arrays.
|
||||||
// It's useless, there are no guarantees with JS GC
|
|
||||||
// whether bigints are removed even if you clean Uint8Arrays.
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function weierstrass(curveDef: CurveType): CurveFn {
|
export function weierstrass(curveDef: CurveType): CurveFn {
|
||||||
const CURVE = validateOpts(curveDef) as ReturnType<typeof validateOpts>;
|
const CURVE = validateOpts(curveDef) as ReturnType<typeof validateOpts>;
|
||||||
const CURVE_ORDER = CURVE.n;
|
const CURVE_ORDER = CURVE.n;
|
||||||
const Fp = CURVE.Fp;
|
const Fp = CURVE.Fp;
|
||||||
const compressedLen = Fp.BYTES + 1; // 33
|
const compressedLen = Fp.BYTES + 1; // e.g. 33 for 32
|
||||||
const uncompressedLen = 2 * Fp.BYTES + 1; // 65
|
const uncompressedLen = 2 * Fp.BYTES + 1; // e.g. 65 for 32
|
||||||
|
|
||||||
function isValidFieldElement(num: bigint): boolean {
|
function isValidFieldElement(num: bigint): boolean {
|
||||||
// 0 is disallowed by arbitrary reasons. Probably because infinity point?
|
// 0 is disallowed by arbitrary reasons. Probably because infinity point?
|
||||||
@@ -847,10 +844,12 @@ export function weierstrass(curveDef: CurveType): CurveFn {
|
|||||||
weierstrassPoints({
|
weierstrassPoints({
|
||||||
...CURVE,
|
...CURVE,
|
||||||
toBytes(c, point, isCompressed: boolean): Uint8Array {
|
toBytes(c, point, isCompressed: boolean): Uint8Array {
|
||||||
|
const x = Fp.toBytes(point.x);
|
||||||
|
const cat = ut.concatBytes;
|
||||||
if (isCompressed) {
|
if (isCompressed) {
|
||||||
return concatBytes(new Uint8Array([point.hasEvenY() ? 0x02 : 0x03]), Fp.toBytes(point.x));
|
return cat(Uint8Array.from([point.hasEvenY() ? 0x02 : 0x03]), x);
|
||||||
} else {
|
} else {
|
||||||
return concatBytes(new Uint8Array([0x04]), Fp.toBytes(point.x), Fp.toBytes(point.y));
|
return cat(Uint8Array.from([0x04]), x, Fp.toBytes(point.y));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
fromBytes(bytes: Uint8Array) {
|
fromBytes(bytes: Uint8Array) {
|
||||||
@@ -858,7 +857,7 @@ export function weierstrass(curveDef: CurveType): CurveFn {
|
|||||||
const header = bytes[0];
|
const header = bytes[0];
|
||||||
// this.assertValidity() is done inside of fromHex
|
// this.assertValidity() is done inside of fromHex
|
||||||
if (len === compressedLen && (header === 0x02 || header === 0x03)) {
|
if (len === compressedLen && (header === 0x02 || header === 0x03)) {
|
||||||
const x = bytesToNumberBE(bytes.subarray(1));
|
const x = ut.bytesToNumberBE(bytes.subarray(1));
|
||||||
if (!isValidFieldElement(x)) throw new Error('Point is not on curve');
|
if (!isValidFieldElement(x)) throw new Error('Point is not on curve');
|
||||||
const y2 = weierstrassEquation(x); // y² = x³ + ax + b
|
const y2 = weierstrassEquation(x); // y² = x³ + ax + b
|
||||||
let y = Fp.sqrt(y2); // y = y² ^ (p+1)/4
|
let y = Fp.sqrt(y2); // y = y² ^ (p+1)/4
|
||||||
@@ -910,15 +909,18 @@ export function weierstrass(curveDef: CurveType): CurveFn {
|
|||||||
return isBiggerThanHalfOrder(s) ? mod.mod(-s, CURVE_ORDER) : s;
|
return isBiggerThanHalfOrder(s) ? mod.mod(-s, CURVE_ORDER) : s;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function bits2int_2(bytes: Uint8Array): bigint {
|
||||||
|
const delta = bytes.length * 8 - CURVE.nBitLength;
|
||||||
|
const num = ut.bytesToNumberBE(bytes);
|
||||||
|
return delta > 0 ? num >> BigInt(delta) : num;
|
||||||
|
}
|
||||||
|
|
||||||
// Ensures ECDSA message hashes are 32 bytes and < curve order
|
// Ensures ECDSA message hashes are 32 bytes and < curve order
|
||||||
function _truncateHash(hash: Uint8Array, truncateOnly = false): bigint {
|
function _truncateHash(hash: Uint8Array, truncateOnly = false): bigint {
|
||||||
const { n, nBitLength } = CURVE;
|
const h = bits2int_2(hash);
|
||||||
const byteLength = hash.length;
|
if (truncateOnly) return h;
|
||||||
const delta = byteLength * 8 - nBitLength; // size of curve.n (252 bits)
|
const { n } = CURVE;
|
||||||
let h = bytesToNumberBE(hash);
|
return h >= n ? h - n : h;
|
||||||
if (delta > 0) h = h >> BigInt(delta);
|
|
||||||
if (!truncateOnly && h >= n) h -= n;
|
|
||||||
return h;
|
|
||||||
}
|
}
|
||||||
const truncateHash = CURVE.truncateHash || _truncateHash;
|
const truncateHash = CURVE.truncateHash || _truncateHash;
|
||||||
|
|
||||||
@@ -930,15 +932,17 @@ export function weierstrass(curveDef: CurveType): CurveFn {
|
|||||||
this.assertValidity();
|
this.assertValidity();
|
||||||
}
|
}
|
||||||
|
|
||||||
// pair (32 bytes of r, 32 bytes of s)
|
// pair (bytes of r, bytes of s)
|
||||||
static fromCompact(hex: Hex) {
|
static fromCompact(hex: Hex) {
|
||||||
const arr = hex instanceof Uint8Array;
|
const arr = hex instanceof Uint8Array;
|
||||||
const name = 'Signature.fromCompact';
|
const name = 'Signature.fromCompact';
|
||||||
if (typeof hex !== 'string' && !arr)
|
if (typeof hex !== 'string' && !arr)
|
||||||
throw new TypeError(`${name}: Expected string or Uint8Array`);
|
throw new TypeError(`${name}: Expected string or Uint8Array`);
|
||||||
const str = arr ? bytesToHex(hex) : hex;
|
const str = arr ? bytesToHex(hex) : hex;
|
||||||
if (str.length !== 128) throw new Error(`${name}: Expected 64-byte hex`);
|
const gl = CURVE.nByteLength * 2; // group length in hex, not ui8a
|
||||||
return new Signature(hexToNumber(str.slice(0, 64)), hexToNumber(str.slice(64, 128)));
|
if (str.length !== 2 * gl) throw new Error(`${name}: Expected ${gl / 2}-byte hex`);
|
||||||
|
const slice = (from: number, to: number) => ut.hexToNumber(str.slice(from, to));
|
||||||
|
return new Signature(slice(0, gl), slice(gl, 2 * gl));
|
||||||
}
|
}
|
||||||
|
|
||||||
// DER encoded ECDSA signature
|
// DER encoded ECDSA signature
|
||||||
@@ -947,7 +951,7 @@ export function weierstrass(curveDef: CurveType): CurveFn {
|
|||||||
const arr = hex instanceof Uint8Array;
|
const arr = hex instanceof Uint8Array;
|
||||||
if (typeof hex !== 'string' && !arr)
|
if (typeof hex !== 'string' && !arr)
|
||||||
throw new TypeError(`Signature.fromDER: Expected string or Uint8Array`);
|
throw new TypeError(`Signature.fromDER: Expected string or Uint8Array`);
|
||||||
const { r, s } = parseDERSignature(arr ? hex : hexToBytes(hex));
|
const { r, s } = DER.parseSig(arr ? hex : ut.hexToBytes(hex));
|
||||||
return new Signature(r, s);
|
return new Signature(r, s);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -964,6 +968,7 @@ export function weierstrass(curveDef: CurveType): CurveFn {
|
|||||||
/**
|
/**
|
||||||
* Recovers public key from signature with recovery bit. Throws on invalid hash.
|
* Recovers public key from signature with recovery bit. Throws on invalid hash.
|
||||||
* https://en.wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm#Public_key_recovery
|
* https://en.wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm#Public_key_recovery
|
||||||
|
* It's also possible to recover key without bit: try all 4 bit values and check for sig match.
|
||||||
*
|
*
|
||||||
* ```
|
* ```
|
||||||
* recover(r, s, h) where
|
* recover(r, s, h) where
|
||||||
@@ -978,16 +983,18 @@ export function weierstrass(curveDef: CurveType): CurveFn {
|
|||||||
recoverPublicKey(msgHash: Hex): Point {
|
recoverPublicKey(msgHash: Hex): Point {
|
||||||
const { r, s, recovery } = this;
|
const { r, s, recovery } = this;
|
||||||
if (recovery == null) throw new Error('Cannot recover: recovery bit is not present');
|
if (recovery == null) throw new Error('Cannot recover: recovery bit is not present');
|
||||||
if (recovery !== 0 && recovery !== 1) throw new Error('Cannot recover: invalid recovery bit');
|
if (![0, 1, 2, 3].includes(recovery)) throw new Error('Cannot recover: invalid recovery bit');
|
||||||
const h = truncateHash(ensureBytes(msgHash));
|
const h = truncateHash(ut.ensureBytes(msgHash));
|
||||||
const { n } = CURVE;
|
const { n } = CURVE;
|
||||||
const rinv = mod.invert(r, n);
|
const radj = recovery === 2 || recovery === 3 ? r + n : r;
|
||||||
|
if (radj >= Fp.ORDER) throw new Error('Cannot recover: bit 2/3 is invalid with current r');
|
||||||
|
const rinv = mod.invert(radj, n);
|
||||||
// Q = u1⋅G + u2⋅R
|
// Q = u1⋅G + u2⋅R
|
||||||
const u1 = mod.mod(-h * rinv, n);
|
const u1 = mod.mod(-h * rinv, n);
|
||||||
const u2 = mod.mod(s * rinv, n);
|
const u2 = mod.mod(s * rinv, n);
|
||||||
const prefix = recovery & 1 ? '03' : '02';
|
const prefix = recovery & 1 ? '03' : '02';
|
||||||
const R = Point.fromHex(prefix + numToFieldStr(r));
|
const R = Point.fromHex(prefix + numToFieldStr(radj));
|
||||||
const Q = Point.BASE.multiplyAndAddUnsafe(R, u1, u2);
|
const Q = Point.BASE.multiplyAndAddUnsafe(R, u1, u2); // unsafe is fine: no priv data leaked
|
||||||
if (!Q) throw new Error('Cannot recover: point at infinify');
|
if (!Q) throw new Error('Cannot recover: point at infinify');
|
||||||
Q.assertValidity();
|
Q.assertValidity();
|
||||||
return Q;
|
return Q;
|
||||||
@@ -1009,22 +1016,24 @@ export function weierstrass(curveDef: CurveType): CurveFn {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// DER-encoded
|
// DER-encoded
|
||||||
toDERRawBytes(isCompressed = false) {
|
toDERRawBytes() {
|
||||||
return hexToBytes(this.toDERHex(isCompressed));
|
return ut.hexToBytes(this.toDERHex());
|
||||||
}
|
}
|
||||||
toDERHex(isCompressed = false) {
|
toDERHex() {
|
||||||
const sHex = sliceDER(numberToHexUnpadded(this.s));
|
const { numberToHexUnpadded: toHex } = ut;
|
||||||
if (isCompressed) return sHex;
|
const sHex = DER.slice(toHex(this.s));
|
||||||
const rHex = sliceDER(numberToHexUnpadded(this.r));
|
const rHex = DER.slice(toHex(this.r));
|
||||||
const rLen = numberToHexUnpadded(rHex.length / 2);
|
const sHexL = sHex.length / 2;
|
||||||
const sLen = numberToHexUnpadded(sHex.length / 2);
|
const rHexL = rHex.length / 2;
|
||||||
const length = numberToHexUnpadded(rHex.length / 2 + sHex.length / 2 + 4);
|
const sLen = toHex(sHexL);
|
||||||
|
const rLen = toHex(rHexL);
|
||||||
|
const length = toHex(rHexL + sHexL + 4);
|
||||||
return `30${length}02${rLen}${rHex}02${sLen}${sHex}`;
|
return `30${length}02${rLen}${rHex}02${sLen}${sHex}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 32 bytes of r, then 32 bytes of s
|
// padded bytes of r, then padded bytes of s
|
||||||
toCompactRawBytes() {
|
toCompactRawBytes() {
|
||||||
return hexToBytes(this.toCompactHex());
|
return ut.hexToBytes(this.toCompactHex());
|
||||||
}
|
}
|
||||||
toCompactHex() {
|
toCompactHex() {
|
||||||
return numToFieldStr(this.r) + numToFieldStr(this.s);
|
return numToFieldStr(this.r) + numToFieldStr(this.s);
|
||||||
@@ -1032,8 +1041,6 @@ export function weierstrass(curveDef: CurveType): CurveFn {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const utils = {
|
const utils = {
|
||||||
mod: (n: bigint, modulo = Fp.ORDER) => mod.mod(n, modulo),
|
|
||||||
invert: Fp.invert,
|
|
||||||
isValidPrivateKey(privateKey: PrivKey) {
|
isValidPrivateKey(privateKey: PrivKey) {
|
||||||
try {
|
try {
|
||||||
normalizePrivateKey(privateKey);
|
normalizePrivateKey(privateKey);
|
||||||
@@ -1053,7 +1060,8 @@ export function weierstrass(curveDef: CurveType): CurveFn {
|
|||||||
/**
|
/**
|
||||||
* Converts some bytes to a valid private key. Needs at least (nBitLength+64) bytes.
|
* Converts some bytes to a valid private key. Needs at least (nBitLength+64) bytes.
|
||||||
*/
|
*/
|
||||||
hashToPrivateKey: (hash: Hex): Uint8Array => numToField(hashToPrivateScalar(hash, CURVE_ORDER)),
|
hashToPrivateKey: (hash: Hex): Uint8Array =>
|
||||||
|
numToField(ut.hashToPrivateScalar(hash, CURVE_ORDER)),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Produces cryptographically secure private key from random of size (nBitLength+64)
|
* Produces cryptographically secure private key from random of size (nBitLength+64)
|
||||||
@@ -1078,10 +1086,10 @@ export function weierstrass(curveDef: CurveType): CurveFn {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Computes public key for a private key.
|
* Computes public key for a private key. Checks for validity of the private key.
|
||||||
* @param privateKey private key
|
* @param privateKey private key
|
||||||
* @param isCompressed whether to return compact, or full key
|
* @param isCompressed whether to return compact (default), or full key
|
||||||
* @returns Public key, full by default; short when isCompressed=true
|
* @returns Public key, full when isCompressed=false; short when isCompressed=true
|
||||||
*/
|
*/
|
||||||
function getPublicKey(privateKey: PrivKey, isCompressed = false): Uint8Array {
|
function getPublicKey(privateKey: PrivKey, isCompressed = false): Uint8Array {
|
||||||
return Point.fromPrivateKey(privateKey).toRawBytes(isCompressed);
|
return Point.fromPrivateKey(privateKey).toRawBytes(isCompressed);
|
||||||
@@ -1101,12 +1109,12 @@ export function weierstrass(curveDef: CurveType): CurveFn {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ECDH (Elliptic Curve Diffie Hellman) implementation.
|
* ECDH (Elliptic Curve Diffie Hellman).
|
||||||
* 1. Checks for validity of private key
|
* Computes shared public key from private key and public key.
|
||||||
* 2. Checks for the public key of being on-curve
|
* Checks: 1) private key validity 2) shared key is on-curve
|
||||||
* @param privateA private key
|
* @param privateA private key
|
||||||
* @param publicB different public key
|
* @param publicB different public key
|
||||||
* @param isCompressed whether to return compact (33-byte), or full (65-byte) key
|
* @param isCompressed whether to return compact (default), or full key
|
||||||
* @returns shared public key
|
* @returns shared public key
|
||||||
*/
|
*/
|
||||||
function getSharedSecret(privateA: PrivKey, publicB: PubKey, isCompressed = false): Uint8Array {
|
function getSharedSecret(privateA: PrivKey, publicB: PubKey, isCompressed = false): Uint8Array {
|
||||||
@@ -1118,9 +1126,21 @@ export function weierstrass(curveDef: CurveType): CurveFn {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// RFC6979 methods
|
// RFC6979 methods
|
||||||
function bits2int(bytes: Uint8Array) {
|
function bits2int(bytes: Uint8Array): bigint {
|
||||||
const slice = bytes.length > Fp.BYTES ? bytes.slice(0, Fp.BYTES) : bytes;
|
const { nByteLength } = CURVE;
|
||||||
return bytesToNumberBE(slice);
|
if (!(bytes instanceof Uint8Array)) throw new Error('Expected Uint8Array');
|
||||||
|
const slice = bytes.length > nByteLength ? bytes.slice(0, nByteLength) : bytes;
|
||||||
|
// const slice = bytes; nByteLength; nBitLength;
|
||||||
|
let num = ut.bytesToNumberBE(slice);
|
||||||
|
// const { nBitLength } = CURVE;
|
||||||
|
// const delta = (bytes.length * 8) - nBitLength;
|
||||||
|
// if (delta > 0) {
|
||||||
|
// // console.log('bits=', bytes.length*8, 'CURVE n=', nBitLength, 'delta=', delta);
|
||||||
|
// // console.log(bytes.length, nBitLength, delta);
|
||||||
|
// // console.log(bytes, new Error().stack);
|
||||||
|
// num >>= BigInt(delta);
|
||||||
|
// }
|
||||||
|
return num;
|
||||||
}
|
}
|
||||||
function bits2octets(bytes: Uint8Array): Uint8Array {
|
function bits2octets(bytes: Uint8Array): Uint8Array {
|
||||||
const z1 = bits2int(bytes);
|
const z1 = bits2int(bytes);
|
||||||
@@ -1128,28 +1148,28 @@ export function weierstrass(curveDef: CurveType): CurveFn {
|
|||||||
return int2octets(z2 < _0n ? z1 : z2);
|
return int2octets(z2 < _0n ? z1 : z2);
|
||||||
}
|
}
|
||||||
function int2octets(num: bigint): Uint8Array {
|
function int2octets(num: bigint): Uint8Array {
|
||||||
return numToField(num); // prohibits >32 bytes
|
return numToField(num); // prohibits >nByteLength bytes
|
||||||
}
|
}
|
||||||
// Steps A, D of RFC6979 3.2
|
// Steps A, D of RFC6979 3.2
|
||||||
// Creates RFC6979 seed; converts msg/privKey to numbers.
|
// Creates RFC6979 seed; converts msg/privKey to numbers.
|
||||||
function initSigArgs(msgHash: Hex, privateKey: PrivKey, extraEntropy?: Entropy) {
|
function initSigArgs(msgHash: Hex, privateKey: PrivKey, extraEntropy?: Entropy) {
|
||||||
if (msgHash == null) throw new Error(`sign: expected valid message hash, not "${msgHash}"`);
|
if (msgHash == null) throw new Error(`sign: expected valid message hash, not "${msgHash}"`);
|
||||||
// Step A is ignored, since we already provide hash instead of msg
|
// Step A is ignored, since we already provide hash instead of msg
|
||||||
const h1 = numToField(truncateHash(ensureBytes(msgHash)));
|
const h1 = numToField(truncateHash(ut.ensureBytes(msgHash)));
|
||||||
const d = normalizePrivateKey(privateKey);
|
const d = normalizePrivateKey(privateKey);
|
||||||
// K = HMAC_K(V || 0x00 || int2octets(x) || bits2octets(h1) || k')
|
// K = HMAC_K(V || 0x00 || int2octets(x) || bits2octets(h1) || k')
|
||||||
const seedArgs = [int2octets(d), bits2octets(h1)];
|
const seedArgs = [int2octets(d), bits2octets(h1)];
|
||||||
// RFC6979 3.6: additional k' could be provided
|
// RFC6979 3.6: additional k' could be provided
|
||||||
if (extraEntropy != null) {
|
if (extraEntropy != null) {
|
||||||
if (extraEntropy === true) extraEntropy = CURVE.randomBytes(Fp.BYTES);
|
if (extraEntropy === true) extraEntropy = CURVE.randomBytes(Fp.BYTES);
|
||||||
const e = ensureBytes(extraEntropy);
|
const e = ut.ensureBytes(extraEntropy);
|
||||||
if (e.length !== Fp.BYTES) throw new Error(`sign: Expected ${Fp.BYTES} bytes of extra data`);
|
if (e.length !== Fp.BYTES) throw new Error(`sign: Expected ${Fp.BYTES} bytes of extra data`);
|
||||||
seedArgs.push(e);
|
seedArgs.push(e);
|
||||||
}
|
}
|
||||||
// seed is constructed from private key and message
|
// seed is constructed from private key and message
|
||||||
// Step D
|
// Step D
|
||||||
// V, 0x00 are done in HmacDRBG constructor.
|
// V, 0x00 are done in HmacDRBG constructor.
|
||||||
const seed = concatBytes(...seedArgs);
|
const seed = ut.concatBytes(...seedArgs);
|
||||||
const m = bits2int(h1);
|
const m = bits2int(h1);
|
||||||
return { seed, m, d };
|
return { seed, m, d };
|
||||||
}
|
}
|
||||||
@@ -1172,9 +1192,10 @@ export function weierstrass(curveDef: CurveType): CurveFn {
|
|||||||
// r = x mod n
|
// r = x mod n
|
||||||
const r = mod.mod(q.x, n);
|
const r = mod.mod(q.x, n);
|
||||||
if (r === _0n) return;
|
if (r === _0n) return;
|
||||||
// s = (1/k * (m + dr) mod n
|
// s = (m + dr)/k mod n where x/k == x*inv(k)
|
||||||
const s = mod.mod(kinv * mod.mod(m + mod.mod(d * r, n), n), n);
|
const s = mod.mod(kinv * mod.mod(m + mod.mod(d * r, n), n), n);
|
||||||
if (s === _0n) return;
|
if (s === _0n) return;
|
||||||
|
// recovery bit is usually 0 or 1; rarely it's 2 or 3, when q.x > n
|
||||||
let recovery = (q.x === r ? 0 : 2) | Number(q.y & _1n);
|
let recovery = (q.x === r ? 0 : 2) | Number(q.y & _1n);
|
||||||
let normS = s;
|
let normS = s;
|
||||||
if (lowS && isBiggerThanHalfOrder(s)) {
|
if (lowS && isBiggerThanHalfOrder(s)) {
|
||||||
@@ -1184,11 +1205,19 @@ export function weierstrass(curveDef: CurveType): CurveFn {
|
|||||||
return new Signature(r, normS, recovery);
|
return new Signature(r, normS, recovery);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const defaultSigOpts: SignOpts = { lowS: CURVE.lowS };
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Signs message hash (not message: you need to hash it by yourself).
|
* Signs message hash (not message: you need to hash it by yourself).
|
||||||
|
* ```
|
||||||
|
* sign(m, d, k) where
|
||||||
|
* (x, y) = G × k
|
||||||
|
* r = x mod n
|
||||||
|
* s = (m + dr)/k mod n
|
||||||
|
* ```
|
||||||
* @param opts `lowS, extraEntropy`
|
* @param opts `lowS, extraEntropy`
|
||||||
*/
|
*/
|
||||||
function sign(msgHash: Hex, privKey: PrivKey, opts: SignOpts = { lowS: CURVE.lowS }): Signature {
|
function sign(msgHash: Hex, privKey: PrivKey, opts = defaultSigOpts): Signature {
|
||||||
// Steps A, D of RFC6979 3.2.
|
// Steps A, D of RFC6979 3.2.
|
||||||
const { seed, m, d } = initSigArgs(msgHash, privKey, opts.extraEntropy);
|
const { seed, m, d } = initSigArgs(msgHash, privKey, opts.extraEntropy);
|
||||||
// Steps B, C, D, E, F, G
|
// Steps B, C, D, E, F, G
|
||||||
@@ -1199,6 +1228,14 @@ export function weierstrass(curveDef: CurveType): CurveFn {
|
|||||||
while (!(sig = kmdToSig(drbg.generateSync(), m, d, opts.lowS))) drbg.reseedSync();
|
while (!(sig = kmdToSig(drbg.generateSync(), m, d, opts.lowS))) drbg.reseedSync();
|
||||||
return sig;
|
return sig;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Signs a message (not message hash).
|
||||||
|
*/
|
||||||
|
function signUnhashed(msg: Uint8Array, privKey: PrivKey, opts = defaultSigOpts): Signature {
|
||||||
|
return sign(CURVE.hash(ut.ensureBytes(msg)), privKey, opts);
|
||||||
|
}
|
||||||
|
|
||||||
// Enable precomputes. Slows down first publicKey computation by 20ms.
|
// Enable precomputes. Slows down first publicKey computation by 20ms.
|
||||||
Point.BASE._setWindowSize(8);
|
Point.BASE._setWindowSize(8);
|
||||||
|
|
||||||
@@ -1234,7 +1271,7 @@ export function weierstrass(curveDef: CurveType): CurveFn {
|
|||||||
signature = Signature.fromCompact(signature as Hex);
|
signature = Signature.fromCompact(signature as Hex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
msgHash = ensureBytes(msgHash);
|
msgHash = ut.ensureBytes(msgHash);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -1265,6 +1302,7 @@ export function weierstrass(curveDef: CurveType): CurveFn {
|
|||||||
getPublicKey,
|
getPublicKey,
|
||||||
getSharedSecret,
|
getSharedSecret,
|
||||||
sign,
|
sign,
|
||||||
|
signUnhashed,
|
||||||
verify,
|
verify,
|
||||||
Point,
|
Point,
|
||||||
ProjectivePoint,
|
ProjectivePoint,
|
||||||
|
|||||||
@@ -1,4 +1,16 @@
|
|||||||
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
|
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
|
||||||
|
|
||||||
|
// The pairing-friendly Barreto-Lynn-Scott elliptic curve construction allows to:
|
||||||
|
// - Construct zk-SNARKs at the 128-bit security
|
||||||
|
// - Use threshold signatures, which allows a user to sign lots of messages with one signature and verify them swiftly in a batch, using Boneh-Lynn-Shacham signature scheme.
|
||||||
|
// Differences from @noble/bls12-381 1.4:
|
||||||
|
// - PointG1 -> G1.Point
|
||||||
|
// - PointG2 -> G2.Point
|
||||||
|
// - PointG2.fromSignature -> Signature.decode
|
||||||
|
// - PointG2.toSignature -> Signature.encode
|
||||||
|
// - Fixed Fp2 ORDER
|
||||||
|
// - Points now have only two coordinates
|
||||||
|
|
||||||
import { sha256 } from '@noble/hashes/sha256';
|
import { sha256 } from '@noble/hashes/sha256';
|
||||||
import { randomBytes } from '@noble/hashes/utils';
|
import { randomBytes } from '@noble/hashes/utils';
|
||||||
import { bls, CurveFn } from './abstract/bls.js';
|
import { bls, CurveFn } from './abstract/bls.js';
|
||||||
@@ -23,14 +35,6 @@ import {
|
|||||||
} from './abstract/weierstrass.js';
|
} from './abstract/weierstrass.js';
|
||||||
import { isogenyMap } from './abstract/hash-to-curve.js';
|
import { isogenyMap } from './abstract/hash-to-curve.js';
|
||||||
|
|
||||||
// Differences from bls12-381:
|
|
||||||
// - PointG1 -> G1.Point
|
|
||||||
// - PointG2 -> G2.Point
|
|
||||||
// - PointG2.fromSignature -> Signature.decode
|
|
||||||
// - PointG2.toSignature -> Signature.encode
|
|
||||||
// - Fixed Fp2 ORDER
|
|
||||||
// Points now have only two coordinates
|
|
||||||
|
|
||||||
// CURVE FIELDS
|
// CURVE FIELDS
|
||||||
// Finite field over p.
|
// Finite field over p.
|
||||||
const Fp =
|
const Fp =
|
||||||
@@ -131,6 +135,7 @@ const Fp2: mod.Field<Fp2> & Fp2Utils = {
|
|||||||
return { c0: Fp.mul(factor, Fp.create(a)), c1: Fp.mul(factor, Fp.create(-b)) };
|
return { c0: Fp.mul(factor, Fp.create(a)), c1: Fp.mul(factor, Fp.create(-b)) };
|
||||||
},
|
},
|
||||||
sqrt: (num) => {
|
sqrt: (num) => {
|
||||||
|
if (Fp2.equals(num, Fp2.ZERO)) return Fp2.ZERO; // Algo doesn't handles this case
|
||||||
// TODO: Optimize this line. It's extremely slow.
|
// TODO: Optimize this line. It's extremely slow.
|
||||||
// Speeding this up would boost aggregateSignatures.
|
// Speeding this up would boost aggregateSignatures.
|
||||||
// https://eprint.iacr.org/2012/685.pdf applicable?
|
// https://eprint.iacr.org/2012/685.pdf applicable?
|
||||||
|
|||||||
@@ -260,7 +260,7 @@ const invertSqrt = (number: bigint) => uvRatio(_1n, number);
|
|||||||
|
|
||||||
const MAX_255B = BigInt('0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff');
|
const MAX_255B = BigInt('0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff');
|
||||||
const bytes255ToNumberLE = (bytes: Uint8Array) =>
|
const bytes255ToNumberLE = (bytes: Uint8Array) =>
|
||||||
ed25519.utils.mod(bytesToNumberLE(bytes) & MAX_255B);
|
ed25519.CURVE.Fp.create(bytesToNumberLE(bytes) & MAX_255B);
|
||||||
|
|
||||||
type ExtendedPoint = ExtendedPointType;
|
type ExtendedPoint = ExtendedPointType;
|
||||||
|
|
||||||
@@ -269,7 +269,7 @@ type ExtendedPoint = ExtendedPointType;
|
|||||||
function calcElligatorRistrettoMap(r0: bigint): ExtendedPoint {
|
function calcElligatorRistrettoMap(r0: bigint): ExtendedPoint {
|
||||||
const { d } = ed25519.CURVE;
|
const { d } = ed25519.CURVE;
|
||||||
const P = ed25519.CURVE.Fp.ORDER;
|
const P = ed25519.CURVE.Fp.ORDER;
|
||||||
const { mod } = ed25519.utils;
|
const mod = ed25519.CURVE.Fp.create;
|
||||||
const r = mod(SQRT_M1 * r0 * r0); // 1
|
const r = mod(SQRT_M1 * r0 * r0); // 1
|
||||||
const Ns = mod((r + _1n) * ONE_MINUS_D_SQ); // 2
|
const Ns = mod((r + _1n) * ONE_MINUS_D_SQ); // 2
|
||||||
let c = BigInt(-1); // 3
|
let c = BigInt(-1); // 3
|
||||||
@@ -327,7 +327,7 @@ export class RistrettoPoint {
|
|||||||
hex = ensureBytes(hex, 32);
|
hex = ensureBytes(hex, 32);
|
||||||
const { a, d } = ed25519.CURVE;
|
const { a, d } = ed25519.CURVE;
|
||||||
const P = ed25519.CURVE.Fp.ORDER;
|
const P = ed25519.CURVE.Fp.ORDER;
|
||||||
const { mod } = ed25519.utils;
|
const mod = ed25519.CURVE.Fp.create;
|
||||||
const emsg = 'RistrettoPoint.fromHex: the hex is not valid encoding of RistrettoPoint';
|
const emsg = 'RistrettoPoint.fromHex: the hex is not valid encoding of RistrettoPoint';
|
||||||
const s = bytes255ToNumberLE(hex);
|
const s = bytes255ToNumberLE(hex);
|
||||||
// 1. Check that s_bytes is the canonical encoding of a field element, or else abort.
|
// 1. Check that s_bytes is the canonical encoding of a field element, or else abort.
|
||||||
@@ -357,7 +357,7 @@ export class RistrettoPoint {
|
|||||||
toRawBytes(): Uint8Array {
|
toRawBytes(): Uint8Array {
|
||||||
let { x, y, z, t } = this.ep;
|
let { x, y, z, t } = this.ep;
|
||||||
const P = ed25519.CURVE.Fp.ORDER;
|
const P = ed25519.CURVE.Fp.ORDER;
|
||||||
const { mod } = ed25519.utils;
|
const mod = ed25519.CURVE.Fp.create;
|
||||||
const u1 = mod(mod(z + y) * mod(z - y)); // 1
|
const u1 = mod(mod(z + y) * mod(z - y)); // 1
|
||||||
const u2 = mod(x * y); // 2
|
const u2 = mod(x * y); // 2
|
||||||
// Square root always exists
|
// Square root always exists
|
||||||
@@ -395,7 +395,7 @@ export class RistrettoPoint {
|
|||||||
assertRstPoint(other);
|
assertRstPoint(other);
|
||||||
const a = this.ep;
|
const a = this.ep;
|
||||||
const b = other.ep;
|
const b = other.ep;
|
||||||
const { mod } = ed25519.utils;
|
const mod = ed25519.CURVE.Fp.create;
|
||||||
// (x1 * y2 == y1 * x2) | (y1 * y2 == x1 * x2)
|
// (x1 * y2 == y1 * x2) | (y1 * y2 == x1 * x2)
|
||||||
const one = mod(a.x * b.y) === mod(a.y * b.x);
|
const one = mod(a.x * b.y) === mod(a.y * b.x);
|
||||||
const two = mod(a.y * b.y) === mod(a.x * b.x);
|
const two = mod(a.y * b.y) === mod(a.x * b.x);
|
||||||
|
|||||||
@@ -138,7 +138,8 @@ const ED448_DEF = {
|
|||||||
),
|
),
|
||||||
// Finite field 𝔽p over which we'll do calculations; 2n ** 448n - 2n ** 224n - 1n
|
// Finite field 𝔽p over which we'll do calculations; 2n ** 448n - 2n ** 224n - 1n
|
||||||
Fp,
|
Fp,
|
||||||
// Subgroup order: how many points ed448 has; 2n**446n - 13818066809895115352007386748515426880336692474882178609894547503885n
|
// Subgroup order: how many points curve has;
|
||||||
|
// 2n**446n - 13818066809895115352007386748515426880336692474882178609894547503885n
|
||||||
n: BigInt(
|
n: BigInt(
|
||||||
'181709681073901722637330951972001133588410340171829515070372549795146003961539585716195755291692375963310293709091662304773755859649779'
|
'181709681073901722637330951972001133588410340171829515070372549795146003961539585716195755291692375963310293709091662304773755859649779'
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
|
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
|
||||||
import { sha256 } from '@noble/hashes/sha256';
|
import { sha512 } from '@noble/hashes/sha512';
|
||||||
import { concatBytes, randomBytes, utf8ToBytes } from '@noble/hashes/utils';
|
import { concatBytes, randomBytes, utf8ToBytes } from '@noble/hashes/utils';
|
||||||
import { twistedEdwards } from './abstract/edwards.js';
|
import { twistedEdwards } from './abstract/edwards.js';
|
||||||
import { blake2s } from '@noble/hashes/blake2s';
|
import { blake2s } from '@noble/hashes/blake2s';
|
||||||
@@ -8,6 +8,7 @@ import { Fp } from './abstract/modular.js';
|
|||||||
/**
|
/**
|
||||||
* jubjub Twisted Edwards curve.
|
* jubjub Twisted Edwards curve.
|
||||||
* https://neuromancer.sk/std/other/JubJub
|
* https://neuromancer.sk/std/other/JubJub
|
||||||
|
* jubjub does not use EdDSA, so `hash`/sha512 params are passed because interface expects them.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export const jubjub = twistedEdwards({
|
export const jubjub = twistedEdwards({
|
||||||
@@ -15,16 +16,16 @@ export const jubjub = twistedEdwards({
|
|||||||
a: BigInt('0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000000'),
|
a: BigInt('0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000000'),
|
||||||
d: BigInt('0x2a9318e74bfa2b48f5fd9207e6bd7fd4292d7f6d37579d2601065fd6d6343eb1'),
|
d: BigInt('0x2a9318e74bfa2b48f5fd9207e6bd7fd4292d7f6d37579d2601065fd6d6343eb1'),
|
||||||
// Finite field 𝔽p over which we'll do calculations
|
// Finite field 𝔽p over which we'll do calculations
|
||||||
|
// Same value as bls12-381 Fr (not Fp)
|
||||||
Fp: Fp(BigInt('0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001')),
|
Fp: Fp(BigInt('0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001')),
|
||||||
// Subgroup order: how many points ed25519 has
|
// Subgroup order: how many points curve has
|
||||||
// 2n ** 252n + 27742317777372353535851937790883648493n;
|
|
||||||
n: BigInt('0xe7db4ea6533afa906673b0101343b00a6682093ccc81082d0970e5ed6f72cb7'),
|
n: BigInt('0xe7db4ea6533afa906673b0101343b00a6682093ccc81082d0970e5ed6f72cb7'),
|
||||||
// Cofactor
|
// Cofactor
|
||||||
h: BigInt(8),
|
h: BigInt(8),
|
||||||
// Base point (x, y) aka generator point
|
// Base point (x, y) aka generator point
|
||||||
Gx: BigInt('0x11dafe5d23e1218086a365b99fbf3d3be72f6afd7d1f72623e6b071492d1122b'),
|
Gx: BigInt('0x11dafe5d23e1218086a365b99fbf3d3be72f6afd7d1f72623e6b071492d1122b'),
|
||||||
Gy: BigInt('0x1d523cf1ddab1a1793132e78c866c0c33e26ba5cc220fed7cc3f870e59d292aa'),
|
Gy: BigInt('0x1d523cf1ddab1a1793132e78c866c0c33e26ba5cc220fed7cc3f870e59d292aa'),
|
||||||
hash: sha256,
|
hash: sha512,
|
||||||
randomBytes,
|
randomBytes,
|
||||||
} as const);
|
} as const);
|
||||||
|
|
||||||
|
|||||||
@@ -15,10 +15,8 @@ import { randomBytes } from '@noble/hashes/utils';
|
|||||||
import { isogenyMap } from './abstract/hash-to-curve.js';
|
import { isogenyMap } from './abstract/hash-to-curve.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* secp256k1 belongs to Koblitz curves: it has
|
* secp256k1 belongs to Koblitz curves: it has efficiently computable endomorphism.
|
||||||
* efficiently computable Frobenius endomorphism.
|
* Endomorphism uses 2x less RAM, speeds up precomputation by 2x and ECDH / key recovery by 20%.
|
||||||
* Endomorphism improves efficiency:
|
|
||||||
* Uses 2x less RAM, speeds up precomputation by 2x and ECDH / sign key recovery by 20%.
|
|
||||||
* Should always be used for Projective's double-and-add multiplication.
|
* Should always be used for Projective's double-and-add multiplication.
|
||||||
* For affines cached multiplication, it trades off 1/2 init time & 1/3 ram for 20% perf hit.
|
* For affines cached multiplication, it trades off 1/2 init time & 1/3 ram for 20% perf hit.
|
||||||
* https://gist.github.com/paulmillr/eb670806793e84df628a7c434a873066
|
* https://gist.github.com/paulmillr/eb670806793e84df628a7c434a873066
|
||||||
@@ -129,7 +127,7 @@ export const secp256k1 = createCurve(
|
|||||||
const b1 = -_1n * BigInt('0xe4437ed6010e88286f547fa90abfe4c3');
|
const b1 = -_1n * BigInt('0xe4437ed6010e88286f547fa90abfe4c3');
|
||||||
const a2 = BigInt('0x114ca50f7a8e2f3f657c1108d9d44cfd8');
|
const a2 = BigInt('0x114ca50f7a8e2f3f657c1108d9d44cfd8');
|
||||||
const b2 = a1;
|
const b2 = a1;
|
||||||
const POW_2_128 = BigInt('0x100000000000000000000000000000000');
|
const POW_2_128 = BigInt('0x100000000000000000000000000000000'); // (2n**128n).toString(16)
|
||||||
|
|
||||||
const c1 = divNearest(b2 * k, n);
|
const c1 = divNearest(b2 * k, n);
|
||||||
const c2 = divNearest(-b1 * k, n);
|
const c2 = divNearest(-b1 * k, n);
|
||||||
@@ -175,20 +173,17 @@ function normalizePublicKey(publicKey: Hex | PointType<bigint>): PointType<bigin
|
|||||||
} else {
|
} else {
|
||||||
const bytes = ensureBytes(publicKey);
|
const bytes = ensureBytes(publicKey);
|
||||||
// Schnorr is 32 bytes
|
// Schnorr is 32 bytes
|
||||||
if (bytes.length === 32) {
|
if (bytes.length !== 32) throw new Error('Schnorr pubkeys must be 32 bytes');
|
||||||
const x = bytesToNumberBE(bytes);
|
const x = bytesToNumberBE(bytes);
|
||||||
if (!isValidFieldElement(x)) throw new Error('Point is not on curve');
|
if (!isValidFieldElement(x)) throw new Error('Point is not on curve');
|
||||||
const y2 = secp256k1.utils._weierstrassEquation(x); // y² = x³ + ax + b
|
const y2 = secp256k1.utils._weierstrassEquation(x); // y² = x³ + ax + b
|
||||||
let y = sqrtMod(y2); // y = y² ^ (p+1)/4
|
let y = sqrtMod(y2); // y = y² ^ (p+1)/4
|
||||||
const isYOdd = (y & _1n) === _1n;
|
const isYOdd = (y & _1n) === _1n;
|
||||||
// Schnorr
|
// Schnorr
|
||||||
if (isYOdd) y = secp256k1.CURVE.Fp.negate(y);
|
if (isYOdd) y = secp256k1.CURVE.Fp.negate(y);
|
||||||
const point = new secp256k1.Point(x, y);
|
const point = new secp256k1.Point(x, y);
|
||||||
point.assertValidity();
|
point.assertValidity();
|
||||||
return point;
|
return point;
|
||||||
}
|
|
||||||
// Do we need that in schnorr at all?
|
|
||||||
return secp256k1.Point.fromHex(publicKey);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -227,10 +222,13 @@ class SchnorrSignature {
|
|||||||
}
|
}
|
||||||
static fromHex(hex: Hex) {
|
static fromHex(hex: Hex) {
|
||||||
const bytes = ensureBytes(hex);
|
const bytes = ensureBytes(hex);
|
||||||
if (bytes.length !== 64)
|
const len = 32; // group length
|
||||||
throw new TypeError(`SchnorrSignature.fromHex: expected 64 bytes, not ${bytes.length}`);
|
if (bytes.length !== 2 * len)
|
||||||
const r = bytesToNumberBE(bytes.subarray(0, 32));
|
throw new TypeError(
|
||||||
const s = bytesToNumberBE(bytes.subarray(32, 64));
|
`SchnorrSignature.fromHex: expected ${2 * len} bytes, not ${bytes.length}`
|
||||||
|
);
|
||||||
|
const r = bytesToNumberBE(bytes.subarray(0, len));
|
||||||
|
const s = bytesToNumberBE(bytes.subarray(len, 2 * len));
|
||||||
return new SchnorrSignature(r, s);
|
return new SchnorrSignature(r, s);
|
||||||
}
|
}
|
||||||
assertValidity() {
|
assertValidity() {
|
||||||
|
|||||||
@@ -138,11 +138,12 @@ function hashKeyWithIndex(key: Uint8Array, index: number) {
|
|||||||
export function grindKey(seed: Hex) {
|
export function grindKey(seed: Hex) {
|
||||||
const _seed = ensureBytes0x(seed);
|
const _seed = ensureBytes0x(seed);
|
||||||
const sha256mask = 2n ** 256n;
|
const sha256mask = 2n ** 256n;
|
||||||
const limit = sha256mask - starkCurve.utils.mod(sha256mask, starkCurve.CURVE.n);
|
const Fn = Fp(CURVE.n);
|
||||||
|
const limit = sha256mask - Fn.create(sha256mask);
|
||||||
for (let i = 0; ; i++) {
|
for (let i = 0; ; i++) {
|
||||||
const key = hashKeyWithIndex(_seed, i);
|
const key = hashKeyWithIndex(_seed, i);
|
||||||
// key should be in [0, limit)
|
// key should be in [0, limit)
|
||||||
if (key < limit) return starkCurve.utils.mod(key, starkCurve.CURVE.n).toString(16);
|
if (key < limit) return Fn.create(key).toString(16);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import { deepStrictEqual, throws } from 'assert';
|
import { deepStrictEqual, throws } from 'assert';
|
||||||
import { should } from 'micro-should';
|
import { should, describe } from 'micro-should';
|
||||||
import * as fc from 'fast-check';
|
import * as fc from 'fast-check';
|
||||||
import * as mod from '../lib/esm/abstract/modular.js';
|
import * as mod from '../lib/esm/abstract/modular.js';
|
||||||
|
import { bytesToHex as toHex } from '../lib/esm/abstract/utils.js';
|
||||||
// Generic tests for all curves in package
|
// Generic tests for all curves in package
|
||||||
import { secp192r1 } from '../lib/esm/p192.js';
|
import { secp192r1 } from '../lib/esm/p192.js';
|
||||||
import { secp224r1 } from '../lib/esm/p224.js';
|
import { secp224r1 } from '../lib/esm/p224.js';
|
||||||
@@ -61,201 +62,235 @@ for (const c in FIELDS) {
|
|||||||
const FC_BIGINT = curve[f][1] ? curve[f][1] : fc.bigInt(1n, Fp.ORDER - 1n);
|
const FC_BIGINT = curve[f][1] ? curve[f][1] : fc.bigInt(1n, Fp.ORDER - 1n);
|
||||||
|
|
||||||
const create = curve[f][2] ? curve[f][2].bind(null, Fp) : (num) => Fp.create(num);
|
const create = curve[f][2] ? curve[f][2].bind(null, Fp) : (num) => Fp.create(num);
|
||||||
should(`${name} equality`, () => {
|
describe(name, () => {
|
||||||
fc.assert(
|
should('equality', () => {
|
||||||
fc.property(FC_BIGINT, (num) => {
|
fc.assert(
|
||||||
const a = create(num);
|
fc.property(FC_BIGINT, (num) => {
|
||||||
const b = create(num);
|
const a = create(num);
|
||||||
deepStrictEqual(Fp.equals(a, b), true);
|
const b = create(num);
|
||||||
deepStrictEqual(Fp.equals(b, a), true);
|
deepStrictEqual(Fp.equals(a, b), true);
|
||||||
})
|
deepStrictEqual(Fp.equals(b, a), true);
|
||||||
);
|
})
|
||||||
});
|
);
|
||||||
should(`${name} non-equality`, () => {
|
});
|
||||||
fc.assert(
|
should('non-equality', () => {
|
||||||
fc.property(FC_BIGINT, FC_BIGINT, (num1, num2) => {
|
fc.assert(
|
||||||
const a = create(num1);
|
fc.property(FC_BIGINT, FC_BIGINT, (num1, num2) => {
|
||||||
const b = create(num2);
|
const a = create(num1);
|
||||||
deepStrictEqual(Fp.equals(a, b), num1 === num2);
|
const b = create(num2);
|
||||||
deepStrictEqual(Fp.equals(b, a), num1 === num2);
|
deepStrictEqual(Fp.equals(a, b), num1 === num2);
|
||||||
})
|
deepStrictEqual(Fp.equals(b, a), num1 === num2);
|
||||||
);
|
})
|
||||||
});
|
);
|
||||||
should(`${name} add/subtract/commutativity`, () => {
|
});
|
||||||
fc.assert(
|
should('add/subtract/commutativity', () => {
|
||||||
fc.property(FC_BIGINT, FC_BIGINT, (num1, num2) => {
|
fc.assert(
|
||||||
const a = create(num1);
|
fc.property(FC_BIGINT, FC_BIGINT, (num1, num2) => {
|
||||||
const b = create(num2);
|
const a = create(num1);
|
||||||
deepStrictEqual(Fp.add(a, b), Fp.add(b, a));
|
const b = create(num2);
|
||||||
})
|
deepStrictEqual(Fp.add(a, b), Fp.add(b, a));
|
||||||
);
|
})
|
||||||
});
|
);
|
||||||
should(`${name} add/subtract/associativity`, () => {
|
});
|
||||||
fc.assert(
|
should('add/subtract/associativity', () => {
|
||||||
fc.property(FC_BIGINT, FC_BIGINT, FC_BIGINT, (num1, num2, num3) => {
|
fc.assert(
|
||||||
const a = create(num1);
|
fc.property(FC_BIGINT, FC_BIGINT, FC_BIGINT, (num1, num2, num3) => {
|
||||||
const b = create(num2);
|
const a = create(num1);
|
||||||
const c = create(num3);
|
const b = create(num2);
|
||||||
deepStrictEqual(Fp.add(a, Fp.add(b, c)), Fp.add(Fp.add(a, b), c));
|
const c = create(num3);
|
||||||
})
|
deepStrictEqual(Fp.add(a, Fp.add(b, c)), Fp.add(Fp.add(a, b), c));
|
||||||
);
|
})
|
||||||
});
|
);
|
||||||
should(`${name} add/subtract/x+0=x`, () => {
|
});
|
||||||
fc.assert(
|
should('add/subtract/x+0=x', () => {
|
||||||
fc.property(FC_BIGINT, (num) => {
|
fc.assert(
|
||||||
const a = create(num);
|
fc.property(FC_BIGINT, (num) => {
|
||||||
deepStrictEqual(Fp.add(a, Fp.ZERO), a);
|
const a = create(num);
|
||||||
})
|
deepStrictEqual(Fp.add(a, Fp.ZERO), a);
|
||||||
);
|
})
|
||||||
});
|
);
|
||||||
should(`${name} add/subtract/x-0=x`, () => {
|
});
|
||||||
fc.assert(
|
should('add/subtract/x-0=x', () => {
|
||||||
fc.property(FC_BIGINT, (num) => {
|
fc.assert(
|
||||||
const a = create(num);
|
fc.property(FC_BIGINT, (num) => {
|
||||||
deepStrictEqual(Fp.sub(a, Fp.ZERO), a);
|
const a = create(num);
|
||||||
deepStrictEqual(Fp.sub(a, a), Fp.ZERO);
|
deepStrictEqual(Fp.sub(a, Fp.ZERO), a);
|
||||||
})
|
deepStrictEqual(Fp.sub(a, a), Fp.ZERO);
|
||||||
);
|
})
|
||||||
});
|
);
|
||||||
should(`${name} add/subtract/negate equality`, () => {
|
});
|
||||||
fc.assert(
|
should('add/subtract/negate equality', () => {
|
||||||
fc.property(FC_BIGINT, (num1) => {
|
fc.assert(
|
||||||
const a = create(num1);
|
fc.property(FC_BIGINT, (num1) => {
|
||||||
const b = create(num1);
|
const a = create(num1);
|
||||||
deepStrictEqual(Fp.sub(Fp.ZERO, a), Fp.negate(a));
|
const b = create(num1);
|
||||||
deepStrictEqual(Fp.sub(a, b), Fp.add(a, Fp.negate(b)));
|
deepStrictEqual(Fp.sub(Fp.ZERO, a), Fp.negate(a));
|
||||||
deepStrictEqual(Fp.sub(a, b), Fp.add(a, Fp.mul(b, Fp.create(-1n))));
|
deepStrictEqual(Fp.sub(a, b), Fp.add(a, Fp.negate(b)));
|
||||||
})
|
deepStrictEqual(Fp.sub(a, b), Fp.add(a, Fp.mul(b, Fp.create(-1n))));
|
||||||
);
|
})
|
||||||
});
|
);
|
||||||
should(`${name} add/subtract/negate`, () => {
|
});
|
||||||
fc.assert(
|
should('add/subtract/negate', () => {
|
||||||
fc.property(FC_BIGINT, (num) => {
|
fc.assert(
|
||||||
const a = create(num);
|
fc.property(FC_BIGINT, (num) => {
|
||||||
deepStrictEqual(Fp.negate(a), Fp.sub(Fp.ZERO, a));
|
const a = create(num);
|
||||||
deepStrictEqual(Fp.negate(a), Fp.mul(a, Fp.create(-1n)));
|
deepStrictEqual(Fp.negate(a), Fp.sub(Fp.ZERO, a));
|
||||||
})
|
deepStrictEqual(Fp.negate(a), Fp.mul(a, Fp.create(-1n)));
|
||||||
);
|
})
|
||||||
});
|
);
|
||||||
|
});
|
||||||
|
should('negate(0)', () => {
|
||||||
|
deepStrictEqual(Fp.negate(Fp.ZERO), Fp.ZERO);
|
||||||
|
});
|
||||||
|
|
||||||
should(`${name} multiply/commutativity`, () => {
|
should('multiply/commutativity', () => {
|
||||||
fc.assert(
|
fc.assert(
|
||||||
fc.property(FC_BIGINT, FC_BIGINT, (num1, num2) => {
|
fc.property(FC_BIGINT, FC_BIGINT, (num1, num2) => {
|
||||||
const a = create(num1);
|
const a = create(num1);
|
||||||
const b = create(num2);
|
const b = create(num2);
|
||||||
deepStrictEqual(Fp.mul(a, b), Fp.mul(b, a));
|
deepStrictEqual(Fp.mul(a, b), Fp.mul(b, a));
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
should(`${name} multiply/associativity`, () => {
|
should('multiply/associativity', () => {
|
||||||
fc.assert(
|
fc.assert(
|
||||||
fc.property(FC_BIGINT, FC_BIGINT, FC_BIGINT, (num1, num2, num3) => {
|
fc.property(FC_BIGINT, FC_BIGINT, FC_BIGINT, (num1, num2, num3) => {
|
||||||
const a = create(num1);
|
const a = create(num1);
|
||||||
const b = create(num2);
|
const b = create(num2);
|
||||||
const c = create(num3);
|
const c = create(num3);
|
||||||
deepStrictEqual(Fp.mul(a, Fp.mul(b, c)), Fp.mul(Fp.mul(a, b), c));
|
deepStrictEqual(Fp.mul(a, Fp.mul(b, c)), Fp.mul(Fp.mul(a, b), c));
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
should(`${name} multiply/distributivity`, () => {
|
should('multiply/distributivity', () => {
|
||||||
fc.assert(
|
fc.assert(
|
||||||
fc.property(FC_BIGINT, FC_BIGINT, FC_BIGINT, (num1, num2, num3) => {
|
fc.property(FC_BIGINT, FC_BIGINT, FC_BIGINT, (num1, num2, num3) => {
|
||||||
const a = create(num1);
|
const a = create(num1);
|
||||||
const b = create(num2);
|
const b = create(num2);
|
||||||
const c = create(num3);
|
const c = create(num3);
|
||||||
deepStrictEqual(Fp.mul(a, Fp.add(b, c)), Fp.add(Fp.mul(b, a), Fp.mul(c, a)));
|
deepStrictEqual(Fp.mul(a, Fp.add(b, c)), Fp.add(Fp.mul(b, a), Fp.mul(c, a)));
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
should(`${name} multiply/add equality`, () => {
|
should('multiply/add equality', () => {
|
||||||
fc.assert(
|
fc.assert(
|
||||||
fc.property(FC_BIGINT, (num) => {
|
fc.property(FC_BIGINT, (num) => {
|
||||||
const a = create(num);
|
const a = create(num);
|
||||||
deepStrictEqual(Fp.mul(a, 0n), Fp.ZERO);
|
deepStrictEqual(Fp.mul(a, 0n), Fp.ZERO);
|
||||||
deepStrictEqual(Fp.mul(a, Fp.ZERO), Fp.ZERO);
|
deepStrictEqual(Fp.mul(a, Fp.ZERO), Fp.ZERO);
|
||||||
deepStrictEqual(Fp.mul(a, 1n), a);
|
deepStrictEqual(Fp.mul(a, 1n), a);
|
||||||
deepStrictEqual(Fp.mul(a, Fp.ONE), a);
|
deepStrictEqual(Fp.mul(a, Fp.ONE), a);
|
||||||
deepStrictEqual(Fp.mul(a, 2n), Fp.add(a, a));
|
deepStrictEqual(Fp.mul(a, 2n), Fp.add(a, a));
|
||||||
deepStrictEqual(Fp.mul(a, 3n), Fp.add(Fp.add(a, a), a));
|
deepStrictEqual(Fp.mul(a, 3n), Fp.add(Fp.add(a, a), a));
|
||||||
deepStrictEqual(Fp.mul(a, 4n), Fp.add(Fp.add(Fp.add(a, a), a), a));
|
deepStrictEqual(Fp.mul(a, 4n), Fp.add(Fp.add(Fp.add(a, a), a), a));
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
should(`${name} multiply/square equality`, () => {
|
should('multiply/square equality', () => {
|
||||||
fc.assert(
|
fc.assert(
|
||||||
fc.property(FC_BIGINT, (num) => {
|
fc.property(FC_BIGINT, (num) => {
|
||||||
const a = create(num);
|
const a = create(num);
|
||||||
deepStrictEqual(Fp.square(a), Fp.mul(a, a));
|
deepStrictEqual(Fp.square(a), Fp.mul(a, a));
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
should(`${name} multiply/pow equality`, () => {
|
should('multiply/pow equality', () => {
|
||||||
fc.assert(
|
fc.assert(
|
||||||
fc.property(FC_BIGINT, (num) => {
|
fc.property(FC_BIGINT, (num) => {
|
||||||
const a = create(num);
|
const a = create(num);
|
||||||
deepStrictEqual(Fp.pow(a, 0n), Fp.ONE);
|
deepStrictEqual(Fp.pow(a, 0n), Fp.ONE);
|
||||||
deepStrictEqual(Fp.pow(a, 1n), a);
|
deepStrictEqual(Fp.pow(a, 1n), a);
|
||||||
deepStrictEqual(Fp.pow(a, 2n), Fp.mul(a, a));
|
deepStrictEqual(Fp.pow(a, 2n), Fp.mul(a, a));
|
||||||
deepStrictEqual(Fp.pow(a, 3n), Fp.mul(Fp.mul(a, a), a));
|
deepStrictEqual(Fp.pow(a, 3n), Fp.mul(Fp.mul(a, a), a));
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
const isSquare = mod.FpIsSquare(Fp);
|
|
||||||
should(`${name} multiply/sqrt`, () => {
|
|
||||||
if (Fp === bls12_381.CURVE.Fp12) return; // Not implemented
|
|
||||||
fc.assert(
|
|
||||||
fc.property(FC_BIGINT, (num) => {
|
|
||||||
const a = create(num);
|
|
||||||
let root;
|
|
||||||
try {
|
|
||||||
root = Fp.sqrt(a);
|
|
||||||
} catch (e) {
|
|
||||||
deepStrictEqual(isSquare(a), false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
deepStrictEqual(isSquare(a), true);
|
|
||||||
deepStrictEqual(Fp.equals(Fp.square(root), a), true, 'sqrt(a)^2 == a');
|
|
||||||
deepStrictEqual(Fp.equals(Fp.square(Fp.negate(root)), a), true, '(-sqrt(a))^2 == a');
|
|
||||||
})
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
should(`${name} div/division by one equality`, () => {
|
should('square(0)', () => {
|
||||||
fc.assert(
|
deepStrictEqual(Fp.square(Fp.ZERO), Fp.ZERO);
|
||||||
fc.property(FC_BIGINT, (num) => {
|
deepStrictEqual(Fp.mul(Fp.ZERO, Fp.ZERO), Fp.ZERO);
|
||||||
const a = create(num);
|
});
|
||||||
if (Fp.equals(a, Fp.ZERO)) return; // No division by zero
|
|
||||||
deepStrictEqual(Fp.div(a, Fp.ONE), a);
|
should('square(1)', () => {
|
||||||
deepStrictEqual(Fp.div(a, a), Fp.ONE);
|
deepStrictEqual(Fp.square(Fp.ONE), Fp.ONE);
|
||||||
})
|
deepStrictEqual(Fp.mul(Fp.ONE, Fp.ONE), Fp.ONE);
|
||||||
);
|
});
|
||||||
});
|
|
||||||
should(`${name} zero division equality`, () => {
|
should('square(-1)', () => {
|
||||||
fc.assert(
|
const minus1 = Fp.negate(Fp.ONE);
|
||||||
fc.property(FC_BIGINT, (num) => {
|
deepStrictEqual(Fp.square(minus1), Fp.ONE);
|
||||||
const a = create(num);
|
deepStrictEqual(Fp.mul(minus1, minus1), Fp.ONE);
|
||||||
deepStrictEqual(Fp.div(Fp.ZERO, a), Fp.ZERO);
|
});
|
||||||
})
|
|
||||||
);
|
const isSquare = mod.FpIsSquare(Fp);
|
||||||
});
|
// Not implemented
|
||||||
should(`${name} div/division distributivity`, () => {
|
if (Fp !== bls12_381.CURVE.Fp12) {
|
||||||
fc.assert(
|
should('multiply/sqrt', () => {
|
||||||
fc.property(FC_BIGINT, FC_BIGINT, FC_BIGINT, (num1, num2, num3) => {
|
fc.assert(
|
||||||
const a = create(num1);
|
fc.property(FC_BIGINT, (num) => {
|
||||||
const b = create(num2);
|
const a = create(num);
|
||||||
const c = create(num3);
|
let root;
|
||||||
deepStrictEqual(Fp.div(Fp.add(a, b), c), Fp.add(Fp.div(a, c), Fp.div(b, c)));
|
try {
|
||||||
})
|
root = Fp.sqrt(a);
|
||||||
);
|
} catch (e) {
|
||||||
});
|
deepStrictEqual(isSquare(a), false);
|
||||||
should(`${name} div/division and multiplication equality`, () => {
|
return;
|
||||||
fc.assert(
|
}
|
||||||
fc.property(FC_BIGINT, FC_BIGINT, (num1, num2) => {
|
deepStrictEqual(isSquare(a), true);
|
||||||
const a = create(num1);
|
deepStrictEqual(Fp.equals(Fp.square(root), a), true, 'sqrt(a)^2 == a');
|
||||||
const b = create(num2);
|
deepStrictEqual(Fp.equals(Fp.square(Fp.negate(root)), a), true, '(-sqrt(a))^2 == a');
|
||||||
deepStrictEqual(Fp.div(a, b), Fp.mul(a, Fp.invert(b)));
|
})
|
||||||
})
|
);
|
||||||
);
|
});
|
||||||
|
|
||||||
|
should('sqrt(0)', () => {
|
||||||
|
deepStrictEqual(Fp.sqrt(Fp.ZERO), Fp.ZERO);
|
||||||
|
const sqrt1 = Fp.sqrt(Fp.ONE);
|
||||||
|
deepStrictEqual(
|
||||||
|
Fp.equals(sqrt1, Fp.ONE) || Fp.equals(sqrt1, Fp.negate(Fp.ONE)),
|
||||||
|
true,
|
||||||
|
'sqrt(1) = 1 or -1'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
should('div/division by one equality', () => {
|
||||||
|
fc.assert(
|
||||||
|
fc.property(FC_BIGINT, (num) => {
|
||||||
|
const a = create(num);
|
||||||
|
if (Fp.equals(a, Fp.ZERO)) return; // No division by zero
|
||||||
|
deepStrictEqual(Fp.div(a, Fp.ONE), a);
|
||||||
|
deepStrictEqual(Fp.div(a, a), Fp.ONE);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
should('zero division equality', () => {
|
||||||
|
fc.assert(
|
||||||
|
fc.property(FC_BIGINT, (num) => {
|
||||||
|
const a = create(num);
|
||||||
|
deepStrictEqual(Fp.div(Fp.ZERO, a), Fp.ZERO);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
should('div/division distributivity', () => {
|
||||||
|
fc.assert(
|
||||||
|
fc.property(FC_BIGINT, FC_BIGINT, FC_BIGINT, (num1, num2, num3) => {
|
||||||
|
const a = create(num1);
|
||||||
|
const b = create(num2);
|
||||||
|
const c = create(num3);
|
||||||
|
deepStrictEqual(Fp.div(Fp.add(a, b), c), Fp.add(Fp.div(a, c), Fp.div(b, c)));
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
should('div/division and multiplication equality', () => {
|
||||||
|
fc.assert(
|
||||||
|
fc.property(FC_BIGINT, FC_BIGINT, (num1, num2) => {
|
||||||
|
const a = create(num1);
|
||||||
|
const b = create(num2);
|
||||||
|
deepStrictEqual(Fp.div(a, b), Fp.mul(a, Fp.invert(b)));
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -278,12 +313,12 @@ const NUM_RUNS = 5;
|
|||||||
const getXY = (p) => ({ x: p.x, y: p.y });
|
const getXY = (p) => ({ x: p.x, y: p.y });
|
||||||
|
|
||||||
function equal(a, b, comment) {
|
function equal(a, b, comment) {
|
||||||
deepStrictEqual(a.equals(b), true, `eq(${comment})`);
|
deepStrictEqual(a.equals(b), true, 'eq(${comment})');
|
||||||
if (a.toAffine && b.toAffine) {
|
if (a.toAffine && b.toAffine) {
|
||||||
deepStrictEqual(getXY(a.toAffine()), getXY(b.toAffine()), `eqToAffine(${comment})`);
|
deepStrictEqual(getXY(a.toAffine()), getXY(b.toAffine()), 'eqToAffine(${comment})');
|
||||||
} else if (!a.toAffine && !b.toAffine) {
|
} else if (!a.toAffine && !b.toAffine) {
|
||||||
// Already affine
|
// Already affine
|
||||||
deepStrictEqual(getXY(a), getXY(b), `eqAffine(${comment})`);
|
deepStrictEqual(getXY(a), getXY(b), 'eqAffine(${comment})');
|
||||||
} else throw new Error('Different point types');
|
} else throw new Error('Different point types');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -308,253 +343,287 @@ for (const name in CURVES) {
|
|||||||
|
|
||||||
const G = [p.ZERO, p.BASE];
|
const G = [p.ZERO, p.BASE];
|
||||||
for (let i = 2; i < 10; i++) G.push(G[1].multiply(i));
|
for (let i = 2; i < 10; i++) G.push(G[1].multiply(i));
|
||||||
// Here we check basic group laws, to verify that points works as group
|
const title = `${name}/${pointName}`;
|
||||||
should(`${name}/${pointName}/Basic group laws (zero)`, () => {
|
describe(title, () => {
|
||||||
equal(G[0].double(), G[0], '(0*G).double() = 0');
|
describe('basic group laws', () => {
|
||||||
equal(G[0].add(G[0]), G[0], '0*G + 0*G = 0');
|
// Here we check basic group laws, to verify that points works as group
|
||||||
equal(G[0].subtract(G[0]), G[0], '0*G - 0*G = 0');
|
should('(zero)', () => {
|
||||||
equal(G[0].negate(), G[0], '-0 = 0');
|
equal(G[0].double(), G[0], '(0*G).double() = 0');
|
||||||
for (let i = 0; i < G.length; i++) {
|
equal(G[0].add(G[0]), G[0], '0*G + 0*G = 0');
|
||||||
const p = G[i];
|
equal(G[0].subtract(G[0]), G[0], '0*G - 0*G = 0');
|
||||||
equal(p, p.add(G[0]), `${i}*G + 0 = ${i}*G`);
|
equal(G[0].negate(), G[0], '-0 = 0');
|
||||||
equal(G[0].multiply(i + 1), G[0], `${i + 1}*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');
|
||||||
should(`${name}/${pointName}/Basic group laws (one)`, () => {
|
equal(G[0].multiply(i + 1), G[0], '${i + 1}*0 = 0');
|
||||||
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('(one)', () => {
|
||||||
});
|
equal(G[1].double(), G[2], '(1*G).double() = 2*G');
|
||||||
should(`${name}/${pointName}/Basic group laws (sanity tests)`, () => {
|
equal(G[1].subtract(G[1]), G[0], '1*G - 1*G = 0');
|
||||||
equal(G[2].double(), G[4], `(2*G).double() = 4*G`);
|
equal(G[1].add(G[1]), G[2], '1*G + 1*G = 2*G');
|
||||||
equal(G[2].add(G[2]), G[4], `2*G + 2*G = 4*G`);
|
});
|
||||||
equal(G[7].add(G[3].negate()), G[4], `7*G - 3*G = 4*G`);
|
should('(sanity tests)', () => {
|
||||||
});
|
equal(G[2].double(), G[4], '(2*G).double() = 4*G');
|
||||||
should(`${name}/${pointName}/Basic group laws (addition commutativity)`, () => {
|
equal(G[2].add(G[2]), G[4], '2*G + 2*G = 4*G');
|
||||||
equal(G[4].add(G[3]), G[3].add(G[4]), `4*G + 3*G = 3*G + 4*G`);
|
equal(G[7].add(G[3].negate()), G[4], '7*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('(addition commutativity)', () => {
|
||||||
should(`${name}/${pointName}/Basic group laws (double)`, () => {
|
equal(G[4].add(G[3]), G[3].add(G[4]), '4*G + 3*G = 3*G + 4*G');
|
||||||
equal(G[3].double(), G[6], '(3*G).double() = 6*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 (multiply)`, () => {
|
should('(double)', () => {
|
||||||
equal(G[2].multiply(3), G[6], '(2*G).multiply(3) = 6*G');
|
equal(G[3].double(), G[6], '(3*G).double() = 6*G');
|
||||||
});
|
});
|
||||||
should(`${name}/${pointName}/Basic group laws (same point addition)`, () => {
|
should('(multiply)', () => {
|
||||||
equal(G[3].add(G[3]), G[6], `3*G + 3*G = 6*G`);
|
equal(G[2].multiply(3), G[6], '(2*G).multiply(3) = 6*G');
|
||||||
});
|
});
|
||||||
should(`${name}/${pointName}/Basic group laws (same point (negative) addition)`, () => {
|
should('(same point addition)', () => {
|
||||||
equal(G[3].add(G[3].negate()), G[0], '3*G + (- 3*G) = 0*G');
|
equal(G[3].add(G[3]), G[6], '3*G + 3*G = 6*G');
|
||||||
equal(G[3].subtract(G[3]), G[0], '3*G - 3*G = 0*G');
|
});
|
||||||
});
|
should('(same point (negative) addition)', () => {
|
||||||
should(`${name}/${pointName}/Basic group laws (curve order)`, () => {
|
equal(G[3].add(G[3].negate()), G[0], '3*G + (- 3*G) = 0*G');
|
||||||
equal(G[1].multiply(CURVE_ORDER - 1n).add(G[1]), G[0], '(N-1)*G + G = 0');
|
equal(G[3].subtract(G[3]), G[0], '3*G - 3*G = 0*G');
|
||||||
equal(G[1].multiply(CURVE_ORDER - 1n).add(G[2]), G[1], '(N-1)*G + 2*G = 1*G');
|
});
|
||||||
equal(G[1].multiply(CURVE_ORDER - 2n).add(G[2]), G[0], '(N-2)*G + 2*G = 0');
|
should('(curve order)', () => {
|
||||||
const half = CURVE_ORDER / 2n;
|
equal(G[1].multiply(CURVE_ORDER - 1n).add(G[1]), G[0], '(N-1)*G + G = 0');
|
||||||
const carry = CURVE_ORDER % 2n === 1n ? G[1] : 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(half).double().add(carry), G[0], '((N/2) * G).double() = 0');
|
equal(G[1].multiply(CURVE_ORDER - 2n).add(G[2]), G[0], '(N-2)*G + 2*G = 0');
|
||||||
});
|
const half = CURVE_ORDER / 2n;
|
||||||
should(`${name}/${pointName}/Basic group laws (inversion)`, () => {
|
const carry = CURVE_ORDER % 2n === 1n ? G[1] : G[0];
|
||||||
const a = 1234n;
|
equal(G[1].multiply(half).double().add(carry), G[0], '((N/2) * G).double() = 0');
|
||||||
const b = 5678n;
|
});
|
||||||
const c = a * b;
|
should('(inversion)', () => {
|
||||||
equal(G[1].multiply(a).multiply(b), G[1].multiply(c), 'a*b*G = c*G');
|
const a = 1234n;
|
||||||
const inv = mod.invert(b, CURVE_ORDER);
|
const b = 5678n;
|
||||||
equal(G[1].multiply(c).multiply(inv), G[1].multiply(a), 'c*G * (1/b)*G = a*G');
|
const c = a * b;
|
||||||
});
|
equal(G[1].multiply(a).multiply(b), G[1].multiply(c), 'a*b*G = c*G');
|
||||||
should(`${name}/${pointName}/Basic group laws (multiply, rand)`, () =>
|
const inv = mod.invert(b, CURVE_ORDER);
|
||||||
fc.assert(
|
equal(G[1].multiply(c).multiply(inv), G[1].multiply(a), 'c*G * (1/b)*G = a*G');
|
||||||
fc.property(FC_BIGINT, FC_BIGINT, (a, b) => {
|
});
|
||||||
const c = mod.mod(a + b, CURVE_ORDER);
|
should('(multiply, rand)', () =>
|
||||||
if (c === CURVE_ORDER || c < 1n) return;
|
fc.assert(
|
||||||
const pA = G[1].multiply(a);
|
fc.property(FC_BIGINT, FC_BIGINT, (a, b) => {
|
||||||
const pB = G[1].multiply(b);
|
const c = mod.mod(a + b, CURVE_ORDER);
|
||||||
const pC = G[1].multiply(c);
|
if (c === CURVE_ORDER || c < 1n) return;
|
||||||
equal(pA.add(pB), pB.add(pA), `pA + pB = pB + pA`);
|
const pA = G[1].multiply(a);
|
||||||
equal(pA.add(pB), pC, `pA + pB = pC`);
|
const pB = G[1].multiply(b);
|
||||||
}),
|
const pC = G[1].multiply(c);
|
||||||
{ numRuns: NUM_RUNS }
|
equal(pA.add(pB), pB.add(pA), 'pA + pB = pB + pA');
|
||||||
)
|
equal(pA.add(pB), pC, 'pA + pB = pC');
|
||||||
);
|
}),
|
||||||
should(`${name}/${pointName}/Basic group laws (multiply2, rand)`, () =>
|
{ numRuns: NUM_RUNS }
|
||||||
fc.assert(
|
)
|
||||||
fc.property(FC_BIGINT, FC_BIGINT, (a, b) => {
|
);
|
||||||
const c = mod.mod(a * b, CURVE_ORDER);
|
should('(multiply2, rand)', () =>
|
||||||
const pA = G[1].multiply(a);
|
fc.assert(
|
||||||
const pB = G[1].multiply(b);
|
fc.property(FC_BIGINT, FC_BIGINT, (a, b) => {
|
||||||
equal(pA.multiply(b), pB.multiply(a), `b*pA = a*pB`);
|
const c = mod.mod(a * b, CURVE_ORDER);
|
||||||
equal(pA.multiply(b), G[1].multiply(c), `b*pA = c*G`);
|
const pA = G[1].multiply(a);
|
||||||
}),
|
const pB = G[1].multiply(b);
|
||||||
{ numRuns: NUM_RUNS }
|
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(''));
|
|
||||||
});
|
|
||||||
|
|
||||||
should(`${name}.verify()/should not verify signature with wrong hash`, () => {
|
for (const op of ['add', 'subtract']) {
|
||||||
const MSG = '01'.repeat(32);
|
describe(op, () => {
|
||||||
const PRIV_KEY = 0x2n;
|
should('type check', () => {
|
||||||
const WRONG_MSG = '11'.repeat(32);
|
throws(() => G[1][op](0), '0');
|
||||||
const signature = C.sign(MSG, PRIV_KEY);
|
throws(() => G[1][op](0n), '0n');
|
||||||
const publicKey = C.getPublicKey(PRIV_KEY);
|
G[1][op](G[2]);
|
||||||
deepStrictEqual(C.verify(signature, WRONG_MSG, publicKey), false);
|
throws(() => G[1][op](CURVE_ORDER), 'CURVE_ORDER');
|
||||||
});
|
throws(() => G[1][op](123.456), '123.456');
|
||||||
// NOTE: fails for ed, because of empty message. Since we convert it to scalar,
|
throws(() => G[1][op](true), 'true');
|
||||||
// need to check what other implementations do. Empty message != new Uint8Array([0]), but what scalar should be in that case?
|
throws(() => G[1][op]('1'), "'1'");
|
||||||
// should(`${name}/should not verify signature with wrong message`, () => {
|
throws(
|
||||||
// fc.assert(
|
() => G[1][op]({ x: 1n, y: 1n, z: 1n, t: 1n }),
|
||||||
// fc.property(
|
'{ x: 1n, y: 1n, z: 1n, t: 1n }'
|
||||||
// fc.array(fc.integer({ min: 0x00, max: 0xff })),
|
);
|
||||||
// fc.array(fc.integer({ min: 0x00, max: 0xff })),
|
throws(() => G[1][op](new Uint8Array([])), 'ui8a([])');
|
||||||
// (bytes, wrongBytes) => {
|
throws(() => G[1][op](new Uint8Array([0])), 'ui8a([0])');
|
||||||
// const privKey = C.utils.randomPrivateKey();
|
throws(() => G[1][op](new Uint8Array([1])), 'ui8a([1])');
|
||||||
// const message = new Uint8Array(bytes);
|
throws(() => G[1][op](new Uint8Array(4096).fill(1)), 'ui8a(4096*[1])');
|
||||||
// const wrongMessage = new Uint8Array(wrongBytes);
|
if (G[1].toAffine) throws(() => G[1][op](C.Point.BASE), 'Point ${op} ${pointName}');
|
||||||
// const publicKey = C.getPublicKey(privKey);
|
throws(() => G[1][op](o.BASE), '${op}/other curve point');
|
||||||
// const signature = C.sign(message, privKey);
|
});
|
||||||
// deepStrictEqual(
|
});
|
||||||
// C.verify(signature, wrongMessage, publicKey),
|
}
|
||||||
// bytes.toString() === wrongBytes.toString()
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
// ),
|
|
||||||
// { numRuns: NUM_RUNS }
|
|
||||||
// );
|
|
||||||
// });
|
|
||||||
|
|
||||||
if (C.getSharedSecret) {
|
should('equals type check', () => {
|
||||||
should(`${name}/getSharedSecret() should be commutative`, () => {
|
throws(() => G[1].equals(0), '0');
|
||||||
for (let i = 0; i < NUM_RUNS; i++) {
|
throws(() => G[1].equals(0n), '0n');
|
||||||
const asec = C.utils.randomPrivateKey();
|
deepStrictEqual(G[1].equals(G[2]), false, '1*G != 2*G');
|
||||||
const apub = C.getPublicKey(asec);
|
deepStrictEqual(G[1].equals(G[1]), true, '1*G == 1*G');
|
||||||
const bsec = C.utils.randomPrivateKey();
|
deepStrictEqual(G[2].equals(G[2]), true, '2*G == 2*G');
|
||||||
const bpub = C.getPublicKey(bsec);
|
throws(() => G[1].equals(CURVE_ORDER), 'CURVE_ORDER');
|
||||||
try {
|
throws(() => G[1].equals(123.456), '123.456');
|
||||||
deepStrictEqual(C.getSharedSecret(asec, bpub), C.getSharedSecret(bsec, apub));
|
throws(() => G[1].equals(true), 'true');
|
||||||
} catch (error) {
|
throws(() => G[1].equals('1'), "'1'");
|
||||||
console.error('not commutative', { asec, apub, bsec, bpub });
|
throws(() => G[1].equals({ x: 1n, y: 1n, z: 1n, t: 1n }), '{ x: 1n, y: 1n, z: 1n, t: 1n }');
|
||||||
throw error;
|
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;
|
||||||
|
describe(op, () => {
|
||||||
|
should('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('toAffine()', () => {
|
||||||
|
equal(p.ZERO.toAffine(), C.Point.ZERO, '0 = 0');
|
||||||
|
equal(p.BASE.toAffine(), C.Point.BASE, '1 = 1');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (p.fromAffine) {
|
||||||
|
should('fromAffine()', () => {
|
||||||
|
equal(p.ZERO, p.fromAffine(C.Point.ZERO), '0 = 0');
|
||||||
|
equal(p.BASE, p.fromAffine(C.Point.BASE), '1 = 1');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// toHex/fromHex (if available)
|
||||||
|
if (p.fromHex && p.BASE.toHex) {
|
||||||
|
should('fromHex(toHex()) roundtrip', () => {
|
||||||
|
fc.assert(
|
||||||
|
fc.property(FC_BIGINT, (x) => {
|
||||||
|
const hex = p.BASE.multiply(x).toHex();
|
||||||
|
deepStrictEqual(p.fromHex(hex).toHex(), hex);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
describe(name, () => {
|
||||||
|
// Generic complex things (getPublicKey/sign/verify/getSharedSecret)
|
||||||
|
should('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('.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,
|
||||||
|
'priv=${toHex(priv)},pub=${toHex(pub)},msg=${msg}'
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
{ numRuns: NUM_RUNS }
|
||||||
|
)
|
||||||
|
);
|
||||||
|
should('.sign() edge cases', () => {
|
||||||
|
throws(() => C.sign());
|
||||||
|
throws(() => C.sign(''));
|
||||||
|
});
|
||||||
|
|
||||||
|
should('.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('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 }
|
||||||
|
// );
|
||||||
|
// });
|
||||||
|
|
||||||
|
if (C.getSharedSecret) {
|
||||||
|
should('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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
should('secp224k1 sqrt bug', () => {
|
||||||
|
const { Fp } = secp224r1.CURVE;
|
||||||
|
const sqrtMinus1 = Fp.sqrt(-1n);
|
||||||
|
// Verified against sage
|
||||||
|
deepStrictEqual(
|
||||||
|
sqrtMinus1,
|
||||||
|
23621584063597419797792593680131996961517196803742576047493035507225n
|
||||||
|
);
|
||||||
|
deepStrictEqual(
|
||||||
|
Fp.negate(sqrtMinus1),
|
||||||
|
3338362603553219996874421406887633712040719456283732096017030791656n
|
||||||
|
);
|
||||||
|
deepStrictEqual(Fp.square(sqrtMinus1), Fp.create(-1n));
|
||||||
|
});
|
||||||
|
|
||||||
// ESM is broken.
|
// ESM is broken.
|
||||||
import url from 'url';
|
import url from 'url';
|
||||||
if (import.meta.url === url.pathToFileURL(process.argv[1]).href) {
|
if (import.meta.url === url.pathToFileURL(process.argv[1]).href) {
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
1269
test/ed25519.test.js
1269
test/ed25519.test.js
File diff suppressed because it is too large
Load Diff
1259
test/ed448.test.js
1259
test/ed448.test.js
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,5 @@
|
|||||||
import { deepStrictEqual } from 'assert';
|
import { deepStrictEqual } from 'assert';
|
||||||
import { should } from 'micro-should';
|
import { describe, should } from 'micro-should';
|
||||||
import { bytesToHex } from '@noble/hashes/utils';
|
import { bytesToHex } from '@noble/hashes/utils';
|
||||||
// Generic tests for all curves in package
|
// Generic tests for all curves in package
|
||||||
import { sha256 } from '@noble/hashes/sha256';
|
import { sha256 } from '@noble/hashes/sha256';
|
||||||
@@ -51,43 +51,51 @@ import { default as ed448_ro } from './hash-to-curve/edwards448_XOF:SHAKE256_ELL
|
|||||||
import { default as ed448_nu } from './hash-to-curve/edwards448_XOF:SHAKE256_ELL2_NU_.json' assert { type: 'json' };
|
import { default as ed448_nu } from './hash-to-curve/edwards448_XOF:SHAKE256_ELL2_NU_.json' assert { type: 'json' };
|
||||||
|
|
||||||
function testExpandXMD(hash, vectors) {
|
function testExpandXMD(hash, vectors) {
|
||||||
for (let i = 0; i < vectors.tests.length; i++) {
|
describe(`${vectors.hash}/${vectors.DST.length}`, () => {
|
||||||
const t = vectors.tests[i];
|
for (let i = 0; i < vectors.tests.length; i++) {
|
||||||
should(`expand_message_xmd/${vectors.hash}/${vectors.DST.length}/${i}`, () => {
|
const t = vectors.tests[i];
|
||||||
const p = expand_message_xmd(
|
should(`${vectors.hash}/${vectors.DST.length}/${i}`, () => {
|
||||||
stringToBytes(t.msg),
|
const p = expand_message_xmd(
|
||||||
stringToBytes(vectors.DST),
|
stringToBytes(t.msg),
|
||||||
t.len_in_bytes,
|
stringToBytes(vectors.DST),
|
||||||
hash
|
t.len_in_bytes,
|
||||||
);
|
hash
|
||||||
deepStrictEqual(bytesToHex(p), t.uniform_bytes);
|
);
|
||||||
});
|
deepStrictEqual(bytesToHex(p), t.uniform_bytes);
|
||||||
}
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
testExpandXMD(sha256, xmd_sha256_38);
|
describe('expand_message_xmd', () => {
|
||||||
testExpandXMD(sha256, xmd_sha256_256);
|
testExpandXMD(sha256, xmd_sha256_38);
|
||||||
testExpandXMD(sha512, xmd_sha512_38);
|
testExpandXMD(sha256, xmd_sha256_256);
|
||||||
|
testExpandXMD(sha512, xmd_sha512_38);
|
||||||
|
});
|
||||||
|
|
||||||
function testExpandXOF(hash, vectors) {
|
function testExpandXOF(hash, vectors) {
|
||||||
for (let i = 0; i < vectors.tests.length; i++) {
|
describe(`${vectors.hash}/${vectors.DST.length}`, () => {
|
||||||
const t = vectors.tests[i];
|
for (let i = 0; i < vectors.tests.length; i++) {
|
||||||
should(`expand_message_xof/${vectors.hash}/${vectors.DST.length}/${i}`, () => {
|
const t = vectors.tests[i];
|
||||||
const p = expand_message_xof(
|
should(`${i}`, () => {
|
||||||
stringToBytes(t.msg),
|
const p = expand_message_xof(
|
||||||
stringToBytes(vectors.DST),
|
stringToBytes(t.msg),
|
||||||
+t.len_in_bytes,
|
stringToBytes(vectors.DST),
|
||||||
vectors.k,
|
+t.len_in_bytes,
|
||||||
hash
|
vectors.k,
|
||||||
);
|
hash
|
||||||
deepStrictEqual(bytesToHex(p), t.uniform_bytes);
|
);
|
||||||
});
|
deepStrictEqual(bytesToHex(p), t.uniform_bytes);
|
||||||
}
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
testExpandXOF(shake128, xof_shake128_36);
|
describe('expand_message_xof', () => {
|
||||||
testExpandXOF(shake128, xof_shake128_256);
|
testExpandXOF(shake128, xof_shake128_36);
|
||||||
testExpandXOF(shake256, xof_shake256_36);
|
testExpandXOF(shake128, xof_shake128_256);
|
||||||
|
testExpandXOF(shake256, xof_shake256_36);
|
||||||
|
});
|
||||||
|
|
||||||
function stringToFp(s) {
|
function stringToFp(s) {
|
||||||
// bls-G2 support
|
// bls-G2 support
|
||||||
@@ -99,26 +107,30 @@ function stringToFp(s) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function testCurve(curve, ro, nu) {
|
function testCurve(curve, ro, nu) {
|
||||||
for (let i = 0; i < ro.vectors.length; i++) {
|
describe(`${ro.curve}/${ro.ciphersuite}`, () => {
|
||||||
const t = ro.vectors[i];
|
for (let i = 0; i < ro.vectors.length; i++) {
|
||||||
should(`${ro.curve}/${ro.ciphersuite}(${i})`, () => {
|
const t = ro.vectors[i];
|
||||||
const p = curve.Point.hashToCurve(stringToBytes(t.msg), {
|
should(`(${i})`, () => {
|
||||||
DST: ro.dst,
|
const p = curve.Point.hashToCurve(stringToBytes(t.msg), {
|
||||||
|
DST: ro.dst,
|
||||||
|
});
|
||||||
|
deepStrictEqual(p.x, stringToFp(t.P.x), 'Px');
|
||||||
|
deepStrictEqual(p.y, stringToFp(t.P.y), 'Py');
|
||||||
});
|
});
|
||||||
deepStrictEqual(p.x, stringToFp(t.P.x), 'Px');
|
}
|
||||||
deepStrictEqual(p.y, stringToFp(t.P.y), 'Py');
|
});
|
||||||
});
|
describe(`${nu.curve}/${nu.ciphersuite}`, () => {
|
||||||
}
|
for (let i = 0; i < nu.vectors.length; i++) {
|
||||||
for (let i = 0; i < nu.vectors.length; i++) {
|
const t = nu.vectors[i];
|
||||||
const t = nu.vectors[i];
|
should(`(${i})`, () => {
|
||||||
should(`${nu.curve}/${nu.ciphersuite}(${i})`, () => {
|
const p = curve.Point.encodeToCurve(stringToBytes(t.msg), {
|
||||||
const p = curve.Point.encodeToCurve(stringToBytes(t.msg), {
|
DST: nu.dst,
|
||||||
DST: nu.dst,
|
});
|
||||||
|
deepStrictEqual(p.x, stringToFp(t.P.x), 'Px');
|
||||||
|
deepStrictEqual(p.y, stringToFp(t.P.y), 'Py');
|
||||||
});
|
});
|
||||||
deepStrictEqual(p.x, stringToFp(t.P.x), 'Px');
|
}
|
||||||
deepStrictEqual(p.y, stringToFp(t.P.y), 'Py');
|
});
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
testCurve(secp256r1, p256_ro, p256_nu);
|
testCurve(secp256r1, p256_ro, p256_nu);
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { jubjub, findGroupHash } from '../lib/esm/jubjub.js';
|
import { jubjub, findGroupHash } from '../lib/esm/jubjub.js';
|
||||||
import { should } from 'micro-should';
|
import { describe, should } from 'micro-should';
|
||||||
import { deepStrictEqual, throws } from 'assert';
|
import { deepStrictEqual, throws } from 'assert';
|
||||||
import { hexToBytes, bytesToHex } from '@noble/hashes/utils';
|
import { hexToBytes, bytesToHex } from '@noble/hashes/utils';
|
||||||
|
|
||||||
@@ -18,53 +18,61 @@ const G_PROOF = new jubjub.ExtendedPoint(
|
|||||||
|
|
||||||
const getXY = (p) => ({ x: p.x, y: p.y });
|
const getXY = (p) => ({ x: p.x, y: p.y });
|
||||||
|
|
||||||
should('toHex/fromHex', () => {
|
describe('jubjub', () => {
|
||||||
// More than field
|
should('toHex/fromHex', () => {
|
||||||
throws(() =>
|
// More than field
|
||||||
jubjub.Point.fromHex(
|
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([
|
new Uint8Array([
|
||||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
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,
|
||||||
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,
|
0,
|
||||||
])
|
])
|
||||||
)
|
);
|
||||||
);
|
deepStrictEqual(tmp.x, 0x8d51ccce760304d0ec030002760300000001000000000000n);
|
||||||
const tmp = jubjub.Point.fromHex(
|
deepStrictEqual(tmp.y, 0n);
|
||||||
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 S = G_SPEND.toAffine().toRawBytes();
|
||||||
const S2 = G_SPEND.double().toAffine().toRawBytes();
|
const S2 = G_SPEND.double().toAffine().toRawBytes();
|
||||||
const P = G_PROOF.toAffine().toRawBytes();
|
const P = G_PROOF.toAffine().toRawBytes();
|
||||||
const P2 = G_PROOF.double().toAffine().toRawBytes();
|
const P2 = G_PROOF.double().toAffine().toRawBytes();
|
||||||
const S_exp = jubjub.Point.fromHex(S);
|
const S_exp = jubjub.Point.fromHex(S);
|
||||||
const S2_exp = jubjub.Point.fromHex(S2);
|
const S2_exp = jubjub.Point.fromHex(S2);
|
||||||
const P_exp = jubjub.Point.fromHex(P);
|
const P_exp = jubjub.Point.fromHex(P);
|
||||||
const P2_exp = jubjub.Point.fromHex(P2);
|
const P2_exp = jubjub.Point.fromHex(P2);
|
||||||
deepStrictEqual(getXY(G_SPEND.toAffine()), getXY(S_exp));
|
deepStrictEqual(getXY(G_SPEND.toAffine()), getXY(S_exp));
|
||||||
deepStrictEqual(getXY(G_SPEND.double().toAffine()), getXY(S2_exp));
|
deepStrictEqual(getXY(G_SPEND.double().toAffine()), getXY(S2_exp));
|
||||||
deepStrictEqual(getXY(G_PROOF.toAffine()), getXY(P_exp));
|
deepStrictEqual(getXY(G_PROOF.toAffine()), getXY(P_exp));
|
||||||
deepStrictEqual(getXY(G_PROOF.double().toAffine()), getXY(P2_exp));
|
deepStrictEqual(getXY(G_PROOF.double().toAffine()), getXY(P2_exp));
|
||||||
});
|
});
|
||||||
|
|
||||||
should('Find generators', () => {
|
should('Find generators', () => {
|
||||||
const spend = findGroupHash(new Uint8Array(), new Uint8Array([90, 99, 97, 115, 104, 95, 71, 95]));
|
const spend = findGroupHash(
|
||||||
const proof = findGroupHash(new Uint8Array(), new Uint8Array([90, 99, 97, 115, 104, 95, 72, 95]));
|
new Uint8Array(),
|
||||||
deepStrictEqual(getXY(spend.toAffine()), getXY(G_SPEND.toAffine()));
|
new Uint8Array([90, 99, 97, 115, 104, 95, 71, 95])
|
||||||
deepStrictEqual(getXY(proof.toAffine()), getXY(G_PROOF.toAffine()));
|
);
|
||||||
|
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.
|
// ESM is broken.
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { deepStrictEqual, throws } from 'assert';
|
import { deepStrictEqual, throws } from 'assert';
|
||||||
import { should } from 'micro-should';
|
import { describe, should } from 'micro-should';
|
||||||
import { secp192r1, P192 } from '../lib/esm/p192.js';
|
import { secp192r1, P192 } from '../lib/esm/p192.js';
|
||||||
import { secp224r1, P224 } from '../lib/esm/p224.js';
|
import { secp224r1, P224 } from '../lib/esm/p224.js';
|
||||||
import { secp256r1, P256 } from '../lib/esm/p256.js';
|
import { secp256r1, P256 } from '../lib/esm/p256.js';
|
||||||
@@ -344,19 +344,21 @@ function runWycheproof(name, CURVE, group, index) {
|
|||||||
|
|
||||||
for (const name in WYCHEPROOF_ECDSA) {
|
for (const name in WYCHEPROOF_ECDSA) {
|
||||||
const { curve, hashes } = WYCHEPROOF_ECDSA[name];
|
const { curve, hashes } = WYCHEPROOF_ECDSA[name];
|
||||||
for (const hName in hashes) {
|
describe('Wycheproof/WYCHEPROOF_ECDSA', () => {
|
||||||
const { hash, tests } = hashes[hName];
|
for (const hName in hashes) {
|
||||||
const CURVE = curve.create(hash);
|
const { hash, tests } = hashes[hName];
|
||||||
should(`Wycheproof/WYCHEPROOF_ECDSA ${name}/${hName}`, () => {
|
const CURVE = curve.create(hash);
|
||||||
for (let i = 0; i < tests.length; i++) {
|
should(`${name}/${hName}`, () => {
|
||||||
const groups = tests[i].testGroups;
|
for (let i = 0; i < tests.length; i++) {
|
||||||
for (let j = 0; j < groups.length; j++) {
|
const groups = tests[i].testGroups;
|
||||||
const group = groups[j];
|
for (let j = 0; j < groups.length; j++) {
|
||||||
runWycheproof(name, CURVE, group, `${i}/${j}`);
|
const group = groups[j];
|
||||||
|
runWycheproof(name, CURVE, group, `${i}/${j}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
});
|
}
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const hexToBigint = (hex) => BigInt(`0x${hex}`);
|
const hexToBigint = (hex) => BigInt(`0x${hex}`);
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user