@noble/curves
Advanced tools
Comparing version 0.5.2 to 0.6.0
@@ -35,9 +35,4 @@ import { randomBytes } from '@noble/hashes/utils'; | ||
} | undefined; | ||
readonly isTorsionFree?: ((c: import("./abstract/weierstrass.js").ProjectiveConstructor<bigint>, point: import("./abstract/weierstrass.js").ProjectivePointType<bigint>) => boolean) | undefined; | ||
readonly clearCofactor?: ((c: import("./abstract/weierstrass.js").ProjectiveConstructor<bigint>, point: import("./abstract/weierstrass.js").ProjectivePointType<bigint>) => import("./abstract/weierstrass.js").ProjectivePointType<bigint>) | undefined; | ||
readonly htfDefaults?: import("./abstract/hash-to-curve.js").htfOpts | undefined; | ||
readonly mapToCurve?: ((scalar: bigint[]) => { | ||
x: bigint; | ||
y: bigint; | ||
}) | undefined; | ||
readonly isTorsionFree?: ((c: import("./abstract/weierstrass.js").ProjConstructor<bigint>, point: import("./abstract/weierstrass.js").ProjPointType<bigint>) => boolean) | undefined; | ||
readonly clearCofactor?: ((c: import("./abstract/weierstrass.js").ProjConstructor<bigint>, point: import("./abstract/weierstrass.js").ProjPointType<bigint>) => import("./abstract/weierstrass.js").ProjPointType<bigint>) | undefined; | ||
lowS: boolean; | ||
@@ -47,22 +42,16 @@ readonly hash: CHash; | ||
readonly randomBytes: (bytesLength?: number | undefined) => Uint8Array; | ||
readonly truncateHash?: ((hash: Uint8Array, truncateOnly?: boolean | undefined) => bigint) | undefined; | ||
readonly bits2int?: ((bytes: Uint8Array) => bigint) | undefined; | ||
readonly bits2int_modN?: ((bytes: Uint8Array) => bigint) | undefined; | ||
}>; | ||
getPublicKey: (privateKey: import("./abstract/utils.js").PrivKey, isCompressed?: boolean | undefined) => Uint8Array; | ||
getSharedSecret: (privateA: import("./abstract/utils.js").PrivKey, publicB: import("./abstract/weierstrass.js").PubKey, isCompressed?: boolean | undefined) => Uint8Array; | ||
getSharedSecret: (privateA: import("./abstract/utils.js").PrivKey, publicB: import("./abstract/utils.js").Hex, isCompressed?: boolean | undefined) => Uint8Array; | ||
sign: (msgHash: import("./abstract/utils.js").Hex, privKey: import("./abstract/utils.js").PrivKey, opts?: import("./abstract/weierstrass.js").SignOpts | undefined) => import("./abstract/weierstrass.js").SignatureType; | ||
signUnhashed: (msg: Uint8Array, privKey: import("./abstract/utils.js").PrivKey, opts?: import("./abstract/weierstrass.js").SignOpts | undefined) => import("./abstract/weierstrass.js").SignatureType; | ||
verify: (signature: import("./abstract/utils.js").Hex | import("./abstract/weierstrass.js").SignatureType, msgHash: import("./abstract/utils.js").Hex, publicKey: import("./abstract/weierstrass.js").PubKey, opts?: { | ||
lowS?: boolean | undefined; | ||
} | undefined) => boolean; | ||
Point: import("./abstract/weierstrass.js").PointConstructor<bigint>; | ||
ProjectivePoint: import("./abstract/weierstrass.js").ProjectiveConstructor<bigint>; | ||
verify: (signature: import("./abstract/utils.js").Hex | { | ||
r: bigint; | ||
s: bigint; | ||
}, msgHash: import("./abstract/utils.js").Hex, publicKey: import("./abstract/utils.js").Hex, opts?: import("./abstract/weierstrass.js").VerOpts | undefined) => boolean; | ||
ProjectivePoint: import("./abstract/weierstrass.js").ProjConstructor<bigint>; | ||
Signature: import("./abstract/weierstrass.js").SignatureConstructor; | ||
utils: { | ||
_bigintToBytes: (num: bigint) => Uint8Array; | ||
_bigintToString: (num: bigint) => string; | ||
_normalizePrivateKey: (key: import("./abstract/utils.js").PrivKey) => bigint; | ||
_normalizePublicKey: (publicKey: import("./abstract/weierstrass.js").PubKey) => import("./abstract/weierstrass.js").PointType<bigint>; | ||
_isWithinCurveOrder: (num: bigint) => boolean; | ||
_isValidFieldElement: (num: bigint) => boolean; | ||
_weierstrassEquation: (x: bigint) => bigint; | ||
isValidPrivateKey(privateKey: import("./abstract/utils.js").PrivKey): boolean; | ||
@@ -69,0 +58,0 @@ hashToPrivateKey: (hash: import("./abstract/utils.js").Hex) => Uint8Array; |
@@ -14,22 +14,27 @@ /*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */ | ||
*/ | ||
import * as mod from './modular.js'; | ||
import * as ut from './utils.js'; | ||
import { Hex, PrivKey } from './utils.js'; | ||
import { htfOpts, stringToBytes, hash_to_field as hashToField, expand_message_xmd as expandMessageXMD } from './hash-to-curve.js'; | ||
import { CurvePointsType, PointType, CurvePointsRes } from './weierstrass.js'; | ||
import { AffinePoint } from './curve.js'; | ||
import { Field } from './modular.js'; | ||
import { Hex, PrivKey, CHash } from './utils.js'; | ||
import * as htf from './hash-to-curve.js'; | ||
import { CurvePointsType, ProjPointType as ProjPointType, CurvePointsRes } from './weierstrass.js'; | ||
declare type Fp = bigint; | ||
export declare type SignatureCoder<Fp2> = { | ||
decode(hex: Hex): PointType<Fp2>; | ||
encode(point: PointType<Fp2>): Uint8Array; | ||
decode(hex: Hex): ProjPointType<Fp2>; | ||
encode(point: ProjPointType<Fp2>): Uint8Array; | ||
}; | ||
export declare type CurveType<Fp, Fp2, Fp6, Fp12> = { | ||
r: bigint; | ||
G1: Omit<CurvePointsType<Fp>, 'n'>; | ||
G1: Omit<CurvePointsType<Fp>, 'n'> & { | ||
mapToCurve: htf.MapToCurve<Fp>; | ||
htfDefaults: htf.Opts; | ||
}; | ||
G2: Omit<CurvePointsType<Fp2>, 'n'> & { | ||
Signature: SignatureCoder<Fp2>; | ||
mapToCurve: htf.MapToCurve<Fp2>; | ||
htfDefaults: htf.Opts; | ||
}; | ||
x: bigint; | ||
Fp: mod.Field<Fp>; | ||
Fr: mod.Field<bigint>; | ||
Fp2: mod.Field<Fp2> & { | ||
Fp: Field<Fp>; | ||
Fr: Field<bigint>; | ||
Fp2: Field<Fp2> & { | ||
reim: (num: Fp2) => { | ||
@@ -42,4 +47,4 @@ re: bigint; | ||
}; | ||
Fp6: mod.Field<Fp6>; | ||
Fp12: mod.Field<Fp12> & { | ||
Fp6: Field<Fp6>; | ||
Fp12: Field<Fp12> & { | ||
frobeniusMap(num: Fp12, power: number): Fp12; | ||
@@ -50,4 +55,4 @@ multiplyBy014(num: Fp12, o0: Fp2, o1: Fp2, o4: Fp2): Fp12; | ||
}; | ||
htfDefaults: htfOpts; | ||
hash: ut.CHash; | ||
htfDefaults: htf.Opts; | ||
hash: CHash; | ||
randomBytes: (bytesLength?: number) => Uint8Array; | ||
@@ -57,7 +62,7 @@ }; | ||
CURVE: CurveType<Fp, Fp2, Fp6, Fp12>; | ||
Fr: mod.Field<bigint>; | ||
Fp: mod.Field<Fp>; | ||
Fp2: mod.Field<Fp2>; | ||
Fp6: mod.Field<Fp6>; | ||
Fp12: mod.Field<Fp12>; | ||
Fr: Field<bigint>; | ||
Fp: Field<Fp>; | ||
Fp2: Field<Fp2>; | ||
Fp6: Field<Fp6>; | ||
Fp12: Field<Fp12>; | ||
G1: CurvePointsRes<Fp>; | ||
@@ -67,25 +72,27 @@ G2: CurvePointsRes<Fp2>; | ||
millerLoop: (ell: [Fp2, Fp2, Fp2][], g1: [Fp, Fp]) => Fp12; | ||
calcPairingPrecomputes: (x: Fp2, y: Fp2) => [Fp2, Fp2, Fp2][]; | ||
pairing: (P: PointType<Fp>, Q: PointType<Fp2>, withFinalExponent?: boolean) => Fp12; | ||
calcPairingPrecomputes: (p: AffinePoint<Fp2>) => [Fp2, Fp2, Fp2][]; | ||
hashToCurve: { | ||
G1: ReturnType<(typeof htf.hashToCurve<Fp>)>; | ||
G2: ReturnType<(typeof htf.hashToCurve<Fp2>)>; | ||
}; | ||
pairing: (P: ProjPointType<Fp>, Q: ProjPointType<Fp2>, withFinalExponent?: boolean) => Fp12; | ||
getPublicKey: (privateKey: PrivKey) => Uint8Array; | ||
sign: { | ||
(message: Hex, privateKey: PrivKey): Uint8Array; | ||
(message: PointType<Fp2>, privateKey: PrivKey): PointType<Fp2>; | ||
(message: ProjPointType<Fp2>, privateKey: PrivKey): ProjPointType<Fp2>; | ||
}; | ||
verify: (signature: Hex | PointType<Fp2>, message: Hex | PointType<Fp2>, publicKey: Hex | PointType<Fp>) => boolean; | ||
verify: (signature: Hex | ProjPointType<Fp2>, message: Hex | ProjPointType<Fp2>, publicKey: Hex | ProjPointType<Fp>) => boolean; | ||
aggregatePublicKeys: { | ||
(publicKeys: Hex[]): Uint8Array; | ||
(publicKeys: PointType<Fp>[]): PointType<Fp>; | ||
(publicKeys: ProjPointType<Fp>[]): ProjPointType<Fp>; | ||
}; | ||
aggregateSignatures: { | ||
(signatures: Hex[]): Uint8Array; | ||
(signatures: PointType<Fp2>[]): PointType<Fp2>; | ||
(signatures: ProjPointType<Fp2>[]): ProjPointType<Fp2>; | ||
}; | ||
verifyBatch: (signature: Hex | PointType<Fp2>, messages: (Hex | PointType<Fp2>)[], publicKeys: (Hex | PointType<Fp>)[]) => boolean; | ||
verifyBatch: (signature: Hex | ProjPointType<Fp2>, messages: (Hex | ProjPointType<Fp2>)[], publicKeys: (Hex | ProjPointType<Fp>)[]) => boolean; | ||
utils: { | ||
stringToBytes: typeof stringToBytes; | ||
hashToField: typeof hashToField; | ||
expandMessageXMD: typeof expandMessageXMD; | ||
getDSTLabel: () => string; | ||
setDSTLabel(newLabel: string): void; | ||
stringToBytes: typeof htf.stringToBytes; | ||
hashToField: typeof htf.hash_to_field; | ||
expandMessageXMD: typeof htf.expand_message_xmd; | ||
}; | ||
@@ -92,0 +99,0 @@ }; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.bls = void 0; | ||
const ut = require("./utils.js"); | ||
const hash_to_curve_js_1 = require("./hash-to-curve.js"); | ||
const modular_js_1 = require("./modular.js"); | ||
const utils_js_1 = require("./utils.js"); | ||
const htf = require("./hash-to-curve.js"); | ||
const weierstrass_js_1 = require("./weierstrass.js"); | ||
@@ -10,7 +11,8 @@ function bls(CURVE) { | ||
const { Fp, Fr, Fp2, Fp6, Fp12 } = CURVE; | ||
const BLS_X_LEN = ut.bitLen(CURVE.x); | ||
const BLS_X_LEN = (0, utils_js_1.bitLen)(CURVE.x); | ||
const groupLen = 32; // TODO: calculate; hardcoded for now | ||
// Pre-compute coefficients for sparse multiplication | ||
// Point addition and point double calculations is reused for coefficients | ||
function calcPairingPrecomputes(x, y) { | ||
function calcPairingPrecomputes(p) { | ||
const { x, y } = p; | ||
// prettier-ignore | ||
@@ -23,16 +25,16 @@ const Qx = x, Qy = y, Qz = Fp2.ONE; | ||
// Double | ||
let t0 = Fp2.square(Ry); // Ry² | ||
let t1 = Fp2.square(Rz); // Rz² | ||
let t0 = Fp2.sqr(Ry); // Ry² | ||
let t1 = Fp2.sqr(Rz); // Rz² | ||
let t2 = Fp2.multiplyByB(Fp2.mul(t1, 3n)); // 3 * T1 * B | ||
let t3 = Fp2.mul(t2, 3n); // 3 * T2 | ||
let t4 = Fp2.sub(Fp2.sub(Fp2.square(Fp2.add(Ry, Rz)), t1), t0); // (Ry + Rz)² - T1 - T0 | ||
let t4 = Fp2.sub(Fp2.sub(Fp2.sqr(Fp2.add(Ry, Rz)), t1), t0); // (Ry + Rz)² - T1 - T0 | ||
ell_coeff.push([ | ||
Fp2.sub(t2, t0), | ||
Fp2.mul(Fp2.square(Rx), 3n), | ||
Fp2.negate(t4), // -T4 | ||
Fp2.mul(Fp2.sqr(Rx), 3n), | ||
Fp2.neg(t4), // -T4 | ||
]); | ||
Rx = Fp2.div(Fp2.mul(Fp2.mul(Fp2.sub(t0, t3), Rx), Ry), 2n); // ((T0 - T3) * Rx * Ry) / 2 | ||
Ry = Fp2.sub(Fp2.square(Fp2.div(Fp2.add(t0, t3), 2n)), Fp2.mul(Fp2.square(t2), 3n)); // ((T0 + T3) / 2)² - 3 * T2² | ||
Ry = Fp2.sub(Fp2.sqr(Fp2.div(Fp2.add(t0, t3), 2n)), Fp2.mul(Fp2.sqr(t2), 3n)); // ((T0 + T3) / 2)² - 3 * T2² | ||
Rz = Fp2.mul(t0, t4); // T0 * T4 | ||
if (ut.bitGet(CURVE.x, i)) { | ||
if ((0, utils_js_1.bitGet)(CURVE.x, i)) { | ||
// Addition | ||
@@ -43,9 +45,9 @@ let t0 = Fp2.sub(Ry, Fp2.mul(Qy, Rz)); // Ry - Qy * Rz | ||
Fp2.sub(Fp2.mul(t0, Qx), Fp2.mul(t1, Qy)), | ||
Fp2.negate(t0), | ||
Fp2.neg(t0), | ||
t1, // T1 | ||
]); | ||
let t2 = Fp2.square(t1); // T1² | ||
let t2 = Fp2.sqr(t1); // T1² | ||
let t3 = Fp2.mul(t2, t1); // T2 * T1 | ||
let t4 = Fp2.mul(t2, Rx); // T2 * Rx | ||
let t5 = Fp2.add(Fp2.sub(t3, Fp2.mul(t4, 2n)), Fp2.mul(Fp2.square(t0), Rz)); // T3 - 2 * T4 + T0² * Rz | ||
let t5 = Fp2.add(Fp2.sub(t3, Fp2.mul(t4, 2n)), Fp2.mul(Fp2.sqr(t0), Rz)); // T3 - 2 * T4 + T0² * Rz | ||
Rx = Fp2.mul(t1, t5); // T1 * T5 | ||
@@ -66,3 +68,3 @@ Ry = Fp2.sub(Fp2.mul(Fp2.sub(t4, t5), t0), Fp2.mul(t3, Ry)); // (T4 - T5) * T0 - T3 * Ry | ||
f12 = Fp12.multiplyBy014(f12, E[0], Fp2.mul(E[1], Px), Fp2.mul(E[2], Py)); | ||
if (ut.bitGet(x, i)) { | ||
if ((0, utils_js_1.bitGet)(x, i)) { | ||
j += 1; | ||
@@ -73,3 +75,3 @@ const F = ell[j]; | ||
if (i !== 0) | ||
f12 = Fp12.square(f12); | ||
f12 = Fp12.sqr(f12); | ||
} | ||
@@ -79,19 +81,11 @@ return Fp12.conjugate(f12); | ||
const utils = { | ||
hexToBytes: ut.hexToBytes, | ||
bytesToHex: ut.bytesToHex, | ||
stringToBytes: hash_to_curve_js_1.stringToBytes, | ||
hexToBytes: utils_js_1.hexToBytes, | ||
bytesToHex: utils_js_1.bytesToHex, | ||
stringToBytes: htf.stringToBytes, | ||
// TODO: do we need to export it here? | ||
hashToField: (msg, count, options = {}) => (0, hash_to_curve_js_1.hash_to_field)(msg, count, { ...CURVE.htfDefaults, ...options }), | ||
expandMessageXMD: (msg, DST, lenInBytes, H = CURVE.hash) => (0, hash_to_curve_js_1.expand_message_xmd)(msg, DST, lenInBytes, H), | ||
hashToPrivateKey: (hash) => Fr.toBytes(ut.hashToPrivateScalar(hash, CURVE.r)), | ||
hashToField: (msg, count, options = {}) => htf.hash_to_field(msg, count, { ...CURVE.htfDefaults, ...options }), | ||
expandMessageXMD: (msg, DST, lenInBytes, H = CURVE.hash) => htf.expand_message_xmd(msg, DST, lenInBytes, H), | ||
hashToPrivateKey: (hash) => Fr.toBytes((0, modular_js_1.hashToPrivateScalar)(hash, CURVE.r)), | ||
randomBytes: (bytesLength = groupLen) => CURVE.randomBytes(bytesLength), | ||
randomPrivateKey: () => utils.hashToPrivateKey(utils.randomBytes(groupLen + 8)), | ||
getDSTLabel: () => CURVE.htfDefaults.DST, | ||
setDSTLabel(newLabel) { | ||
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-3.1 | ||
if (typeof newLabel !== 'string' || newLabel.length > 2048 || newLabel.length === 0) { | ||
throw new TypeError('Invalid DST'); | ||
} | ||
CURVE.htfDefaults.DST = newLabel; | ||
}, | ||
}; | ||
@@ -103,2 +97,6 @@ // Point on G1 curve: (x, y) | ||
}); | ||
const G1HashToCurve = htf.hashToCurve(G1.ProjectivePoint, CURVE.G1.mapToCurve, { | ||
...CURVE.htfDefaults, | ||
...CURVE.G1.htfDefaults, | ||
}); | ||
function pairingPrecomputes(point) { | ||
@@ -108,13 +106,10 @@ const p = point; | ||
return p._PPRECOMPUTES; | ||
p._PPRECOMPUTES = calcPairingPrecomputes(p.x, p.y); | ||
p._PPRECOMPUTES = calcPairingPrecomputes(point.toAffine()); | ||
return p._PPRECOMPUTES; | ||
} | ||
function clearPairingPrecomputes(point) { | ||
const p = point; | ||
p._PPRECOMPUTES = undefined; | ||
} | ||
clearPairingPrecomputes; | ||
function millerLoopG1(Q, P) { | ||
return millerLoop(pairingPrecomputes(P), [Q.x, Q.y]); | ||
} | ||
// TODO: export | ||
// function clearPairingPrecomputes(point: G2) { | ||
// const p = point as G2 & withPairingPrecomputes; | ||
// p._PPRECOMPUTES = undefined; | ||
// } | ||
// Point on G2 curve (complex numbers): (x₁, x₂+i), (y₁, y₂+i) | ||
@@ -125,21 +120,29 @@ const G2 = (0, weierstrass_js_1.weierstrassPoints)({ | ||
}); | ||
const C = G2.ProjectivePoint; // TODO: fix | ||
const G2HashToCurve = htf.hashToCurve(C, CURVE.G2.mapToCurve, { | ||
...CURVE.htfDefaults, | ||
...CURVE.G2.htfDefaults, | ||
}); | ||
const { Signature } = CURVE.G2; | ||
// Calculates bilinear pairing | ||
function pairing(P, Q, withFinalExponent = true) { | ||
if (P.equals(G1.Point.ZERO) || Q.equals(G2.Point.ZERO)) | ||
throw new Error('No pairings at point of Infinity'); | ||
function pairing(Q, P, withFinalExponent = true) { | ||
if (Q.equals(G1.ProjectivePoint.ZERO) || P.equals(G2.ProjectivePoint.ZERO)) | ||
throw new Error('pairing is not available for ZERO point'); | ||
Q.assertValidity(); | ||
P.assertValidity(); | ||
Q.assertValidity(); | ||
// Performance: 9ms for millerLoop and ~14ms for exp. | ||
const looped = millerLoopG1(P, Q); | ||
const Qa = Q.toAffine(); | ||
const looped = millerLoop(pairingPrecomputes(P), [Qa.x, Qa.y]); | ||
return withFinalExponent ? Fp12.finalExponentiate(looped) : looped; | ||
} | ||
function normP1(point) { | ||
return point instanceof G1.Point ? point : G1.Point.fromHex(point); | ||
return point instanceof G1.ProjectivePoint ? point : G1.ProjectivePoint.fromHex(point); | ||
} | ||
function normP2(point) { | ||
return point instanceof G2.Point ? point : Signature.decode(point); | ||
return point instanceof G2.ProjectivePoint ? point : Signature.decode(point); | ||
} | ||
function normP2Hash(point) { | ||
return point instanceof G2.Point ? point : G2.Point.hashToCurve(point); | ||
function normP2Hash(point, htfOpts) { | ||
return point instanceof G2.ProjectivePoint | ||
? point | ||
: G2HashToCurve.hashToCurve(point, htfOpts); | ||
} | ||
@@ -149,9 +152,9 @@ // Multiplies generator by private key. | ||
function getPublicKey(privateKey) { | ||
return G1.Point.fromPrivateKey(privateKey).toRawBytes(true); | ||
return G1.ProjectivePoint.fromPrivateKey(privateKey).toRawBytes(true); | ||
} | ||
function sign(message, privateKey) { | ||
const msgPoint = normP2Hash(message); | ||
function sign(message, privateKey, htfOpts) { | ||
const msgPoint = normP2Hash(message, htfOpts); | ||
msgPoint.assertValidity(); | ||
const sigPoint = msgPoint.multiply(G1.normalizePrivateKey(privateKey)); | ||
if (message instanceof G2.Point) | ||
if (message instanceof G2.ProjectivePoint) | ||
return sigPoint; | ||
@@ -162,6 +165,6 @@ return Signature.encode(sigPoint); | ||
// e(P, H(m)) == e(G, S) | ||
function verify(signature, message, publicKey) { | ||
function verify(signature, message, publicKey, htfOpts) { | ||
const P = normP1(publicKey); | ||
const Hm = normP2Hash(message); | ||
const G = G1.Point.BASE; | ||
const Hm = normP2Hash(message, htfOpts); | ||
const G = G1.ProjectivePoint.BASE; | ||
const S = normP2(signature); | ||
@@ -173,3 +176,3 @@ // Instead of doing 2 exponentiations, we use property of billinear maps | ||
const exp = Fp12.finalExponentiate(Fp12.mul(eGS, ePHm)); | ||
return Fp12.equals(exp, Fp12.ONE); | ||
return Fp12.eql(exp, Fp12.ONE); | ||
} | ||
@@ -179,7 +182,5 @@ function aggregatePublicKeys(publicKeys) { | ||
throw new Error('Expected non-empty array'); | ||
const agg = publicKeys | ||
.map(normP1) | ||
.reduce((sum, p) => sum.add(G1.ProjectivePoint.fromAffine(p)), G1.ProjectivePoint.ZERO); | ||
const aggAffine = agg.toAffine(); | ||
if (publicKeys[0] instanceof G1.Point) { | ||
const agg = publicKeys.map(normP1).reduce((sum, p) => sum.add(p), G1.ProjectivePoint.ZERO); | ||
const aggAffine = agg; //.toAffine(); | ||
if (publicKeys[0] instanceof G1.ProjectivePoint) { | ||
aggAffine.assertValidity(); | ||
@@ -194,7 +195,5 @@ return aggAffine; | ||
throw new Error('Expected non-empty array'); | ||
const agg = signatures | ||
.map(normP2) | ||
.reduce((sum, s) => sum.add(G2.ProjectivePoint.fromAffine(s)), G2.ProjectivePoint.ZERO); | ||
const aggAffine = agg.toAffine(); | ||
if (signatures[0] instanceof G2.Point) { | ||
const agg = signatures.map(normP2).reduce((sum, s) => sum.add(s), G2.ProjectivePoint.ZERO); | ||
const aggAffine = agg; //.toAffine(); | ||
if (signatures[0] instanceof G2.ProjectivePoint) { | ||
aggAffine.assertValidity(); | ||
@@ -207,3 +206,5 @@ return aggAffine; | ||
// e(G, S) = e(G, SUM(n)(Si)) = MUL(n)(e(G, Si)) | ||
function verifyBatch(signature, messages, publicKeys) { | ||
function verifyBatch(signature, messages, publicKeys, htfOpts) { | ||
// @ts-ignore | ||
// console.log('verifyBatch', bytesToHex(signature as any), messages, publicKeys.map(bytesToHex)); | ||
if (!messages.length) | ||
@@ -214,3 +215,3 @@ throw new Error('Expected non-empty messages array'); | ||
const sig = normP2(signature); | ||
const nMessages = messages.map(normP2Hash); | ||
const nMessages = messages.map((i) => normP2Hash(i, htfOpts)); | ||
const nPublicKeys = publicKeys.map(normP1); | ||
@@ -220,3 +221,3 @@ try { | ||
for (const message of new Set(nMessages)) { | ||
const groupPublicKey = nMessages.reduce((groupPublicKey, subMessage, i) => subMessage === message ? groupPublicKey.add(nPublicKeys[i]) : groupPublicKey, G1.Point.ZERO); | ||
const groupPublicKey = nMessages.reduce((groupPublicKey, subMessage, i) => subMessage === message ? groupPublicKey.add(nPublicKeys[i]) : groupPublicKey, G1.ProjectivePoint.ZERO); | ||
// const msg = message instanceof PointG2 ? message : await PointG2.hashToCurve(message); | ||
@@ -226,6 +227,6 @@ // Possible to batch pairing for same msg with different groupPublicKey here | ||
} | ||
paired.push(pairing(G1.Point.BASE.negate(), sig, false)); | ||
paired.push(pairing(G1.ProjectivePoint.BASE.negate(), sig, false)); | ||
const product = paired.reduce((a, b) => Fp12.mul(a, b), Fp12.ONE); | ||
const exp = Fp12.finalExponentiate(product); | ||
return Fp12.equals(exp, Fp12.ONE); | ||
return Fp12.eql(exp, Fp12.ONE); | ||
} | ||
@@ -236,4 +237,3 @@ catch { | ||
} | ||
// Pre-compute points. Refer to README. | ||
G1.Point.BASE._setWindowSize(4); | ||
G1.ProjectivePoint.BASE._setWindowSize(4); | ||
return { | ||
@@ -251,2 +251,3 @@ CURVE, | ||
calcPairingPrecomputes, | ||
hashToCurve: { G1: G1HashToCurve, G2: G2HashToCurve }, | ||
pairing, | ||
@@ -253,0 +254,0 @@ getPublicKey, |
@@ -1,11 +0,7 @@ | ||
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */ | ||
import * as mod from './modular.js'; | ||
import * as ut from './utils.js'; | ||
import { Hex, PrivKey } from './utils.js'; | ||
import { Group, GroupConstructor } from './group.js'; | ||
import { htfOpts } from './hash-to-curve.js'; | ||
export declare type CurveType = ut.BasicCurve<bigint> & { | ||
import { FHash, Hex } from './utils.js'; | ||
import { Group, GroupConstructor, AbstractCurve, AffinePoint } from './curve.js'; | ||
export declare type CurveType = AbstractCurve<bigint> & { | ||
a: bigint; | ||
d: bigint; | ||
hash: ut.CHash; | ||
hash: FHash; | ||
randomBytes: (bytesLength?: number) => Uint8Array; | ||
@@ -18,8 +14,4 @@ adjustScalarBytes?: (bytes: Uint8Array) => Uint8Array; | ||
}; | ||
preHash?: ut.CHash; | ||
htfDefaults?: htfOpts; | ||
mapToCurve?: (scalar: bigint[]) => { | ||
x: bigint; | ||
y: bigint; | ||
}; | ||
preHash?: FHash; | ||
mapToCurve?: (scalar: bigint[]) => AffinePoint<bigint>; | ||
}; | ||
@@ -29,3 +21,3 @@ declare function validateOpts(curve: CurveType): Readonly<{ | ||
readonly nByteLength: number; | ||
readonly Fp: mod.Field<bigint>; | ||
readonly Fp: import("./modular.js").Field<bigint>; | ||
readonly n: bigint; | ||
@@ -40,3 +32,3 @@ readonly h: bigint; | ||
readonly d: bigint; | ||
readonly hash: ut.CHash; | ||
readonly hash: FHash; | ||
readonly randomBytes: (bytesLength?: number | undefined) => Uint8Array; | ||
@@ -49,71 +41,37 @@ readonly adjustScalarBytes?: ((bytes: Uint8Array) => Uint8Array) | undefined; | ||
}) | undefined; | ||
readonly preHash?: ut.CHash | undefined; | ||
readonly htfDefaults?: htfOpts | undefined; | ||
readonly mapToCurve?: ((scalar: bigint[]) => { | ||
x: bigint; | ||
y: bigint; | ||
}) | undefined; | ||
readonly preHash?: FHash | undefined; | ||
readonly mapToCurve?: ((scalar: bigint[]) => AffinePoint<bigint>) | undefined; | ||
}>; | ||
export interface SignatureType { | ||
readonly r: PointType; | ||
readonly s: bigint; | ||
assertValidity(): SignatureType; | ||
toRawBytes(): Uint8Array; | ||
toHex(): string; | ||
} | ||
export declare type SignatureConstructor = { | ||
new (r: PointType, s: bigint): SignatureType; | ||
fromHex(hex: Hex): SignatureType; | ||
}; | ||
export interface ExtendedPointType extends Group<ExtendedPointType> { | ||
readonly x: bigint; | ||
readonly y: bigint; | ||
readonly z: bigint; | ||
readonly t: bigint; | ||
multiply(scalar: number | bigint, affinePoint?: PointType): ExtendedPointType; | ||
multiplyUnsafe(scalar: number | bigint): ExtendedPointType; | ||
export interface ExtPointType extends Group<ExtPointType> { | ||
readonly ex: bigint; | ||
readonly ey: bigint; | ||
readonly ez: bigint; | ||
readonly et: bigint; | ||
assertValidity(): void; | ||
multiply(scalar: bigint): ExtPointType; | ||
multiplyUnsafe(scalar: bigint): ExtPointType; | ||
isSmallOrder(): boolean; | ||
isTorsionFree(): boolean; | ||
toAffine(invZ?: bigint): PointType; | ||
clearCofactor(): ExtendedPointType; | ||
clearCofactor(): ExtPointType; | ||
toAffine(iz?: bigint): AffinePoint<bigint>; | ||
} | ||
export interface ExtendedPointConstructor extends GroupConstructor<ExtendedPointType> { | ||
new (x: bigint, y: bigint, z: bigint, t: bigint): ExtendedPointType; | ||
fromAffine(p: PointType): ExtendedPointType; | ||
toAffineBatch(points: ExtendedPointType[]): PointType[]; | ||
normalizeZ(points: ExtendedPointType[]): ExtendedPointType[]; | ||
export interface ExtPointConstructor extends GroupConstructor<ExtPointType> { | ||
new (x: bigint, y: bigint, z: bigint, t: bigint): ExtPointType; | ||
fromAffine(p: AffinePoint<bigint>): ExtPointType; | ||
fromHex(hex: Hex): ExtPointType; | ||
fromPrivateKey(privateKey: Hex): ExtPointType; | ||
} | ||
export interface PointType extends Group<PointType> { | ||
readonly x: bigint; | ||
readonly y: bigint; | ||
_setWindowSize(windowSize: number): void; | ||
toRawBytes(isCompressed?: boolean): Uint8Array; | ||
toHex(isCompressed?: boolean): string; | ||
isTorsionFree(): boolean; | ||
clearCofactor(): PointType; | ||
} | ||
export interface PointConstructor extends GroupConstructor<PointType> { | ||
new (x: bigint, y: bigint): PointType; | ||
fromHex(hex: Hex): PointType; | ||
fromPrivateKey(privateKey: PrivKey): PointType; | ||
hashToCurve(msg: Hex, options?: Partial<htfOpts>): PointType; | ||
encodeToCurve(msg: Hex, options?: Partial<htfOpts>): PointType; | ||
} | ||
export declare type PubKey = Hex | PointType; | ||
export declare type SigType = Hex | SignatureType; | ||
export declare type CurveFn = { | ||
CURVE: ReturnType<typeof validateOpts>; | ||
getPublicKey: (privateKey: PrivKey, isCompressed?: boolean) => Uint8Array; | ||
getPublicKey: (privateKey: Hex) => Uint8Array; | ||
sign: (message: Hex, privateKey: Hex) => Uint8Array; | ||
verify: (sig: SigType, message: Hex, publicKey: PubKey) => boolean; | ||
Point: PointConstructor; | ||
ExtendedPoint: ExtendedPointConstructor; | ||
Signature: SignatureConstructor; | ||
verify: (sig: Hex, message: Hex, publicKey: Hex) => boolean; | ||
ExtendedPoint: ExtPointConstructor; | ||
utils: { | ||
randomPrivateKey: () => Uint8Array; | ||
getExtendedPublicKey: (key: PrivKey) => { | ||
getExtendedPublicKey: (key: Hex) => { | ||
head: Uint8Array; | ||
prefix: Uint8Array; | ||
scalar: bigint; | ||
point: PointType; | ||
point: ExtPointType; | ||
pointBytes: Uint8Array; | ||
@@ -120,0 +78,0 @@ }; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.twistedEdwards = void 0; | ||
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */ | ||
// Twisted Edwards curve. The formula is: ax² + y² = 1 + dx²y² | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.twistedEdwards = void 0; | ||
// Differences from @noble/ed25519 1.7: | ||
// 1. Variable field element lengths between EDDSA/ECDH: | ||
// EDDSA (RFC8032) is 456 bits / 57 bytes, ECDH (RFC7748) is 448 bits / 56 bytes | ||
// 2. Different addition formula (doubling is same) | ||
// 3. uvRatio differs between curves (half-expected, not only pow fn changes) | ||
// 4. Point decompression code is different (unexpected), now using generalized formula | ||
// 5. Domain function was no-op for ed25519, but adds some data even with empty context for ed448 | ||
const mod = require("./modular.js"); | ||
const ut = require("./utils.js"); | ||
const modular_js_1 = require("./modular.js"); | ||
const utils_js_1 = require("./utils.js"); | ||
const group_js_1 = require("./group.js"); | ||
const hash_to_curve_js_1 = require("./hash-to-curve.js"); | ||
const curve_js_1 = require("./curve.js"); | ||
// Be friendly to bad ECMAScript parsers by not using bigint literals like 123n | ||
@@ -24,4 +15,4 @@ const _0n = BigInt(0); | ||
function validateOpts(curve) { | ||
const opts = ut.validateOpts(curve); | ||
if (typeof opts.hash !== 'function' || !ut.isPositiveInt(opts.hash.outputLen)) | ||
const opts = (0, curve_js_1.validateAbsOpts)(curve); | ||
if (typeof opts.hash !== 'function') | ||
throw new Error('Invalid hash function'); | ||
@@ -43,16 +34,11 @@ for (const i of ['a', 'd']) { | ||
} | ||
if (opts.htfDefaults !== undefined) | ||
(0, hash_to_curve_js_1.validateHTFOpts)(opts.htfDefaults); | ||
// Set defaults | ||
return Object.freeze({ ...opts }); | ||
} | ||
// NOTE: it is not generic twisted curve for now, but ed25519/ed448 generic implementation | ||
// It is not generic twisted curve for now, but ed25519/ed448 generic implementation | ||
function twistedEdwards(curveDef) { | ||
const CURVE = validateOpts(curveDef); | ||
const Fp = CURVE.Fp; | ||
const CURVE_ORDER = CURVE.n; | ||
const maxGroupElement = _2n ** BigInt(CURVE.nByteLength * 8); | ||
// Function overrides | ||
const { randomBytes } = CURVE; | ||
const modP = Fp.create; | ||
const { Fp, n: CURVE_ORDER, preHash, hash: cHash, randomBytes, nByteLength, h: cofactor } = CURVE; | ||
const MASK = _2n ** BigInt(nByteLength * 8); | ||
const modP = Fp.create; // Function overrides | ||
// sqrt(u/v) | ||
@@ -62,3 +48,3 @@ const uvRatio = CURVE.uvRatio || | ||
try { | ||
return { isValid: true, value: Fp.sqrt(u * Fp.invert(v)) }; | ||
return { isValid: true, value: Fp.sqrt(u * Fp.inv(v)) }; | ||
} | ||
@@ -76,37 +62,66 @@ catch (e) { | ||
}); // NOOP | ||
/** | ||
* Extended Point works in extended coordinates: (x, y, z, t) ∋ (x=x/z, y=y/z, t=xy). | ||
* Default Point works in affine coordinates: (x, y) | ||
* https://en.wikipedia.org/wiki/Twisted_Edwards_curve#Extended_coordinates | ||
*/ | ||
class ExtendedPoint { | ||
constructor(x, y, z, t) { | ||
this.x = x; | ||
this.y = y; | ||
this.z = z; | ||
this.t = t; | ||
const inBig = (n) => typeof n === 'bigint' && 0n < n; // n in [1..] | ||
const inRange = (n, max) => inBig(n) && inBig(max) && n < max; // n in [1..max-1] | ||
const in0MaskRange = (n) => n === _0n || inRange(n, MASK); // n in [0..MASK-1] | ||
function assertInRange(n, max) { | ||
// n in [1..max-1] | ||
if (inRange(n, max)) | ||
return n; | ||
throw new Error(`Expected valid scalar < ${max}, got ${typeof n} ${n}`); | ||
} | ||
function assertGE0(n) { | ||
// n in [0..CURVE_ORDER-1] | ||
return n === _0n ? n : assertInRange(n, CURVE_ORDER); // GE = prime subgroup, not full group | ||
} | ||
const pointPrecomputes = new Map(); | ||
function isPoint(other) { | ||
if (!(other instanceof Point)) | ||
throw new Error('ExtendedPoint expected'); | ||
} | ||
// Extended Point works in extended coordinates: (x, y, z, t) ∋ (x=x/z, y=y/z, t=xy). | ||
// https://en.wikipedia.org/wiki/Twisted_Edwards_curve#Extended_coordinates | ||
class Point { | ||
constructor(ex, ey, ez, et) { | ||
this.ex = ex; | ||
this.ey = ey; | ||
this.ez = ez; | ||
this.et = et; | ||
if (!in0MaskRange(ex)) | ||
throw new Error('x required'); | ||
if (!in0MaskRange(ey)) | ||
throw new Error('y required'); | ||
if (!in0MaskRange(ez)) | ||
throw new Error('z required'); | ||
if (!in0MaskRange(et)) | ||
throw new Error('t required'); | ||
} | ||
get x() { | ||
return this.toAffine().x; | ||
} | ||
get y() { | ||
return this.toAffine().y; | ||
} | ||
static fromAffine(p) { | ||
if (!(p instanceof Point)) { | ||
throw new TypeError('ExtendedPoint#fromAffine: expected Point'); | ||
} | ||
if (p.equals(Point.ZERO)) | ||
return ExtendedPoint.ZERO; | ||
return new ExtendedPoint(p.x, p.y, _1n, modP(p.x * p.y)); | ||
if (p instanceof Point) | ||
throw new Error('extended point not allowed'); | ||
const { x, y } = p || {}; | ||
if (!in0MaskRange(x) || !in0MaskRange(y)) | ||
throw new Error('invalid affine point'); | ||
return new Point(x, y, _1n, modP(x * y)); | ||
} | ||
// Takes a bunch of Jacobian Points but executes only one | ||
// invert on all of them. invert is very slow operation, | ||
// so this improves performance massively. | ||
static toAffineBatch(points) { | ||
const toInv = Fp.invertBatch(points.map((p) => p.z)); | ||
return points.map((p, i) => p.toAffine(toInv[i])); | ||
} | ||
static normalizeZ(points) { | ||
return this.toAffineBatch(points).map(this.fromAffine); | ||
const toInv = Fp.invertBatch(points.map((p) => p.ez)); | ||
return points.map((p, i) => p.toAffine(toInv[i])).map(Point.fromAffine); | ||
} | ||
// "Private method", don't use it directly | ||
_setWindowSize(windowSize) { | ||
this._WINDOW_SIZE = windowSize; | ||
pointPrecomputes.delete(this); | ||
} | ||
assertValidity() { } | ||
// Compare one point to another. | ||
equals(other) { | ||
assertExtPoint(other); | ||
const { x: X1, y: Y1, z: Z1 } = this; | ||
const { x: X2, y: Y2, z: Z2 } = other; | ||
isPoint(other); | ||
const { ex: X1, ey: Y1, ez: Z1 } = this; | ||
const { ex: X2, ey: Y2, ez: Z2 } = other; | ||
const X1Z2 = modP(X1 * Z2); | ||
@@ -118,5 +133,8 @@ const X2Z1 = modP(X2 * Z1); | ||
} | ||
// Inverses point to one corresponding to (x, -y) in Affine coordinates. | ||
is0() { | ||
return this.equals(Point.ZERO); | ||
} | ||
negate() { | ||
return new ExtendedPoint(modP(-this.x), this.y, this.z, modP(-this.t)); | ||
// Flips point sign to a negative one (-x, y in affine coords) | ||
return new Point(modP(-this.ex), this.ey, this.ez, modP(-this.et)); | ||
} | ||
@@ -128,3 +146,3 @@ // Fast algo for doubling Extended Point. | ||
const { a } = CURVE; | ||
const { x: X1, y: Y1, z: Z1 } = this; | ||
const { ex: X1, ey: Y1, ez: Z1 } = this; | ||
const A = modP(X1 * X1); // A = X12 | ||
@@ -143,3 +161,3 @@ const B = modP(Y1 * Y1); // B = Y12 | ||
const Z3 = modP(F * G); // Z3 = F*G | ||
return new ExtendedPoint(X3, Y3, Z3, T3); | ||
return new Point(X3, Y3, Z3, T3); | ||
} | ||
@@ -150,6 +168,6 @@ // Fast algo for adding 2 Extended Points. | ||
add(other) { | ||
assertExtPoint(other); | ||
isPoint(other); | ||
const { a, d } = CURVE; | ||
const { x: X1, y: Y1, z: Z1, t: T1 } = this; | ||
const { x: X2, y: Y2, z: Z2, t: T2 } = other; | ||
const { ex: X1, ey: Y1, ez: Z1, et: T1 } = this; | ||
const { ex: X2, ey: Y2, ez: Z2, et: T2 } = other; | ||
// Faster algo for adding 2 Extended Points when curve's a=-1. | ||
@@ -174,3 +192,3 @@ // http://hyperelliptic.org/EFD/g1p/auto-twisted-extended-1.html#addition-add-2008-hwcd-4 | ||
const Z3 = modP(F * G); | ||
return new ExtendedPoint(X3, Y3, Z3, T3); | ||
return new Point(X3, Y3, Z3, T3); | ||
} | ||
@@ -189,3 +207,3 @@ const A = modP(X1 * X2); // A = X1*X2 | ||
const Z3 = modP(F * G); // Z3 = F*G | ||
return new ExtendedPoint(X3, Y3, Z3, T3); | ||
return new Point(X3, Y3, Z3, T3); | ||
} | ||
@@ -195,22 +213,9 @@ subtract(other) { | ||
} | ||
wNAF(n, affinePoint) { | ||
if (!affinePoint && this.equals(ExtendedPoint.BASE)) | ||
affinePoint = Point.BASE; | ||
const W = (affinePoint && affinePoint._WINDOW_SIZE) || 1; | ||
let precomputes = affinePoint && pointPrecomputes.get(affinePoint); | ||
if (!precomputes) { | ||
precomputes = wnaf.precomputeWindow(this, W); | ||
if (affinePoint && W !== 1) { | ||
precomputes = ExtendedPoint.normalizeZ(precomputes); | ||
pointPrecomputes.set(affinePoint, precomputes); | ||
} | ||
} | ||
const { p, f } = wnaf.wNAF(W, precomputes, n); | ||
return ExtendedPoint.normalizeZ([p, f])[0]; | ||
wNAF(n) { | ||
return wnaf.wNAFCached(this, pointPrecomputes, n, Point.normalizeZ); | ||
} | ||
// Constant time multiplication. | ||
// Uses wNAF method. Windowed method may be 10% faster, | ||
// but takes 2x longer to generate and consumes 2x memory. | ||
multiply(scalar, affinePoint) { | ||
return this.wNAF(normalizeScalar(scalar, CURVE_ORDER), affinePoint); | ||
// Constant-time multiplication. | ||
multiply(scalar) { | ||
const { p, f } = this.wNAF(assertInRange(scalar, CURVE_ORDER)); | ||
return Point.normalizeZ([p, f])[0]; | ||
} | ||
@@ -221,10 +226,9 @@ // Non-constant-time multiplication. Uses double-and-add algorithm. | ||
multiplyUnsafe(scalar) { | ||
let n = normalizeScalar(scalar, CURVE_ORDER, false); | ||
const P0 = ExtendedPoint.ZERO; | ||
let n = assertGE0(scalar); | ||
if (n === _0n) | ||
return P0; | ||
if (this.equals(P0) || n === _1n) | ||
return I; | ||
if (this.equals(I) || n === _1n) | ||
return this; | ||
if (this.equals(ExtendedPoint.BASE)) | ||
return this.wNAF(n); | ||
if (this.equals(G)) | ||
return this.wNAF(n).p; | ||
return wnaf.unsafeLadder(this, n); | ||
@@ -237,24 +241,24 @@ } | ||
isSmallOrder() { | ||
return this.multiplyUnsafe(CURVE.h).equals(ExtendedPoint.ZERO); | ||
return this.multiplyUnsafe(cofactor).is0(); | ||
} | ||
// Multiplies point by curve order (very big scalar CURVE.n) and checks if the result is 0. | ||
// Multiplies point by curve order and checks if the result is 0. | ||
// Returns `false` is the point is dirty. | ||
isTorsionFree() { | ||
return wnaf.unsafeLadder(this, CURVE_ORDER).equals(ExtendedPoint.ZERO); | ||
return wnaf.unsafeLadder(this, CURVE_ORDER).is0(); | ||
} | ||
// Converts Extended point to default (x, y) coordinates. | ||
// Can accept precomputed Z^-1 - for example, from invertBatch. | ||
toAffine(invZ) { | ||
const { x, y, z } = this; | ||
const is0 = this.equals(ExtendedPoint.ZERO); | ||
if (invZ == null) | ||
invZ = is0 ? _8n : Fp.invert(z); // 8 was chosen arbitrarily | ||
const ax = modP(x * invZ); | ||
const ay = modP(y * invZ); | ||
const zz = modP(z * invZ); | ||
toAffine(iz) { | ||
const { ex: x, ey: y, ez: z } = this; | ||
const is0 = this.is0(); | ||
if (iz == null) | ||
iz = is0 ? _8n : Fp.inv(z); // 8 was chosen arbitrarily | ||
const ax = modP(x * iz); | ||
const ay = modP(y * iz); | ||
const zz = modP(z * iz); | ||
if (is0) | ||
return Point.ZERO; | ||
return { x: _0n, y: _1n }; | ||
if (zz !== _1n) | ||
throw new Error('invZ was invalid'); | ||
return new Point(ax, ay); | ||
return { x: ax, y: ay }; | ||
} | ||
@@ -267,25 +271,2 @@ clearCofactor() { | ||
} | ||
} | ||
ExtendedPoint.BASE = new ExtendedPoint(CURVE.Gx, CURVE.Gy, _1n, modP(CURVE.Gx * CURVE.Gy)); | ||
ExtendedPoint.ZERO = new ExtendedPoint(_0n, _1n, _1n, _0n); | ||
const wnaf = (0, group_js_1.wNAF)(ExtendedPoint, CURVE.nByteLength * 8); | ||
function assertExtPoint(other) { | ||
if (!(other instanceof ExtendedPoint)) | ||
throw new TypeError('ExtendedPoint expected'); | ||
} | ||
// Stores precomputed values for points. | ||
const pointPrecomputes = new WeakMap(); | ||
/** | ||
* Default Point works in affine coordinates: (x, y) | ||
*/ | ||
class Point { | ||
constructor(x, y) { | ||
this.x = x; | ||
this.y = y; | ||
} | ||
// "Private method", don't use it directly. | ||
_setWindowSize(windowSize) { | ||
this._WINDOW_SIZE = windowSize; | ||
pointPrecomputes.delete(this); | ||
} | ||
// Converts hash string or Uint8Array to Point. | ||
@@ -296,274 +277,118 @@ // Uses algo from RFC8032 5.1.3. | ||
const len = Fp.BYTES; | ||
hex = (0, utils_js_1.ensureBytes)(hex, len); | ||
// 1. First, interpret the string as an integer in little-endian | ||
// representation. Bit 255 of this number is the least significant | ||
// bit of the x-coordinate and denote this value x_0. The | ||
// y-coordinate is recovered simply by clearing this bit. If the | ||
// resulting value is >= p, decoding fails. | ||
const normed = hex.slice(); | ||
const lastByte = hex[len - 1]; | ||
normed[len - 1] = lastByte & ~0x80; | ||
const y = ut.bytesToNumberLE(normed); | ||
if (strict && y >= Fp.ORDER) | ||
throw new Error('Expected 0 < hex < P'); | ||
if (!strict && y >= maxGroupElement) | ||
throw new Error('Expected 0 < hex < CURVE.n'); | ||
// 2. To recover the x-coordinate, the curve equation implies | ||
// Ed25519: x² = (y² - 1) / (d y² + 1) (mod p). | ||
// Ed448: x² = (y² - 1) / (d y² - 1) (mod p). | ||
// For generic case: | ||
// a*x²+y²=1+d*x²*y² | ||
// -> y²-1 = d*x²*y²-a*x² | ||
// -> y²-1 = x² (d*y²-a) | ||
// -> x² = (y²-1) / (d*y²-a) | ||
// The denominator is always non-zero mod p. Let u = y² - 1 and v = d y² + 1. | ||
const y2 = modP(y * y); | ||
const u = modP(y2 - _1n); | ||
const v = modP(d * y2 - a); | ||
let { isValid, value: x } = uvRatio(u, v); | ||
hex = (0, utils_js_1.ensureBytes)(hex, len); // copy hex to a new array | ||
const normed = hex.slice(); // copy again, we'll manipulate it | ||
const lastByte = hex[len - 1]; // select last byte | ||
normed[len - 1] = lastByte & ~0x80; // clear last bit | ||
const y = (0, utils_js_1.bytesToNumberLE)(normed); | ||
if (y === _0n) { | ||
// y=0 is allowed | ||
} | ||
else { | ||
// RFC8032 prohibits >= p, but ZIP215 doesn't | ||
if (strict) | ||
assertInRange(y, Fp.ORDER); // strict=true [1..P-1] (2^255-19-1 for ed25519) | ||
else | ||
assertInRange(y, MASK); // strict=false [1..MASK-1] (2^256-1 for ed25519) | ||
} | ||
// Ed25519: x² = (y²-1)/(dy²+1) mod p. Ed448: x² = (y²-1)/(dy²-1) mod p. Generic case: | ||
// ax²+y²=1+dx²y² => y²-1=dx²y²-ax² => y²-1=x²(dy²-a) => x²=(y²-1)/(dy²-a) | ||
const y2 = modP(y * y); // denominator is always non-0 mod p. | ||
const u = modP(y2 - _1n); // u = y² - 1 | ||
const v = modP(d * y2 - a); // v = d y² + 1. | ||
let { isValid, value: x } = uvRatio(u, v); // √(u/v) | ||
if (!isValid) | ||
throw new Error('Point.fromHex: invalid y coordinate'); | ||
// 4. Finally, use the x_0 bit to select the right square root. If | ||
// x = 0, and x_0 = 1, decoding fails. Otherwise, if x_0 != x mod | ||
// 2, set x <-- p - x. Return the decoded point (x,y). | ||
const isXOdd = (x & _1n) === _1n; | ||
const isLastByteOdd = (lastByte & 0x80) !== 0; | ||
const isXOdd = (x & _1n) === _1n; // There are 2 square roots. Use x_0 bit to select proper | ||
const isLastByteOdd = (lastByte & 0x80) !== 0; // if x=0 and x_0 = 1, fail | ||
if (isLastByteOdd !== isXOdd) | ||
x = modP(-x); | ||
return new Point(x, y); | ||
x = modP(-x); // if x_0 != x mod 2, set x = p-x | ||
return Point.fromAffine({ x, y }); | ||
} | ||
static fromPrivateKey(privateKey) { | ||
return getExtendedPublicKey(privateKey).point; | ||
static fromPrivateKey(privKey) { | ||
return getExtendedPublicKey(privKey).point; | ||
} | ||
// There can always be only two x values (x, -x) for any y | ||
// When compressing point, it's enough to only store its y coordinate | ||
// and use the last byte to encode sign of x. | ||
toRawBytes() { | ||
const bytes = ut.numberToBytesLE(this.y, Fp.BYTES); | ||
bytes[Fp.BYTES - 1] |= this.x & _1n ? 0x80 : 0; | ||
return bytes; | ||
const { x, y } = this.toAffine(); | ||
const bytes = (0, utils_js_1.numberToBytesLE)(y, Fp.BYTES); // each y has 2 x values (x, -y) | ||
bytes[bytes.length - 1] |= x & _1n ? 0x80 : 0; // when compressing, it's enough to store y | ||
return bytes; // and use the last byte to encode sign of x | ||
} | ||
// Same as toRawBytes, but returns string. | ||
toHex() { | ||
return ut.bytesToHex(this.toRawBytes()); | ||
return (0, utils_js_1.bytesToHex)(this.toRawBytes()); // Same as toRawBytes, but returns string. | ||
} | ||
// Determines if point is in prime-order subgroup. | ||
// Returns `false` is the point is dirty. | ||
isTorsionFree() { | ||
return ExtendedPoint.fromAffine(this).isTorsionFree(); | ||
} | ||
equals(other) { | ||
if (!(other instanceof Point)) | ||
throw new TypeError('Point#equals: expected Point'); | ||
return this.x === other.x && this.y === other.y; | ||
} | ||
negate() { | ||
return new Point(modP(-this.x), this.y); | ||
} | ||
double() { | ||
return ExtendedPoint.fromAffine(this).double().toAffine(); | ||
} | ||
add(other) { | ||
return ExtendedPoint.fromAffine(this).add(ExtendedPoint.fromAffine(other)).toAffine(); | ||
} | ||
subtract(other) { | ||
return this.add(other.negate()); | ||
} | ||
/** | ||
* Constant time multiplication. | ||
* @param scalar Big-Endian number | ||
* @returns new point | ||
*/ | ||
multiply(scalar) { | ||
return ExtendedPoint.fromAffine(this).multiply(scalar, this).toAffine(); | ||
} | ||
clearCofactor() { | ||
return ExtendedPoint.fromAffine(this).clearCofactor().toAffine(); | ||
} | ||
// Encodes byte string to elliptic curve | ||
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-3 | ||
static hashToCurve(msg, options) { | ||
const { mapToCurve, htfDefaults } = CURVE; | ||
if (!mapToCurve) | ||
throw new Error('No mapToCurve defined for curve'); | ||
const u = (0, hash_to_curve_js_1.hash_to_field)((0, utils_js_1.ensureBytes)(msg), 2, { ...htfDefaults, ...options }); | ||
const { x: x0, y: y0 } = mapToCurve(u[0]); | ||
const { x: x1, y: y1 } = mapToCurve(u[1]); | ||
const p = new Point(x0, y0).add(new Point(x1, y1)).clearCofactor(); | ||
return p; | ||
} | ||
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-16#section-3 | ||
static encodeToCurve(msg, options) { | ||
const { mapToCurve, htfDefaults } = CURVE; | ||
if (!mapToCurve) | ||
throw new Error('No mapToCurve defined for curve'); | ||
const u = (0, hash_to_curve_js_1.hash_to_field)((0, utils_js_1.ensureBytes)(msg), 1, { ...htfDefaults, ...options }); | ||
const { x, y } = mapToCurve(u[0]); | ||
return new Point(x, y).clearCofactor(); | ||
} | ||
} | ||
// Base point aka generator | ||
// public_key = Point.BASE * private_key | ||
Point.BASE = new Point(CURVE.Gx, CURVE.Gy); | ||
// Identity point aka point at infinity | ||
// point = point + zero_point | ||
Point.ZERO = new Point(_0n, _1n); | ||
/** | ||
* EDDSA signature. | ||
*/ | ||
class Signature { | ||
constructor(r, s) { | ||
this.r = r; | ||
this.s = s; | ||
this.assertValidity(); | ||
} | ||
static fromHex(hex) { | ||
const len = Fp.BYTES; | ||
const bytes = (0, utils_js_1.ensureBytes)(hex, 2 * len); | ||
const r = Point.fromHex(bytes.slice(0, len), false); | ||
const s = ut.bytesToNumberLE(bytes.slice(len, 2 * len)); | ||
return new Signature(r, s); | ||
} | ||
assertValidity() { | ||
const { r, s } = this; | ||
if (!(r instanceof Point)) | ||
throw new Error('Expected Point instance'); | ||
// 0 <= s < l | ||
normalizeScalar(s, CURVE_ORDER, false); | ||
return this; | ||
} | ||
toRawBytes() { | ||
return ut.concatBytes(this.r.toRawBytes(), ut.numberToBytesLE(this.s, Fp.BYTES)); | ||
} | ||
toHex() { | ||
return ut.bytesToHex(this.toRawBytes()); | ||
} | ||
Point.BASE = new Point(CURVE.Gx, CURVE.Gy, _1n, modP(CURVE.Gx * CURVE.Gy)); | ||
Point.ZERO = new Point(_0n, _1n, _1n, _0n); // 0, 1, 1, 0 | ||
const { BASE: G, ZERO: I } = Point; | ||
const wnaf = (0, curve_js_1.wNAF)(Point, nByteLength * 8); | ||
function modN(a) { | ||
return (0, modular_js_1.mod)(a, CURVE_ORDER); | ||
} | ||
// Little-endian SHA512 with modulo n | ||
function modnLE(hash) { | ||
return mod.mod(ut.bytesToNumberLE(hash), CURVE_ORDER); | ||
function modN_LE(hash) { | ||
return modN((0, utils_js_1.bytesToNumberLE)(hash)); | ||
} | ||
/** | ||
* Checks for num to be in range: | ||
* For strict == true: `0 < num < max`. | ||
* For strict == false: `0 <= num < max`. | ||
* Converts non-float safe numbers to bigints. | ||
*/ | ||
function normalizeScalar(num, max, strict = true) { | ||
if (!max) | ||
throw new TypeError('Specify max value'); | ||
if (ut.isPositiveInt(num)) | ||
num = BigInt(num); | ||
if (typeof num === 'bigint' && num < max) { | ||
if (strict) { | ||
if (_0n < num) | ||
return num; | ||
} | ||
else { | ||
if (_0n <= num) | ||
return num; | ||
} | ||
} | ||
throw new TypeError(`Expected valid scalar: 0 < scalar < ${max}`); | ||
function isHex(item, err) { | ||
if (typeof item !== 'string' && !(item instanceof Uint8Array)) | ||
throw new Error(`${err} must be hex string or Uint8Array`); | ||
} | ||
/** Convenience method that creates public key and other stuff. RFC8032 5.1.5 */ | ||
function getExtendedPublicKey(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; | ||
isHex(key, 'private key'); | ||
const len = nByteLength; | ||
// 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 = (0, utils_js_1.ensureBytes)(CURVE.hash((0, utils_js_1.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(); | ||
// Check byte lengths: ensure(64, h(ensure(32, key))) | ||
const hashed = (0, utils_js_1.ensureBytes)(cHash((0, utils_js_1.ensureBytes)(key, len)), 2 * len); | ||
const head = adjustScalarBytes(hashed.slice(0, len)); // clear first half bits, produce FE | ||
const prefix = hashed.slice(len, 2 * len); // second half is called key prefix (5.1.6) | ||
const scalar = modN_LE(head); // The actual private scalar | ||
const point = G.multiply(scalar); // Point on Edwards curve aka public key | ||
const pointBytes = point.toRawBytes(); // Uint8Array representation | ||
return { head, prefix, scalar, point, pointBytes }; | ||
} | ||
/** | ||
* Calculates ed25519 public key. RFC8032 5.1.5 | ||
* 1. private key is hashed with sha512, then first 32 bytes are taken from the hash | ||
* 2. 3 least significant bits of the first byte are cleared | ||
*/ | ||
function getPublicKey(privateKey) { | ||
return getExtendedPublicKey(privateKey).pointBytes; | ||
// Calculates EdDSA pub key. RFC8032 5.1.5. Privkey is hashed. Use first half with 3 bits cleared | ||
function getPublicKey(privKey) { | ||
return getExtendedPublicKey(privKey).pointBytes; | ||
} | ||
const EMPTY = new Uint8Array(); | ||
function hashDomainToScalar(message, context = EMPTY) { | ||
context = (0, utils_js_1.ensureBytes)(context); | ||
return modnLE(CURVE.hash(domain(message, context, !!CURVE.preHash))); | ||
// int('LE', SHA512(dom2(F, C) || msgs)) mod N | ||
function hashDomainToScalar(context = new Uint8Array(), ...msgs) { | ||
const msg = (0, utils_js_1.concatBytes)(...msgs); | ||
return modN_LE(cHash(domain(msg, (0, utils_js_1.ensureBytes)(context), !!preHash))); | ||
} | ||
/** Signs message with privateKey. RFC8032 5.1.6 */ | ||
function sign(message, privateKey, context) { | ||
message = (0, utils_js_1.ensureBytes)(message); | ||
if (CURVE.preHash) | ||
message = CURVE.preHash(message); | ||
const { prefix, scalar, pointBytes } = getExtendedPublicKey(privateKey); | ||
const r = hashDomainToScalar(ut.concatBytes(prefix, message), context); | ||
const R = Point.BASE.multiply(r); // R = rG | ||
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 | ||
return new Signature(R, s).toRawBytes(); | ||
function sign(msg, privKey, context) { | ||
isHex(msg, 'message'); | ||
msg = (0, utils_js_1.ensureBytes)(msg); | ||
if (preHash) | ||
msg = preHash(msg); // for ed25519ph etc. | ||
const { prefix, scalar, pointBytes } = getExtendedPublicKey(privKey); | ||
const r = hashDomainToScalar(context, prefix, msg); // r = dom2(F, C) || prefix || PH(M) | ||
const R = G.multiply(r).toRawBytes(); // R = rG | ||
const k = hashDomainToScalar(context, R, pointBytes, msg); // R || A || PH(M) | ||
const s = modN(r + k * scalar); // S = (r + k * s) mod L | ||
assertGE0(s); // 0 <= s < l | ||
const res = (0, utils_js_1.concatBytes)(R, (0, utils_js_1.numberToBytesLE)(s, Fp.BYTES)); | ||
return (0, utils_js_1.ensureBytes)(res, nByteLength * 2); // 64-byte signature | ||
} | ||
/** | ||
* Verifies EdDSA signature against message and public key. | ||
* An extended group equation is checked. | ||
* RFC8032 5.1.7 | ||
* Compliant with ZIP215: | ||
* 0 <= sig.R/publicKey < 2**256 (can be >= curve.P) | ||
* 0 <= sig.s < l | ||
* Not compliant with RFC8032: it's not possible to comply to both ZIP & RFC at the same time. | ||
*/ | ||
function verify(sig, message, publicKey, context) { | ||
message = (0, utils_js_1.ensureBytes)(message); | ||
if (CURVE.preHash) | ||
message = CURVE.preHash(message); | ||
// When hex is passed, we check public key fully. | ||
// When Point instance is passed, we assume it has already been checked, for performance. | ||
// If user passes Point/Sig instance, we assume it has been already verified. | ||
// We don't check its equations for performance. We do check for valid bounds for s though | ||
// We always check for: a) s bounds. b) hex validity | ||
if (publicKey instanceof Point) { | ||
// ignore | ||
} | ||
else if (publicKey instanceof Uint8Array || typeof publicKey === 'string') { | ||
publicKey = Point.fromHex(publicKey, false); | ||
} | ||
else { | ||
throw new Error(`Invalid publicKey: ${publicKey}`); | ||
} | ||
if (sig instanceof Signature) | ||
sig.assertValidity(); | ||
else if (sig instanceof Uint8Array || typeof sig === 'string') | ||
sig = Signature.fromHex(sig); | ||
else | ||
throw new Error(`Wrong signature: ${sig}`); | ||
const { r, s } = sig; | ||
const SB = ExtendedPoint.BASE.multiplyUnsafe(s); | ||
const k = hashDomainToScalar(ut.concatBytes(r.toRawBytes(), publicKey.toRawBytes(), message), context); | ||
const kA = ExtendedPoint.fromAffine(publicKey).multiplyUnsafe(k); | ||
const RkA = ExtendedPoint.fromAffine(r).add(kA); | ||
function verify(sig, msg, publicKey, context) { | ||
isHex(sig, 'sig'); | ||
isHex(msg, 'message'); | ||
const len = Fp.BYTES; // Verifies EdDSA signature against message and public key. RFC8032 5.1.7. | ||
sig = (0, utils_js_1.ensureBytes)(sig, 2 * len); // An extended group equation is checked. | ||
msg = (0, utils_js_1.ensureBytes)(msg); // ZIP215 compliant, which means not fully RFC8032 compliant. | ||
if (preHash) | ||
msg = preHash(msg); // for ed25519ph, etc | ||
const A = Point.fromHex(publicKey, false); // Check for s bounds, hex validity | ||
const R = Point.fromHex(sig.slice(0, len), false); // 0 <= R < 2^256: ZIP215 R can be >= P | ||
const s = (0, utils_js_1.bytesToNumberLE)(sig.slice(len, 2 * len)); // 0 <= s < l | ||
const SB = G.multiplyUnsafe(s); | ||
const k = hashDomainToScalar(context, R.toRawBytes(), A.toRawBytes(), msg); | ||
const RkA = R.add(A.multiplyUnsafe(k)); | ||
// [8][S]B = [8]R + [8][k]A' | ||
return RkA.subtract(SB).clearCofactor().equals(ExtendedPoint.ZERO); | ||
return RkA.subtract(SB).clearCofactor().equals(Point.ZERO); | ||
} | ||
// Enable precomputes. Slows down first publicKey computation by 20ms. | ||
Point.BASE._setWindowSize(8); | ||
G._setWindowSize(8); // Enable precomputes. Slows down first publicKey computation by 20ms. | ||
const utils = { | ||
getExtendedPublicKey, | ||
/** | ||
* Not needed for ed25519 private keys. Needed if you use scalars directly (rare). | ||
*/ | ||
hashToPrivateScalar: (hash) => ut.hashToPrivateScalar(hash, CURVE_ORDER, true), | ||
/** | ||
* ed25519 private keys are uniform 32-bit strings. We do not need to check for | ||
* modulo bias like we do in secp256k1 randomPrivateKey() | ||
*/ | ||
// ed25519 private keys are uniform 32b. No need to check for modulo bias, like in secp256k1. | ||
randomPrivateKey: () => randomBytes(Fp.BYTES), | ||
@@ -577,6 +402,5 @@ /** | ||
precompute(windowSize = 8, point = Point.BASE) { | ||
const cached = point.equals(Point.BASE) ? point : new Point(point.x, point.y); | ||
cached._setWindowSize(windowSize); | ||
cached.multiply(_2n); | ||
return cached; | ||
point._setWindowSize(windowSize); | ||
point.multiply(BigInt(3)); | ||
return point; | ||
}, | ||
@@ -589,5 +413,3 @@ }; | ||
verify, | ||
ExtendedPoint, | ||
Point, | ||
Signature, | ||
ExtendedPoint: Point, | ||
utils, | ||
@@ -594,0 +416,0 @@ }; |
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */ | ||
import { CHash } from './utils.js'; | ||
import * as mod from './modular.js'; | ||
export declare type htfOpts = { | ||
import type { Group, GroupConstructor, AffinePoint } from './curve.js'; | ||
import { Field } from './modular.js'; | ||
import { CHash, Hex } from './utils.js'; | ||
export declare type Opts = { | ||
DST: string; | ||
encodeDST: string; | ||
p: bigint; | ||
@@ -12,3 +14,3 @@ m: number; | ||
}; | ||
export declare function validateHTFOpts(opts: htfOpts): void; | ||
export declare function validateOpts(opts: Opts): void; | ||
export declare function stringToBytes(str: string): Uint8Array; | ||
@@ -25,6 +27,23 @@ export declare function expand_message_xmd(msg: Uint8Array, DST: Uint8Array, lenInBytes: number, H: CHash): Uint8Array; | ||
*/ | ||
export declare function hash_to_field(msg: Uint8Array, count: number, options: htfOpts): bigint[][]; | ||
export declare function isogenyMap<T, F extends mod.Field<T>>(field: F, map: [T[], T[], T[], T[]]): (x: T, y: T) => { | ||
export declare function hash_to_field(msg: Uint8Array, count: number, options: Opts): bigint[][]; | ||
export declare function isogenyMap<T, F extends Field<T>>(field: F, map: [T[], T[], T[], T[]]): (x: T, y: T) => { | ||
x: T; | ||
y: T; | ||
}; | ||
export interface H2CPoint<T> extends Group<H2CPoint<T>> { | ||
add(rhs: H2CPoint<T>): H2CPoint<T>; | ||
toAffine(iz?: bigint): AffinePoint<T>; | ||
clearCofactor(): H2CPoint<T>; | ||
assertValidity(): void; | ||
} | ||
export interface H2CPointConstructor<T> extends GroupConstructor<H2CPoint<T>> { | ||
fromAffine(ap: AffinePoint<T>): H2CPoint<T>; | ||
} | ||
export declare type MapToCurve<T> = (scalar: bigint[]) => AffinePoint<T>; | ||
export declare type htfBasicOpts = { | ||
DST: string; | ||
}; | ||
export declare function hashToCurve<T>(Point: H2CPointConstructor<T>, mapToCurve: MapToCurve<T>, def: Opts): { | ||
hashToCurve(msg: Hex, options?: htfBasicOpts): H2CPoint<T>; | ||
encodeToCurve(msg: Hex, options?: htfBasicOpts): H2CPoint<T>; | ||
}; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.isogenyMap = exports.hash_to_field = exports.expand_message_xof = exports.expand_message_xmd = exports.stringToBytes = exports.validateHTFOpts = void 0; | ||
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */ | ||
exports.hashToCurve = exports.isogenyMap = exports.hash_to_field = exports.expand_message_xof = exports.expand_message_xmd = exports.stringToBytes = exports.validateOpts = void 0; | ||
const modular_js_1 = require("./modular.js"); | ||
const utils_js_1 = require("./utils.js"); | ||
const mod = require("./modular.js"); | ||
function validateHTFOpts(opts) { | ||
function validateOpts(opts) { | ||
if (typeof opts.DST !== 'string') | ||
@@ -21,10 +20,8 @@ throw new Error('Invalid htf/DST'); | ||
} | ||
exports.validateHTFOpts = validateHTFOpts; | ||
// UTF8 to ui8a | ||
// TODO: looks broken, ASCII only, why not TextEncoder/TextDecoder? it is in hashes anyway | ||
exports.validateOpts = validateOpts; | ||
function stringToBytes(str) { | ||
const bytes = new Uint8Array(str.length); | ||
for (let i = 0; i < str.length; i++) | ||
bytes[i] = str.charCodeAt(i); | ||
return bytes; | ||
if (typeof str !== 'string') { | ||
throw new TypeError(`utf8ToBytes expected string, got ${typeof str}`); | ||
} | ||
return new TextEncoder().encode(str); | ||
} | ||
@@ -131,3 +128,3 @@ exports.stringToBytes = stringToBytes; | ||
const tv = pseudo_random_bytes.subarray(elm_offset, elm_offset + L); | ||
e[j] = mod.mod(os2ip(tv), options.p); | ||
e[j] = (0, modular_js_1.mod)(os2ip(tv), options.p); | ||
} | ||
@@ -150,1 +147,32 @@ u[i] = e; | ||
exports.isogenyMap = isogenyMap; | ||
function hashToCurve(Point, mapToCurve, def) { | ||
validateOpts(def); | ||
if (typeof mapToCurve !== 'function') | ||
throw new Error('hashToCurve: mapToCurve() has not been defined'); | ||
return { | ||
// Encodes byte string to elliptic curve | ||
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-3 | ||
hashToCurve(msg, options) { | ||
if (!mapToCurve) | ||
throw new Error('CURVE.mapToCurve() has not been defined'); | ||
msg = (0, utils_js_1.ensureBytes)(msg); | ||
const u = hash_to_field(msg, 2, { ...def, DST: def.DST, ...options }); | ||
const P = Point.fromAffine(mapToCurve(u[0])) | ||
.add(Point.fromAffine(mapToCurve(u[1]))) | ||
.clearCofactor(); | ||
P.assertValidity(); | ||
return P; | ||
}, | ||
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-16#section-3 | ||
encodeToCurve(msg, options) { | ||
if (!mapToCurve) | ||
throw new Error('CURVE.mapToCurve() has not been defined'); | ||
msg = (0, utils_js_1.ensureBytes)(msg); | ||
const u = hash_to_field(msg, 1, { ...def, DST: def.encodeDST, ...options }); | ||
const P = Point.fromAffine(mapToCurve(u[0])).clearCofactor(); | ||
P.assertValidity(); | ||
return P; | ||
}, | ||
}; | ||
} | ||
exports.hashToCurve = hashToCurve; |
@@ -23,8 +23,8 @@ export declare function mod(a: bigint, b: bigint): bigint; | ||
isValid: (num: T) => boolean; | ||
isZero: (num: T) => boolean; | ||
negate(num: T): T; | ||
invert(num: T): T; | ||
is0: (num: T) => boolean; | ||
neg(num: T): T; | ||
inv(num: T): T; | ||
sqrt(num: T): T; | ||
square(num: T): T; | ||
equals(lhs: T, rhs: T): boolean; | ||
sqr(num: T): T; | ||
eql(lhs: T, rhs: T): boolean; | ||
add(lhs: T, rhs: T): T; | ||
@@ -38,5 +38,4 @@ sub(lhs: T, rhs: T): T; | ||
mulN(lhs: T, rhs: T | bigint): T; | ||
squareN(num: T): T; | ||
sqrN(num: T): T; | ||
isOdd?(num: T): boolean; | ||
legendre?(num: T): T; | ||
pow(lhs: T, power: bigint): T; | ||
@@ -53,2 +52,6 @@ invertBatch: (lst: T[]) => T[]; | ||
export declare function FpIsSquare<T>(f: Field<T>): (x: T) => boolean; | ||
export declare function nLength(n: bigint, nBitLength?: number): { | ||
nBitLength: number; | ||
nByteLength: number; | ||
}; | ||
declare type FpField = Field<bigint> & Required<Pick<Field<bigint>, 'isOdd'>>; | ||
@@ -58,2 +61,12 @@ export declare function Fp(ORDER: bigint, bitLen?: number, isLE?: boolean, redef?: Partial<Field<bigint>>): Readonly<FpField>; | ||
export declare function FpSqrtEven<T>(Fp: Field<T>, elm: T): T; | ||
/** | ||
* FIPS 186 B.4.1-compliant "constant-time" private key generation utility. | ||
* Can take (n+8) or more bytes of uniform input e.g. from CSPRNG or KDF | ||
* and convert them into private scalar, with the modulo bias being neglible. | ||
* Needs at least 40 bytes of input for 32-byte private key. | ||
* https://research.kudelskisecurity.com/2020/07/28/the-definitive-guide-to-modulo-bias-and-how-to-avoid-it/ | ||
* @param hash hash output from SHA3 or a similar function | ||
* @returns valid private scalar | ||
*/ | ||
export declare function hashToPrivateScalar(hash: string | Uint8Array, groupOrder: bigint, isLE?: boolean): bigint; | ||
export {}; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.FpSqrtEven = exports.FpSqrtOdd = exports.Fp = exports.FpIsSquare = exports.FpDiv = exports.FpInvertBatch = exports.FpPow = exports.validateField = exports.isNegativeLE = exports.FpSqrt = exports.tonelliShanks = exports.invert = exports.pow2 = exports.pow = exports.mod = void 0; | ||
exports.hashToPrivateScalar = exports.FpSqrtEven = exports.FpSqrtOdd = exports.Fp = exports.nLength = exports.FpIsSquare = exports.FpDiv = exports.FpInvertBatch = exports.FpPow = exports.validateField = exports.isNegativeLE = exports.FpSqrt = exports.tonelliShanks = exports.invert = exports.pow2 = exports.pow = exports.mod = void 0; | ||
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */ | ||
// TODO: remove circular imports | ||
const utils = require("./utils.js"); | ||
// Utilities for modular arithmetics and finite fields | ||
const utils_js_1 = require("./utils.js"); | ||
// prettier-ignore | ||
@@ -101,3 +100,3 @@ const _0n = BigInt(0), _1n = BigInt(1), _2n = BigInt(2), _3n = BigInt(3); | ||
const root = Fp.pow(n, p1div4); | ||
if (!Fp.equals(Fp.square(root), n)) | ||
if (!Fp.eql(Fp.sqr(root), n)) | ||
throw new Error('Cannot find square root'); | ||
@@ -111,3 +110,3 @@ return root; | ||
// Step 0: Check that n is indeed a square: (n | p) should not be ≡ -1 | ||
if (Fp.pow(n, legendreC) === Fp.negate(Fp.ONE)) | ||
if (Fp.pow(n, legendreC) === Fp.neg(Fp.ONE)) | ||
throw new Error('Cannot find square root'); | ||
@@ -119,15 +118,15 @@ let r = S; | ||
let b = Fp.pow(n, Q); // first guess at the fudge factor | ||
while (!Fp.equals(b, Fp.ONE)) { | ||
if (Fp.equals(b, Fp.ZERO)) | ||
while (!Fp.eql(b, Fp.ONE)) { | ||
if (Fp.eql(b, Fp.ZERO)) | ||
return Fp.ZERO; // https://en.wikipedia.org/wiki/Tonelli%E2%80%93Shanks_algorithm (4. If t = 0, return r = 0) | ||
// Find m such b^(2^m)==1 | ||
let m = 1; | ||
for (let t2 = Fp.square(b); m < r; m++) { | ||
if (Fp.equals(t2, Fp.ONE)) | ||
for (let t2 = Fp.sqr(b); m < r; m++) { | ||
if (Fp.eql(t2, Fp.ONE)) | ||
break; | ||
t2 = Fp.square(t2); // t2 *= t2 | ||
t2 = Fp.sqr(t2); // t2 *= t2 | ||
} | ||
// NOTE: r-m-1 can be bigger than 32, need to convert to bigint before shift, otherwise there will be overflow | ||
const ge = Fp.pow(g, _1n << BigInt(r - m - 1)); // ge = 2^(r-m-1) | ||
g = Fp.square(ge); // g = ge * ge | ||
g = Fp.sqr(ge); // g = ge * ge | ||
x = Fp.mul(x, ge); // x *= ge | ||
@@ -155,3 +154,3 @@ b = Fp.mul(b, g); // b *= g | ||
// Throw if root**2 != n | ||
if (!Fp.equals(Fp.square(root), n)) | ||
if (!Fp.eql(Fp.sqr(root), n)) | ||
throw new Error('Cannot find square root'); | ||
@@ -170,3 +169,3 @@ return root; | ||
const root = Fp.mul(nv, Fp.sub(i, Fp.ONE)); | ||
if (!Fp.equals(Fp.square(root), n)) | ||
if (!Fp.eql(Fp.sqr(root), n)) | ||
throw new Error('Cannot find square root'); | ||
@@ -207,5 +206,5 @@ return root; | ||
const FIELD_FIELDS = [ | ||
'create', 'isValid', 'isZero', 'negate', 'invert', 'sqrt', 'square', | ||
'equals', 'add', 'sub', 'mul', 'pow', 'div', | ||
'addN', 'subN', 'mulN', 'squareN' | ||
'create', 'isValid', 'is0', 'neg', 'inv', 'sqrt', 'sqr', | ||
'eql', 'add', 'sub', 'mul', 'pow', 'div', | ||
'addN', 'subN', 'mulN', 'sqrN' | ||
]; | ||
@@ -242,3 +241,3 @@ function validateField(field) { | ||
p = f.mul(p, d); | ||
d = f.square(d); | ||
d = f.sqr(d); | ||
power >>= 1n; | ||
@@ -253,3 +252,3 @@ } | ||
const lastMultiplied = nums.reduce((acc, num, i) => { | ||
if (f.isZero(num)) | ||
if (f.is0(num)) | ||
return acc; | ||
@@ -260,6 +259,6 @@ tmp[i] = acc; | ||
// Invert last element | ||
const inverted = f.invert(lastMultiplied); | ||
const inverted = f.inv(lastMultiplied); | ||
// Walk from last to first, multiply them by inverted each other MOD p | ||
nums.reduceRight((acc, num, i) => { | ||
if (f.isZero(num)) | ||
if (f.is0(num)) | ||
return acc; | ||
@@ -273,3 +272,3 @@ tmp[i] = f.mul(acc, tmp[i]); | ||
function FpDiv(f, lhs, rhs) { | ||
return f.mul(lhs, typeof rhs === 'bigint' ? invert(rhs, f.ORDER) : f.invert(rhs)); | ||
return f.mul(lhs, typeof rhs === 'bigint' ? invert(rhs, f.ORDER) : f.inv(rhs)); | ||
} | ||
@@ -282,10 +281,18 @@ exports.FpDiv = FpDiv; | ||
const p = f.pow(x, legendreConst); | ||
return f.equals(p, f.ZERO) || f.equals(p, f.ONE); | ||
return f.eql(p, f.ZERO) || f.eql(p, f.ONE); | ||
}; | ||
} | ||
exports.FpIsSquare = FpIsSquare; | ||
// CURVE.n lengths | ||
function nLength(n, nBitLength) { | ||
// Bit size, byte size of CURVE.n | ||
const _nBitLength = nBitLength !== undefined ? nBitLength : n.toString(2).length; | ||
const nByteLength = Math.ceil(_nBitLength / 8); | ||
return { nBitLength: _nBitLength, nByteLength }; | ||
} | ||
exports.nLength = nLength; | ||
function Fp(ORDER, bitLen, isLE = false, redef = {}) { | ||
if (ORDER <= _0n) | ||
throw new Error(`Expected Fp ORDER > 0, got ${ORDER}`); | ||
const { nBitLength: BITS, nByteLength: BYTES } = utils.nLength(ORDER, bitLen); | ||
const { nBitLength: BITS, nByteLength: BYTES } = nLength(ORDER, bitLen); | ||
if (BYTES > 2048) | ||
@@ -298,3 +305,3 @@ throw new Error('Field lengths over 2048 bytes are not supported'); | ||
BYTES, | ||
MASK: utils.bitMask(BITS), | ||
MASK: (0, utils_js_1.bitMask)(BITS), | ||
ZERO: _0n, | ||
@@ -306,9 +313,9 @@ ONE: _1n, | ||
throw new Error(`Invalid field element: expected bigint, got ${typeof num}`); | ||
return _0n <= num && num < ORDER; | ||
return _0n <= num && num < ORDER; // 0 is valid element, but it's not invertible | ||
}, | ||
isZero: (num) => num === _0n, | ||
is0: (num) => num === _0n, | ||
isOdd: (num) => (num & _1n) === _1n, | ||
negate: (num) => mod(-num, ORDER), | ||
equals: (lhs, rhs) => lhs === rhs, | ||
square: (num) => mod(num * num, ORDER), | ||
neg: (num) => mod(-num, ORDER), | ||
eql: (lhs, rhs) => lhs === rhs, | ||
sqr: (num) => mod(num * num, ORDER), | ||
add: (lhs, rhs) => mod(lhs + rhs, ORDER), | ||
@@ -320,7 +327,7 @@ sub: (lhs, rhs) => mod(lhs - rhs, ORDER), | ||
// Same as above, but doesn't normalize | ||
squareN: (num) => num * num, | ||
sqrN: (num) => num * num, | ||
addN: (lhs, rhs) => lhs + rhs, | ||
subN: (lhs, rhs) => lhs - rhs, | ||
mulN: (lhs, rhs) => lhs * rhs, | ||
invert: (num) => invert(num, ORDER), | ||
inv: (num) => invert(num, ORDER), | ||
sqrt: redef.sqrt || ((n) => sqrtP(f, n)), | ||
@@ -331,7 +338,7 @@ invertBatch: (lst) => FpInvertBatch(f, lst), | ||
cmov: (a, b, c) => (c ? b : a), | ||
toBytes: (num) => isLE ? utils.numberToBytesLE(num, BYTES) : utils.numberToBytesBE(num, BYTES), | ||
toBytes: (num) => (isLE ? (0, utils_js_1.numberToBytesLE)(num, BYTES) : (0, utils_js_1.numberToBytesBE)(num, BYTES)), | ||
fromBytes: (bytes) => { | ||
if (bytes.length !== BYTES) | ||
throw new Error(`Fp.fromBytes: expected ${BYTES}, got ${bytes.length}`); | ||
return isLE ? utils.bytesToNumberLE(bytes) : utils.bytesToNumberBE(bytes); | ||
return isLE ? (0, utils_js_1.bytesToNumberLE)(bytes) : (0, utils_js_1.bytesToNumberBE)(bytes); | ||
}, | ||
@@ -346,3 +353,3 @@ }); | ||
const root = Fp.sqrt(elm); | ||
return Fp.isOdd(root) ? root : Fp.negate(root); | ||
return Fp.isOdd(root) ? root : Fp.neg(root); | ||
} | ||
@@ -354,4 +361,23 @@ exports.FpSqrtOdd = FpSqrtOdd; | ||
const root = Fp.sqrt(elm); | ||
return Fp.isOdd(root) ? Fp.negate(root) : root; | ||
return Fp.isOdd(root) ? Fp.neg(root) : root; | ||
} | ||
exports.FpSqrtEven = FpSqrtEven; | ||
/** | ||
* FIPS 186 B.4.1-compliant "constant-time" private key generation utility. | ||
* Can take (n+8) or more bytes of uniform input e.g. from CSPRNG or KDF | ||
* and convert them into private scalar, with the modulo bias being neglible. | ||
* Needs at least 40 bytes of input for 32-byte private key. | ||
* https://research.kudelskisecurity.com/2020/07/28/the-definitive-guide-to-modulo-bias-and-how-to-avoid-it/ | ||
* @param hash hash output from SHA3 or a similar function | ||
* @returns valid private scalar | ||
*/ | ||
function hashToPrivateScalar(hash, groupOrder, isLE = false) { | ||
hash = (0, utils_js_1.ensureBytes)(hash); | ||
const hashLen = hash.length; | ||
const minLen = nLength(groupOrder).nByteLength + 8; | ||
if (minLen < 24 || hashLen < minLen || hashLen > 1024) | ||
throw new Error(`hashToPrivateScalar: expected ${minLen}-1024 bytes of input, got ${hashLen}`); | ||
const num = isLE ? (0, utils_js_1.bytesToNumberLE)(hash) : (0, utils_js_1.bytesToNumberBE)(hash); | ||
return mod(num, groupOrder - _1n) + _1n; | ||
} | ||
exports.hashToPrivateScalar = hashToPrivateScalar; |
@@ -5,3 +5,3 @@ "use strict"; | ||
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */ | ||
const mod = require("./modular.js"); | ||
const modular_js_1 = require("./modular.js"); | ||
const utils_js_1 = require("./utils.js"); | ||
@@ -18,3 +18,3 @@ const _0n = BigInt(0); | ||
continue; // Optional | ||
if (!(0, utils_js_1.isPositiveInt)(curve[i])) | ||
if (!Number.isSafeInteger(curve[i])) | ||
throw new Error(`Invalid curve param ${i}=${curve[i]} (${typeof curve[i]})`); | ||
@@ -35,3 +35,2 @@ } | ||
// Set defaults | ||
// ...nLength(curve.n, curve.nBitLength), | ||
return Object.freeze({ ...curve }); | ||
@@ -44,3 +43,3 @@ } | ||
const { P } = CURVE; | ||
const modP = (a) => mod.mod(a, P); | ||
const modP = (a) => (0, modular_js_1.mod)(a, P); | ||
const montgomeryBits = CURVE.montgomeryBits; | ||
@@ -50,3 +49,3 @@ const montgomeryBytes = Math.ceil(montgomeryBits / 8); | ||
const adjustScalarBytes = CURVE.adjustScalarBytes || ((bytes) => bytes); | ||
const powPminus2 = CURVE.powPminus2 || ((x) => mod.pow(x, P - BigInt(2), P)); | ||
const powPminus2 = CURVE.powPminus2 || ((x) => (0, modular_js_1.pow)(x, P - BigInt(2), P)); | ||
/** | ||
@@ -53,0 +52,0 @@ * Checks for num to be in range: |
@@ -1,5 +0,3 @@ | ||
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */ | ||
import * as mod from './modular.js'; | ||
export declare type Hex = Uint8Array | string; | ||
export declare type PrivKey = Hex | bigint | number; | ||
export declare type PrivKey = Hex | bigint; | ||
export declare type CHash = { | ||
@@ -13,20 +11,4 @@ (message: Uint8Array | string): Uint8Array; | ||
}; | ||
export declare type BasicCurve<T> = { | ||
Fp: mod.Field<T>; | ||
n: bigint; | ||
nBitLength?: number; | ||
nByteLength?: number; | ||
h: bigint; | ||
hEff?: bigint; | ||
Gx: T; | ||
Gy: T; | ||
wrapPrivateKey?: boolean; | ||
allowInfinityPoint?: boolean; | ||
}; | ||
export declare function isPositiveInt(num: any): num is number; | ||
export declare function validateOpts<FP, T>(curve: BasicCurve<FP> & T): Readonly<{ | ||
readonly nBitLength: number; | ||
readonly nByteLength: number; | ||
} & BasicCurve<FP> & T>; | ||
export declare function bytesToHex(uint8a: Uint8Array): string; | ||
export declare type FHash = (message: Uint8Array | string) => Uint8Array; | ||
export declare function bytesToHex(bytes: Uint8Array): string; | ||
export declare function numberToHexUnpadded(num: number | bigint): string; | ||
@@ -36,21 +18,8 @@ export declare function hexToNumber(hex: string): bigint; | ||
export declare function bytesToNumberBE(bytes: Uint8Array): bigint; | ||
export declare function bytesToNumberLE(uint8a: Uint8Array): bigint; | ||
export declare function bytesToNumberLE(bytes: Uint8Array): bigint; | ||
export declare const numberToBytesBE: (n: bigint, len: number) => Uint8Array; | ||
export declare const numberToBytesLE: (n: bigint, len: number) => Uint8Array; | ||
export declare const numberToVarBytesBE: (n: bigint) => Uint8Array; | ||
export declare function ensureBytes(hex: Hex, expectedLength?: number): Uint8Array; | ||
export declare function concatBytes(...arrays: Uint8Array[]): Uint8Array; | ||
export declare function nLength(n: bigint, nBitLength?: number): { | ||
nBitLength: number; | ||
nByteLength: 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 | ||
* and convert them into private scalar, with the modulo bias being neglible. | ||
* Needs at least 40 bytes of input for 32-byte private key. | ||
* https://research.kudelskisecurity.com/2020/07/28/the-definitive-guide-to-modulo-bias-and-how-to-avoid-it/ | ||
* @param hash hash output from SHA3 or a similar function | ||
* @returns valid private scalar | ||
*/ | ||
export declare function hashToPrivateScalar(hash: Hex, groupOrder: bigint, isLE?: boolean): bigint; | ||
export declare function equalBytes(b1: Uint8Array, b2: Uint8Array): boolean; | ||
@@ -57,0 +26,0 @@ export declare function bitLen(n: bigint): number; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.bitMask = exports.bitSet = exports.bitGet = exports.bitLen = exports.equalBytes = exports.hashToPrivateScalar = exports.nLength = exports.concatBytes = exports.ensureBytes = exports.numberToBytesLE = exports.numberToBytesBE = exports.bytesToNumberLE = exports.bytesToNumberBE = exports.hexToBytes = exports.hexToNumber = exports.numberToHexUnpadded = exports.bytesToHex = exports.validateOpts = exports.isPositiveInt = void 0; | ||
exports.bitMask = exports.bitSet = exports.bitGet = exports.bitLen = exports.equalBytes = exports.concatBytes = exports.ensureBytes = exports.numberToVarBytesBE = exports.numberToBytesLE = exports.numberToBytesBE = exports.bytesToNumberLE = exports.bytesToNumberBE = exports.hexToBytes = exports.hexToNumber = exports.numberToHexUnpadded = exports.bytesToHex = void 0; | ||
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */ | ||
const mod = require("./modular.js"); | ||
const _0n = BigInt(0); | ||
const _1n = BigInt(1); | ||
const _2n = BigInt(2); | ||
// Bans floats and integers above 2^53-1 | ||
function isPositiveInt(num) { | ||
return typeof num === 'number' && Number.isSafeInteger(num) && num > 0; | ||
} | ||
exports.isPositiveInt = isPositiveInt; | ||
function validateOpts(curve) { | ||
mod.validateField(curve.Fp); | ||
for (const i of ['n', 'h']) { | ||
const val = curve[i]; | ||
if (typeof val !== 'bigint') | ||
throw new Error(`Invalid curve param ${i}=${val} (${typeof val})`); | ||
} | ||
if (!curve.Fp.isValid(curve.Gx)) | ||
throw new Error('Invalid generator X coordinate Fp element'); | ||
if (!curve.Fp.isValid(curve.Gy)) | ||
throw new Error('Invalid generator Y coordinate Fp element'); | ||
for (const i of ['nBitLength', 'nByteLength']) { | ||
const val = curve[i]; | ||
if (val === undefined) | ||
continue; // Optional | ||
if (!isPositiveInt(val)) | ||
throw new Error(`Invalid curve param ${i}=${val} (${typeof val})`); | ||
} | ||
// Set defaults | ||
return Object.freeze({ ...nLength(curve.n, curve.nBitLength), ...curve }); | ||
} | ||
exports.validateOpts = validateOpts; | ||
const u8a = (a) => a instanceof Uint8Array; | ||
const hexes = Array.from({ length: 256 }, (v, i) => i.toString(16).padStart(2, '0')); | ||
function bytesToHex(uint8a) { | ||
if (!(uint8a instanceof Uint8Array)) | ||
function bytesToHex(bytes) { | ||
if (!u8a(bytes)) | ||
throw new Error('Expected Uint8Array'); | ||
// pre-caching improves the speed 6x | ||
let hex = ''; | ||
for (let i = 0; i < uint8a.length; i++) { | ||
hex += hexes[uint8a[i]]; | ||
for (let i = 0; i < bytes.length; i++) { | ||
hex += hexes[bytes[i]]; | ||
} | ||
@@ -54,5 +27,4 @@ return hex; | ||
function hexToNumber(hex) { | ||
if (typeof hex !== 'string') { | ||
throw new TypeError('hexToNumber: expected string, got ' + typeof hex); | ||
} | ||
if (typeof hex !== 'string') | ||
throw new Error('hexToNumber: expected string, got ' + typeof hex); | ||
// Big Endian | ||
@@ -64,5 +36,4 @@ return BigInt(`0x${hex}`); | ||
function hexToBytes(hex) { | ||
if (typeof hex !== 'string') { | ||
throw new TypeError('hexToBytes: expected string, got ' + typeof hex); | ||
} | ||
if (typeof hex !== 'string') | ||
throw new Error('hexToBytes: expected string, got ' + typeof hex); | ||
if (hex.length % 2) | ||
@@ -87,6 +58,6 @@ throw new Error('hexToBytes: received invalid unpadded hex ' + hex.length); | ||
exports.bytesToNumberBE = bytesToNumberBE; | ||
function bytesToNumberLE(uint8a) { | ||
if (!(uint8a instanceof Uint8Array)) | ||
function bytesToNumberLE(bytes) { | ||
if (!u8a(bytes)) | ||
throw new Error('Expected Uint8Array'); | ||
return BigInt('0x' + bytesToHex(Uint8Array.from(uint8a).reverse())); | ||
return hexToNumber(bytesToHex(Uint8Array.from(bytes).reverse())); | ||
} | ||
@@ -98,6 +69,14 @@ exports.bytesToNumberLE = bytesToNumberLE; | ||
exports.numberToBytesLE = numberToBytesLE; | ||
// Returns variable number bytes (minimal bigint encoding?) | ||
const numberToVarBytesBE = (n) => { | ||
let hex = n.toString(16); | ||
if (hex.length & 1) | ||
hex = '0' + hex; | ||
return hexToBytes(hex); | ||
}; | ||
exports.numberToVarBytesBE = numberToVarBytesBE; | ||
function ensureBytes(hex, expectedLength) { | ||
// Uint8Array.from() instead of hash.slice() because node.js Buffer | ||
// is instance of Uint8Array, and its slice() creates **mutable** copy | ||
const bytes = hex instanceof Uint8Array ? Uint8Array.from(hex) : hexToBytes(hex); | ||
const bytes = u8a(hex) ? Uint8Array.from(hex) : hexToBytes(hex); | ||
if (typeof expectedLength === 'number' && bytes.length !== expectedLength) | ||
@@ -110,3 +89,3 @@ throw new Error(`Expected ${expectedLength} bytes`); | ||
function concatBytes(...arrays) { | ||
if (!arrays.every((b) => b instanceof Uint8Array)) | ||
if (!arrays.every((b) => u8a(b))) | ||
throw new Error('Uint8Array list expected'); | ||
@@ -125,29 +104,2 @@ if (arrays.length === 1) | ||
exports.concatBytes = concatBytes; | ||
// CURVE.n lengths | ||
function nLength(n, nBitLength) { | ||
// Bit size, byte size of CURVE.n | ||
const _nBitLength = nBitLength !== undefined ? nBitLength : n.toString(2).length; | ||
const nByteLength = Math.ceil(_nBitLength / 8); | ||
return { nBitLength: _nBitLength, nByteLength }; | ||
} | ||
exports.nLength = nLength; | ||
/** | ||
* FIPS 186 B.4.1-compliant "constant-time" private key generation utility. | ||
* Can take (n+8) or more bytes of uniform input e.g. from CSPRNG or KDF | ||
* and convert them into private scalar, with the modulo bias being neglible. | ||
* Needs at least 40 bytes of input for 32-byte private key. | ||
* https://research.kudelskisecurity.com/2020/07/28/the-definitive-guide-to-modulo-bias-and-how-to-avoid-it/ | ||
* @param hash hash output from SHA3 or a similar function | ||
* @returns valid private scalar | ||
*/ | ||
function hashToPrivateScalar(hash, groupOrder, isLE = false) { | ||
hash = ensureBytes(hash); | ||
const hashLen = hash.length; | ||
const minLen = nLength(groupOrder).nByteLength + 8; | ||
if (minLen < 24 || hashLen < minLen || hashLen > 1024) | ||
throw new Error(`hashToPrivateScalar: expected ${minLen}-1024 bytes of input, got ${hashLen}`); | ||
const num = isLE ? bytesToNumberLE(hash) : bytesToNumberBE(hash); | ||
return mod.mod(num, groupOrder - _1n) + _1n; | ||
} | ||
exports.hashToPrivateScalar = hashToPrivateScalar; | ||
function equalBytes(b1, b2) { | ||
@@ -154,0 +106,0 @@ // We don't care about timing attacks here |
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */ | ||
import * as mod from './modular.js'; | ||
import * as ut from './utils.js'; | ||
import { Hex, PrivKey } from './utils.js'; | ||
import { htfOpts } from './hash-to-curve.js'; | ||
import { Group, GroupConstructor } from './group.js'; | ||
import { Hex, PrivKey, CHash } from './utils.js'; | ||
import { Group, GroupConstructor, AbstractCurve, AffinePoint } from './curve.js'; | ||
export type { AffinePoint }; | ||
declare type HmacFnSync = (key: Uint8Array, ...messages: Uint8Array[]) => Uint8Array; | ||
@@ -17,3 +17,3 @@ declare type EndomorphismOpts = { | ||
}; | ||
export declare type BasicCurve<T> = ut.BasicCurve<T> & { | ||
export declare type BasicCurve<T> = AbstractCurve<T> & { | ||
a: T; | ||
@@ -24,9 +24,4 @@ b: T; | ||
endo?: EndomorphismOpts; | ||
isTorsionFree?: (c: ProjectiveConstructor<T>, point: ProjectivePointType<T>) => boolean; | ||
clearCofactor?: (c: ProjectiveConstructor<T>, point: ProjectivePointType<T>) => ProjectivePointType<T>; | ||
htfDefaults?: htfOpts; | ||
mapToCurve?: (scalar: bigint[]) => { | ||
x: T; | ||
y: T; | ||
}; | ||
isTorsionFree?: (c: ProjConstructor<T>, point: ProjPointType<T>) => boolean; | ||
clearCofactor?: (c: ProjConstructor<T>, point: ProjPointType<T>) => ProjPointType<T>; | ||
}; | ||
@@ -37,3 +32,8 @@ declare type Entropy = Hex | true; | ||
extraEntropy?: Entropy; | ||
prehash?: boolean; | ||
}; | ||
export declare type VerOpts = { | ||
lowS?: boolean; | ||
prehash?: boolean; | ||
}; | ||
/** | ||
@@ -60,43 +60,31 @@ * ### Design rationale for types | ||
*/ | ||
export interface ProjectivePointType<T> extends Group<ProjectivePointType<T>> { | ||
readonly x: T; | ||
readonly y: T; | ||
readonly z: T; | ||
multiply(scalar: number | bigint, affinePoint?: PointType<T>): ProjectivePointType<T>; | ||
multiplyUnsafe(scalar: bigint): ProjectivePointType<T>; | ||
toAffine(invZ?: T): PointType<T>; | ||
} | ||
export interface ProjectiveConstructor<T> extends GroupConstructor<ProjectivePointType<T>> { | ||
new (x: T, y: T, z: T): ProjectivePointType<T>; | ||
fromAffine(p: PointType<T>): ProjectivePointType<T>; | ||
toAffineBatch(points: ProjectivePointType<T>[]): PointType<T>[]; | ||
normalizeZ(points: ProjectivePointType<T>[]): ProjectivePointType<T>[]; | ||
} | ||
export interface PointType<T> extends Group<PointType<T>> { | ||
readonly x: T; | ||
readonly y: T; | ||
export interface ProjPointType<T> extends Group<ProjPointType<T>> { | ||
readonly px: T; | ||
readonly py: T; | ||
readonly pz: T; | ||
multiply(scalar: bigint): ProjPointType<T>; | ||
multiplyUnsafe(scalar: bigint): ProjPointType<T>; | ||
multiplyAndAddUnsafe(Q: ProjPointType<T>, a: bigint, b: bigint): ProjPointType<T> | undefined; | ||
_setWindowSize(windowSize: number): void; | ||
toAffine(iz?: T): AffinePoint<T>; | ||
isTorsionFree(): boolean; | ||
clearCofactor(): ProjPointType<T>; | ||
assertValidity(): void; | ||
hasEvenY(): boolean; | ||
toRawBytes(isCompressed?: boolean): Uint8Array; | ||
toHex(isCompressed?: boolean): string; | ||
assertValidity(): void; | ||
multiplyAndAddUnsafe(Q: PointType<T>, a: bigint, b: bigint): PointType<T> | undefined; | ||
} | ||
export interface PointConstructor<T> extends GroupConstructor<PointType<T>> { | ||
new (x: T, y: T): PointType<T>; | ||
fromHex(hex: Hex): PointType<T>; | ||
fromPrivateKey(privateKey: PrivKey): PointType<T>; | ||
hashToCurve(msg: Hex, options?: Partial<htfOpts>): PointType<T>; | ||
encodeToCurve(msg: Hex, options?: Partial<htfOpts>): PointType<T>; | ||
export interface ProjConstructor<T> extends GroupConstructor<ProjPointType<T>> { | ||
new (x: T, y: T, z: T): ProjPointType<T>; | ||
fromAffine(p: AffinePoint<T>): ProjPointType<T>; | ||
fromHex(hex: Hex): ProjPointType<T>; | ||
fromPrivateKey(privateKey: PrivKey): ProjPointType<T>; | ||
normalizeZ(points: ProjPointType<T>[]): ProjPointType<T>[]; | ||
} | ||
export declare type CurvePointsType<T> = BasicCurve<T> & { | ||
fromBytes: (bytes: Uint8Array) => { | ||
x: T; | ||
y: T; | ||
}; | ||
toBytes: (c: PointConstructor<T>, point: PointType<T>, compressed: boolean) => Uint8Array; | ||
fromBytes: (bytes: Uint8Array) => AffinePoint<T>; | ||
toBytes: (c: ProjConstructor<T>, point: ProjPointType<T>, compressed: boolean) => Uint8Array; | ||
}; | ||
export declare type CurvePointsRes<T> = { | ||
Point: PointConstructor<T>; | ||
ProjectivePoint: ProjectiveConstructor<T>; | ||
ProjectivePoint: ProjConstructor<T>; | ||
normalizePrivateKey: (key: PrivKey) => bigint; | ||
@@ -107,4 +95,3 @@ weierstrassEquation: (x: T) => T; | ||
export declare function weierstrassPoints<T>(opts: CurvePointsType<T>): { | ||
Point: PointConstructor<T>; | ||
ProjectivePoint: ProjectiveConstructor<T>; | ||
ProjectivePoint: ProjConstructor<T>; | ||
normalizePrivateKey: (key: PrivKey) => bigint; | ||
@@ -119,10 +106,10 @@ weierstrassEquation: (x: T) => T; | ||
assertValidity(): void; | ||
copyWithRecoveryBit(recovery: number): SignatureType; | ||
addRecoveryBit(recovery: number): SignatureType; | ||
hasHighS(): boolean; | ||
normalizeS(): SignatureType; | ||
recoverPublicKey(msgHash: Hex): PointType<bigint>; | ||
recoverPublicKey(msgHash: Hex): ProjPointType<bigint>; | ||
toCompactRawBytes(): Uint8Array; | ||
toCompactHex(): string; | ||
toDERRawBytes(isCompressed?: boolean): Uint8Array; | ||
toDERHex(isCompressed?: boolean): string; | ||
toCompactRawBytes(): Uint8Array; | ||
toCompactHex(): string; | ||
} | ||
@@ -134,9 +121,14 @@ export declare type SignatureConstructor = { | ||
}; | ||
export declare type PubKey = Hex | PointType<bigint>; | ||
declare type SignatureLike = { | ||
r: bigint; | ||
s: bigint; | ||
}; | ||
export declare type PubKey = Hex | ProjPointType<bigint>; | ||
export declare type CurveType = BasicCurve<bigint> & { | ||
lowS?: boolean; | ||
hash: ut.CHash; | ||
hash: CHash; | ||
hmac: HmacFnSync; | ||
randomBytes: (bytesLength?: number) => Uint8Array; | ||
truncateHash?: (hash: Uint8Array, truncateOnly?: boolean) => bigint; | ||
bits2int?: (bytes: Uint8Array) => bigint; | ||
bits2int_modN?: (bytes: Uint8Array) => bigint; | ||
}; | ||
@@ -158,9 +150,4 @@ declare function validateOpts(curve: CurveType): Readonly<{ | ||
readonly endo?: EndomorphismOpts | undefined; | ||
readonly isTorsionFree?: ((c: ProjectiveConstructor<bigint>, point: ProjectivePointType<bigint>) => boolean) | undefined; | ||
readonly clearCofactor?: ((c: ProjectiveConstructor<bigint>, point: ProjectivePointType<bigint>) => ProjectivePointType<bigint>) | undefined; | ||
readonly htfDefaults?: htfOpts | undefined; | ||
readonly mapToCurve?: ((scalar: bigint[]) => { | ||
x: bigint; | ||
y: bigint; | ||
}) | undefined; | ||
readonly isTorsionFree?: ((c: ProjConstructor<bigint>, point: ProjPointType<bigint>) => boolean) | undefined; | ||
readonly clearCofactor?: ((c: ProjConstructor<bigint>, point: ProjPointType<bigint>) => ProjPointType<bigint>) | undefined; | ||
lowS: boolean; | ||
@@ -170,3 +157,4 @@ readonly hash: ut.CHash; | ||
readonly randomBytes: (bytesLength?: number | undefined) => Uint8Array; | ||
readonly truncateHash?: ((hash: Uint8Array, truncateOnly?: boolean | undefined) => bigint) | undefined; | ||
readonly bits2int?: ((bytes: Uint8Array) => bigint) | undefined; | ||
readonly bits2int_modN?: ((bytes: Uint8Array) => bigint) | undefined; | ||
}>; | ||
@@ -176,19 +164,9 @@ export declare type CurveFn = { | ||
getPublicKey: (privateKey: PrivKey, isCompressed?: boolean) => Uint8Array; | ||
getSharedSecret: (privateA: PrivKey, publicB: PubKey, isCompressed?: boolean) => Uint8Array; | ||
getSharedSecret: (privateA: PrivKey, publicB: Hex, isCompressed?: boolean) => Uint8Array; | ||
sign: (msgHash: Hex, privKey: PrivKey, opts?: SignOpts) => SignatureType; | ||
signUnhashed: (msg: Uint8Array, privKey: PrivKey, opts?: SignOpts) => SignatureType; | ||
verify: (signature: Hex | SignatureType, msgHash: Hex, publicKey: PubKey, opts?: { | ||
lowS?: boolean; | ||
}) => boolean; | ||
Point: PointConstructor<bigint>; | ||
ProjectivePoint: ProjectiveConstructor<bigint>; | ||
verify: (signature: Hex | SignatureLike, msgHash: Hex, publicKey: Hex, opts?: VerOpts) => boolean; | ||
ProjectivePoint: ProjConstructor<bigint>; | ||
Signature: SignatureConstructor; | ||
utils: { | ||
_bigintToBytes: (num: bigint) => Uint8Array; | ||
_bigintToString: (num: bigint) => string; | ||
_normalizePrivateKey: (key: PrivKey) => bigint; | ||
_normalizePublicKey: (publicKey: PubKey) => PointType<bigint>; | ||
_isWithinCurveOrder: (num: bigint) => boolean; | ||
_isValidFieldElement: (num: bigint) => boolean; | ||
_weierstrassEquation: (x: bigint) => bigint; | ||
isValidPrivateKey(privateKey: PrivKey): boolean; | ||
@@ -212,2 +190,1 @@ hashToPrivateKey: (hash: Hex) => Uint8Array; | ||
}; | ||
export {}; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.mapToCurveSimpleSWU = exports.SWUFpSqrtRatio = exports.weierstrass = exports.weierstrassPoints = void 0; | ||
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */ | ||
// Short Weierstrass curve. The formula is: y² = x³ + ax + b | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.mapToCurveSimpleSWU = exports.SWUFpSqrtRatio = exports.weierstrass = exports.weierstrassPoints = void 0; | ||
// Differences from @noble/secp256k1 1.7: | ||
// 1. Different double() formula (but same addition) | ||
// 2. Different sqrt() function | ||
// 3. truncateHash() truncateOnly mode | ||
// 4. DRBG supports outputLen bigger than outputLen of hmac | ||
// 5. Support for different hash functions | ||
const mod = require("./modular.js"); | ||
const ut = require("./utils.js"); | ||
const utils_js_1 = require("./utils.js"); | ||
const hash_to_curve_js_1 = require("./hash-to-curve.js"); | ||
const group_js_1 = require("./group.js"); | ||
// ASN.1 DER encoding utilities | ||
class DERError extends Error { | ||
constructor(message) { | ||
super(message); | ||
} | ||
} | ||
const DER = { | ||
slice(s) { | ||
// Proof: any([(i>=0x80) == (int(hex(i).replace('0x', '').zfill(2)[0], 16)>=8) for i in range(0, 256)]) | ||
// Padding done by numberToHex | ||
return Number.parseInt(s[0], 16) >= 8 ? '00' + s : s; | ||
}, | ||
parseInt(data) { | ||
if (data.length < 2 || data[0] !== 0x02) { | ||
throw new DERError(`Invalid signature integer tag: ${(0, utils_js_1.bytesToHex)(data)}`); | ||
} | ||
const len = data[1]; | ||
const res = data.subarray(2, len + 2); | ||
if (!len || res.length !== len) { | ||
throw new DERError(`Invalid signature integer: wrong length`); | ||
} | ||
// Strange condition, its not about length, but about first bytes of number. | ||
if (res[0] === 0x00 && res[1] <= 0x7f) { | ||
throw new DERError('Invalid signature integer: trailing length'); | ||
} | ||
return { data: ut.bytesToNumberBE(res), left: data.subarray(len + 2) }; | ||
}, | ||
parseSig(data) { | ||
if (data.length < 2 || data[0] != 0x30) { | ||
throw new DERError(`Invalid signature tag: ${(0, utils_js_1.bytesToHex)(data)}`); | ||
} | ||
if (data[1] !== data.length - 2) { | ||
throw new DERError('Invalid signature: incorrect length'); | ||
} | ||
const { data: r, left: sBytes } = DER.parseInt(data.subarray(2)); | ||
const { data: s, left: rBytesLeft } = DER.parseInt(sBytes); | ||
if (rBytesLeft.length) { | ||
throw new DERError(`Invalid signature: left bytes after parsing: ${(0, utils_js_1.bytesToHex)(rBytesLeft)}`); | ||
} | ||
return { r, s }; | ||
}, | ||
}; | ||
const curve_js_1 = require("./curve.js"); | ||
function validatePointOpts(curve) { | ||
const opts = ut.validateOpts(curve); | ||
const opts = (0, curve_js_1.validateAbsOpts)(curve); | ||
const Fp = opts.Fp; | ||
@@ -66,3 +17,3 @@ for (const i of ['a', 'b']) { | ||
} | ||
for (const i of ['isTorsionFree', 'clearCofactor', 'mapToCurve']) { | ||
for (const i of ['isTorsionFree', 'clearCofactor']) { | ||
if (curve[i] === undefined) | ||
@@ -75,3 +26,3 @@ continue; // Optional | ||
if (endo) { | ||
if (!Fp.equals(opts.a, Fp.ZERO)) { | ||
if (!Fp.eql(opts.a, Fp.ZERO)) { | ||
throw new Error('Endomorphism can only be defined for Koblitz curves that have a=0'); | ||
@@ -89,12 +40,62 @@ } | ||
throw new Error('Invalid fromBytes function'); | ||
// Requires including hashToCurve file | ||
if (opts.htfDefaults !== undefined) | ||
(0, hash_to_curve_js_1.validateHTFOpts)(opts.htfDefaults); | ||
// Set defaults | ||
return Object.freeze({ ...opts }); | ||
} | ||
// ASN.1 DER encoding utilities | ||
const { bytesToNumberBE: b2n, hexToBytes: h2b } = ut; | ||
const DER = { | ||
// asn.1 DER encoding utils | ||
Err: class DERErr extends Error { | ||
constructor(m = '') { | ||
super(m); | ||
} | ||
}, | ||
_parseInt(data) { | ||
const { Err: E } = DER; | ||
if (data.length < 2 || data[0] !== 0x02) | ||
throw new E('Invalid signature integer tag'); | ||
const len = data[1]; | ||
const res = data.subarray(2, len + 2); | ||
if (!len || res.length !== len) | ||
throw new E('Invalid signature integer: wrong length'); | ||
if (res[0] === 0x00 && res[1] <= 0x7f) | ||
throw new E('Invalid signature integer: trailing length'); | ||
// ^ Weird condition: not about length, but about first bytes of number. | ||
return { d: b2n(res), l: data.subarray(len + 2) }; // d is data, l is left | ||
}, | ||
toSig(hex) { | ||
// parse DER signature | ||
const { Err: E } = DER; | ||
const data = typeof hex === 'string' ? h2b(hex) : hex; | ||
if (!(data instanceof Uint8Array)) | ||
throw new Error('ui8a expected'); | ||
let l = data.length; | ||
if (l < 2 || data[0] != 0x30) | ||
throw new E('Invalid signature tag'); | ||
if (data[1] !== l - 2) | ||
throw new E('Invalid signature: incorrect length'); | ||
const { d: r, l: sBytes } = DER._parseInt(data.subarray(2)); | ||
const { d: s, l: rBytesLeft } = DER._parseInt(sBytes); | ||
if (rBytesLeft.length) | ||
throw new E('Invalid signature: left bytes after parsing'); | ||
return { r, s }; | ||
}, | ||
hexFromSig(sig) { | ||
const slice = (s) => (Number.parseInt(s[0], 16) >= 8 ? '00' + s : s); // slice DER | ||
const h = (num) => { | ||
const hex = num.toString(16); | ||
return hex.length & 1 ? `0${hex}` : hex; | ||
}; | ||
const s = slice(h(sig.s)); | ||
const r = slice(h(sig.r)); | ||
const shl = s.length / 2; | ||
const rhl = r.length / 2; | ||
const sl = h(shl); | ||
const rl = h(rhl); | ||
return `30${h(rhl + shl + 4)}02${rl}${r}02${sl}${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); | ||
function weierstrassPoints(opts) { | ||
@@ -109,3 +110,3 @@ const CURVE = validatePointOpts(opts); | ||
const { a, b } = CURVE; | ||
const x2 = Fp.square(x); // x * x | ||
const x2 = Fp.sqr(x); // x * x | ||
const x3 = Fp.mul(x2, x); // x2 * x | ||
@@ -116,4 +117,8 @@ return Fp.add(Fp.add(x3, Fp.mul(x, a)), b); // x3 + a * x + b | ||
function isWithinCurveOrder(num) { | ||
return _0n < num && num < CURVE.n; | ||
return typeof num === 'bigint' && _0n < num && num < CURVE.n; | ||
} | ||
function assertGE(num) { | ||
if (!isWithinCurveOrder(num)) | ||
throw new Error('Expected valid bigint: 0 < bigint < curve.n'); | ||
} | ||
/** | ||
@@ -126,3 +131,3 @@ * Validates if a private key is valid and converts it to bigint form. | ||
function normalizePrivateKey(key) { | ||
const { normalizePrivateKey: custom, nByteLength: groupLen, wrapPrivateKey, n: order } = CURVE; | ||
const { normalizePrivateKey: custom, nByteLength: groupLen, wrapPrivateKey, n } = CURVE; | ||
if (typeof custom === 'function') | ||
@@ -135,36 +140,26 @@ key = custom(key); | ||
} | ||
else if (ut.isPositiveInt(key)) { | ||
num = BigInt(key); | ||
} | ||
else if (typeof key === 'string') { | ||
if (key.length !== 2 * groupLen) | ||
throw new Error(`Expected ${groupLen} bytes of private key`); | ||
throw new Error(`must be ${groupLen} bytes`); | ||
// Validates individual octets | ||
num = ut.hexToNumber(key); | ||
num = ut.bytesToNumberBE((0, utils_js_1.ensureBytes)(key)); | ||
} | ||
else if (key instanceof Uint8Array) { | ||
if (key.length !== groupLen) | ||
throw new Error(`Expected ${groupLen} bytes of private key`); | ||
throw new Error(`must be ${groupLen} bytes`); | ||
num = ut.bytesToNumberBE(key); | ||
} | ||
else { | ||
throw new TypeError('Expected valid private key'); | ||
throw new Error('private key must be bytes, hex or bigint, not ' + typeof key); | ||
} | ||
// 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'); | ||
num = mod.mod(num, n); | ||
assertGE(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) { | ||
if (ut.isPositiveInt(num)) | ||
return BigInt(num); | ||
if (typeof num === 'bigint' && isWithinCurveOrder(num)) | ||
return num; | ||
throw new TypeError('Expected valid private scalar: 0 < scalar < curve.n'); | ||
const pointPrecomputes = new Map(); | ||
function assertPrjPoint(other) { | ||
if (!(other instanceof Point)) | ||
throw new Error('ProjectivePoint expected'); | ||
} | ||
@@ -176,17 +171,32 @@ /** | ||
*/ | ||
class ProjectivePoint { | ||
constructor(x, y, z) { | ||
this.x = x; | ||
this.y = y; | ||
this.z = z; | ||
class Point { | ||
constructor(px, py, pz) { | ||
this.px = px; | ||
this.py = py; | ||
this.pz = pz; | ||
if (px == null || !Fp.isValid(px)) | ||
throw new Error('x required'); | ||
if (py == null || !Fp.isValid(py)) | ||
throw new Error('y required'); | ||
if (pz == null || !Fp.isValid(pz)) | ||
throw new Error('z required'); | ||
} | ||
static fromAffine(p) { | ||
if (!(p instanceof Point)) { | ||
throw new TypeError('ProjectivePoint#fromAffine: expected Point'); | ||
} | ||
const { x, y } = p || {}; | ||
if (!p || !Fp.isValid(x) || !Fp.isValid(y)) | ||
throw new Error('invalid affine point'); | ||
if (p instanceof Point) | ||
throw new Error('projective point not allowed'); | ||
const is0 = (i) => Fp.eql(i, Fp.ZERO); | ||
// fromAffine(x:0, y:0) would produce (x:0, y:0, z:1), but we need (x:0, y:1, z:0) | ||
if (p.equals(Point.ZERO)) | ||
return ProjectivePoint.ZERO; | ||
return new ProjectivePoint(p.x, p.y, Fp.ONE); | ||
if (is0(x) && is0(y)) | ||
return Point.ZERO; | ||
return new Point(x, y, Fp.ONE); | ||
} | ||
get x() { | ||
return this.toAffine().x; | ||
} | ||
get y() { | ||
return this.toAffine().y; | ||
} | ||
/** | ||
@@ -196,13 +206,52 @@ * Takes a bunch of Projective Points but executes only one | ||
* so this improves performance massively. | ||
* Optimization: converts a list of projective points to a list of identical points with Z=1. | ||
*/ | ||
static toAffineBatch(points) { | ||
const toInv = Fp.invertBatch(points.map((p) => p.z)); | ||
return points.map((p, i) => p.toAffine(toInv[i])); | ||
static normalizeZ(points) { | ||
const toInv = Fp.invertBatch(points.map((p) => p.pz)); | ||
return points.map((p, i) => p.toAffine(toInv[i])).map(Point.fromAffine); | ||
} | ||
/** | ||
* Optimization: converts a list of projective points to a list of identical points with Z=1. | ||
* Converts hash string or Uint8Array to Point. | ||
* @param hex short/long ECDSA hex | ||
*/ | ||
static normalizeZ(points) { | ||
return ProjectivePoint.toAffineBatch(points).map(ProjectivePoint.fromAffine); | ||
static fromHex(hex) { | ||
const P = Point.fromAffine(CURVE.fromBytes((0, utils_js_1.ensureBytes)(hex))); | ||
P.assertValidity(); | ||
return P; | ||
} | ||
// Multiplies generator point by privateKey. | ||
static fromPrivateKey(privateKey) { | ||
return Point.BASE.multiply(normalizePrivateKey(privateKey)); | ||
} | ||
// "Private method", don't use it directly | ||
_setWindowSize(windowSize) { | ||
this._WINDOW_SIZE = windowSize; | ||
pointPrecomputes.delete(this); | ||
} | ||
// A point on curve is valid if it conforms to equation. | ||
assertValidity() { | ||
// Zero is valid point too! | ||
if (this.is0()) { | ||
if (CURVE.allowInfinityPoint) | ||
return; | ||
throw new Error('bad point: ZERO'); | ||
} | ||
// Some 3rd-party test vectors require different wording between here & `fromCompressedHex` | ||
const { x, y } = this.toAffine(); | ||
// Check if x, y are valid field elements | ||
if (!Fp.isValid(x) || !Fp.isValid(y)) | ||
throw new Error('bad point: x or y not FE'); | ||
const left = Fp.sqr(y); // y² | ||
const right = weierstrassEquation(x); // x³ + ax + b | ||
if (!Fp.eql(left, right)) | ||
throw new Error('bad point: equation left != right'); | ||
if (!this.isTorsionFree()) | ||
throw new Error('bad point: not in prime-order subgroup'); | ||
} | ||
hasEvenY() { | ||
const { y } = this.toAffine(); | ||
if (Fp.isOdd) | ||
return !Fp.isOdd(y); | ||
throw new Error("Field doesn't support isOdd"); | ||
} | ||
/** | ||
@@ -213,6 +262,6 @@ * Compare one point to another. | ||
assertPrjPoint(other); | ||
const { x: X1, y: Y1, z: Z1 } = this; | ||
const { x: X2, y: Y2, z: Z2 } = other; | ||
const U1 = Fp.equals(Fp.mul(X1, Z2), Fp.mul(X2, Z1)); | ||
const U2 = Fp.equals(Fp.mul(Y1, Z2), Fp.mul(Y2, Z1)); | ||
const { px: X1, py: Y1, pz: Z1 } = this; | ||
const { px: X2, py: Y2, pz: Z2 } = other; | ||
const U1 = Fp.eql(Fp.mul(X1, Z2), Fp.mul(X2, Z1)); | ||
const U2 = Fp.eql(Fp.mul(Y1, Z2), Fp.mul(Y2, Z1)); | ||
return U1 && U2; | ||
@@ -224,3 +273,3 @@ } | ||
negate() { | ||
return new ProjectivePoint(this.x, Fp.negate(this.y), this.z); | ||
return new Point(this.px, Fp.neg(this.py), this.pz); | ||
} | ||
@@ -234,3 +283,3 @@ // Renes-Costello-Batina exception-free doubling formula. | ||
const b3 = Fp.mul(b, 3n); | ||
const { x: X1, y: Y1, z: Z1 } = this; | ||
const { px: X1, py: Y1, pz: Z1 } = this; | ||
let X3 = Fp.ZERO, Y3 = Fp.ZERO, Z3 = Fp.ZERO; // prettier-ignore | ||
@@ -268,3 +317,3 @@ let t0 = Fp.mul(X1, X1); // step 1 | ||
Z3 = Fp.add(Z3, Z3); | ||
return new ProjectivePoint(X3, Y3, Z3); | ||
return new Point(X3, Y3, Z3); | ||
} | ||
@@ -277,4 +326,4 @@ // Renes-Costello-Batina exception-free addition formula. | ||
assertPrjPoint(other); | ||
const { x: X1, y: Y1, z: Z1 } = this; | ||
const { x: X2, y: Y2, z: Z2 } = other; | ||
const { px: X1, py: Y1, pz: Z1 } = this; | ||
const { px: X2, py: Y2, pz: Z2 } = other; | ||
let X3 = Fp.ZERO, Y3 = Fp.ZERO, Z3 = Fp.ZERO; // prettier-ignore | ||
@@ -323,3 +372,3 @@ const a = CURVE.a; | ||
Z3 = Fp.add(Z3, t0); // step 40 | ||
return new ProjectivePoint(X3, Y3, Z3); | ||
return new Point(X3, Y3, Z3); | ||
} | ||
@@ -329,2 +378,11 @@ subtract(other) { | ||
} | ||
is0() { | ||
return this.equals(Point.ZERO); | ||
} | ||
wNAF(n) { | ||
return wnaf.wNAFCached(this, pointPrecomputes, n, (comp) => { | ||
const toInv = Fp.invertBatch(comp.map((p) => p.pz)); | ||
return comp.map((p, i) => p.toAffine(toInv[i])).map(Point.fromAffine); | ||
}); | ||
} | ||
/** | ||
@@ -335,16 +393,16 @@ * Non-constant-time multiplication. Uses double-and-add algorithm. | ||
*/ | ||
multiplyUnsafe(scalar) { | ||
const P0 = ProjectivePoint.ZERO; | ||
if (typeof scalar === 'bigint' && scalar === _0n) | ||
return P0; | ||
// Will throw on 0 | ||
let n = normalizeScalar(scalar); | ||
multiplyUnsafe(n) { | ||
const I = Point.ZERO; | ||
if (n === _0n) | ||
return I; | ||
assertGE(n); // Will throw on 0 | ||
if (n === _1n) | ||
return this; | ||
if (!CURVE.endo) | ||
const { endo } = CURVE; | ||
if (!endo) | ||
return wnaf.unsafeLadder(this, n); | ||
// Apply endomorphism | ||
let { k1neg, k1, k2neg, k2 } = CURVE.endo.splitScalar(n); | ||
let k1p = P0; | ||
let k2p = P0; | ||
let { k1neg, k1, k2neg, k2 } = endo.splitScalar(n); | ||
let k1p = I; | ||
let k2p = I; | ||
let d = this; | ||
@@ -364,24 +422,6 @@ while (k1 > _0n || k2 > _0n) { | ||
k2p = k2p.negate(); | ||
k2p = new ProjectivePoint(Fp.mul(k2p.x, CURVE.endo.beta), k2p.y, k2p.z); | ||
k2p = new Point(Fp.mul(k2p.px, endo.beta), k2p.py, k2p.pz); | ||
return k1p.add(k2p); | ||
} | ||
/** | ||
* Implements w-ary non-adjacent form for calculating ec multiplication. | ||
*/ | ||
wNAF(n, affinePoint) { | ||
if (!affinePoint && this.equals(ProjectivePoint.BASE)) | ||
affinePoint = Point.BASE; | ||
const W = (affinePoint && affinePoint._WINDOW_SIZE) || 1; | ||
// Calculate precomputes on a first run, reuse them after | ||
let precomputes = affinePoint && pointPrecomputes.get(affinePoint); | ||
if (!precomputes) { | ||
precomputes = wnaf.precomputeWindow(this, W); | ||
if (affinePoint && W !== 1) { | ||
precomputes = ProjectivePoint.normalizeZ(precomputes); | ||
pointPrecomputes.set(affinePoint, precomputes); | ||
} | ||
} | ||
return wnaf.wNAF(W, precomputes, n); | ||
} | ||
/** | ||
* Constant time multiplication. | ||
@@ -394,15 +434,14 @@ * Uses wNAF method. Windowed method may be 10% faster, | ||
*/ | ||
multiply(scalar, affinePoint) { | ||
let n = normalizeScalar(scalar); | ||
// Real point. | ||
let point; | ||
// Fake point, we use it to achieve constant-time multiplication. | ||
let fake; | ||
if (CURVE.endo) { | ||
const { k1neg, k1, k2neg, k2 } = CURVE.endo.splitScalar(n); | ||
let { p: k1p, f: f1p } = this.wNAF(k1, affinePoint); | ||
let { p: k2p, f: f2p } = this.wNAF(k2, affinePoint); | ||
multiply(scalar) { | ||
assertGE(scalar); | ||
let n = scalar; | ||
let point, fake; // Fake point is used to const-time mult | ||
const { endo } = CURVE; | ||
if (endo) { | ||
const { k1neg, k1, k2neg, k2 } = endo.splitScalar(n); | ||
let { p: k1p, f: f1p } = this.wNAF(k1); | ||
let { p: k2p, f: f2p } = this.wNAF(k2); | ||
k1p = wnaf.constTimeNegate(k1neg, k1p); | ||
k2p = wnaf.constTimeNegate(k2neg, k2p); | ||
k2p = new ProjectivePoint(Fp.mul(k2p.x, CURVE.endo.beta), k2p.y, k2p.z); | ||
k2p = new Point(Fp.mul(k2p.px, endo.beta), k2p.py, k2p.pz); | ||
point = k1p.add(k2p); | ||
@@ -412,3 +451,3 @@ fake = f1p.add(f2p); | ||
else { | ||
const { p, f } = this.wNAF(n, affinePoint); | ||
const { p, f } = this.wNAF(n); | ||
point = p; | ||
@@ -418,22 +457,33 @@ fake = f; | ||
// Normalize `z` for both points, but return only real one | ||
return ProjectivePoint.normalizeZ([point, fake])[0]; | ||
return Point.normalizeZ([point, fake])[0]; | ||
} | ||
/** | ||
* Efficiently calculate `aP + bQ`. Unsafe, can expose private key, if used incorrectly. | ||
* @returns non-zero affine point | ||
*/ | ||
multiplyAndAddUnsafe(Q, a, b) { | ||
const G = Point.BASE; // No Strauss-Shamir trick: we have 10% faster G precomputes | ||
const mul = (P, a // Select faster multiply() method | ||
) => (a === _0n || a === _1n || !P.equals(G) ? P.multiplyUnsafe(a) : P.multiply(a)); | ||
const sum = mul(this, a).add(mul(Q, b)); | ||
return sum.is0() ? undefined : sum; | ||
} | ||
// Converts Projective point to affine (x, y) coordinates. | ||
// Can accept precomputed Z^-1 - for example, from invertBatch. | ||
// (x, y, z) ∋ (x=x/z, y=y/z) | ||
toAffine(invZ) { | ||
const { x, y, z } = this; | ||
const is0 = this.equals(ProjectivePoint.ZERO); | ||
toAffine(iz) { | ||
const { px: x, py: y, pz: z } = this; | ||
const is0 = this.is0(); | ||
// If invZ was 0, we return zero point. However we still want to execute | ||
// all operations, so we replace invZ with a random number, 1. | ||
if (invZ == null) | ||
invZ = is0 ? Fp.ONE : Fp.invert(z); | ||
const ax = Fp.mul(x, invZ); | ||
const ay = Fp.mul(y, invZ); | ||
const zz = Fp.mul(z, invZ); | ||
if (iz == null) | ||
iz = is0 ? Fp.ONE : Fp.inv(z); | ||
const ax = Fp.mul(x, iz); | ||
const ay = Fp.mul(y, iz); | ||
const zz = Fp.mul(z, iz); | ||
if (is0) | ||
return Point.ZERO; | ||
if (!Fp.equals(zz, Fp.ONE)) | ||
return { x: Fp.ZERO, y: Fp.ZERO }; | ||
if (!Fp.eql(zz, Fp.ONE)) | ||
throw new Error('invZ was invalid'); | ||
return new Point(ax, ay); | ||
return { x: ax, y: ay }; | ||
} | ||
@@ -445,3 +495,3 @@ isTorsionFree() { | ||
if (isTorsionFree) | ||
return isTorsionFree(ProjectivePoint, this); | ||
return isTorsionFree(Point, this); | ||
throw new Error('isTorsionFree() has not been declared for the elliptic curve'); | ||
@@ -454,158 +504,19 @@ } | ||
if (clearCofactor) | ||
return clearCofactor(ProjectivePoint, this); | ||
return clearCofactor(Point, this); | ||
return this.multiplyUnsafe(CURVE.h); | ||
} | ||
} | ||
ProjectivePoint.BASE = new ProjectivePoint(CURVE.Gx, CURVE.Gy, Fp.ONE); | ||
ProjectivePoint.ZERO = new ProjectivePoint(Fp.ZERO, Fp.ONE, Fp.ZERO); | ||
const _bits = CURVE.nBitLength; | ||
const wnaf = (0, group_js_1.wNAF)(ProjectivePoint, CURVE.endo ? Math.ceil(_bits / 2) : _bits); | ||
function assertPrjPoint(other) { | ||
if (!(other instanceof ProjectivePoint)) | ||
throw new TypeError('ProjectivePoint expected'); | ||
} | ||
// Stores precomputed values for points. | ||
const pointPrecomputes = new WeakMap(); | ||
/** | ||
* Default Point works in default aka affine coordinates: (x, y) | ||
*/ | ||
class Point { | ||
constructor(x, y) { | ||
this.x = x; | ||
this.y = y; | ||
} | ||
// "Private method", don't use it directly | ||
_setWindowSize(windowSize) { | ||
this._WINDOW_SIZE = windowSize; | ||
pointPrecomputes.delete(this); | ||
} | ||
// Checks for y % 2 == 0 | ||
hasEvenY() { | ||
if (Fp.isOdd) | ||
return !Fp.isOdd(this.y); | ||
throw new Error("Field doesn't support isOdd"); | ||
} | ||
/** | ||
* Converts hash string or Uint8Array to Point. | ||
* @param hex short/long ECDSA hex | ||
*/ | ||
static fromHex(hex) { | ||
const { x, y } = CURVE.fromBytes(ut.ensureBytes(hex)); | ||
const point = new Point(x, y); | ||
point.assertValidity(); | ||
return point; | ||
} | ||
// Multiplies generator point by privateKey. | ||
static fromPrivateKey(privateKey) { | ||
return Point.BASE.multiply(normalizePrivateKey(privateKey)); | ||
} | ||
toRawBytes(isCompressed = false) { | ||
toRawBytes(isCompressed = true) { | ||
this.assertValidity(); | ||
return CURVE.toBytes(Point, this, isCompressed); | ||
} | ||
toHex(isCompressed = false) { | ||
return (0, utils_js_1.bytesToHex)(this.toRawBytes(isCompressed)); | ||
toHex(isCompressed = true) { | ||
return ut.bytesToHex(this.toRawBytes(isCompressed)); | ||
} | ||
// A point on curve is valid if it conforms to equation. | ||
assertValidity() { | ||
// Zero is valid point too! | ||
if (this.equals(Point.ZERO)) { | ||
if (CURVE.allowInfinityPoint) | ||
return; | ||
throw new Error('Point at infinity'); | ||
} | ||
// Some 3rd-party test vectors require different wording between here & `fromCompressedHex` | ||
const msg = 'Point is not on elliptic curve'; | ||
const { x, y } = this; | ||
// Check if x, y are valid field elements | ||
if (!Fp.isValid(x) || !Fp.isValid(y)) | ||
throw new Error(msg); | ||
const left = Fp.square(y); // y² | ||
const right = weierstrassEquation(x); // x³ + ax + b | ||
if (!Fp.equals(left, right)) | ||
throw new Error(msg); | ||
if (!this.isTorsionFree()) | ||
throw new Error('Point must be of prime-order subgroup'); | ||
} | ||
equals(other) { | ||
if (!(other instanceof Point)) | ||
throw new TypeError('Point#equals: expected Point'); | ||
return Fp.equals(this.x, other.x) && Fp.equals(this.y, other.y); | ||
} | ||
// Returns the same point with inverted `y` | ||
negate() { | ||
return new Point(this.x, Fp.negate(this.y)); | ||
} | ||
toProj() { | ||
return ProjectivePoint.fromAffine(this); | ||
} | ||
// Adds point to itself | ||
double() { | ||
return this.toProj().double().toAffine(); | ||
} | ||
add(other) { | ||
return this.toProj().add(ProjectivePoint.fromAffine(other)).toAffine(); | ||
} | ||
subtract(other) { | ||
return this.add(other.negate()); | ||
} | ||
multiply(scalar) { | ||
return this.toProj().multiply(scalar, this).toAffine(); | ||
} | ||
multiplyUnsafe(scalar) { | ||
return this.toProj().multiplyUnsafe(scalar).toAffine(); | ||
} | ||
clearCofactor() { | ||
return this.toProj().clearCofactor().toAffine(); | ||
} | ||
isTorsionFree() { | ||
return this.toProj().isTorsionFree(); | ||
} | ||
/** | ||
* Efficiently calculate `aP + bQ`. | ||
* Unsafe, can expose private key, if used incorrectly. | ||
* TODO: Utilize Shamir's trick | ||
* @returns non-zero affine point | ||
*/ | ||
multiplyAndAddUnsafe(Q, a, b) { | ||
const P = this.toProj(); | ||
const aP = a === _0n || a === _1n || this !== Point.BASE ? P.multiplyUnsafe(a) : P.multiply(a); | ||
const bQ = ProjectivePoint.fromAffine(Q).multiplyUnsafe(b); | ||
const sum = aP.add(bQ); | ||
return sum.equals(ProjectivePoint.ZERO) ? undefined : sum.toAffine(); | ||
} | ||
// Encodes byte string to elliptic curve | ||
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-3 | ||
static hashToCurve(msg, options) { | ||
const { mapToCurve } = CURVE; | ||
if (!mapToCurve) | ||
throw new Error('CURVE.mapToCurve() has not been defined'); | ||
msg = ut.ensureBytes(msg); | ||
const u = (0, hash_to_curve_js_1.hash_to_field)(msg, 2, { ...CURVE.htfDefaults, ...options }); | ||
const { x: x0, y: y0 } = mapToCurve(u[0]); | ||
const { x: x1, y: y1 } = mapToCurve(u[1]); | ||
return new Point(x0, y0).add(new Point(x1, y1)).clearCofactor(); | ||
} | ||
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-16#section-3 | ||
static encodeToCurve(msg, options) { | ||
const { mapToCurve } = CURVE; | ||
if (!mapToCurve) | ||
throw new Error('CURVE.mapToCurve() has not been defined'); | ||
msg = ut.ensureBytes(msg); | ||
const u = (0, hash_to_curve_js_1.hash_to_field)(msg, 1, { ...CURVE.htfDefaults, ...options }); | ||
const { x, y } = mapToCurve(u[0]); | ||
return new Point(x, y).clearCofactor(); | ||
} | ||
} | ||
/** | ||
* Base point aka generator. Any public_key = Point.BASE * private_key | ||
*/ | ||
Point.BASE = new Point(CURVE.Gx, CURVE.Gy); | ||
/** | ||
* Identity point aka point at infinity. p - p = zero_p; p + zero_p = p | ||
*/ | ||
Point.ZERO = new Point(Fp.ZERO, Fp.ZERO); | ||
Point.BASE = new Point(CURVE.Gx, CURVE.Gy, Fp.ONE); | ||
Point.ZERO = new Point(Fp.ZERO, Fp.ONE, Fp.ZERO); | ||
const _bits = CURVE.nBitLength; | ||
const wnaf = (0, curve_js_1.wNAF)(Point, CURVE.endo ? Math.ceil(_bits / 2) : _bits); | ||
return { | ||
Point: Point, | ||
ProjectivePoint: ProjectivePoint, | ||
ProjectivePoint: Point, | ||
normalizePrivateKey, | ||
@@ -618,4 +529,4 @@ weierstrassEquation, | ||
function validateOpts(curve) { | ||
const opts = ut.validateOpts(curve); | ||
if (typeof opts.hash !== 'function' || !ut.isPositiveInt(opts.hash.outputLen)) | ||
const opts = (0, curve_js_1.validateAbsOpts)(curve); | ||
if (typeof opts.hash !== 'function' || !Number.isSafeInteger(opts.hash.outputLen)) | ||
throw new Error('Invalid hash function'); | ||
@@ -629,51 +540,54 @@ if (typeof opts.hmac !== 'function') | ||
} | ||
/** | ||
* Minimal HMAC-DRBG (NIST 800-90) for signatures. | ||
* Used only for RFC6979, does not fully implement DRBG spec. | ||
*/ | ||
class HmacDrbg { | ||
constructor(hashLen, qByteLen, hmacFn) { | ||
this.hashLen = hashLen; | ||
this.qByteLen = qByteLen; | ||
this.hmacFn = hmacFn; | ||
if (typeof hashLen !== 'number' || hashLen < 2) | ||
throw new Error('hashLen must be a number'); | ||
if (typeof qByteLen !== 'number' || qByteLen < 2) | ||
throw new Error('qByteLen must be a number'); | ||
if (typeof hmacFn !== 'function') | ||
throw new Error('hmacFn must be a function'); | ||
// Step B, Step C: set hashLen to 8*ceil(hlen/8) | ||
this.v = new Uint8Array(hashLen).fill(1); | ||
this.k = new Uint8Array(hashLen).fill(0); | ||
this.counter = 0; | ||
} | ||
hmacSync(...values) { | ||
return this.hmacFn(this.k, ...values); | ||
} | ||
incr() { | ||
if (this.counter >= 1000) | ||
throw new Error('Tried 1,000 k values for sign(), all were invalid'); | ||
this.counter += 1; | ||
} | ||
reseedSync(seed = new Uint8Array()) { | ||
this.k = this.hmacSync(this.v, Uint8Array.from([0x00]), seed); | ||
this.v = this.hmacSync(this.v); | ||
const u8n = (data) => new Uint8Array(data); // creates Uint8Array | ||
const u8fr = (arr) => Uint8Array.from(arr); // another shortcut | ||
function hmacDrbg(hashLen, qByteLen, hmacFn) { | ||
if (typeof hashLen !== 'number' || hashLen < 2) | ||
throw new Error('hashLen must be a number'); | ||
if (typeof qByteLen !== 'number' || qByteLen < 2) | ||
throw new Error('qByteLen must be a number'); | ||
if (typeof hmacFn !== 'function') | ||
throw new Error('hmacFn must be a function'); | ||
// Step B, Step C: set hashLen to 8*ceil(hlen/8) | ||
let v = u8n(hashLen); // Minimal non-full-spec HMAC-DRBG from NIST 800-90 for RFC6979 sigs. | ||
let k = u8n(hashLen); // Steps B and C of RFC6979 3.2: set hashLen, in our case always same | ||
let i = 0; // Iterations counter, will throw when over 1000 | ||
const reset = () => { | ||
v.fill(1); | ||
k.fill(0); | ||
i = 0; | ||
}; | ||
const h = (...b) => hmacFn(k, v, ...b); // hmac(k)(v, ...values) | ||
const reseed = (seed = u8n()) => { | ||
// HMAC-DRBG reseed() function. Steps D-G | ||
k = h(u8fr([0x00]), seed); // k = hmac(k || v || 0x00 || seed) | ||
v = h(); // v = hmac(k || v) | ||
if (seed.length === 0) | ||
return; | ||
this.k = this.hmacSync(this.v, Uint8Array.from([0x01]), seed); | ||
this.v = this.hmacSync(this.v); | ||
} | ||
// TODO: review | ||
generateSync() { | ||
this.incr(); | ||
k = h(u8fr([0x01]), seed); // k = hmac(k || v || 0x01 || seed) | ||
v = h(); // v = hmac(k || v) | ||
}; | ||
const gen = () => { | ||
// HMAC-DRBG generate() function | ||
if (i++ >= 1000) | ||
throw new Error('drbg: tried 1000 values'); | ||
let len = 0; | ||
const out = []; | ||
while (len < this.qByteLen) { | ||
this.v = this.hmacSync(this.v); | ||
const sl = this.v.slice(); | ||
while (len < qByteLen) { | ||
v = h(); | ||
const sl = v.slice(); | ||
out.push(sl); | ||
len += this.v.length; | ||
len += v.length; | ||
} | ||
return ut.concatBytes(...out); | ||
} | ||
}; | ||
const genUntil = (seed, pred) => { | ||
reset(); | ||
reseed(seed); // Steps D-G | ||
let res = undefined; // Step H: grind until k is in [1..n-1] | ||
while (!(res = pred(gen()))) | ||
reseed(); | ||
reset(); | ||
return res; | ||
}; | ||
return genUntil; | ||
} | ||
@@ -687,15 +601,22 @@ function weierstrass(curveDef) { | ||
function isValidFieldElement(num) { | ||
// 0 is disallowed by arbitrary reasons. Probably because infinity point? | ||
return _0n < num && num < Fp.ORDER; | ||
return _0n < num && num < Fp.ORDER; // 0 is banned since it's not invertible FE | ||
} | ||
const { Point, ProjectivePoint, normalizePrivateKey, weierstrassEquation, isWithinCurveOrder } = weierstrassPoints({ | ||
function modN(a) { | ||
return mod.mod(a, CURVE_ORDER); | ||
} | ||
function invN(a) { | ||
return mod.invert(a, CURVE_ORDER); | ||
} | ||
const { ProjectivePoint: Point, normalizePrivateKey, weierstrassEquation, isWithinCurveOrder, } = weierstrassPoints({ | ||
...CURVE, | ||
toBytes(c, point, isCompressed) { | ||
const x = Fp.toBytes(point.x); | ||
const a = point.toAffine(); | ||
const x = Fp.toBytes(a.x); | ||
const cat = ut.concatBytes; | ||
if (isCompressed) { | ||
// TODO: hasEvenY | ||
return cat(Uint8Array.from([point.hasEvenY() ? 0x02 : 0x03]), x); | ||
} | ||
else { | ||
return cat(Uint8Array.from([0x04]), x, Fp.toBytes(point.y)); | ||
return cat(Uint8Array.from([0x04]), x, Fp.toBytes(a.y)); | ||
} | ||
@@ -705,6 +626,7 @@ }, | ||
const len = bytes.length; | ||
const header = bytes[0]; | ||
const head = bytes[0]; | ||
const tail = bytes.subarray(1); | ||
// this.assertValidity() is done inside of fromHex | ||
if (len === compressedLen && (header === 0x02 || header === 0x03)) { | ||
const x = ut.bytesToNumberBE(bytes.subarray(1)); | ||
if (len === compressedLen && (head === 0x02 || head === 0x03)) { | ||
const x = ut.bytesToNumberBE(tail); | ||
if (!isValidFieldElement(x)) | ||
@@ -716,10 +638,10 @@ throw new Error('Point is not on curve'); | ||
// ECDSA | ||
const isFirstByteOdd = (bytes[0] & 1) === 1; | ||
if (isFirstByteOdd !== isYOdd) | ||
y = Fp.negate(y); | ||
const isHeadOdd = (head & 1) === 1; | ||
if (isHeadOdd !== isYOdd) | ||
y = Fp.neg(y); | ||
return { x, y }; | ||
} | ||
else if (len === uncompressedLen && header === 0x04) { | ||
const x = Fp.fromBytes(bytes.subarray(1, Fp.BYTES + 1)); | ||
const y = Fp.fromBytes(bytes.subarray(Fp.BYTES + 1, 2 * Fp.BYTES + 1)); | ||
else if (len === uncompressedLen && head === 0x04) { | ||
const x = Fp.fromBytes(tail.subarray(0, Fp.BYTES)); | ||
const y = Fp.fromBytes(tail.subarray(Fp.BYTES, 2 * Fp.BYTES)); | ||
return { x, y }; | ||
@@ -732,26 +654,3 @@ } | ||
}); | ||
// Do we need these functions at all? | ||
function numToField(num) { | ||
if (typeof num !== 'bigint') | ||
throw new Error('Expected bigint'); | ||
if (!(_0n <= num && num < Fp.MASK)) | ||
throw new Error(`Expected number < 2^${Fp.BYTES * 8}`); | ||
return Fp.toBytes(num); | ||
} | ||
const numToFieldStr = (num) => (0, utils_js_1.bytesToHex)(numToField(num)); | ||
/** | ||
* Normalizes hex, bytes, Point to Point. Checks for curve equation. | ||
*/ | ||
function normalizePublicKey(publicKey) { | ||
if (publicKey instanceof Point) { | ||
publicKey.assertValidity(); | ||
return publicKey; | ||
} | ||
else if (publicKey instanceof Uint8Array || typeof publicKey === 'string') { | ||
return Point.fromHex(publicKey); | ||
// This can happen because PointType can be instance of different class | ||
} | ||
else | ||
throw new Error(`Unknown type of public key: ${publicKey}`); | ||
} | ||
const numToNByteStr = (num) => ut.bytesToHex(ut.numberToBytesBE(num, CURVE.nByteLength)); | ||
function isBiggerThanHalfOrder(number) { | ||
@@ -762,18 +661,6 @@ const HALF = CURVE_ORDER >> _1n; | ||
function normalizeS(s) { | ||
return isBiggerThanHalfOrder(s) ? mod.mod(-s, CURVE_ORDER) : s; | ||
return isBiggerThanHalfOrder(s) ? modN(-s) : s; | ||
} | ||
function bits2int_2(bytes) { | ||
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 | ||
function _truncateHash(hash, truncateOnly = false) { | ||
const h = bits2int_2(hash); | ||
if (truncateOnly) | ||
return h; | ||
const { n } = CURVE; | ||
return h >= n ? h - n : h; | ||
} | ||
const truncateHash = CURVE.truncateHash || _truncateHash; | ||
// slice bytes num | ||
const slcNum = (b, from, to) => ut.bytesToNumberBE(b.slice(from, to)); | ||
/** | ||
@@ -791,12 +678,5 @@ * ECDSA signature with its (r, s) properties. Supports DER & compact representations. | ||
static fromCompact(hex) { | ||
const arr = hex instanceof Uint8Array; | ||
const name = 'Signature.fromCompact'; | ||
if (typeof hex !== 'string' && !arr) | ||
throw new TypeError(`${name}: Expected string or Uint8Array`); | ||
const str = arr ? (0, utils_js_1.bytesToHex)(hex) : hex; | ||
const gl = CURVE.nByteLength * 2; // group length in hex, not ui8a | ||
if (str.length !== 2 * gl) | ||
throw new Error(`${name}: Expected ${gl / 2}-byte hex`); | ||
const slice = (from, to) => ut.hexToNumber(str.slice(from, to)); | ||
return new Signature(slice(0, gl), slice(gl, 2 * gl)); | ||
const gl = CURVE.nByteLength; | ||
hex = (0, utils_js_1.ensureBytes)(hex, gl * 2); | ||
return new Signature(slcNum(hex, 0, gl), slcNum(hex, gl, 2 * gl)); | ||
} | ||
@@ -806,61 +686,38 @@ // DER encoded ECDSA signature | ||
static fromDER(hex) { | ||
const arr = hex instanceof Uint8Array; | ||
if (typeof hex !== 'string' && !arr) | ||
throw new TypeError(`Signature.fromDER: Expected string or Uint8Array`); | ||
const { r, s } = DER.parseSig(arr ? hex : ut.hexToBytes(hex)); | ||
if (typeof hex !== 'string' && !(hex instanceof Uint8Array)) | ||
throw new Error(`Signature.fromDER: Expected string or Uint8Array`); | ||
const { r, s } = DER.toSig((0, utils_js_1.ensureBytes)(hex)); | ||
return new Signature(r, s); | ||
} | ||
assertValidity() { | ||
const { r, s } = this; | ||
if (!isWithinCurveOrder(r)) | ||
throw new Error('Invalid Signature: r must be 0 < r < n'); | ||
if (!isWithinCurveOrder(s)) | ||
throw new Error('Invalid Signature: s must be 0 < s < n'); | ||
// can use assertGE here | ||
if (!isWithinCurveOrder(this.r)) | ||
throw new Error('r must be 0 < r < n'); | ||
if (!isWithinCurveOrder(this.s)) | ||
throw new Error('s must be 0 < s < n'); | ||
} | ||
copyWithRecoveryBit(recovery) { | ||
addRecoveryBit(recovery) { | ||
return new Signature(this.r, this.s, recovery); | ||
} | ||
/** | ||
* 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 | ||
* It's also possible to recover key without bit: try all 4 bit values and check for sig match. | ||
* | ||
* ``` | ||
* recover(r, s, h) where | ||
* u1 = hs^-1 mod n | ||
* u2 = sr^-1 mod n | ||
* Q = u1⋅G + u2⋅R | ||
* ``` | ||
* | ||
* @param msgHash message hash | ||
* @returns Point corresponding to public key | ||
*/ | ||
recoverPublicKey(msgHash) { | ||
const { r, s, recovery } = this; | ||
if (recovery == null) | ||
throw new Error('Cannot recover: recovery bit is not present'); | ||
if (![0, 1, 2, 3].includes(recovery)) | ||
throw new Error('Cannot recover: invalid recovery bit'); | ||
const h = truncateHash(ut.ensureBytes(msgHash)); | ||
const { n } = CURVE; | ||
const radj = recovery === 2 || recovery === 3 ? r + n : r; | ||
const { n: N } = CURVE; // ECDSA public key recovery secg.org/sec1-v2.pdf 4.1.6 | ||
const { r, s, recovery: rec } = this; | ||
const h = bits2int_modN((0, utils_js_1.ensureBytes)(msgHash)); // Truncate hash | ||
if (rec == null || ![0, 1, 2, 3].includes(rec)) | ||
throw new Error('recovery id invalid'); | ||
const radj = rec === 2 || rec === 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 | ||
const u1 = mod.mod(-h * rinv, n); | ||
const u2 = mod.mod(s * rinv, n); | ||
const prefix = recovery & 1 ? '03' : '02'; | ||
const R = Point.fromHex(prefix + numToFieldStr(radj)); | ||
const Q = Point.BASE.multiplyAndAddUnsafe(R, u1, u2); // unsafe is fine: no priv data leaked | ||
throw new Error('recovery id 2 or 3 invalid'); | ||
const prefix = (rec & 1) === 0 ? '02' : '03'; | ||
const R = Point.fromHex(prefix + numToNByteStr(radj)); | ||
const ir = invN(radj); // r^-1 | ||
const u1 = modN(-h * ir); // -hr^-1 | ||
const u2 = modN(s * ir); // sr^-1 | ||
const Q = Point.BASE.multiplyAndAddUnsafe(R, u1, u2); // (sr^-1)R-(hr^-1)G = -(hr^-1)G + (sr^-1) | ||
if (!Q) | ||
throw new Error('Cannot recover: point at infinify'); | ||
throw new Error('point at infinify'); // unsafe is fine: no priv data leaked | ||
Q.assertValidity(); | ||
return Q; | ||
} | ||
/** | ||
* Default signatures are always low-s, to prevent malleability. | ||
* `sign(lowS: true)` always produces low-s sigs. | ||
* `verify(lowS: true)` always fails for high-s. | ||
*/ | ||
// Signatures should be low-s, to prevent malleability. | ||
hasHighS() { | ||
@@ -870,5 +727,3 @@ return isBiggerThanHalfOrder(this.s); | ||
normalizeS() { | ||
return this.hasHighS() | ||
? new Signature(this.r, mod.mod(-this.s, CURVE_ORDER), this.recovery) | ||
: this; | ||
return this.hasHighS() ? new Signature(this.r, modN(-this.s), this.recovery) : this; | ||
} | ||
@@ -880,11 +735,3 @@ // DER-encoded | ||
toDERHex() { | ||
const { numberToHexUnpadded: toHex } = ut; | ||
const sHex = DER.slice(toHex(this.s)); | ||
const rHex = DER.slice(toHex(this.r)); | ||
const sHexL = sHex.length / 2; | ||
const rHexL = rHex.length / 2; | ||
const sLen = toHex(sHexL); | ||
const rLen = toHex(rHexL); | ||
const length = toHex(rHexL + sHexL + 4); | ||
return `30${length}02${rLen}${rHex}02${sLen}${sHex}`; | ||
return DER.hexFromSig({ r: this.r, s: this.s }); | ||
} | ||
@@ -896,3 +743,3 @@ // padded bytes of r, then padded bytes of s | ||
toCompactHex() { | ||
return numToFieldStr(this.r) + numToFieldStr(this.s); | ||
return numToNByteStr(this.r) + numToNByteStr(this.s); | ||
} | ||
@@ -910,13 +757,7 @@ } | ||
}, | ||
_bigintToBytes: numToField, | ||
_bigintToString: numToFieldStr, | ||
_normalizePrivateKey: normalizePrivateKey, | ||
_normalizePublicKey: normalizePublicKey, | ||
_isWithinCurveOrder: isWithinCurveOrder, | ||
_isValidFieldElement: isValidFieldElement, | ||
_weierstrassEquation: weierstrassEquation, | ||
/** | ||
* Converts some bytes to a valid private key. Needs at least (nBitLength+64) bytes. | ||
*/ | ||
hashToPrivateKey: (hash) => numToField(ut.hashToPrivateScalar(hash, CURVE_ORDER)), | ||
hashToPrivateKey: (hash) => ut.numberToBytesBE(mod.hashToPrivateScalar(hash, CURVE_ORDER), CURVE.nByteLength), | ||
/** | ||
@@ -936,6 +777,5 @@ * Produces cryptographically secure private key from random of size (nBitLength+64) | ||
precompute(windowSize = 8, point = Point.BASE) { | ||
const cached = point === Point.BASE ? point : new Point(point.x, point.y); | ||
cached._setWindowSize(windowSize); | ||
cached.multiply(_3n); | ||
return cached; | ||
point._setWindowSize(windowSize); | ||
point.multiply(BigInt(3)); | ||
return point; | ||
}, | ||
@@ -949,3 +789,3 @@ }; | ||
*/ | ||
function getPublicKey(privateKey, isCompressed = false) { | ||
function getPublicKey(privateKey, isCompressed = true) { | ||
return Point.fromPrivateKey(privateKey).toRawBytes(isCompressed); | ||
@@ -977,52 +817,68 @@ } | ||
*/ | ||
function getSharedSecret(privateA, publicB, isCompressed = false) { | ||
function getSharedSecret(privateA, publicB, isCompressed = true) { | ||
if (isProbPub(privateA)) | ||
throw new TypeError('getSharedSecret: first arg must be private key'); | ||
throw new Error('first arg must be private key'); | ||
if (!isProbPub(publicB)) | ||
throw new TypeError('getSharedSecret: second arg must be public key'); | ||
const b = normalizePublicKey(publicB); | ||
b.assertValidity(); | ||
throw new Error('second arg must be public key'); | ||
const b = Point.fromHex(publicB); // check for being on-curve | ||
return b.multiply(normalizePrivateKey(privateA)).toRawBytes(isCompressed); | ||
} | ||
// RFC6979 methods | ||
function bits2int(bytes) { | ||
const { nByteLength } = CURVE; | ||
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) { | ||
const z1 = bits2int(bytes); | ||
const z2 = mod.mod(z1, CURVE_ORDER); | ||
return int2octets(z2 < _0n ? z1 : z2); | ||
} | ||
// RFC6979: ensure ECDSA msg is X bytes and < N. RFC suggests optional truncating via bits2octets. | ||
// FIPS 186-4 4.6 suggests the leftmost min(nBitLen, outLen) bits, which matches bits2int. | ||
// bits2int can produce res>N, we can do mod(res, N) since the bitLen is the same. | ||
// int2octets can't be used; pads small msgs with 0: unacceptatble for trunc as per RFC vectors | ||
const bits2int = CURVE.bits2int || | ||
function (bytes) { | ||
// For curves with nBitLength % 8 !== 0: bits2octets(bits2octets(m)) !== bits2octets(m) | ||
// for some cases, since bytes.length * 8 is not actual bitLength. | ||
const delta = bytes.length * 8 - CURVE.nBitLength; // truncate to nBitLength leftmost bits | ||
const num = ut.bytesToNumberBE(bytes); // check for == u8 done here | ||
return delta > 0 ? num >> BigInt(delta) : num; | ||
}; | ||
const bits2int_modN = CURVE.bits2int_modN || | ||
function (bytes) { | ||
return modN(bits2int(bytes)); // can't use bytesToNumberBE here | ||
}; | ||
// NOTE: pads output with zero as per spec | ||
const ORDER_MASK = ut.bitMask(CURVE.nBitLength); | ||
function int2octets(num) { | ||
return numToField(num); // prohibits >nByteLength bytes | ||
if (typeof num !== 'bigint') | ||
throw new Error('Expected bigint'); | ||
if (!(_0n <= num && num < ORDER_MASK)) | ||
throw new Error(`Expected number < 2^${CURVE.nBitLength}`); | ||
// works with order, can have different size than numToField! | ||
return ut.numberToBytesBE(num, CURVE.nByteLength); | ||
} | ||
// Steps A, D of RFC6979 3.2 | ||
// Creates RFC6979 seed; converts msg/privKey to numbers. | ||
function initSigArgs(msgHash, privateKey, extraEntropy) { | ||
// Used only in sign, not in verify. | ||
// NOTE: we cannot assume here that msgHash has same amount of bytes as curve order, this will be wrong at least for P521. | ||
// Also it can be bigger for P224 + SHA256 | ||
function prepSig(msgHash, privateKey, opts = defaultSigOpts) { | ||
if (msgHash == null) | ||
throw new Error(`sign: expected valid message hash, not "${msgHash}"`); | ||
if (['recovered', 'canonical'].some((k) => k in opts)) | ||
// Ban legacy options | ||
throw new Error('sign() legacy options not supported'); | ||
let { lowS, prehash, extraEntropy: ent } = opts; // generates low-s sigs by default | ||
if (prehash) | ||
msgHash = CURVE.hash((0, utils_js_1.ensureBytes)(msgHash)); | ||
if (lowS == null) | ||
lowS = true; // RFC6979 3.2: we skip step A, because | ||
// Step A is ignored, since we already provide hash instead of msg | ||
const h1 = numToField(truncateHash(ut.ensureBytes(msgHash))); | ||
// NOTE: instead of bits2int, we calling here truncateHash, since we need | ||
// custom truncation for stark. For other curves it is essentially same as calling bits2int + mod | ||
// However, we cannot later call bits2octets (which is truncateHash + int2octets), since nested bits2int is broken | ||
// for curves where nBitLength % 8 !== 0, so we unwrap it here as int2octets call. | ||
// const bits2octets = (bits)=>int2octets(bytesToNumberBE(truncateHash(bits))) | ||
const h1int = bits2int_modN((0, utils_js_1.ensureBytes)(msgHash)); | ||
const h1octets = int2octets(h1int); | ||
const d = normalizePrivateKey(privateKey); | ||
// K = HMAC_K(V || 0x00 || int2octets(x) || bits2octets(h1) || k') | ||
const seedArgs = [int2octets(d), bits2octets(h1)]; | ||
// RFC6979 3.6: additional k' could be provided | ||
if (extraEntropy != null) { | ||
if (extraEntropy === true) | ||
extraEntropy = CURVE.randomBytes(Fp.BYTES); | ||
const e = ut.ensureBytes(extraEntropy); | ||
const seedArgs = [int2octets(d), h1octets]; | ||
if (ent != null) { | ||
// RFC6979 3.6: additional k' (optional) | ||
if (ent === true) | ||
ent = CURVE.randomBytes(Fp.BYTES); | ||
const e = (0, utils_js_1.ensureBytes)(ent); | ||
if (e.length !== Fp.BYTES) | ||
@@ -1032,43 +888,30 @@ throw new Error(`sign: Expected ${Fp.BYTES} bytes of extra data`); | ||
} | ||
// seed is constructed from private key and message | ||
// Step D | ||
// V, 0x00 are done in HmacDRBG constructor. | ||
const seed = ut.concatBytes(...seedArgs); | ||
const m = bits2int(h1); | ||
return { seed, m, d }; | ||
} | ||
/** | ||
* Converts signature params into point & r/s, checks them for validity. | ||
* k must be in range [1, n-1] | ||
* @param k signature's k param: deterministic in our case, random in non-rfc6979 sigs | ||
* @param m message that would be signed | ||
* @param d private key | ||
* @returns Signature with its point on curve Q OR undefined if params were invalid | ||
*/ | ||
function kmdToSig(kBytes, m, d, lowS = true) { | ||
const { n } = CURVE; | ||
const k = truncateHash(kBytes, true); | ||
if (!isWithinCurveOrder(k)) | ||
return; | ||
// Important: all mod() calls in the function must be done over `n` | ||
const kinv = mod.invert(k, n); | ||
const q = Point.BASE.multiply(k); | ||
// r = x mod n | ||
const r = mod.mod(q.x, n); | ||
if (r === _0n) | ||
return; | ||
// 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); | ||
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 normS = s; | ||
if (lowS && isBiggerThanHalfOrder(s)) { | ||
normS = normalizeS(s); | ||
recovery ^= 1; | ||
const seed = ut.concatBytes(...seedArgs); // Step D of RFC6979 3.2 | ||
const m = h1int; // NOTE: no need to call bits2int second time here, it is inside truncateHash! | ||
// Converts signature params into point w r/s, checks result for validity. | ||
function k2sig(kBytes) { | ||
// RFC 6979 Section 3.2, step 3: k = bits2int(T) | ||
const k = bits2int(kBytes); // Cannot use fields methods, since it is group element | ||
if (!isWithinCurveOrder(k)) | ||
return; // Important: all mod() calls here must be done over N | ||
const ik = invN(k); // k^-1 mod n | ||
const q = Point.BASE.multiply(k).toAffine(); // q = Gk | ||
const r = modN(q.x); // r = q.x mod n | ||
if (r === _0n) | ||
return; | ||
const s = modN(ik * modN(m + modN(d * r))); // s = k^-1(m + rd) mod n | ||
if (s === _0n) | ||
return; | ||
let recovery = (q.x === r ? 0 : 2) | Number(q.y & _1n); // recovery bit (2 or 3, when q.x > n) | ||
let normS = s; | ||
if (lowS && isBiggerThanHalfOrder(s)) { | ||
normS = normalizeS(s); // if lowS was passed, ensure s is always | ||
recovery ^= 1; // // in the bottom half of N | ||
} | ||
return new Signature(r, normS, recovery); // use normS, not s | ||
} | ||
return new Signature(r, normS, recovery); | ||
return { seed, k2sig }; | ||
} | ||
const defaultSigOpts = { lowS: CURVE.lowS }; | ||
const defaultSigOpts = { lowS: CURVE.lowS, prehash: false }; | ||
const defaultVerOpts = { lowS: CURVE.lowS, prehash: false }; | ||
/** | ||
@@ -1082,24 +925,12 @@ * Signs message hash (not message: you need to hash it by yourself). | ||
* ``` | ||
* @param opts `lowS, extraEntropy` | ||
* @param opts `lowS, extraEntropy, prehash` | ||
*/ | ||
function sign(msgHash, privKey, opts = defaultSigOpts) { | ||
// Steps A, D of RFC6979 3.2. | ||
const { seed, m, d } = initSigArgs(msgHash, privKey, opts.extraEntropy); | ||
// Steps B, C, D, E, F, G | ||
const drbg = new HmacDrbg(CURVE.hash.outputLen, CURVE.nByteLength, CURVE.hmac); | ||
drbg.reseedSync(seed); | ||
// Step H3, repeat until k is in range [1, n-1] | ||
let sig; | ||
while (!(sig = kmdToSig(drbg.generateSync(), m, d, opts.lowS))) | ||
drbg.reseedSync(); | ||
return sig; | ||
const { seed, k2sig } = prepSig(msgHash, privKey, opts); // Steps A, D of RFC6979 3.2. | ||
const genUntil = hmacDrbg(CURVE.hash.outputLen, CURVE.nByteLength, CURVE.hmac); | ||
return genUntil(seed, k2sig); // Steps B, C, D, E, F, G | ||
} | ||
/** | ||
* Signs a message (not message hash). | ||
*/ | ||
function signUnhashed(msg, privKey, opts = defaultSigOpts) { | ||
return sign(CURVE.hash(ut.ensureBytes(msg)), privKey, opts); | ||
} | ||
// Enable precomputes. Slows down first publicKey computation by 20ms. | ||
Point.BASE._setWindowSize(8); | ||
// utils.precompute(8, ProjectivePoint.BASE) | ||
/** | ||
@@ -1118,20 +949,26 @@ * Verifies a signature against message hash and public key. | ||
*/ | ||
function verify(signature, msgHash, publicKey, opts = { lowS: CURVE.lowS }) { | ||
function verify(signature, msgHash, publicKey, opts = defaultVerOpts) { | ||
let P; | ||
let _sig = undefined; | ||
if (publicKey instanceof Point) | ||
throw new Error('publicKey must be hex'); | ||
try { | ||
if (signature instanceof Signature) { | ||
signature.assertValidity(); | ||
if (signature && typeof signature === 'object' && !(signature instanceof Uint8Array)) { | ||
const { r, s } = signature; | ||
_sig = new Signature(r, s); // assertValidity() is executed on creation | ||
} | ||
else { | ||
// Signature can be represented in 2 ways: compact (64-byte) & DER (variable-length). | ||
// Since DER can also be 64 bytes, we check for it first. | ||
// Signature can be represented in 2 ways: compact (2*nByteLength) & DER (variable-length). | ||
// Since DER can also be 2*nByteLength bytes, we check for it first. | ||
try { | ||
signature = Signature.fromDER(signature); | ||
_sig = Signature.fromDER(signature); | ||
} | ||
catch (derError) { | ||
if (!(derError instanceof DERError)) | ||
if (!(derError instanceof DER.Err)) | ||
throw derError; | ||
signature = Signature.fromCompact(signature); | ||
_sig = Signature.fromCompact(signature); | ||
} | ||
} | ||
msgHash = ut.ensureBytes(msgHash); | ||
msgHash = (0, utils_js_1.ensureBytes)(msgHash); | ||
P = Point.fromHex(publicKey); | ||
} | ||
@@ -1141,24 +978,15 @@ catch (error) { | ||
} | ||
if (opts.lowS && signature.hasHighS()) | ||
if (opts.lowS && _sig.hasHighS()) | ||
return false; | ||
let P; | ||
try { | ||
P = normalizePublicKey(publicKey); | ||
} | ||
catch (error) { | ||
return false; | ||
} | ||
const { n } = CURVE; | ||
const { r, s } = signature; | ||
const h = truncateHash(msgHash); | ||
const sinv = mod.invert(s, n); // s^-1 | ||
// R = u1⋅G - u2⋅P | ||
const u1 = mod.mod(h * sinv, n); | ||
const u2 = mod.mod(r * sinv, n); | ||
// Some implementations compare R.x in projective, without inversion. | ||
// The speed-up is <5%, so we don't complicate the code. | ||
const R = Point.BASE.multiplyAndAddUnsafe(P, u1, u2); | ||
if (opts.prehash) | ||
msgHash = CURVE.hash(msgHash); | ||
const { r, s } = _sig; | ||
const h = bits2int_modN(msgHash); // Cannot use fields methods, since it is group element | ||
const is = invN(s); // s^-1 | ||
const u1 = modN(h * is); // u1 = hs^-1 mod n | ||
const u2 = modN(r * is); // u2 = rs^-1 mod n | ||
const R = Point.BASE.multiplyAndAddUnsafe(P, u1, u2)?.toAffine(); // R = u1⋅G + u2⋅P | ||
if (!R) | ||
return false; | ||
const v = mod.mod(R.x, n); | ||
const v = modN(R.x); | ||
return v === r; | ||
@@ -1171,6 +999,5 @@ } | ||
sign, | ||
signUnhashed, | ||
verify, | ||
Point, | ||
ProjectivePoint, | ||
// Point, | ||
ProjectivePoint: Point, | ||
Signature, | ||
@@ -1182,3 +1009,3 @@ utils, | ||
// Implementation of the Shallue and van de Woestijne method for any Weierstrass curve | ||
// TODO: check if there is a way to merge this with uvRation in Edwards && move to modular? | ||
// TODO: check if there is a way to merge this with uvRatio in Edwards && move to modular? | ||
// b = True and y = sqrt(u / v) if (u / v) is square in F, and | ||
@@ -1202,3 +1029,3 @@ // b = False and y = sqrt(Z * (u / v)) otherwise. | ||
let tv2 = Fp.pow(v, c4); // 2. tv2 = v^c4 | ||
let tv3 = Fp.square(tv2); // 3. tv3 = tv2^2 | ||
let tv3 = Fp.sqr(tv2); // 3. tv3 = tv2^2 | ||
tv3 = Fp.mul(tv3, v); // 4. tv3 = tv3 * v | ||
@@ -1212,3 +1039,3 @@ let tv5 = Fp.mul(u, tv3); // 5. tv5 = u * tv3 | ||
tv5 = Fp.pow(tv4, c5); // 11. tv5 = tv4^c5 | ||
let isQR = Fp.equals(tv5, Fp.ONE); // 12. isQR = tv5 == 1 | ||
let isQR = Fp.eql(tv5, Fp.ONE); // 12. isQR = tv5 == 1 | ||
tv2 = Fp.mul(tv3, c7); // 13. tv2 = tv3 * c7 | ||
@@ -1222,3 +1049,3 @@ tv5 = Fp.mul(tv4, tv1); // 14. tv5 = tv4 * tv1 | ||
let tvv5 = Fp.pow(tv4, tv5); // 20. tv5 = tv4^tv5 | ||
const e1 = Fp.equals(tvv5, Fp.ONE); // 21. e1 = tv5 == 1 | ||
const e1 = Fp.eql(tvv5, Fp.ONE); // 21. e1 = tv5 == 1 | ||
tv2 = Fp.mul(tv3, tv1); // 22. tv2 = tv3 * tv1 | ||
@@ -1235,5 +1062,5 @@ tv1 = Fp.mul(tv1, tv1); // 23. tv1 = tv1 * tv1 | ||
const c1 = (Fp.ORDER - 3n) / 4n; // 1. c1 = (q - 3) / 4 # Integer arithmetic | ||
const c2 = Fp.sqrt(Fp.negate(Z)); // 2. c2 = sqrt(-Z) | ||
const c2 = Fp.sqrt(Fp.neg(Z)); // 2. c2 = sqrt(-Z) | ||
sqrtRatio = (u, v) => { | ||
let tv1 = Fp.square(v); // 1. tv1 = v^2 | ||
let tv1 = Fp.sqr(v); // 1. tv1 = v^2 | ||
const tv2 = Fp.mul(u, v); // 2. tv2 = u * v | ||
@@ -1244,4 +1071,4 @@ tv1 = Fp.mul(tv1, tv2); // 3. tv1 = tv1 * tv2 | ||
const y2 = Fp.mul(y1, c2); // 6. y2 = y1 * c2 | ||
const tv3 = Fp.mul(Fp.square(y1), v); // 7. tv3 = y1^2; 8. tv3 = tv3 * v | ||
const isQR = Fp.equals(tv3, u); // 9. isQR = tv3 == u | ||
const tv3 = Fp.mul(Fp.sqr(y1), v); // 7. tv3 = y1^2; 8. tv3 = tv3 * v | ||
const isQR = Fp.eql(tv3, u); // 9. isQR = tv3 == u | ||
let y = Fp.cmov(y2, y1, isQR); // 10. y = CMOV(y2, y1, isQR) | ||
@@ -1269,12 +1096,12 @@ return { isValid: isQR, value: y }; // 11. return (isQR, y) isQR ? y : y*c2 | ||
let tv1, tv2, tv3, tv4, tv5, tv6, x, y; | ||
tv1 = Fp.square(u); // 1. tv1 = u^2 | ||
tv1 = Fp.sqr(u); // 1. tv1 = u^2 | ||
tv1 = Fp.mul(tv1, opts.Z); // 2. tv1 = Z * tv1 | ||
tv2 = Fp.square(tv1); // 3. tv2 = tv1^2 | ||
tv2 = Fp.sqr(tv1); // 3. tv2 = tv1^2 | ||
tv2 = Fp.add(tv2, tv1); // 4. tv2 = tv2 + tv1 | ||
tv3 = Fp.add(tv2, Fp.ONE); // 5. tv3 = tv2 + 1 | ||
tv3 = Fp.mul(tv3, opts.B); // 6. tv3 = B * tv3 | ||
tv4 = Fp.cmov(opts.Z, Fp.negate(tv2), !Fp.equals(tv2, Fp.ZERO)); // 7. tv4 = CMOV(Z, -tv2, tv2 != 0) | ||
tv4 = Fp.cmov(opts.Z, Fp.neg(tv2), !Fp.eql(tv2, Fp.ZERO)); // 7. tv4 = CMOV(Z, -tv2, tv2 != 0) | ||
tv4 = Fp.mul(tv4, opts.A); // 8. tv4 = A * tv4 | ||
tv2 = Fp.square(tv3); // 9. tv2 = tv3^2 | ||
tv6 = Fp.square(tv4); // 10. tv6 = tv4^2 | ||
tv2 = Fp.sqr(tv3); // 9. tv2 = tv3^2 | ||
tv6 = Fp.sqr(tv4); // 10. tv6 = tv4^2 | ||
tv5 = Fp.mul(tv6, opts.A); // 11. tv5 = A * tv6 | ||
@@ -1293,3 +1120,3 @@ tv2 = Fp.add(tv2, tv5); // 12. tv2 = tv2 + tv5 | ||
const e1 = Fp.isOdd(u) === Fp.isOdd(y); // 23. e1 = sgn0(u) == sgn0(y) | ||
y = Fp.cmov(Fp.negate(y), y, e1); // 24. y = CMOV(-y, y, e1) | ||
y = Fp.cmov(Fp.neg(y), y, e1); // 24. y = CMOV(-y, y, e1) | ||
x = Fp.div(x, tv4); // 25. x = x / tv4 | ||
@@ -1296,0 +1123,0 @@ return { x, y }; |
@@ -71,5 +71,5 @@ "use strict"; | ||
isValid: ({ c0, c1 }) => typeof c0 === 'bigint' && typeof c1 === 'bigint', | ||
isZero: ({ c0, c1 }) => Fp.isZero(c0) && Fp.isZero(c1), | ||
equals: ({ c0, c1 }, { c0: r0, c1: r1 }) => Fp.equals(c0, r0) && Fp.equals(c1, r1), | ||
negate: ({ c0, c1 }) => ({ c0: Fp.negate(c0), c1: Fp.negate(c1) }), | ||
is0: ({ c0, c1 }) => Fp.is0(c0) && Fp.is0(c1), | ||
eql: ({ c0, c1 }, { c0: r0, c1: r1 }) => Fp.eql(c0, r0) && Fp.eql(c1, r1), | ||
neg: ({ c0, c1 }) => ({ c0: Fp.neg(c0), c1: Fp.neg(c1) }), | ||
pow: (num, power) => mod.FpPow(Fp2, num, power), | ||
@@ -81,3 +81,3 @@ invertBatch: (nums) => mod.FpInvertBatch(Fp2, nums), | ||
mul: Fp2Multiply, | ||
square: Fp2Square, | ||
sqr: Fp2Square, | ||
// NonNormalized stuff | ||
@@ -87,6 +87,6 @@ addN: Fp2Add, | ||
mulN: Fp2Multiply, | ||
squareN: Fp2Square, | ||
sqrN: Fp2Square, | ||
// Why inversion for bigint inside Fp instead of Fp2? it is even used in that context? | ||
div: (lhs, rhs) => Fp2.mul(lhs, typeof rhs === 'bigint' ? Fp.invert(Fp.create(rhs)) : Fp2.invert(rhs)), | ||
invert: ({ c0: a, c1: b }) => { | ||
div: (lhs, rhs) => Fp2.mul(lhs, typeof rhs === 'bigint' ? Fp.inv(Fp.create(rhs)) : Fp2.inv(rhs)), | ||
inv: ({ c0: a, c1: b }) => { | ||
// We wish to find the multiplicative inverse of a nonzero | ||
@@ -105,7 +105,7 @@ // element a + bu in Fp2. We leverage an identity | ||
// only a single inversion in Fp. | ||
const factor = Fp.invert(Fp.create(a * a + b * b)); | ||
const factor = Fp.inv(Fp.create(a * a + b * b)); | ||
return { c0: Fp.mul(factor, Fp.create(a)), c1: Fp.mul(factor, Fp.create(-b)) }; | ||
}, | ||
sqrt: (num) => { | ||
if (Fp2.equals(num, Fp2.ZERO)) | ||
if (Fp2.eql(num, Fp2.ZERO)) | ||
return Fp2.ZERO; // Algo doesn't handles this case | ||
@@ -119,5 +119,5 @@ // TODO: Optimize this line. It's extremely slow. | ||
const candidateSqrt = Fp2.pow(num, (Fp2.ORDER + 8n) / 16n); | ||
const check = Fp2.div(Fp2.square(candidateSqrt), num); // candidateSqrt.square().div(this); | ||
const check = Fp2.div(Fp2.sqr(candidateSqrt), num); // candidateSqrt.square().div(this); | ||
const R = FP2_ROOTS_OF_UNITY; | ||
const divisor = [R[0], R[2], R[4], R[6]].find((r) => Fp2.equals(r, check)); | ||
const divisor = [R[0], R[2], R[4], R[6]].find((r) => Fp2.eql(r, check)); | ||
if (!divisor) | ||
@@ -130,3 +130,3 @@ throw new Error('No root'); | ||
const x1 = Fp2.div(candidateSqrt, root); | ||
const x2 = Fp2.negate(x1); | ||
const x2 = Fp2.neg(x1); | ||
const { re: re1, im: im1 } = Fp2.reim(x1); | ||
@@ -242,6 +242,6 @@ const { re: re2, im: im2 } = Fp2.reim(x2); | ||
const Fp6Square = ({ c0, c1, c2 }) => { | ||
let t0 = Fp2.square(c0); // c0² | ||
let t0 = Fp2.sqr(c0); // c0² | ||
let t1 = Fp2.mul(Fp2.mul(c0, c1), 2n); // 2 * c0 * c1 | ||
let t3 = Fp2.mul(Fp2.mul(c1, c2), 2n); // 2 * c1 * c2 | ||
let t4 = Fp2.square(c2); // c2² | ||
let t4 = Fp2.sqr(c2); // c2² | ||
return { | ||
@@ -251,3 +251,3 @@ c0: Fp2.add(Fp2.mulByNonresidue(t3), t0), | ||
// T1 + (c0 - c1 + c2)² + T3 - T0 - T4 | ||
c2: Fp2.sub(Fp2.sub(Fp2.add(Fp2.add(t1, Fp2.square(Fp2.add(Fp2.sub(c0, c1), c2))), t3), t0), t4), | ||
c2: Fp2.sub(Fp2.sub(Fp2.add(Fp2.add(t1, Fp2.sqr(Fp2.add(Fp2.sub(c0, c1), c2))), t3), t0), t4), | ||
}; | ||
@@ -264,5 +264,5 @@ }; | ||
isValid: ({ c0, c1, c2 }) => Fp2.isValid(c0) && Fp2.isValid(c1) && Fp2.isValid(c2), | ||
isZero: ({ c0, c1, c2 }) => Fp2.isZero(c0) && Fp2.isZero(c1) && Fp2.isZero(c2), | ||
negate: ({ c0, c1, c2 }) => ({ c0: Fp2.negate(c0), c1: Fp2.negate(c1), c2: Fp2.negate(c2) }), | ||
equals: ({ c0, c1, c2 }, { c0: r0, c1: r1, c2: r2 }) => Fp2.equals(c0, r0) && Fp2.equals(c1, r1) && Fp2.equals(c2, r2), | ||
is0: ({ c0, c1, c2 }) => Fp2.is0(c0) && Fp2.is0(c1) && Fp2.is0(c2), | ||
neg: ({ c0, c1, c2 }) => ({ c0: Fp2.neg(c0), c1: Fp2.neg(c1), c2: Fp2.neg(c2) }), | ||
eql: ({ c0, c1, c2 }, { c0: r0, c1: r1, c2: r2 }) => Fp2.eql(c0, r0) && Fp2.eql(c1, r1) && Fp2.eql(c2, r2), | ||
sqrt: () => { | ||
@@ -272,3 +272,3 @@ throw new Error('Not implemented'); | ||
// Do we need division by bigint at all? Should be done via order: | ||
div: (lhs, rhs) => Fp6.mul(lhs, typeof rhs === 'bigint' ? Fp.invert(Fp.create(rhs)) : Fp6.invert(rhs)), | ||
div: (lhs, rhs) => Fp6.mul(lhs, typeof rhs === 'bigint' ? Fp.inv(Fp.create(rhs)) : Fp6.inv(rhs)), | ||
pow: (num, power) => mod.FpPow(Fp6, num, power), | ||
@@ -280,3 +280,3 @@ invertBatch: (nums) => mod.FpInvertBatch(Fp6, nums), | ||
mul: Fp6Multiply, | ||
square: Fp6Square, | ||
sqr: Fp6Square, | ||
// NonNormalized stuff | ||
@@ -286,9 +286,9 @@ addN: Fp6Add, | ||
mulN: Fp6Multiply, | ||
squareN: Fp6Square, | ||
invert: ({ c0, c1, c2 }) => { | ||
let t0 = Fp2.sub(Fp2.square(c0), Fp2.mulByNonresidue(Fp2.mul(c2, c1))); // c0² - c2 * c1 * (u + 1) | ||
let t1 = Fp2.sub(Fp2.mulByNonresidue(Fp2.square(c2)), Fp2.mul(c0, c1)); // c2² * (u + 1) - c0 * c1 | ||
let t2 = Fp2.sub(Fp2.square(c1), Fp2.mul(c0, c2)); // c1² - c0 * c2 | ||
sqrN: Fp6Square, | ||
inv: ({ c0, c1, c2 }) => { | ||
let t0 = Fp2.sub(Fp2.sqr(c0), Fp2.mulByNonresidue(Fp2.mul(c2, c1))); // c0² - c2 * c1 * (u + 1) | ||
let t1 = Fp2.sub(Fp2.mulByNonresidue(Fp2.sqr(c2)), Fp2.mul(c0, c1)); // c2² * (u + 1) - c0 * c1 | ||
let t2 = Fp2.sub(Fp2.sqr(c1), Fp2.mul(c0, c2)); // c1² - c0 * c2 | ||
// 1/(((c2 * T1 + c1 * T2) * v) + c0 * T0) | ||
let t4 = Fp2.invert(Fp2.add(Fp2.mulByNonresidue(Fp2.add(Fp2.mul(c2, t1), Fp2.mul(c1, t2))), Fp2.mul(c0, t0))); | ||
let t4 = Fp2.inv(Fp2.add(Fp2.mulByNonresidue(Fp2.add(Fp2.mul(c2, t1), Fp2.mul(c1, t2))), Fp2.mul(c0, t0))); | ||
return { c0: Fp2.mul(t4, t0), c1: Fp2.mul(t4, t1), c2: Fp2.mul(t4, t2) }; | ||
@@ -434,7 +434,7 @@ }, | ||
function Fp4Square(a, b) { | ||
const a2 = Fp2.square(a); | ||
const b2 = Fp2.square(b); | ||
const a2 = Fp2.sqr(a); | ||
const b2 = Fp2.sqr(b); | ||
return { | ||
first: Fp2.add(Fp2.mulByNonresidue(b2), a2), | ||
second: Fp2.sub(Fp2.sub(Fp2.square(Fp2.add(a, b)), a2), b2), // (a + b)² - a² - b² | ||
second: Fp2.sub(Fp2.sub(Fp2.sqr(Fp2.add(a, b)), a2), b2), // (a + b)² - a² - b² | ||
}; | ||
@@ -451,13 +451,13 @@ } | ||
isValid: ({ c0, c1 }) => Fp6.isValid(c0) && Fp6.isValid(c1), | ||
isZero: ({ c0, c1 }) => Fp6.isZero(c0) && Fp6.isZero(c1), | ||
negate: ({ c0, c1 }) => ({ c0: Fp6.negate(c0), c1: Fp6.negate(c1) }), | ||
equals: ({ c0, c1 }, { c0: r0, c1: r1 }) => Fp6.equals(c0, r0) && Fp6.equals(c1, r1), | ||
is0: ({ c0, c1 }) => Fp6.is0(c0) && Fp6.is0(c1), | ||
neg: ({ c0, c1 }) => ({ c0: Fp6.neg(c0), c1: Fp6.neg(c1) }), | ||
eql: ({ c0, c1 }, { c0: r0, c1: r1 }) => Fp6.eql(c0, r0) && Fp6.eql(c1, r1), | ||
sqrt: () => { | ||
throw new Error('Not implemented'); | ||
}, | ||
invert: ({ c0, c1 }) => { | ||
let t = Fp6.invert(Fp6.sub(Fp6.square(c0), Fp6.mulByNonresidue(Fp6.square(c1)))); // 1 / (c0² - c1² * v) | ||
return { c0: Fp6.mul(c0, t), c1: Fp6.negate(Fp6.mul(c1, t)) }; // ((C0 * T) * T) + (-C1 * T) * w | ||
inv: ({ c0, c1 }) => { | ||
let t = Fp6.inv(Fp6.sub(Fp6.sqr(c0), Fp6.mulByNonresidue(Fp6.sqr(c1)))); // 1 / (c0² - c1² * v) | ||
return { c0: Fp6.mul(c0, t), c1: Fp6.neg(Fp6.mul(c1, t)) }; // ((C0 * T) * T) + (-C1 * T) * w | ||
}, | ||
div: (lhs, rhs) => Fp12.mul(lhs, typeof rhs === 'bigint' ? Fp.invert(Fp.create(rhs)) : Fp12.invert(rhs)), | ||
div: (lhs, rhs) => Fp12.mul(lhs, typeof rhs === 'bigint' ? Fp.inv(Fp.create(rhs)) : Fp12.inv(rhs)), | ||
pow: (num, power) => mod.FpPow(Fp12, num, power), | ||
@@ -469,3 +469,3 @@ invertBatch: (nums) => mod.FpInvertBatch(Fp12, nums), | ||
mul: Fp12Multiply, | ||
square: Fp12Square, | ||
sqr: Fp12Square, | ||
// NonNormalized stuff | ||
@@ -475,3 +475,3 @@ addN: Fp12Add, | ||
mulN: Fp12Multiply, | ||
squareN: Fp12Square, | ||
sqrN: Fp12Square, | ||
// Bytes utils | ||
@@ -530,3 +530,3 @@ fromBytes: (b) => { | ||
}), | ||
conjugate: ({ c0, c1 }) => ({ c0, c1: Fp6.negate(c1) }), | ||
conjugate: ({ c0, c1 }) => ({ c0, c1: Fp6.neg(c1) }), | ||
// A cyclotomic group is a subgroup of Fp^n defined by | ||
@@ -810,3 +810,3 @@ // GΦₙ(p) = {α ∈ Fpⁿ : α^Φₙ(p) = 1} | ||
function psi2(x, y) { | ||
return [Fp2.mul(x, PSI2_C1), Fp2.negate(y)]; | ||
return [Fp2.mul(x, PSI2_C1), Fp2.neg(y)]; | ||
} | ||
@@ -833,2 +833,3 @@ function G2psi2(c, P) { | ||
DST: 'BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_NUL_', | ||
encodeDST: 'BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_NUL_', | ||
// p: the characteristic of F | ||
@@ -900,3 +901,3 @@ // where F is a finite field of characteristic p and order q = p^m | ||
const cubicRootOfUnityModP = 0x5f19672fdf76ce51ba69c6076a0f77eaddb3a93be6f89688de17d813620a00022e01fffffffefffen; | ||
const phi = new c(Fp.mul(point.x, cubicRootOfUnityModP), point.y, point.z); | ||
const phi = new c(Fp.mul(point.px, cubicRootOfUnityModP), point.py, point.pz); | ||
// todo: unroll | ||
@@ -943,3 +944,3 @@ const xP = point.multiplyUnsafe(exports.bls12_381.CURVE.x).negate(); // [x]P | ||
if ((y * 2n) / P !== aflag) | ||
y = Fp.negate(y); | ||
y = Fp.neg(y); | ||
return { x: Fp.create(x), y: Fp.create(y) }; | ||
@@ -950,3 +951,3 @@ } | ||
if ((bytes[0] & (1 << 6)) !== 0) | ||
return exports.bls12_381.G1.Point.ZERO; | ||
return exports.bls12_381.G1.ProjectivePoint.ZERO.toAffine(); | ||
const x = (0, utils_js_1.bytesToNumberBE)(bytes.slice(0, Fp.BYTES)); | ||
@@ -962,3 +963,3 @@ const y = (0, utils_js_1.bytesToNumberBE)(bytes.slice(Fp.BYTES)); | ||
const isZero = point.equals(c.ZERO); | ||
const { x, y } = point; | ||
const { x, y } = point.toAffine(); | ||
if (isCompressed) { | ||
@@ -1050,2 +1051,4 @@ if (isZero) | ||
const bitS = m_byte & 0x20; // sign bit | ||
const L = Fp.BYTES; | ||
const slc = (b, from, to) => (0, utils_js_1.bytesToNumberBE)(b.slice(from, to)); | ||
if (bytes.length === 96 && bitC) { | ||
@@ -1062,4 +1065,4 @@ const { b } = exports.bls12_381.CURVE.G2; | ||
} | ||
const x_1 = (0, utils_js_1.bytesToNumberBE)(bytes.slice(0, Fp.BYTES)); | ||
const x_0 = (0, utils_js_1.bytesToNumberBE)(bytes.slice(Fp.BYTES)); | ||
const x_1 = slc(bytes, 0, L); | ||
const x_0 = slc(bytes, L, 2 * L); | ||
const x = Fp2.create({ c0: Fp.create(x_0), c1: Fp.create(x_1) }); | ||
@@ -1069,3 +1072,3 @@ const right = Fp2.add(Fp2.pow(x, 3n), b); // y² = x³ + 4 * (u+1) = x³ + b | ||
const Y_bit = y.c1 === 0n ? (y.c0 * 2n) / P : (y.c1 * 2n) / P ? 1n : 0n; | ||
y = bitS > 0 && Y_bit > 0 ? y : Fp2.negate(y); | ||
y = bitS > 0 && Y_bit > 0 ? y : Fp2.neg(y); | ||
return { x, y }; | ||
@@ -1078,6 +1081,6 @@ } | ||
} | ||
const x1 = (0, utils_js_1.bytesToNumberBE)(bytes.slice(0, Fp.BYTES)); | ||
const x0 = (0, utils_js_1.bytesToNumberBE)(bytes.slice(Fp.BYTES, 2 * Fp.BYTES)); | ||
const y1 = (0, utils_js_1.bytesToNumberBE)(bytes.slice(2 * Fp.BYTES, 3 * Fp.BYTES)); | ||
const y0 = (0, utils_js_1.bytesToNumberBE)(bytes.slice(3 * Fp.BYTES)); | ||
const x1 = slc(bytes, 0, L); | ||
const x0 = slc(bytes, L, 2 * L); | ||
const y1 = slc(bytes, 2 * L, 3 * L); | ||
const y0 = slc(bytes, 3 * L, 4 * L); | ||
return { x: Fp2.fromBigTuple([x0, x1]), y: Fp2.fromBigTuple([y0, y1]) }; | ||
@@ -1091,3 +1094,3 @@ } | ||
const isZero = point.equals(c.ZERO); | ||
const { x, y } = point; | ||
const { x, y } = point.toAffine(); | ||
if (isCompressed) { | ||
@@ -1124,3 +1127,3 @@ const P = Fp.ORDER; | ||
if (bflag1 === 1n) | ||
return exports.bls12_381.G2.Point.ZERO; | ||
return exports.bls12_381.G2.ProjectivePoint.ZERO; | ||
const x1 = Fp.create(z1 & Fp.MASK); | ||
@@ -1141,4 +1144,5 @@ const x2 = Fp.create(z2); | ||
if (isGreater || isZero) | ||
y = Fp2.negate(y); | ||
const point = new exports.bls12_381.G2.Point(x, y); | ||
y = Fp2.neg(y); | ||
const point = exports.bls12_381.G2.ProjectivePoint.fromAffine({ x, y }); | ||
// console.log('Signature.decode', point); | ||
point.assertValidity(); | ||
@@ -1150,6 +1154,7 @@ return point; | ||
point.assertValidity(); | ||
if (point.equals(exports.bls12_381.G2.Point.ZERO)) | ||
if (point.equals(exports.bls12_381.G2.ProjectivePoint.ZERO)) | ||
return (0, utils_js_1.concatBytes)(COMPRESSED_ZERO, (0, utils_js_1.numberToBytesBE)(0n, Fp.BYTES)); | ||
const { re: x0, im: x1 } = Fp2.reim(point.x); | ||
const { re: y0, im: y1 } = Fp2.reim(point.y); | ||
const a = point.toAffine(); | ||
const { re: x0, im: x1 } = Fp2.reim(a.x); | ||
const { re: y0, im: y1 } = Fp2.reim(a.y); | ||
const tmp = y1 > 0n ? y1 * 2n : y0 * 2n; | ||
@@ -1156,0 +1161,0 @@ const aflag1 = Boolean((tmp / Fp.ORDER) & 1n); |
@@ -5,4 +5,4 @@ "use strict"; | ||
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */ | ||
const sha256_1 = require("@noble/hashes/sha256"); | ||
const weierstrass_js_1 = require("./abstract/weierstrass.js"); | ||
const sha256_1 = require("@noble/hashes/sha256"); | ||
const _shortw_utils_js_1 = require("./_shortw_utils.js"); | ||
@@ -9,0 +9,0 @@ const modular_js_1 = require("./abstract/modular.js"); |
@@ -1,3 +0,4 @@ | ||
import { ExtendedPointType } from './abstract/edwards.js'; | ||
import { ExtPointType } from './abstract/edwards.js'; | ||
import { Hex } from './abstract/utils.js'; | ||
import * as htf from './abstract/hash-to-curve.js'; | ||
export declare const ED25519_TORSION_SUBGROUP: string[]; | ||
@@ -8,3 +9,5 @@ export declare const ed25519: import("./abstract/edwards.js").CurveFn; | ||
export declare const x25519: import("./abstract/montgomery.js").CurveFn; | ||
declare type ExtendedPoint = ExtendedPointType; | ||
declare const hashToCurve: (msg: Hex, options?: htf.htfBasicOpts | undefined) => htf.H2CPoint<bigint>, encodeToCurve: (msg: Hex, options?: htf.htfBasicOpts | undefined) => htf.H2CPoint<bigint>; | ||
export { hashToCurve, encodeToCurve }; | ||
declare type ExtendedPoint = ExtPointType; | ||
/** | ||
@@ -46,5 +49,4 @@ * Each ed25519/ExtendedPoint has 8 different equivalent points. This can be | ||
subtract(other: RistrettoPoint): RistrettoPoint; | ||
multiply(scalar: number | bigint): RistrettoPoint; | ||
multiplyUnsafe(scalar: number | bigint): RistrettoPoint; | ||
multiply(scalar: bigint): RistrettoPoint; | ||
multiplyUnsafe(scalar: bigint): RistrettoPoint; | ||
} | ||
export {}; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.RistrettoPoint = exports.x25519 = exports.ed25519ph = exports.ed25519ctx = exports.ed25519 = exports.ED25519_TORSION_SUBGROUP = void 0; | ||
exports.RistrettoPoint = exports.encodeToCurve = exports.hashToCurve = exports.x25519 = exports.ed25519ph = exports.ed25519ctx = exports.ed25519 = exports.ED25519_TORSION_SUBGROUP = void 0; | ||
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */ | ||
@@ -11,2 +11,3 @@ const sha512_1 = require("@noble/hashes/sha512"); | ||
const utils_js_1 = require("./abstract/utils.js"); | ||
const htf = require("./abstract/hash-to-curve.js"); | ||
/** | ||
@@ -86,2 +87,52 @@ * ed25519 Twisted Edwards curve with following addons: | ||
const Fp = (0, modular_js_1.Fp)(ED25519_P, undefined, true); | ||
const ED25519_DEF = { | ||
// Param: a | ||
a: BigInt(-1), | ||
// Equal to -121665/121666 over finite field. | ||
// Negative number is P - number, and division is invert(number, P) | ||
d: BigInt('37095705934669439343138083508754565189542113879843219016388785533085940283555'), | ||
// Finite field 𝔽p over which we'll do calculations; 2n ** 255n - 19n | ||
Fp, | ||
// Subgroup order: how many points ed25519 has | ||
// 2n ** 252n + 27742317777372353535851937790883648493n; | ||
n: BigInt('7237005577332262213973186563042994240857116359379907606001950938285454250989'), | ||
// Cofactor | ||
h: BigInt(8), | ||
// Base point (x, y) aka generator point | ||
Gx: BigInt('15112221349535400772501151409588531511454012693041857206046113283949847762202'), | ||
Gy: BigInt('46316835694926478169428394003475163141307993866256225615783033603165251855960'), | ||
hash: sha512_1.sha512, | ||
randomBytes: utils_1.randomBytes, | ||
adjustScalarBytes, | ||
// dom2 | ||
// Ratio of u to v. Allows us to combine inversion and square root. Uses algo from RFC8032 5.1.3. | ||
// Constant-time, u/√v | ||
uvRatio, | ||
}; | ||
exports.ed25519 = (0, edwards_js_1.twistedEdwards)(ED25519_DEF); | ||
function ed25519_domain(data, ctx, phflag) { | ||
if (ctx.length > 255) | ||
throw new Error('Context is too big'); | ||
return (0, utils_1.concatBytes)((0, utils_1.utf8ToBytes)('SigEd25519 no Ed25519 collisions'), new Uint8Array([phflag ? 1 : 0, ctx.length]), ctx, data); | ||
} | ||
exports.ed25519ctx = (0, edwards_js_1.twistedEdwards)({ ...ED25519_DEF, domain: ed25519_domain }); | ||
exports.ed25519ph = (0, edwards_js_1.twistedEdwards)({ | ||
...ED25519_DEF, | ||
domain: ed25519_domain, | ||
preHash: sha512_1.sha512, | ||
}); | ||
exports.x25519 = (0, montgomery_js_1.montgomery)({ | ||
P: ED25519_P, | ||
a24: BigInt('121665'), | ||
montgomeryBits: 255, | ||
nByteLength: 32, | ||
Gu: '0900000000000000000000000000000000000000000000000000000000000000', | ||
powPminus2: (x) => { | ||
const P = ED25519_P; | ||
// x^(p-2) aka x^(2^255-21) | ||
const { pow_p_5_8, b2 } = ed25519_pow_2_252_3(x); | ||
return (0, modular_js_1.mod)((0, modular_js_1.pow2)(pow_p_5_8, BigInt(3), P) * b2, P); | ||
}, | ||
adjustScalarBytes, | ||
}); | ||
// Hash To Curve Elligator2 Map (NOTE: different from ristretto255 elligator) | ||
@@ -92,3 +143,3 @@ // NOTE: very important part is usage of FpSqrtEven for ELL2_C1_EDWARDS, since | ||
const ELL2_C2 = Fp.pow(_2n, ELL2_C1); // 2. c2 = 2^c1 | ||
const ELL2_C3 = Fp.sqrt(Fp.negate(Fp.ONE)); // 3. c3 = sqrt(-1) | ||
const ELL2_C3 = Fp.sqrt(Fp.neg(Fp.ONE)); // 3. c3 = sqrt(-1) | ||
const ELL2_C4 = (Fp.ORDER - BigInt(5)) / BigInt(8); // 4. c4 = (q - 5) / 8 # Integer arithmetic | ||
@@ -98,7 +149,7 @@ const ELL2_J = BigInt(486662); | ||
function map_to_curve_elligator2_curve25519(u) { | ||
let tv1 = Fp.square(u); // 1. tv1 = u^2 | ||
let tv1 = Fp.sqr(u); // 1. tv1 = u^2 | ||
tv1 = Fp.mul(tv1, _2n); // 2. tv1 = 2 * tv1 | ||
let xd = Fp.add(tv1, Fp.ONE); // 3. xd = tv1 + 1 # Nonzero: -1 is square (mod p), tv1 is not | ||
let x1n = Fp.negate(ELL2_J); // 4. x1n = -J # x1 = x1n / xd = -J / (1 + 2 * u^2) | ||
let tv2 = Fp.square(xd); // 5. tv2 = xd^2 | ||
let x1n = Fp.neg(ELL2_J); // 4. x1n = -J # x1 = x1n / xd = -J / (1 + 2 * u^2) | ||
let tv2 = Fp.sqr(xd); // 5. tv2 = xd^2 | ||
let gxd = Fp.mul(tv2, xd); // 6. gxd = tv2 * xd # gxd = xd^3 | ||
@@ -109,4 +160,4 @@ let gx1 = Fp.mul(tv1, ELL2_J); // 7. gx1 = J * tv1 # x1n + J * xd | ||
gx1 = Fp.mul(gx1, x1n); // 10. gx1 = gx1 * x1n # x1n^3 + J * x1n^2 * xd + x1n * xd^2 | ||
let tv3 = Fp.square(gxd); // 11. tv3 = gxd^2 | ||
tv2 = Fp.square(tv3); // 12. tv2 = tv3^2 # gxd^4 | ||
let tv3 = Fp.sqr(gxd); // 11. tv3 = gxd^2 | ||
tv2 = Fp.sqr(tv3); // 12. tv2 = tv3^2 # gxd^4 | ||
tv3 = Fp.mul(tv3, gxd); // 13. tv3 = tv3 * gxd # gxd^3 | ||
@@ -118,5 +169,5 @@ tv3 = Fp.mul(tv3, gx1); // 14. tv3 = tv3 * gx1 # gx1 * gxd^3 | ||
let y12 = Fp.mul(y11, ELL2_C3); // 18. y12 = y11 * c3 | ||
tv2 = Fp.square(y11); // 19. tv2 = y11^2 | ||
tv2 = Fp.sqr(y11); // 19. tv2 = y11^2 | ||
tv2 = Fp.mul(tv2, gxd); // 20. tv2 = tv2 * gxd | ||
let e1 = Fp.equals(tv2, gx1); // 21. e1 = tv2 == gx1 | ||
let e1 = Fp.eql(tv2, gx1); // 21. e1 = tv2 == gx1 | ||
let y1 = Fp.cmov(y12, y11, e1); // 22. y1 = CMOV(y12, y11, e1) # If g(x1) is square, this is its sqrt | ||
@@ -128,16 +179,16 @@ let x2n = Fp.mul(x1n, tv1); // 23. x2n = x1n * tv1 # x2 = x2n / xd = 2 * u^2 * x1n / xd | ||
let gx2 = Fp.mul(gx1, tv1); // 27. gx2 = gx1 * tv1 # g(x2) = gx2 / gxd = 2 * u^2 * g(x1) | ||
tv2 = Fp.square(y21); // 28. tv2 = y21^2 | ||
tv2 = Fp.sqr(y21); // 28. tv2 = y21^2 | ||
tv2 = Fp.mul(tv2, gxd); // 29. tv2 = tv2 * gxd | ||
let e2 = Fp.equals(tv2, gx2); // 30. e2 = tv2 == gx2 | ||
let e2 = Fp.eql(tv2, gx2); // 30. e2 = tv2 == gx2 | ||
let y2 = Fp.cmov(y22, y21, e2); // 31. y2 = CMOV(y22, y21, e2) # If g(x2) is square, this is its sqrt | ||
tv2 = Fp.square(y1); // 32. tv2 = y1^2 | ||
tv2 = Fp.sqr(y1); // 32. tv2 = y1^2 | ||
tv2 = Fp.mul(tv2, gxd); // 33. tv2 = tv2 * gxd | ||
let e3 = Fp.equals(tv2, gx1); // 34. e3 = tv2 == gx1 | ||
let e3 = Fp.eql(tv2, gx1); // 34. e3 = tv2 == gx1 | ||
let xn = Fp.cmov(x2n, x1n, e3); // 35. xn = CMOV(x2n, x1n, e3) # If e3, x = x1, else x = x2 | ||
let y = Fp.cmov(y2, y1, e3); // 36. y = CMOV(y2, y1, e3) # If e3, y = y1, else y = y2 | ||
let e4 = Fp.isOdd(y); // 37. e4 = sgn0(y) == 1 # Fix sign of y | ||
y = Fp.cmov(y, Fp.negate(y), e3 !== e4); // 38. y = CMOV(y, -y, e3 XOR e4) | ||
y = Fp.cmov(y, Fp.neg(y), e3 !== e4); // 38. y = CMOV(y, -y, e3 XOR e4) | ||
return { xMn: xn, xMd: xd, yMn: y, yMd: 1n }; // 39. return (xn, xd, y, 1) | ||
} | ||
const ELL2_C1_EDWARDS = (0, modular_js_1.FpSqrtEven)(Fp, Fp.negate(BigInt(486664))); // sgn0(c1) MUST equal 0 | ||
const ELL2_C1_EDWARDS = (0, modular_js_1.FpSqrtEven)(Fp, Fp.neg(BigInt(486664))); // sgn0(c1) MUST equal 0 | ||
function map_to_curve_elligator2_edwards25519(u) { | ||
@@ -151,3 +202,3 @@ const { xMn, xMd, yMn, yMd } = map_to_curve_elligator2_curve25519(u); // 1. (xMn, xMd, yMn, yMd) = map_to_curve_elligator2_curve25519(u) | ||
let tv1 = Fp.mul(xd, yd); // 7. tv1 = xd * yd | ||
let e = Fp.equals(tv1, Fp.ZERO); // 8. e = tv1 == 0 | ||
let e = Fp.eql(tv1, Fp.ZERO); // 8. e = tv1 == 0 | ||
xn = Fp.cmov(xn, Fp.ZERO, e); // 9. xn = CMOV(xn, 0, e) | ||
@@ -160,64 +211,16 @@ xd = Fp.cmov(xd, Fp.ONE, e); // 10. xd = CMOV(xd, 1, e) | ||
} | ||
const ED25519_DEF = { | ||
// Param: a | ||
a: BigInt(-1), | ||
// Equal to -121665/121666 over finite field. | ||
// Negative number is P - number, and division is invert(number, P) | ||
d: BigInt('37095705934669439343138083508754565189542113879843219016388785533085940283555'), | ||
// Finite field 𝔽p over which we'll do calculations; 2n ** 255n - 19n | ||
Fp, | ||
// Subgroup order: how many points ed25519 has | ||
// 2n ** 252n + 27742317777372353535851937790883648493n; | ||
n: BigInt('7237005577332262213973186563042994240857116359379907606001950938285454250989'), | ||
// Cofactor | ||
h: BigInt(8), | ||
// Base point (x, y) aka generator point | ||
Gx: BigInt('15112221349535400772501151409588531511454012693041857206046113283949847762202'), | ||
Gy: BigInt('46316835694926478169428394003475163141307993866256225615783033603165251855960'), | ||
const { hashToCurve, encodeToCurve } = htf.hashToCurve(exports.ed25519.ExtendedPoint, (scalars) => map_to_curve_elligator2_edwards25519(scalars[0]), { | ||
DST: 'edwards25519_XMD:SHA-512_ELL2_RO_', | ||
encodeDST: 'edwards25519_XMD:SHA-512_ELL2_NU_', | ||
p: Fp.ORDER, | ||
m: 1, | ||
k: 128, | ||
expand: 'xmd', | ||
hash: sha512_1.sha512, | ||
randomBytes: utils_1.randomBytes, | ||
adjustScalarBytes, | ||
// dom2 | ||
// Ratio of u to v. Allows us to combine inversion and square root. Uses algo from RFC8032 5.1.3. | ||
// Constant-time, u/√v | ||
uvRatio, | ||
htfDefaults: { | ||
DST: 'edwards25519_XMD:SHA-512_ELL2_RO_', | ||
p: Fp.ORDER, | ||
m: 1, | ||
k: 128, | ||
expand: 'xmd', | ||
hash: sha512_1.sha512, | ||
}, | ||
mapToCurve: (scalars) => map_to_curve_elligator2_edwards25519(scalars[0]), | ||
}; | ||
exports.ed25519 = (0, edwards_js_1.twistedEdwards)(ED25519_DEF); | ||
function ed25519_domain(data, ctx, phflag) { | ||
if (ctx.length > 255) | ||
throw new Error('Context is too big'); | ||
return (0, utils_1.concatBytes)((0, utils_1.utf8ToBytes)('SigEd25519 no Ed25519 collisions'), new Uint8Array([phflag ? 1 : 0, ctx.length]), ctx, data); | ||
} | ||
exports.ed25519ctx = (0, edwards_js_1.twistedEdwards)({ ...ED25519_DEF, domain: ed25519_domain }); | ||
exports.ed25519ph = (0, edwards_js_1.twistedEdwards)({ | ||
...ED25519_DEF, | ||
domain: ed25519_domain, | ||
preHash: sha512_1.sha512, | ||
}); | ||
exports.x25519 = (0, montgomery_js_1.montgomery)({ | ||
P: ED25519_P, | ||
a24: BigInt('121665'), | ||
montgomeryBits: 255, | ||
nByteLength: 32, | ||
Gu: '0900000000000000000000000000000000000000000000000000000000000000', | ||
powPminus2: (x) => { | ||
const P = ED25519_P; | ||
// x^(p-2) aka x^(2^255-21) | ||
const { pow_p_5_8, b2 } = ed25519_pow_2_252_3(x); | ||
return (0, modular_js_1.mod)((0, modular_js_1.pow2)(pow_p_5_8, BigInt(3), P) * b2, P); | ||
}, | ||
adjustScalarBytes, | ||
}); | ||
exports.hashToCurve = hashToCurve; | ||
exports.encodeToCurve = encodeToCurve; | ||
function assertRstPoint(other) { | ||
if (!(other instanceof RistrettoPoint)) | ||
throw new TypeError('RistrettoPoint expected'); | ||
throw new Error('RistrettoPoint expected'); | ||
} | ||
@@ -331,3 +334,3 @@ // √(-1) aka √(a) aka 2^((p-1)/4) | ||
toRawBytes() { | ||
let { x, y, z, t } = this.ep; | ||
let { ex: x, ey: y, ez: z, et: t } = this.ep; | ||
const P = exports.ed25519.CURVE.Fp.ORDER; | ||
@@ -370,8 +373,8 @@ const mod = exports.ed25519.CURVE.Fp.create; | ||
assertRstPoint(other); | ||
const a = this.ep; | ||
const b = other.ep; | ||
const { ex: X1, ey: Y1 } = this.ep; | ||
const { ex: X2, ey: Y2 } = this.ep; | ||
const mod = exports.ed25519.CURVE.Fp.create; | ||
// (x1 * y2 == y1 * x2) | (y1 * y2 == x1 * x2) | ||
const one = mod(a.x * b.y) === mod(a.y * b.x); | ||
const two = mod(a.y * b.y) === mod(a.x * b.x); | ||
const one = mod(X1 * Y2) === mod(Y1 * X2); | ||
const two = mod(Y1 * Y2) === mod(X1 * X2); | ||
return one || two; | ||
@@ -378,0 +381,0 @@ } |
@@ -0,3 +1,6 @@ | ||
import * as htf from './abstract/hash-to-curve.js'; | ||
export declare const ed448: import("./abstract/edwards.js").CurveFn; | ||
export declare const ed448ph: import("./abstract/edwards.js").CurveFn; | ||
export declare const x448: import("./abstract/montgomery.js").CurveFn; | ||
declare const hashToCurve: (msg: import("./abstract/utils.js").Hex, options?: htf.htfBasicOpts | undefined) => htf.H2CPoint<bigint>, encodeToCurve: (msg: import("./abstract/utils.js").Hex, options?: htf.htfBasicOpts | undefined) => htf.H2CPoint<bigint>; | ||
export { hashToCurve, encodeToCurve }; |
169
lib/ed448.js
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.x448 = exports.ed448ph = exports.ed448 = void 0; | ||
exports.encodeToCurve = exports.hashToCurve = exports.x448 = exports.ed448ph = exports.ed448 = void 0; | ||
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */ | ||
@@ -10,2 +10,3 @@ const sha3_1 = require("@noble/hashes/sha3"); | ||
const montgomery_js_1 = require("./abstract/montgomery.js"); | ||
const htf = require("./abstract/hash-to-curve.js"); | ||
/** | ||
@@ -53,75 +54,2 @@ * Edwards448 (not Ed448-Goldilocks) curve with following addons: | ||
const Fp = (0, modular_js_1.Fp)(ed448P, 456, true); | ||
// Hash To Curve Elligator2 Map | ||
const ELL2_C1 = (Fp.ORDER - BigInt(3)) / BigInt(4); // 1. c1 = (q - 3) / 4 # Integer arithmetic | ||
const ELL2_J = BigInt(156326); | ||
function map_to_curve_elligator2_curve448(u) { | ||
let tv1 = Fp.square(u); // 1. tv1 = u^2 | ||
let e1 = Fp.equals(tv1, Fp.ONE); // 2. e1 = tv1 == 1 | ||
tv1 = Fp.cmov(tv1, Fp.ZERO, e1); // 3. tv1 = CMOV(tv1, 0, e1) # If Z * u^2 == -1, set tv1 = 0 | ||
let xd = Fp.sub(Fp.ONE, tv1); // 4. xd = 1 - tv1 | ||
let x1n = Fp.negate(ELL2_J); // 5. x1n = -J | ||
let tv2 = Fp.square(xd); // 6. tv2 = xd^2 | ||
let gxd = Fp.mul(tv2, xd); // 7. gxd = tv2 * xd # gxd = xd^3 | ||
let gx1 = Fp.mul(tv1, Fp.negate(ELL2_J)); // 8. gx1 = -J * tv1 # x1n + J * xd | ||
gx1 = Fp.mul(gx1, x1n); // 9. gx1 = gx1 * x1n # x1n^2 + J * x1n * xd | ||
gx1 = Fp.add(gx1, tv2); // 10. gx1 = gx1 + tv2 # x1n^2 + J * x1n * xd + xd^2 | ||
gx1 = Fp.mul(gx1, x1n); // 11. gx1 = gx1 * x1n # x1n^3 + J * x1n^2 * xd + x1n * xd^2 | ||
let tv3 = Fp.square(gxd); // 12. tv3 = gxd^2 | ||
tv2 = Fp.mul(gx1, gxd); // 13. tv2 = gx1 * gxd # gx1 * gxd | ||
tv3 = Fp.mul(tv3, tv2); // 14. tv3 = tv3 * tv2 # gx1 * gxd^3 | ||
let y1 = Fp.pow(tv3, ELL2_C1); // 15. y1 = tv3^c1 # (gx1 * gxd^3)^((p - 3) / 4) | ||
y1 = Fp.mul(y1, tv2); // 16. y1 = y1 * tv2 # gx1 * gxd * (gx1 * gxd^3)^((p - 3) / 4) | ||
let x2n = Fp.mul(x1n, Fp.negate(tv1)); // 17. x2n = -tv1 * x1n # x2 = x2n / xd = -1 * u^2 * x1n / xd | ||
let y2 = Fp.mul(y1, u); // 18. y2 = y1 * u | ||
y2 = Fp.cmov(y2, Fp.ZERO, e1); // 19. y2 = CMOV(y2, 0, e1) | ||
tv2 = Fp.square(y1); // 20. tv2 = y1^2 | ||
tv2 = Fp.mul(tv2, gxd); // 21. tv2 = tv2 * gxd | ||
let e2 = Fp.equals(tv2, gx1); // 22. e2 = tv2 == gx1 | ||
let xn = Fp.cmov(x2n, x1n, e2); // 23. xn = CMOV(x2n, x1n, e2) # If e2, x = x1, else x = x2 | ||
let y = Fp.cmov(y2, y1, e2); // 24. y = CMOV(y2, y1, e2) # If e2, y = y1, else y = y2 | ||
let e3 = Fp.isOdd(y); // 25. e3 = sgn0(y) == 1 # Fix sign of y | ||
y = Fp.cmov(y, Fp.negate(y), e2 !== e3); // 26. y = CMOV(y, -y, e2 XOR e3) | ||
return { xn, xd, yn: y, yd: Fp.ONE }; // 27. return (xn, xd, y, 1) | ||
} | ||
function map_to_curve_elligator2_edwards448(u) { | ||
let { xn, xd, yn, yd } = map_to_curve_elligator2_curve448(u); // 1. (xn, xd, yn, yd) = map_to_curve_elligator2_curve448(u) | ||
let xn2 = Fp.square(xn); // 2. xn2 = xn^2 | ||
let xd2 = Fp.square(xd); // 3. xd2 = xd^2 | ||
let xd4 = Fp.square(xd2); // 4. xd4 = xd2^2 | ||
let yn2 = Fp.square(yn); // 5. yn2 = yn^2 | ||
let yd2 = Fp.square(yd); // 6. yd2 = yd^2 | ||
let xEn = Fp.sub(xn2, xd2); // 7. xEn = xn2 - xd2 | ||
let tv2 = Fp.sub(xEn, xd2); // 8. tv2 = xEn - xd2 | ||
xEn = Fp.mul(xEn, xd2); // 9. xEn = xEn * xd2 | ||
xEn = Fp.mul(xEn, yd); // 10. xEn = xEn * yd | ||
xEn = Fp.mul(xEn, yn); // 11. xEn = xEn * yn | ||
xEn = Fp.mul(xEn, 4n); // 12. xEn = xEn * 4 | ||
tv2 = Fp.mul(tv2, xn2); // 13. tv2 = tv2 * xn2 | ||
tv2 = Fp.mul(tv2, yd2); // 14. tv2 = tv2 * yd2 | ||
let tv3 = Fp.mul(yn2, 4n); // 15. tv3 = 4 * yn2 | ||
let tv1 = Fp.add(tv3, yd2); // 16. tv1 = tv3 + yd2 | ||
tv1 = Fp.mul(tv1, xd4); // 17. tv1 = tv1 * xd4 | ||
let xEd = Fp.add(tv1, tv2); // 18. xEd = tv1 + tv2 | ||
tv2 = Fp.mul(tv2, xn); // 19. tv2 = tv2 * xn | ||
let tv4 = Fp.mul(xn, xd4); // 20. tv4 = xn * xd4 | ||
let yEn = Fp.sub(tv3, yd2); // 21. yEn = tv3 - yd2 | ||
yEn = Fp.mul(yEn, tv4); // 22. yEn = yEn * tv4 | ||
yEn = Fp.sub(yEn, tv2); // 23. yEn = yEn - tv2 | ||
tv1 = Fp.add(xn2, xd2); // 24. tv1 = xn2 + xd2 | ||
tv1 = Fp.mul(tv1, xd2); // 25. tv1 = tv1 * xd2 | ||
tv1 = Fp.mul(tv1, xd); // 26. tv1 = tv1 * xd | ||
tv1 = Fp.mul(tv1, yn2); // 27. tv1 = tv1 * yn2 | ||
tv1 = Fp.mul(tv1, BigInt(-2)); // 28. tv1 = -2 * tv1 | ||
let yEd = Fp.add(tv2, tv1); // 29. yEd = tv2 + tv1 | ||
tv4 = Fp.mul(tv4, yd2); // 30. tv4 = tv4 * yd2 | ||
yEd = Fp.add(yEd, tv4); // 31. yEd = yEd + tv4 | ||
tv1 = Fp.mul(xEd, yEd); // 32. tv1 = xEd * yEd | ||
let e = Fp.equals(tv1, Fp.ZERO); // 33. e = tv1 == 0 | ||
xEn = Fp.cmov(xEn, Fp.ZERO, e); // 34. xEn = CMOV(xEn, 0, e) | ||
xEd = Fp.cmov(xEd, Fp.ONE, e); // 35. xEd = CMOV(xEd, 1, e) | ||
yEn = Fp.cmov(yEn, Fp.ONE, e); // 36. yEn = CMOV(yEn, 1, e) | ||
yEd = Fp.cmov(yEd, Fp.ONE, e); // 37. yEd = CMOV(yEd, 1, e) | ||
const inv = Fp.invertBatch([xEd, yEd]); // batch division | ||
return { x: Fp.mul(xEn, inv[0]), y: Fp.mul(yEn, inv[1]) }; // 38. return (xEn, xEd, yEn, yEd) | ||
} | ||
const ED448_DEF = { | ||
@@ -174,11 +102,2 @@ // Param: a | ||
}, | ||
htfDefaults: { | ||
DST: 'edwards448_XOF:SHAKE256_ELL2_RO_', | ||
p: Fp.ORDER, | ||
m: 1, | ||
k: 224, | ||
expand: 'xof', | ||
hash: sha3_1.shake256, | ||
}, | ||
mapToCurve: (scalars) => map_to_curve_elligator2_edwards448(scalars[0]), | ||
}; | ||
@@ -216,1 +135,85 @@ exports.ed448 = (0, edwards_js_1.twistedEdwards)(ED448_DEF); | ||
}); | ||
// Hash To Curve Elligator2 Map | ||
const ELL2_C1 = (Fp.ORDER - BigInt(3)) / BigInt(4); // 1. c1 = (q - 3) / 4 # Integer arithmetic | ||
const ELL2_J = BigInt(156326); | ||
function map_to_curve_elligator2_curve448(u) { | ||
let tv1 = Fp.sqr(u); // 1. tv1 = u^2 | ||
let e1 = Fp.eql(tv1, Fp.ONE); // 2. e1 = tv1 == 1 | ||
tv1 = Fp.cmov(tv1, Fp.ZERO, e1); // 3. tv1 = CMOV(tv1, 0, e1) # If Z * u^2 == -1, set tv1 = 0 | ||
let xd = Fp.sub(Fp.ONE, tv1); // 4. xd = 1 - tv1 | ||
let x1n = Fp.neg(ELL2_J); // 5. x1n = -J | ||
let tv2 = Fp.sqr(xd); // 6. tv2 = xd^2 | ||
let gxd = Fp.mul(tv2, xd); // 7. gxd = tv2 * xd # gxd = xd^3 | ||
let gx1 = Fp.mul(tv1, Fp.neg(ELL2_J)); // 8. gx1 = -J * tv1 # x1n + J * xd | ||
gx1 = Fp.mul(gx1, x1n); // 9. gx1 = gx1 * x1n # x1n^2 + J * x1n * xd | ||
gx1 = Fp.add(gx1, tv2); // 10. gx1 = gx1 + tv2 # x1n^2 + J * x1n * xd + xd^2 | ||
gx1 = Fp.mul(gx1, x1n); // 11. gx1 = gx1 * x1n # x1n^3 + J * x1n^2 * xd + x1n * xd^2 | ||
let tv3 = Fp.sqr(gxd); // 12. tv3 = gxd^2 | ||
tv2 = Fp.mul(gx1, gxd); // 13. tv2 = gx1 * gxd # gx1 * gxd | ||
tv3 = Fp.mul(tv3, tv2); // 14. tv3 = tv3 * tv2 # gx1 * gxd^3 | ||
let y1 = Fp.pow(tv3, ELL2_C1); // 15. y1 = tv3^c1 # (gx1 * gxd^3)^((p - 3) / 4) | ||
y1 = Fp.mul(y1, tv2); // 16. y1 = y1 * tv2 # gx1 * gxd * (gx1 * gxd^3)^((p - 3) / 4) | ||
let x2n = Fp.mul(x1n, Fp.neg(tv1)); // 17. x2n = -tv1 * x1n # x2 = x2n / xd = -1 * u^2 * x1n / xd | ||
let y2 = Fp.mul(y1, u); // 18. y2 = y1 * u | ||
y2 = Fp.cmov(y2, Fp.ZERO, e1); // 19. y2 = CMOV(y2, 0, e1) | ||
tv2 = Fp.sqr(y1); // 20. tv2 = y1^2 | ||
tv2 = Fp.mul(tv2, gxd); // 21. tv2 = tv2 * gxd | ||
let e2 = Fp.eql(tv2, gx1); // 22. e2 = tv2 == gx1 | ||
let xn = Fp.cmov(x2n, x1n, e2); // 23. xn = CMOV(x2n, x1n, e2) # If e2, x = x1, else x = x2 | ||
let y = Fp.cmov(y2, y1, e2); // 24. y = CMOV(y2, y1, e2) # If e2, y = y1, else y = y2 | ||
let e3 = Fp.isOdd(y); // 25. e3 = sgn0(y) == 1 # Fix sign of y | ||
y = Fp.cmov(y, Fp.neg(y), e2 !== e3); // 26. y = CMOV(y, -y, e2 XOR e3) | ||
return { xn, xd, yn: y, yd: Fp.ONE }; // 27. return (xn, xd, y, 1) | ||
} | ||
function map_to_curve_elligator2_edwards448(u) { | ||
let { xn, xd, yn, yd } = map_to_curve_elligator2_curve448(u); // 1. (xn, xd, yn, yd) = map_to_curve_elligator2_curve448(u) | ||
let xn2 = Fp.sqr(xn); // 2. xn2 = xn^2 | ||
let xd2 = Fp.sqr(xd); // 3. xd2 = xd^2 | ||
let xd4 = Fp.sqr(xd2); // 4. xd4 = xd2^2 | ||
let yn2 = Fp.sqr(yn); // 5. yn2 = yn^2 | ||
let yd2 = Fp.sqr(yd); // 6. yd2 = yd^2 | ||
let xEn = Fp.sub(xn2, xd2); // 7. xEn = xn2 - xd2 | ||
let tv2 = Fp.sub(xEn, xd2); // 8. tv2 = xEn - xd2 | ||
xEn = Fp.mul(xEn, xd2); // 9. xEn = xEn * xd2 | ||
xEn = Fp.mul(xEn, yd); // 10. xEn = xEn * yd | ||
xEn = Fp.mul(xEn, yn); // 11. xEn = xEn * yn | ||
xEn = Fp.mul(xEn, 4n); // 12. xEn = xEn * 4 | ||
tv2 = Fp.mul(tv2, xn2); // 13. tv2 = tv2 * xn2 | ||
tv2 = Fp.mul(tv2, yd2); // 14. tv2 = tv2 * yd2 | ||
let tv3 = Fp.mul(yn2, 4n); // 15. tv3 = 4 * yn2 | ||
let tv1 = Fp.add(tv3, yd2); // 16. tv1 = tv3 + yd2 | ||
tv1 = Fp.mul(tv1, xd4); // 17. tv1 = tv1 * xd4 | ||
let xEd = Fp.add(tv1, tv2); // 18. xEd = tv1 + tv2 | ||
tv2 = Fp.mul(tv2, xn); // 19. tv2 = tv2 * xn | ||
let tv4 = Fp.mul(xn, xd4); // 20. tv4 = xn * xd4 | ||
let yEn = Fp.sub(tv3, yd2); // 21. yEn = tv3 - yd2 | ||
yEn = Fp.mul(yEn, tv4); // 22. yEn = yEn * tv4 | ||
yEn = Fp.sub(yEn, tv2); // 23. yEn = yEn - tv2 | ||
tv1 = Fp.add(xn2, xd2); // 24. tv1 = xn2 + xd2 | ||
tv1 = Fp.mul(tv1, xd2); // 25. tv1 = tv1 * xd2 | ||
tv1 = Fp.mul(tv1, xd); // 26. tv1 = tv1 * xd | ||
tv1 = Fp.mul(tv1, yn2); // 27. tv1 = tv1 * yn2 | ||
tv1 = Fp.mul(tv1, BigInt(-2)); // 28. tv1 = -2 * tv1 | ||
let yEd = Fp.add(tv2, tv1); // 29. yEd = tv2 + tv1 | ||
tv4 = Fp.mul(tv4, yd2); // 30. tv4 = tv4 * yd2 | ||
yEd = Fp.add(yEd, tv4); // 31. yEd = yEd + tv4 | ||
tv1 = Fp.mul(xEd, yEd); // 32. tv1 = xEd * yEd | ||
let e = Fp.eql(tv1, Fp.ZERO); // 33. e = tv1 == 0 | ||
xEn = Fp.cmov(xEn, Fp.ZERO, e); // 34. xEn = CMOV(xEn, 0, e) | ||
xEd = Fp.cmov(xEd, Fp.ONE, e); // 35. xEd = CMOV(xEd, 1, e) | ||
yEn = Fp.cmov(yEn, Fp.ONE, e); // 36. yEn = CMOV(yEn, 1, e) | ||
yEd = Fp.cmov(yEd, Fp.ONE, e); // 37. yEd = CMOV(yEd, 1, e) | ||
const inv = Fp.invertBatch([xEd, yEd]); // batch division | ||
return { x: Fp.mul(xEn, inv[0]), y: Fp.mul(yEn, inv[1]) }; // 38. return (xEn, xEd, yEn, yEd) | ||
} | ||
const { hashToCurve, encodeToCurve } = htf.hashToCurve(exports.ed448.ExtendedPoint, (scalars) => map_to_curve_elligator2_edwards448(scalars[0]), { | ||
DST: 'edwards448_XOF:SHAKE256_ELL2_RO_', | ||
encodeDST: 'edwards448_XOF:SHAKE256_ELL2_NU_', | ||
p: Fp.ORDER, | ||
m: 1, | ||
k: 224, | ||
expand: 'xof', | ||
hash: sha3_1.shake256, | ||
}); | ||
exports.hashToCurve = hashToCurve; | ||
exports.encodeToCurve = encodeToCurve; |
@@ -1,12 +0,14 @@ | ||
import * as ut from './utils.js'; | ||
import { stringToBytes, hash_to_field as hashToField, expand_message_xmd as expandMessageXMD, } from './hash-to-curve.js'; | ||
import { weierstrassPoints } from './weierstrass.js'; | ||
import { hashToPrivateScalar } from './modular.js'; | ||
import { bitLen, bitGet, hexToBytes, bytesToHex } from './utils.js'; | ||
import * as htf from './hash-to-curve.js'; | ||
import { weierstrassPoints, } from './weierstrass.js'; | ||
export function bls(CURVE) { | ||
// Fields looks pretty specific for curve, so for now we need to pass them with options | ||
const { Fp, Fr, Fp2, Fp6, Fp12 } = CURVE; | ||
const BLS_X_LEN = ut.bitLen(CURVE.x); | ||
const BLS_X_LEN = bitLen(CURVE.x); | ||
const groupLen = 32; // TODO: calculate; hardcoded for now | ||
// Pre-compute coefficients for sparse multiplication | ||
// Point addition and point double calculations is reused for coefficients | ||
function calcPairingPrecomputes(x, y) { | ||
function calcPairingPrecomputes(p) { | ||
const { x, y } = p; | ||
// prettier-ignore | ||
@@ -19,16 +21,16 @@ const Qx = x, Qy = y, Qz = Fp2.ONE; | ||
// Double | ||
let t0 = Fp2.square(Ry); // Ry² | ||
let t1 = Fp2.square(Rz); // Rz² | ||
let t0 = Fp2.sqr(Ry); // Ry² | ||
let t1 = Fp2.sqr(Rz); // Rz² | ||
let t2 = Fp2.multiplyByB(Fp2.mul(t1, 3n)); // 3 * T1 * B | ||
let t3 = Fp2.mul(t2, 3n); // 3 * T2 | ||
let t4 = Fp2.sub(Fp2.sub(Fp2.square(Fp2.add(Ry, Rz)), t1), t0); // (Ry + Rz)² - T1 - T0 | ||
let t4 = Fp2.sub(Fp2.sub(Fp2.sqr(Fp2.add(Ry, Rz)), t1), t0); // (Ry + Rz)² - T1 - T0 | ||
ell_coeff.push([ | ||
Fp2.sub(t2, t0), | ||
Fp2.mul(Fp2.square(Rx), 3n), | ||
Fp2.negate(t4), // -T4 | ||
Fp2.mul(Fp2.sqr(Rx), 3n), | ||
Fp2.neg(t4), // -T4 | ||
]); | ||
Rx = Fp2.div(Fp2.mul(Fp2.mul(Fp2.sub(t0, t3), Rx), Ry), 2n); // ((T0 - T3) * Rx * Ry) / 2 | ||
Ry = Fp2.sub(Fp2.square(Fp2.div(Fp2.add(t0, t3), 2n)), Fp2.mul(Fp2.square(t2), 3n)); // ((T0 + T3) / 2)² - 3 * T2² | ||
Ry = Fp2.sub(Fp2.sqr(Fp2.div(Fp2.add(t0, t3), 2n)), Fp2.mul(Fp2.sqr(t2), 3n)); // ((T0 + T3) / 2)² - 3 * T2² | ||
Rz = Fp2.mul(t0, t4); // T0 * T4 | ||
if (ut.bitGet(CURVE.x, i)) { | ||
if (bitGet(CURVE.x, i)) { | ||
// Addition | ||
@@ -39,9 +41,9 @@ let t0 = Fp2.sub(Ry, Fp2.mul(Qy, Rz)); // Ry - Qy * Rz | ||
Fp2.sub(Fp2.mul(t0, Qx), Fp2.mul(t1, Qy)), | ||
Fp2.negate(t0), | ||
Fp2.neg(t0), | ||
t1, // T1 | ||
]); | ||
let t2 = Fp2.square(t1); // T1² | ||
let t2 = Fp2.sqr(t1); // T1² | ||
let t3 = Fp2.mul(t2, t1); // T2 * T1 | ||
let t4 = Fp2.mul(t2, Rx); // T2 * Rx | ||
let t5 = Fp2.add(Fp2.sub(t3, Fp2.mul(t4, 2n)), Fp2.mul(Fp2.square(t0), Rz)); // T3 - 2 * T4 + T0² * Rz | ||
let t5 = Fp2.add(Fp2.sub(t3, Fp2.mul(t4, 2n)), Fp2.mul(Fp2.sqr(t0), Rz)); // T3 - 2 * T4 + T0² * Rz | ||
Rx = Fp2.mul(t1, t5); // T1 * T5 | ||
@@ -62,3 +64,3 @@ Ry = Fp2.sub(Fp2.mul(Fp2.sub(t4, t5), t0), Fp2.mul(t3, Ry)); // (T4 - T5) * T0 - T3 * Ry | ||
f12 = Fp12.multiplyBy014(f12, E[0], Fp2.mul(E[1], Px), Fp2.mul(E[2], Py)); | ||
if (ut.bitGet(x, i)) { | ||
if (bitGet(x, i)) { | ||
j += 1; | ||
@@ -69,3 +71,3 @@ const F = ell[j]; | ||
if (i !== 0) | ||
f12 = Fp12.square(f12); | ||
f12 = Fp12.sqr(f12); | ||
} | ||
@@ -75,19 +77,11 @@ return Fp12.conjugate(f12); | ||
const utils = { | ||
hexToBytes: ut.hexToBytes, | ||
bytesToHex: ut.bytesToHex, | ||
stringToBytes: stringToBytes, | ||
hexToBytes: hexToBytes, | ||
bytesToHex: bytesToHex, | ||
stringToBytes: htf.stringToBytes, | ||
// TODO: do we need to export it here? | ||
hashToField: (msg, count, options = {}) => hashToField(msg, count, { ...CURVE.htfDefaults, ...options }), | ||
expandMessageXMD: (msg, DST, lenInBytes, H = CURVE.hash) => expandMessageXMD(msg, DST, lenInBytes, H), | ||
hashToPrivateKey: (hash) => Fr.toBytes(ut.hashToPrivateScalar(hash, CURVE.r)), | ||
hashToField: (msg, count, options = {}) => htf.hash_to_field(msg, count, { ...CURVE.htfDefaults, ...options }), | ||
expandMessageXMD: (msg, DST, lenInBytes, H = CURVE.hash) => htf.expand_message_xmd(msg, DST, lenInBytes, H), | ||
hashToPrivateKey: (hash) => Fr.toBytes(hashToPrivateScalar(hash, CURVE.r)), | ||
randomBytes: (bytesLength = groupLen) => CURVE.randomBytes(bytesLength), | ||
randomPrivateKey: () => utils.hashToPrivateKey(utils.randomBytes(groupLen + 8)), | ||
getDSTLabel: () => CURVE.htfDefaults.DST, | ||
setDSTLabel(newLabel) { | ||
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-3.1 | ||
if (typeof newLabel !== 'string' || newLabel.length > 2048 || newLabel.length === 0) { | ||
throw new TypeError('Invalid DST'); | ||
} | ||
CURVE.htfDefaults.DST = newLabel; | ||
}, | ||
}; | ||
@@ -99,2 +93,6 @@ // Point on G1 curve: (x, y) | ||
}); | ||
const G1HashToCurve = htf.hashToCurve(G1.ProjectivePoint, CURVE.G1.mapToCurve, { | ||
...CURVE.htfDefaults, | ||
...CURVE.G1.htfDefaults, | ||
}); | ||
function pairingPrecomputes(point) { | ||
@@ -104,13 +102,10 @@ const p = point; | ||
return p._PPRECOMPUTES; | ||
p._PPRECOMPUTES = calcPairingPrecomputes(p.x, p.y); | ||
p._PPRECOMPUTES = calcPairingPrecomputes(point.toAffine()); | ||
return p._PPRECOMPUTES; | ||
} | ||
function clearPairingPrecomputes(point) { | ||
const p = point; | ||
p._PPRECOMPUTES = undefined; | ||
} | ||
clearPairingPrecomputes; | ||
function millerLoopG1(Q, P) { | ||
return millerLoop(pairingPrecomputes(P), [Q.x, Q.y]); | ||
} | ||
// TODO: export | ||
// function clearPairingPrecomputes(point: G2) { | ||
// const p = point as G2 & withPairingPrecomputes; | ||
// p._PPRECOMPUTES = undefined; | ||
// } | ||
// Point on G2 curve (complex numbers): (x₁, x₂+i), (y₁, y₂+i) | ||
@@ -121,21 +116,29 @@ const G2 = weierstrassPoints({ | ||
}); | ||
const C = G2.ProjectivePoint; // TODO: fix | ||
const G2HashToCurve = htf.hashToCurve(C, CURVE.G2.mapToCurve, { | ||
...CURVE.htfDefaults, | ||
...CURVE.G2.htfDefaults, | ||
}); | ||
const { Signature } = CURVE.G2; | ||
// Calculates bilinear pairing | ||
function pairing(P, Q, withFinalExponent = true) { | ||
if (P.equals(G1.Point.ZERO) || Q.equals(G2.Point.ZERO)) | ||
throw new Error('No pairings at point of Infinity'); | ||
function pairing(Q, P, withFinalExponent = true) { | ||
if (Q.equals(G1.ProjectivePoint.ZERO) || P.equals(G2.ProjectivePoint.ZERO)) | ||
throw new Error('pairing is not available for ZERO point'); | ||
Q.assertValidity(); | ||
P.assertValidity(); | ||
Q.assertValidity(); | ||
// Performance: 9ms for millerLoop and ~14ms for exp. | ||
const looped = millerLoopG1(P, Q); | ||
const Qa = Q.toAffine(); | ||
const looped = millerLoop(pairingPrecomputes(P), [Qa.x, Qa.y]); | ||
return withFinalExponent ? Fp12.finalExponentiate(looped) : looped; | ||
} | ||
function normP1(point) { | ||
return point instanceof G1.Point ? point : G1.Point.fromHex(point); | ||
return point instanceof G1.ProjectivePoint ? point : G1.ProjectivePoint.fromHex(point); | ||
} | ||
function normP2(point) { | ||
return point instanceof G2.Point ? point : Signature.decode(point); | ||
return point instanceof G2.ProjectivePoint ? point : Signature.decode(point); | ||
} | ||
function normP2Hash(point) { | ||
return point instanceof G2.Point ? point : G2.Point.hashToCurve(point); | ||
function normP2Hash(point, htfOpts) { | ||
return point instanceof G2.ProjectivePoint | ||
? point | ||
: G2HashToCurve.hashToCurve(point, htfOpts); | ||
} | ||
@@ -145,9 +148,9 @@ // Multiplies generator by private key. | ||
function getPublicKey(privateKey) { | ||
return G1.Point.fromPrivateKey(privateKey).toRawBytes(true); | ||
return G1.ProjectivePoint.fromPrivateKey(privateKey).toRawBytes(true); | ||
} | ||
function sign(message, privateKey) { | ||
const msgPoint = normP2Hash(message); | ||
function sign(message, privateKey, htfOpts) { | ||
const msgPoint = normP2Hash(message, htfOpts); | ||
msgPoint.assertValidity(); | ||
const sigPoint = msgPoint.multiply(G1.normalizePrivateKey(privateKey)); | ||
if (message instanceof G2.Point) | ||
if (message instanceof G2.ProjectivePoint) | ||
return sigPoint; | ||
@@ -158,6 +161,6 @@ return Signature.encode(sigPoint); | ||
// e(P, H(m)) == e(G, S) | ||
function verify(signature, message, publicKey) { | ||
function verify(signature, message, publicKey, htfOpts) { | ||
const P = normP1(publicKey); | ||
const Hm = normP2Hash(message); | ||
const G = G1.Point.BASE; | ||
const Hm = normP2Hash(message, htfOpts); | ||
const G = G1.ProjectivePoint.BASE; | ||
const S = normP2(signature); | ||
@@ -169,3 +172,3 @@ // Instead of doing 2 exponentiations, we use property of billinear maps | ||
const exp = Fp12.finalExponentiate(Fp12.mul(eGS, ePHm)); | ||
return Fp12.equals(exp, Fp12.ONE); | ||
return Fp12.eql(exp, Fp12.ONE); | ||
} | ||
@@ -175,7 +178,5 @@ function aggregatePublicKeys(publicKeys) { | ||
throw new Error('Expected non-empty array'); | ||
const agg = publicKeys | ||
.map(normP1) | ||
.reduce((sum, p) => sum.add(G1.ProjectivePoint.fromAffine(p)), G1.ProjectivePoint.ZERO); | ||
const aggAffine = agg.toAffine(); | ||
if (publicKeys[0] instanceof G1.Point) { | ||
const agg = publicKeys.map(normP1).reduce((sum, p) => sum.add(p), G1.ProjectivePoint.ZERO); | ||
const aggAffine = agg; //.toAffine(); | ||
if (publicKeys[0] instanceof G1.ProjectivePoint) { | ||
aggAffine.assertValidity(); | ||
@@ -190,7 +191,5 @@ return aggAffine; | ||
throw new Error('Expected non-empty array'); | ||
const agg = signatures | ||
.map(normP2) | ||
.reduce((sum, s) => sum.add(G2.ProjectivePoint.fromAffine(s)), G2.ProjectivePoint.ZERO); | ||
const aggAffine = agg.toAffine(); | ||
if (signatures[0] instanceof G2.Point) { | ||
const agg = signatures.map(normP2).reduce((sum, s) => sum.add(s), G2.ProjectivePoint.ZERO); | ||
const aggAffine = agg; //.toAffine(); | ||
if (signatures[0] instanceof G2.ProjectivePoint) { | ||
aggAffine.assertValidity(); | ||
@@ -203,3 +202,5 @@ return aggAffine; | ||
// e(G, S) = e(G, SUM(n)(Si)) = MUL(n)(e(G, Si)) | ||
function verifyBatch(signature, messages, publicKeys) { | ||
function verifyBatch(signature, messages, publicKeys, htfOpts) { | ||
// @ts-ignore | ||
// console.log('verifyBatch', bytesToHex(signature as any), messages, publicKeys.map(bytesToHex)); | ||
if (!messages.length) | ||
@@ -210,3 +211,3 @@ throw new Error('Expected non-empty messages array'); | ||
const sig = normP2(signature); | ||
const nMessages = messages.map(normP2Hash); | ||
const nMessages = messages.map((i) => normP2Hash(i, htfOpts)); | ||
const nPublicKeys = publicKeys.map(normP1); | ||
@@ -216,3 +217,3 @@ try { | ||
for (const message of new Set(nMessages)) { | ||
const groupPublicKey = nMessages.reduce((groupPublicKey, subMessage, i) => subMessage === message ? groupPublicKey.add(nPublicKeys[i]) : groupPublicKey, G1.Point.ZERO); | ||
const groupPublicKey = nMessages.reduce((groupPublicKey, subMessage, i) => subMessage === message ? groupPublicKey.add(nPublicKeys[i]) : groupPublicKey, G1.ProjectivePoint.ZERO); | ||
// const msg = message instanceof PointG2 ? message : await PointG2.hashToCurve(message); | ||
@@ -222,6 +223,6 @@ // Possible to batch pairing for same msg with different groupPublicKey here | ||
} | ||
paired.push(pairing(G1.Point.BASE.negate(), sig, false)); | ||
paired.push(pairing(G1.ProjectivePoint.BASE.negate(), sig, false)); | ||
const product = paired.reduce((a, b) => Fp12.mul(a, b), Fp12.ONE); | ||
const exp = Fp12.finalExponentiate(product); | ||
return Fp12.equals(exp, Fp12.ONE); | ||
return Fp12.eql(exp, Fp12.ONE); | ||
} | ||
@@ -232,4 +233,3 @@ catch { | ||
} | ||
// Pre-compute points. Refer to README. | ||
G1.Point.BASE._setWindowSize(4); | ||
G1.ProjectivePoint.BASE._setWindowSize(4); | ||
return { | ||
@@ -247,2 +247,3 @@ CURVE, | ||
calcPairingPrecomputes, | ||
hashToCurve: { G1: G1HashToCurve, G2: G2HashToCurve }, | ||
pairing, | ||
@@ -249,0 +250,0 @@ getPublicKey, |
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */ | ||
// Twisted Edwards curve. The formula is: ax² + y² = 1 + dx²y² | ||
// Differences from @noble/ed25519 1.7: | ||
// 1. Variable field element lengths between EDDSA/ECDH: | ||
// EDDSA (RFC8032) is 456 bits / 57 bytes, ECDH (RFC7748) is 448 bits / 56 bytes | ||
// 2. Different addition formula (doubling is same) | ||
// 3. uvRatio differs between curves (half-expected, not only pow fn changes) | ||
// 4. Point decompression code is different (unexpected), now using generalized formula | ||
// 5. Domain function was no-op for ed25519, but adds some data even with empty context for ed448 | ||
import * as mod from './modular.js'; | ||
import * as ut from './utils.js'; | ||
import { ensureBytes } from './utils.js'; | ||
import { wNAF } from './group.js'; | ||
import { hash_to_field as hashToField, validateHTFOpts } from './hash-to-curve.js'; | ||
import { mod } from './modular.js'; | ||
import { bytesToHex, bytesToNumberLE, concatBytes, ensureBytes, numberToBytesLE, } from './utils.js'; | ||
import { wNAF, validateAbsOpts, } from './curve.js'; | ||
// Be friendly to bad ECMAScript parsers by not using bigint literals like 123n | ||
@@ -21,4 +12,4 @@ const _0n = BigInt(0); | ||
function validateOpts(curve) { | ||
const opts = ut.validateOpts(curve); | ||
if (typeof opts.hash !== 'function' || !ut.isPositiveInt(opts.hash.outputLen)) | ||
const opts = validateAbsOpts(curve); | ||
if (typeof opts.hash !== 'function') | ||
throw new Error('Invalid hash function'); | ||
@@ -40,16 +31,11 @@ for (const i of ['a', 'd']) { | ||
} | ||
if (opts.htfDefaults !== undefined) | ||
validateHTFOpts(opts.htfDefaults); | ||
// Set defaults | ||
return Object.freeze({ ...opts }); | ||
} | ||
// NOTE: it is not generic twisted curve for now, but ed25519/ed448 generic implementation | ||
// It is not generic twisted curve for now, but ed25519/ed448 generic implementation | ||
export function twistedEdwards(curveDef) { | ||
const CURVE = validateOpts(curveDef); | ||
const Fp = CURVE.Fp; | ||
const CURVE_ORDER = CURVE.n; | ||
const maxGroupElement = _2n ** BigInt(CURVE.nByteLength * 8); | ||
// Function overrides | ||
const { randomBytes } = CURVE; | ||
const modP = Fp.create; | ||
const { Fp, n: CURVE_ORDER, preHash, hash: cHash, randomBytes, nByteLength, h: cofactor } = CURVE; | ||
const MASK = _2n ** BigInt(nByteLength * 8); | ||
const modP = Fp.create; // Function overrides | ||
// sqrt(u/v) | ||
@@ -59,3 +45,3 @@ const uvRatio = CURVE.uvRatio || | ||
try { | ||
return { isValid: true, value: Fp.sqrt(u * Fp.invert(v)) }; | ||
return { isValid: true, value: Fp.sqrt(u * Fp.inv(v)) }; | ||
} | ||
@@ -73,37 +59,66 @@ catch (e) { | ||
}); // NOOP | ||
/** | ||
* Extended Point works in extended coordinates: (x, y, z, t) ∋ (x=x/z, y=y/z, t=xy). | ||
* Default Point works in affine coordinates: (x, y) | ||
* https://en.wikipedia.org/wiki/Twisted_Edwards_curve#Extended_coordinates | ||
*/ | ||
class ExtendedPoint { | ||
constructor(x, y, z, t) { | ||
this.x = x; | ||
this.y = y; | ||
this.z = z; | ||
this.t = t; | ||
const inBig = (n) => typeof n === 'bigint' && 0n < n; // n in [1..] | ||
const inRange = (n, max) => inBig(n) && inBig(max) && n < max; // n in [1..max-1] | ||
const in0MaskRange = (n) => n === _0n || inRange(n, MASK); // n in [0..MASK-1] | ||
function assertInRange(n, max) { | ||
// n in [1..max-1] | ||
if (inRange(n, max)) | ||
return n; | ||
throw new Error(`Expected valid scalar < ${max}, got ${typeof n} ${n}`); | ||
} | ||
function assertGE0(n) { | ||
// n in [0..CURVE_ORDER-1] | ||
return n === _0n ? n : assertInRange(n, CURVE_ORDER); // GE = prime subgroup, not full group | ||
} | ||
const pointPrecomputes = new Map(); | ||
function isPoint(other) { | ||
if (!(other instanceof Point)) | ||
throw new Error('ExtendedPoint expected'); | ||
} | ||
// Extended Point works in extended coordinates: (x, y, z, t) ∋ (x=x/z, y=y/z, t=xy). | ||
// https://en.wikipedia.org/wiki/Twisted_Edwards_curve#Extended_coordinates | ||
class Point { | ||
constructor(ex, ey, ez, et) { | ||
this.ex = ex; | ||
this.ey = ey; | ||
this.ez = ez; | ||
this.et = et; | ||
if (!in0MaskRange(ex)) | ||
throw new Error('x required'); | ||
if (!in0MaskRange(ey)) | ||
throw new Error('y required'); | ||
if (!in0MaskRange(ez)) | ||
throw new Error('z required'); | ||
if (!in0MaskRange(et)) | ||
throw new Error('t required'); | ||
} | ||
get x() { | ||
return this.toAffine().x; | ||
} | ||
get y() { | ||
return this.toAffine().y; | ||
} | ||
static fromAffine(p) { | ||
if (!(p instanceof Point)) { | ||
throw new TypeError('ExtendedPoint#fromAffine: expected Point'); | ||
} | ||
if (p.equals(Point.ZERO)) | ||
return ExtendedPoint.ZERO; | ||
return new ExtendedPoint(p.x, p.y, _1n, modP(p.x * p.y)); | ||
if (p instanceof Point) | ||
throw new Error('extended point not allowed'); | ||
const { x, y } = p || {}; | ||
if (!in0MaskRange(x) || !in0MaskRange(y)) | ||
throw new Error('invalid affine point'); | ||
return new Point(x, y, _1n, modP(x * y)); | ||
} | ||
// Takes a bunch of Jacobian Points but executes only one | ||
// invert on all of them. invert is very slow operation, | ||
// so this improves performance massively. | ||
static toAffineBatch(points) { | ||
const toInv = Fp.invertBatch(points.map((p) => p.z)); | ||
return points.map((p, i) => p.toAffine(toInv[i])); | ||
} | ||
static normalizeZ(points) { | ||
return this.toAffineBatch(points).map(this.fromAffine); | ||
const toInv = Fp.invertBatch(points.map((p) => p.ez)); | ||
return points.map((p, i) => p.toAffine(toInv[i])).map(Point.fromAffine); | ||
} | ||
// "Private method", don't use it directly | ||
_setWindowSize(windowSize) { | ||
this._WINDOW_SIZE = windowSize; | ||
pointPrecomputes.delete(this); | ||
} | ||
assertValidity() { } | ||
// Compare one point to another. | ||
equals(other) { | ||
assertExtPoint(other); | ||
const { x: X1, y: Y1, z: Z1 } = this; | ||
const { x: X2, y: Y2, z: Z2 } = other; | ||
isPoint(other); | ||
const { ex: X1, ey: Y1, ez: Z1 } = this; | ||
const { ex: X2, ey: Y2, ez: Z2 } = other; | ||
const X1Z2 = modP(X1 * Z2); | ||
@@ -115,5 +130,8 @@ const X2Z1 = modP(X2 * Z1); | ||
} | ||
// Inverses point to one corresponding to (x, -y) in Affine coordinates. | ||
is0() { | ||
return this.equals(Point.ZERO); | ||
} | ||
negate() { | ||
return new ExtendedPoint(modP(-this.x), this.y, this.z, modP(-this.t)); | ||
// Flips point sign to a negative one (-x, y in affine coords) | ||
return new Point(modP(-this.ex), this.ey, this.ez, modP(-this.et)); | ||
} | ||
@@ -125,3 +143,3 @@ // Fast algo for doubling Extended Point. | ||
const { a } = CURVE; | ||
const { x: X1, y: Y1, z: Z1 } = this; | ||
const { ex: X1, ey: Y1, ez: Z1 } = this; | ||
const A = modP(X1 * X1); // A = X12 | ||
@@ -140,3 +158,3 @@ const B = modP(Y1 * Y1); // B = Y12 | ||
const Z3 = modP(F * G); // Z3 = F*G | ||
return new ExtendedPoint(X3, Y3, Z3, T3); | ||
return new Point(X3, Y3, Z3, T3); | ||
} | ||
@@ -147,6 +165,6 @@ // Fast algo for adding 2 Extended Points. | ||
add(other) { | ||
assertExtPoint(other); | ||
isPoint(other); | ||
const { a, d } = CURVE; | ||
const { x: X1, y: Y1, z: Z1, t: T1 } = this; | ||
const { x: X2, y: Y2, z: Z2, t: T2 } = other; | ||
const { ex: X1, ey: Y1, ez: Z1, et: T1 } = this; | ||
const { ex: X2, ey: Y2, ez: Z2, et: T2 } = other; | ||
// Faster algo for adding 2 Extended Points when curve's a=-1. | ||
@@ -171,3 +189,3 @@ // http://hyperelliptic.org/EFD/g1p/auto-twisted-extended-1.html#addition-add-2008-hwcd-4 | ||
const Z3 = modP(F * G); | ||
return new ExtendedPoint(X3, Y3, Z3, T3); | ||
return new Point(X3, Y3, Z3, T3); | ||
} | ||
@@ -186,3 +204,3 @@ const A = modP(X1 * X2); // A = X1*X2 | ||
const Z3 = modP(F * G); // Z3 = F*G | ||
return new ExtendedPoint(X3, Y3, Z3, T3); | ||
return new Point(X3, Y3, Z3, T3); | ||
} | ||
@@ -192,22 +210,9 @@ subtract(other) { | ||
} | ||
wNAF(n, affinePoint) { | ||
if (!affinePoint && this.equals(ExtendedPoint.BASE)) | ||
affinePoint = Point.BASE; | ||
const W = (affinePoint && affinePoint._WINDOW_SIZE) || 1; | ||
let precomputes = affinePoint && pointPrecomputes.get(affinePoint); | ||
if (!precomputes) { | ||
precomputes = wnaf.precomputeWindow(this, W); | ||
if (affinePoint && W !== 1) { | ||
precomputes = ExtendedPoint.normalizeZ(precomputes); | ||
pointPrecomputes.set(affinePoint, precomputes); | ||
} | ||
} | ||
const { p, f } = wnaf.wNAF(W, precomputes, n); | ||
return ExtendedPoint.normalizeZ([p, f])[0]; | ||
wNAF(n) { | ||
return wnaf.wNAFCached(this, pointPrecomputes, n, Point.normalizeZ); | ||
} | ||
// Constant time multiplication. | ||
// Uses wNAF method. Windowed method may be 10% faster, | ||
// but takes 2x longer to generate and consumes 2x memory. | ||
multiply(scalar, affinePoint) { | ||
return this.wNAF(normalizeScalar(scalar, CURVE_ORDER), affinePoint); | ||
// Constant-time multiplication. | ||
multiply(scalar) { | ||
const { p, f } = this.wNAF(assertInRange(scalar, CURVE_ORDER)); | ||
return Point.normalizeZ([p, f])[0]; | ||
} | ||
@@ -218,10 +223,9 @@ // Non-constant-time multiplication. Uses double-and-add algorithm. | ||
multiplyUnsafe(scalar) { | ||
let n = normalizeScalar(scalar, CURVE_ORDER, false); | ||
const P0 = ExtendedPoint.ZERO; | ||
let n = assertGE0(scalar); | ||
if (n === _0n) | ||
return P0; | ||
if (this.equals(P0) || n === _1n) | ||
return I; | ||
if (this.equals(I) || n === _1n) | ||
return this; | ||
if (this.equals(ExtendedPoint.BASE)) | ||
return this.wNAF(n); | ||
if (this.equals(G)) | ||
return this.wNAF(n).p; | ||
return wnaf.unsafeLadder(this, n); | ||
@@ -234,24 +238,24 @@ } | ||
isSmallOrder() { | ||
return this.multiplyUnsafe(CURVE.h).equals(ExtendedPoint.ZERO); | ||
return this.multiplyUnsafe(cofactor).is0(); | ||
} | ||
// Multiplies point by curve order (very big scalar CURVE.n) and checks if the result is 0. | ||
// Multiplies point by curve order and checks if the result is 0. | ||
// Returns `false` is the point is dirty. | ||
isTorsionFree() { | ||
return wnaf.unsafeLadder(this, CURVE_ORDER).equals(ExtendedPoint.ZERO); | ||
return wnaf.unsafeLadder(this, CURVE_ORDER).is0(); | ||
} | ||
// Converts Extended point to default (x, y) coordinates. | ||
// Can accept precomputed Z^-1 - for example, from invertBatch. | ||
toAffine(invZ) { | ||
const { x, y, z } = this; | ||
const is0 = this.equals(ExtendedPoint.ZERO); | ||
if (invZ == null) | ||
invZ = is0 ? _8n : Fp.invert(z); // 8 was chosen arbitrarily | ||
const ax = modP(x * invZ); | ||
const ay = modP(y * invZ); | ||
const zz = modP(z * invZ); | ||
toAffine(iz) { | ||
const { ex: x, ey: y, ez: z } = this; | ||
const is0 = this.is0(); | ||
if (iz == null) | ||
iz = is0 ? _8n : Fp.inv(z); // 8 was chosen arbitrarily | ||
const ax = modP(x * iz); | ||
const ay = modP(y * iz); | ||
const zz = modP(z * iz); | ||
if (is0) | ||
return Point.ZERO; | ||
return { x: _0n, y: _1n }; | ||
if (zz !== _1n) | ||
throw new Error('invZ was invalid'); | ||
return new Point(ax, ay); | ||
return { x: ax, y: ay }; | ||
} | ||
@@ -264,25 +268,2 @@ clearCofactor() { | ||
} | ||
} | ||
ExtendedPoint.BASE = new ExtendedPoint(CURVE.Gx, CURVE.Gy, _1n, modP(CURVE.Gx * CURVE.Gy)); | ||
ExtendedPoint.ZERO = new ExtendedPoint(_0n, _1n, _1n, _0n); | ||
const wnaf = wNAF(ExtendedPoint, CURVE.nByteLength * 8); | ||
function assertExtPoint(other) { | ||
if (!(other instanceof ExtendedPoint)) | ||
throw new TypeError('ExtendedPoint expected'); | ||
} | ||
// Stores precomputed values for points. | ||
const pointPrecomputes = new WeakMap(); | ||
/** | ||
* Default Point works in affine coordinates: (x, y) | ||
*/ | ||
class Point { | ||
constructor(x, y) { | ||
this.x = x; | ||
this.y = y; | ||
} | ||
// "Private method", don't use it directly. | ||
_setWindowSize(windowSize) { | ||
this._WINDOW_SIZE = windowSize; | ||
pointPrecomputes.delete(this); | ||
} | ||
// Converts hash string or Uint8Array to Point. | ||
@@ -293,274 +274,118 @@ // Uses algo from RFC8032 5.1.3. | ||
const len = Fp.BYTES; | ||
hex = ensureBytes(hex, len); | ||
// 1. First, interpret the string as an integer in little-endian | ||
// representation. Bit 255 of this number is the least significant | ||
// bit of the x-coordinate and denote this value x_0. The | ||
// y-coordinate is recovered simply by clearing this bit. If the | ||
// resulting value is >= p, decoding fails. | ||
const normed = hex.slice(); | ||
const lastByte = hex[len - 1]; | ||
normed[len - 1] = lastByte & ~0x80; | ||
const y = ut.bytesToNumberLE(normed); | ||
if (strict && y >= Fp.ORDER) | ||
throw new Error('Expected 0 < hex < P'); | ||
if (!strict && y >= maxGroupElement) | ||
throw new Error('Expected 0 < hex < CURVE.n'); | ||
// 2. To recover the x-coordinate, the curve equation implies | ||
// Ed25519: x² = (y² - 1) / (d y² + 1) (mod p). | ||
// Ed448: x² = (y² - 1) / (d y² - 1) (mod p). | ||
// For generic case: | ||
// a*x²+y²=1+d*x²*y² | ||
// -> y²-1 = d*x²*y²-a*x² | ||
// -> y²-1 = x² (d*y²-a) | ||
// -> x² = (y²-1) / (d*y²-a) | ||
// The denominator is always non-zero mod p. Let u = y² - 1 and v = d y² + 1. | ||
const y2 = modP(y * y); | ||
const u = modP(y2 - _1n); | ||
const v = modP(d * y2 - a); | ||
let { isValid, value: x } = uvRatio(u, v); | ||
hex = ensureBytes(hex, len); // copy hex to a new array | ||
const normed = hex.slice(); // copy again, we'll manipulate it | ||
const lastByte = hex[len - 1]; // select last byte | ||
normed[len - 1] = lastByte & ~0x80; // clear last bit | ||
const y = bytesToNumberLE(normed); | ||
if (y === _0n) { | ||
// y=0 is allowed | ||
} | ||
else { | ||
// RFC8032 prohibits >= p, but ZIP215 doesn't | ||
if (strict) | ||
assertInRange(y, Fp.ORDER); // strict=true [1..P-1] (2^255-19-1 for ed25519) | ||
else | ||
assertInRange(y, MASK); // strict=false [1..MASK-1] (2^256-1 for ed25519) | ||
} | ||
// Ed25519: x² = (y²-1)/(dy²+1) mod p. Ed448: x² = (y²-1)/(dy²-1) mod p. Generic case: | ||
// ax²+y²=1+dx²y² => y²-1=dx²y²-ax² => y²-1=x²(dy²-a) => x²=(y²-1)/(dy²-a) | ||
const y2 = modP(y * y); // denominator is always non-0 mod p. | ||
const u = modP(y2 - _1n); // u = y² - 1 | ||
const v = modP(d * y2 - a); // v = d y² + 1. | ||
let { isValid, value: x } = uvRatio(u, v); // √(u/v) | ||
if (!isValid) | ||
throw new Error('Point.fromHex: invalid y coordinate'); | ||
// 4. Finally, use the x_0 bit to select the right square root. If | ||
// x = 0, and x_0 = 1, decoding fails. Otherwise, if x_0 != x mod | ||
// 2, set x <-- p - x. Return the decoded point (x,y). | ||
const isXOdd = (x & _1n) === _1n; | ||
const isLastByteOdd = (lastByte & 0x80) !== 0; | ||
const isXOdd = (x & _1n) === _1n; // There are 2 square roots. Use x_0 bit to select proper | ||
const isLastByteOdd = (lastByte & 0x80) !== 0; // if x=0 and x_0 = 1, fail | ||
if (isLastByteOdd !== isXOdd) | ||
x = modP(-x); | ||
return new Point(x, y); | ||
x = modP(-x); // if x_0 != x mod 2, set x = p-x | ||
return Point.fromAffine({ x, y }); | ||
} | ||
static fromPrivateKey(privateKey) { | ||
return getExtendedPublicKey(privateKey).point; | ||
static fromPrivateKey(privKey) { | ||
return getExtendedPublicKey(privKey).point; | ||
} | ||
// There can always be only two x values (x, -x) for any y | ||
// When compressing point, it's enough to only store its y coordinate | ||
// and use the last byte to encode sign of x. | ||
toRawBytes() { | ||
const bytes = ut.numberToBytesLE(this.y, Fp.BYTES); | ||
bytes[Fp.BYTES - 1] |= this.x & _1n ? 0x80 : 0; | ||
return bytes; | ||
const { x, y } = this.toAffine(); | ||
const bytes = numberToBytesLE(y, Fp.BYTES); // each y has 2 x values (x, -y) | ||
bytes[bytes.length - 1] |= x & _1n ? 0x80 : 0; // when compressing, it's enough to store y | ||
return bytes; // and use the last byte to encode sign of x | ||
} | ||
// Same as toRawBytes, but returns string. | ||
toHex() { | ||
return ut.bytesToHex(this.toRawBytes()); | ||
return bytesToHex(this.toRawBytes()); // Same as toRawBytes, but returns string. | ||
} | ||
// Determines if point is in prime-order subgroup. | ||
// Returns `false` is the point is dirty. | ||
isTorsionFree() { | ||
return ExtendedPoint.fromAffine(this).isTorsionFree(); | ||
} | ||
equals(other) { | ||
if (!(other instanceof Point)) | ||
throw new TypeError('Point#equals: expected Point'); | ||
return this.x === other.x && this.y === other.y; | ||
} | ||
negate() { | ||
return new Point(modP(-this.x), this.y); | ||
} | ||
double() { | ||
return ExtendedPoint.fromAffine(this).double().toAffine(); | ||
} | ||
add(other) { | ||
return ExtendedPoint.fromAffine(this).add(ExtendedPoint.fromAffine(other)).toAffine(); | ||
} | ||
subtract(other) { | ||
return this.add(other.negate()); | ||
} | ||
/** | ||
* Constant time multiplication. | ||
* @param scalar Big-Endian number | ||
* @returns new point | ||
*/ | ||
multiply(scalar) { | ||
return ExtendedPoint.fromAffine(this).multiply(scalar, this).toAffine(); | ||
} | ||
clearCofactor() { | ||
return ExtendedPoint.fromAffine(this).clearCofactor().toAffine(); | ||
} | ||
// Encodes byte string to elliptic curve | ||
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-3 | ||
static hashToCurve(msg, options) { | ||
const { mapToCurve, htfDefaults } = CURVE; | ||
if (!mapToCurve) | ||
throw new Error('No mapToCurve defined for curve'); | ||
const u = hashToField(ensureBytes(msg), 2, { ...htfDefaults, ...options }); | ||
const { x: x0, y: y0 } = mapToCurve(u[0]); | ||
const { x: x1, y: y1 } = mapToCurve(u[1]); | ||
const p = new Point(x0, y0).add(new Point(x1, y1)).clearCofactor(); | ||
return p; | ||
} | ||
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-16#section-3 | ||
static encodeToCurve(msg, options) { | ||
const { mapToCurve, htfDefaults } = CURVE; | ||
if (!mapToCurve) | ||
throw new Error('No mapToCurve defined for curve'); | ||
const u = hashToField(ensureBytes(msg), 1, { ...htfDefaults, ...options }); | ||
const { x, y } = mapToCurve(u[0]); | ||
return new Point(x, y).clearCofactor(); | ||
} | ||
} | ||
// Base point aka generator | ||
// public_key = Point.BASE * private_key | ||
Point.BASE = new Point(CURVE.Gx, CURVE.Gy); | ||
// Identity point aka point at infinity | ||
// point = point + zero_point | ||
Point.ZERO = new Point(_0n, _1n); | ||
/** | ||
* EDDSA signature. | ||
*/ | ||
class Signature { | ||
constructor(r, s) { | ||
this.r = r; | ||
this.s = s; | ||
this.assertValidity(); | ||
} | ||
static fromHex(hex) { | ||
const len = Fp.BYTES; | ||
const bytes = ensureBytes(hex, 2 * len); | ||
const r = Point.fromHex(bytes.slice(0, len), false); | ||
const s = ut.bytesToNumberLE(bytes.slice(len, 2 * len)); | ||
return new Signature(r, s); | ||
} | ||
assertValidity() { | ||
const { r, s } = this; | ||
if (!(r instanceof Point)) | ||
throw new Error('Expected Point instance'); | ||
// 0 <= s < l | ||
normalizeScalar(s, CURVE_ORDER, false); | ||
return this; | ||
} | ||
toRawBytes() { | ||
return ut.concatBytes(this.r.toRawBytes(), ut.numberToBytesLE(this.s, Fp.BYTES)); | ||
} | ||
toHex() { | ||
return ut.bytesToHex(this.toRawBytes()); | ||
} | ||
Point.BASE = new Point(CURVE.Gx, CURVE.Gy, _1n, modP(CURVE.Gx * CURVE.Gy)); | ||
Point.ZERO = new Point(_0n, _1n, _1n, _0n); // 0, 1, 1, 0 | ||
const { BASE: G, ZERO: I } = Point; | ||
const wnaf = wNAF(Point, nByteLength * 8); | ||
function modN(a) { | ||
return mod(a, CURVE_ORDER); | ||
} | ||
// Little-endian SHA512 with modulo n | ||
function modnLE(hash) { | ||
return mod.mod(ut.bytesToNumberLE(hash), CURVE_ORDER); | ||
function modN_LE(hash) { | ||
return modN(bytesToNumberLE(hash)); | ||
} | ||
/** | ||
* Checks for num to be in range: | ||
* For strict == true: `0 < num < max`. | ||
* For strict == false: `0 <= num < max`. | ||
* Converts non-float safe numbers to bigints. | ||
*/ | ||
function normalizeScalar(num, max, strict = true) { | ||
if (!max) | ||
throw new TypeError('Specify max value'); | ||
if (ut.isPositiveInt(num)) | ||
num = BigInt(num); | ||
if (typeof num === 'bigint' && num < max) { | ||
if (strict) { | ||
if (_0n < num) | ||
return num; | ||
} | ||
else { | ||
if (_0n <= num) | ||
return num; | ||
} | ||
} | ||
throw new TypeError(`Expected valid scalar: 0 < scalar < ${max}`); | ||
function isHex(item, err) { | ||
if (typeof item !== 'string' && !(item instanceof Uint8Array)) | ||
throw new Error(`${err} must be hex string or Uint8Array`); | ||
} | ||
/** Convenience method that creates public key and other stuff. RFC8032 5.1.5 */ | ||
function getExtendedPublicKey(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; | ||
isHex(key, 'private key'); | ||
const len = nByteLength; | ||
// 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(); | ||
// Check byte lengths: ensure(64, h(ensure(32, key))) | ||
const hashed = ensureBytes(cHash(ensureBytes(key, len)), 2 * len); | ||
const head = adjustScalarBytes(hashed.slice(0, len)); // clear first half bits, produce FE | ||
const prefix = hashed.slice(len, 2 * len); // second half is called key prefix (5.1.6) | ||
const scalar = modN_LE(head); // The actual private scalar | ||
const point = G.multiply(scalar); // Point on Edwards curve aka public key | ||
const pointBytes = point.toRawBytes(); // Uint8Array representation | ||
return { head, prefix, scalar, point, pointBytes }; | ||
} | ||
/** | ||
* Calculates ed25519 public key. RFC8032 5.1.5 | ||
* 1. private key is hashed with sha512, then first 32 bytes are taken from the hash | ||
* 2. 3 least significant bits of the first byte are cleared | ||
*/ | ||
function getPublicKey(privateKey) { | ||
return getExtendedPublicKey(privateKey).pointBytes; | ||
// Calculates EdDSA pub key. RFC8032 5.1.5. Privkey is hashed. Use first half with 3 bits cleared | ||
function getPublicKey(privKey) { | ||
return getExtendedPublicKey(privKey).pointBytes; | ||
} | ||
const EMPTY = new Uint8Array(); | ||
function hashDomainToScalar(message, context = EMPTY) { | ||
context = ensureBytes(context); | ||
return modnLE(CURVE.hash(domain(message, context, !!CURVE.preHash))); | ||
// int('LE', SHA512(dom2(F, C) || msgs)) mod N | ||
function hashDomainToScalar(context = new Uint8Array(), ...msgs) { | ||
const msg = concatBytes(...msgs); | ||
return modN_LE(cHash(domain(msg, ensureBytes(context), !!preHash))); | ||
} | ||
/** Signs message with privateKey. RFC8032 5.1.6 */ | ||
function sign(message, privateKey, context) { | ||
message = ensureBytes(message); | ||
if (CURVE.preHash) | ||
message = CURVE.preHash(message); | ||
const { prefix, scalar, pointBytes } = getExtendedPublicKey(privateKey); | ||
const r = hashDomainToScalar(ut.concatBytes(prefix, message), context); | ||
const R = Point.BASE.multiply(r); // R = rG | ||
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 | ||
return new Signature(R, s).toRawBytes(); | ||
function sign(msg, privKey, context) { | ||
isHex(msg, 'message'); | ||
msg = ensureBytes(msg); | ||
if (preHash) | ||
msg = preHash(msg); // for ed25519ph etc. | ||
const { prefix, scalar, pointBytes } = getExtendedPublicKey(privKey); | ||
const r = hashDomainToScalar(context, prefix, msg); // r = dom2(F, C) || prefix || PH(M) | ||
const R = G.multiply(r).toRawBytes(); // R = rG | ||
const k = hashDomainToScalar(context, R, pointBytes, msg); // R || A || PH(M) | ||
const s = modN(r + k * scalar); // S = (r + k * s) mod L | ||
assertGE0(s); // 0 <= s < l | ||
const res = concatBytes(R, numberToBytesLE(s, Fp.BYTES)); | ||
return ensureBytes(res, nByteLength * 2); // 64-byte signature | ||
} | ||
/** | ||
* Verifies EdDSA signature against message and public key. | ||
* An extended group equation is checked. | ||
* RFC8032 5.1.7 | ||
* Compliant with ZIP215: | ||
* 0 <= sig.R/publicKey < 2**256 (can be >= curve.P) | ||
* 0 <= sig.s < l | ||
* Not compliant with RFC8032: it's not possible to comply to both ZIP & RFC at the same time. | ||
*/ | ||
function verify(sig, message, publicKey, context) { | ||
message = ensureBytes(message); | ||
if (CURVE.preHash) | ||
message = CURVE.preHash(message); | ||
// When hex is passed, we check public key fully. | ||
// When Point instance is passed, we assume it has already been checked, for performance. | ||
// If user passes Point/Sig instance, we assume it has been already verified. | ||
// We don't check its equations for performance. We do check for valid bounds for s though | ||
// We always check for: a) s bounds. b) hex validity | ||
if (publicKey instanceof Point) { | ||
// ignore | ||
} | ||
else if (publicKey instanceof Uint8Array || typeof publicKey === 'string') { | ||
publicKey = Point.fromHex(publicKey, false); | ||
} | ||
else { | ||
throw new Error(`Invalid publicKey: ${publicKey}`); | ||
} | ||
if (sig instanceof Signature) | ||
sig.assertValidity(); | ||
else if (sig instanceof Uint8Array || typeof sig === 'string') | ||
sig = Signature.fromHex(sig); | ||
else | ||
throw new Error(`Wrong signature: ${sig}`); | ||
const { r, s } = sig; | ||
const SB = ExtendedPoint.BASE.multiplyUnsafe(s); | ||
const k = hashDomainToScalar(ut.concatBytes(r.toRawBytes(), publicKey.toRawBytes(), message), context); | ||
const kA = ExtendedPoint.fromAffine(publicKey).multiplyUnsafe(k); | ||
const RkA = ExtendedPoint.fromAffine(r).add(kA); | ||
function verify(sig, msg, publicKey, context) { | ||
isHex(sig, 'sig'); | ||
isHex(msg, 'message'); | ||
const len = Fp.BYTES; // Verifies EdDSA signature against message and public key. RFC8032 5.1.7. | ||
sig = ensureBytes(sig, 2 * len); // An extended group equation is checked. | ||
msg = ensureBytes(msg); // ZIP215 compliant, which means not fully RFC8032 compliant. | ||
if (preHash) | ||
msg = preHash(msg); // for ed25519ph, etc | ||
const A = Point.fromHex(publicKey, false); // Check for s bounds, hex validity | ||
const R = Point.fromHex(sig.slice(0, len), false); // 0 <= R < 2^256: ZIP215 R can be >= P | ||
const s = bytesToNumberLE(sig.slice(len, 2 * len)); // 0 <= s < l | ||
const SB = G.multiplyUnsafe(s); | ||
const k = hashDomainToScalar(context, R.toRawBytes(), A.toRawBytes(), msg); | ||
const RkA = R.add(A.multiplyUnsafe(k)); | ||
// [8][S]B = [8]R + [8][k]A' | ||
return RkA.subtract(SB).clearCofactor().equals(ExtendedPoint.ZERO); | ||
return RkA.subtract(SB).clearCofactor().equals(Point.ZERO); | ||
} | ||
// Enable precomputes. Slows down first publicKey computation by 20ms. | ||
Point.BASE._setWindowSize(8); | ||
G._setWindowSize(8); // Enable precomputes. Slows down first publicKey computation by 20ms. | ||
const utils = { | ||
getExtendedPublicKey, | ||
/** | ||
* Not needed for ed25519 private keys. Needed if you use scalars directly (rare). | ||
*/ | ||
hashToPrivateScalar: (hash) => ut.hashToPrivateScalar(hash, CURVE_ORDER, true), | ||
/** | ||
* ed25519 private keys are uniform 32-bit strings. We do not need to check for | ||
* modulo bias like we do in secp256k1 randomPrivateKey() | ||
*/ | ||
// ed25519 private keys are uniform 32b. No need to check for modulo bias, like in secp256k1. | ||
randomPrivateKey: () => randomBytes(Fp.BYTES), | ||
@@ -574,6 +399,5 @@ /** | ||
precompute(windowSize = 8, point = Point.BASE) { | ||
const cached = point.equals(Point.BASE) ? point : new Point(point.x, point.y); | ||
cached._setWindowSize(windowSize); | ||
cached.multiply(_2n); | ||
return cached; | ||
point._setWindowSize(windowSize); | ||
point.multiply(BigInt(3)); | ||
return point; | ||
}, | ||
@@ -586,7 +410,5 @@ }; | ||
verify, | ||
ExtendedPoint, | ||
Point, | ||
Signature, | ||
ExtendedPoint: Point, | ||
utils, | ||
}; | ||
} |
@@ -1,5 +0,4 @@ | ||
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */ | ||
import { concatBytes } from './utils.js'; | ||
import * as mod from './modular.js'; | ||
export function validateHTFOpts(opts) { | ||
import { mod } from './modular.js'; | ||
import { concatBytes, ensureBytes } from './utils.js'; | ||
export function validateOpts(opts) { | ||
if (typeof opts.DST !== 'string') | ||
@@ -18,9 +17,7 @@ throw new Error('Invalid htf/DST'); | ||
} | ||
// UTF8 to ui8a | ||
// TODO: looks broken, ASCII only, why not TextEncoder/TextDecoder? it is in hashes anyway | ||
export function stringToBytes(str) { | ||
const bytes = new Uint8Array(str.length); | ||
for (let i = 0; i < str.length; i++) | ||
bytes[i] = str.charCodeAt(i); | ||
return bytes; | ||
if (typeof str !== 'string') { | ||
throw new TypeError(`utf8ToBytes expected string, got ${typeof str}`); | ||
} | ||
return new TextEncoder().encode(str); | ||
} | ||
@@ -124,3 +121,3 @@ // Octet Stream to Integer (bytesToNumberBE) | ||
const tv = pseudo_random_bytes.subarray(elm_offset, elm_offset + L); | ||
e[j] = mod.mod(os2ip(tv), options.p); | ||
e[j] = mod(os2ip(tv), options.p); | ||
} | ||
@@ -141,1 +138,31 @@ u[i] = e; | ||
} | ||
export function hashToCurve(Point, mapToCurve, def) { | ||
validateOpts(def); | ||
if (typeof mapToCurve !== 'function') | ||
throw new Error('hashToCurve: mapToCurve() has not been defined'); | ||
return { | ||
// Encodes byte string to elliptic curve | ||
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-3 | ||
hashToCurve(msg, options) { | ||
if (!mapToCurve) | ||
throw new Error('CURVE.mapToCurve() has not been defined'); | ||
msg = ensureBytes(msg); | ||
const u = hash_to_field(msg, 2, { ...def, DST: def.DST, ...options }); | ||
const P = Point.fromAffine(mapToCurve(u[0])) | ||
.add(Point.fromAffine(mapToCurve(u[1]))) | ||
.clearCofactor(); | ||
P.assertValidity(); | ||
return P; | ||
}, | ||
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-16#section-3 | ||
encodeToCurve(msg, options) { | ||
if (!mapToCurve) | ||
throw new Error('CURVE.mapToCurve() has not been defined'); | ||
msg = ensureBytes(msg); | ||
const u = hash_to_field(msg, 1, { ...def, DST: def.encodeDST, ...options }); | ||
const P = Point.fromAffine(mapToCurve(u[0])).clearCofactor(); | ||
P.assertValidity(); | ||
return P; | ||
}, | ||
}; | ||
} |
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */ | ||
// TODO: remove circular imports | ||
import * as utils from './utils.js'; | ||
// Utilities for modular arithmetics and finite fields | ||
import { bitMask, numberToBytesBE, numberToBytesLE, bytesToNumberBE, bytesToNumberLE, ensureBytes, } from './utils.js'; | ||
// prettier-ignore | ||
@@ -94,3 +93,3 @@ const _0n = BigInt(0), _1n = BigInt(1), _2n = BigInt(2), _3n = BigInt(3); | ||
const root = Fp.pow(n, p1div4); | ||
if (!Fp.equals(Fp.square(root), n)) | ||
if (!Fp.eql(Fp.sqr(root), n)) | ||
throw new Error('Cannot find square root'); | ||
@@ -104,3 +103,3 @@ return root; | ||
// Step 0: Check that n is indeed a square: (n | p) should not be ≡ -1 | ||
if (Fp.pow(n, legendreC) === Fp.negate(Fp.ONE)) | ||
if (Fp.pow(n, legendreC) === Fp.neg(Fp.ONE)) | ||
throw new Error('Cannot find square root'); | ||
@@ -112,15 +111,15 @@ let r = S; | ||
let b = Fp.pow(n, Q); // first guess at the fudge factor | ||
while (!Fp.equals(b, Fp.ONE)) { | ||
if (Fp.equals(b, Fp.ZERO)) | ||
while (!Fp.eql(b, Fp.ONE)) { | ||
if (Fp.eql(b, Fp.ZERO)) | ||
return Fp.ZERO; // https://en.wikipedia.org/wiki/Tonelli%E2%80%93Shanks_algorithm (4. If t = 0, return r = 0) | ||
// Find m such b^(2^m)==1 | ||
let m = 1; | ||
for (let t2 = Fp.square(b); m < r; m++) { | ||
if (Fp.equals(t2, Fp.ONE)) | ||
for (let t2 = Fp.sqr(b); m < r; m++) { | ||
if (Fp.eql(t2, Fp.ONE)) | ||
break; | ||
t2 = Fp.square(t2); // t2 *= t2 | ||
t2 = Fp.sqr(t2); // t2 *= t2 | ||
} | ||
// NOTE: r-m-1 can be bigger than 32, need to convert to bigint before shift, otherwise there will be overflow | ||
const ge = Fp.pow(g, _1n << BigInt(r - m - 1)); // ge = 2^(r-m-1) | ||
g = Fp.square(ge); // g = ge * ge | ||
g = Fp.sqr(ge); // g = ge * ge | ||
x = Fp.mul(x, ge); // x *= ge | ||
@@ -147,3 +146,3 @@ b = Fp.mul(b, g); // b *= g | ||
// Throw if root**2 != n | ||
if (!Fp.equals(Fp.square(root), n)) | ||
if (!Fp.eql(Fp.sqr(root), n)) | ||
throw new Error('Cannot find square root'); | ||
@@ -162,3 +161,3 @@ return root; | ||
const root = Fp.mul(nv, Fp.sub(i, Fp.ONE)); | ||
if (!Fp.equals(Fp.square(root), n)) | ||
if (!Fp.eql(Fp.sqr(root), n)) | ||
throw new Error('Cannot find square root'); | ||
@@ -197,5 +196,5 @@ return root; | ||
const FIELD_FIELDS = [ | ||
'create', 'isValid', 'isZero', 'negate', 'invert', 'sqrt', 'square', | ||
'equals', 'add', 'sub', 'mul', 'pow', 'div', | ||
'addN', 'subN', 'mulN', 'squareN' | ||
'create', 'isValid', 'is0', 'neg', 'inv', 'sqrt', 'sqr', | ||
'eql', 'add', 'sub', 'mul', 'pow', 'div', | ||
'addN', 'subN', 'mulN', 'sqrN' | ||
]; | ||
@@ -231,3 +230,3 @@ export function validateField(field) { | ||
p = f.mul(p, d); | ||
d = f.square(d); | ||
d = f.sqr(d); | ||
power >>= 1n; | ||
@@ -241,3 +240,3 @@ } | ||
const lastMultiplied = nums.reduce((acc, num, i) => { | ||
if (f.isZero(num)) | ||
if (f.is0(num)) | ||
return acc; | ||
@@ -248,6 +247,6 @@ tmp[i] = acc; | ||
// Invert last element | ||
const inverted = f.invert(lastMultiplied); | ||
const inverted = f.inv(lastMultiplied); | ||
// Walk from last to first, multiply them by inverted each other MOD p | ||
nums.reduceRight((acc, num, i) => { | ||
if (f.isZero(num)) | ||
if (f.is0(num)) | ||
return acc; | ||
@@ -260,3 +259,3 @@ tmp[i] = f.mul(acc, tmp[i]); | ||
export function FpDiv(f, lhs, rhs) { | ||
return f.mul(lhs, typeof rhs === 'bigint' ? invert(rhs, f.ORDER) : f.invert(rhs)); | ||
return f.mul(lhs, typeof rhs === 'bigint' ? invert(rhs, f.ORDER) : f.inv(rhs)); | ||
} | ||
@@ -268,9 +267,16 @@ // This function returns True whenever the value x is a square in the field F. | ||
const p = f.pow(x, legendreConst); | ||
return f.equals(p, f.ZERO) || f.equals(p, f.ONE); | ||
return f.eql(p, f.ZERO) || f.eql(p, f.ONE); | ||
}; | ||
} | ||
// CURVE.n lengths | ||
export function nLength(n, nBitLength) { | ||
// Bit size, byte size of CURVE.n | ||
const _nBitLength = nBitLength !== undefined ? nBitLength : n.toString(2).length; | ||
const nByteLength = Math.ceil(_nBitLength / 8); | ||
return { nBitLength: _nBitLength, nByteLength }; | ||
} | ||
export function Fp(ORDER, bitLen, isLE = false, redef = {}) { | ||
if (ORDER <= _0n) | ||
throw new Error(`Expected Fp ORDER > 0, got ${ORDER}`); | ||
const { nBitLength: BITS, nByteLength: BYTES } = utils.nLength(ORDER, bitLen); | ||
const { nBitLength: BITS, nByteLength: BYTES } = nLength(ORDER, bitLen); | ||
if (BYTES > 2048) | ||
@@ -283,3 +289,3 @@ throw new Error('Field lengths over 2048 bytes are not supported'); | ||
BYTES, | ||
MASK: utils.bitMask(BITS), | ||
MASK: bitMask(BITS), | ||
ZERO: _0n, | ||
@@ -291,9 +297,9 @@ ONE: _1n, | ||
throw new Error(`Invalid field element: expected bigint, got ${typeof num}`); | ||
return _0n <= num && num < ORDER; | ||
return _0n <= num && num < ORDER; // 0 is valid element, but it's not invertible | ||
}, | ||
isZero: (num) => num === _0n, | ||
is0: (num) => num === _0n, | ||
isOdd: (num) => (num & _1n) === _1n, | ||
negate: (num) => mod(-num, ORDER), | ||
equals: (lhs, rhs) => lhs === rhs, | ||
square: (num) => mod(num * num, ORDER), | ||
neg: (num) => mod(-num, ORDER), | ||
eql: (lhs, rhs) => lhs === rhs, | ||
sqr: (num) => mod(num * num, ORDER), | ||
add: (lhs, rhs) => mod(lhs + rhs, ORDER), | ||
@@ -305,7 +311,7 @@ sub: (lhs, rhs) => mod(lhs - rhs, ORDER), | ||
// Same as above, but doesn't normalize | ||
squareN: (num) => num * num, | ||
sqrN: (num) => num * num, | ||
addN: (lhs, rhs) => lhs + rhs, | ||
subN: (lhs, rhs) => lhs - rhs, | ||
mulN: (lhs, rhs) => lhs * rhs, | ||
invert: (num) => invert(num, ORDER), | ||
inv: (num) => invert(num, ORDER), | ||
sqrt: redef.sqrt || ((n) => sqrtP(f, n)), | ||
@@ -316,7 +322,7 @@ invertBatch: (lst) => FpInvertBatch(f, lst), | ||
cmov: (a, b, c) => (c ? b : a), | ||
toBytes: (num) => isLE ? utils.numberToBytesLE(num, BYTES) : utils.numberToBytesBE(num, BYTES), | ||
toBytes: (num) => (isLE ? numberToBytesLE(num, BYTES) : numberToBytesBE(num, BYTES)), | ||
fromBytes: (bytes) => { | ||
if (bytes.length !== BYTES) | ||
throw new Error(`Fp.fromBytes: expected ${BYTES}, got ${bytes.length}`); | ||
return isLE ? utils.bytesToNumberLE(bytes) : utils.bytesToNumberBE(bytes); | ||
return isLE ? bytesToNumberLE(bytes) : bytesToNumberBE(bytes); | ||
}, | ||
@@ -330,3 +336,3 @@ }); | ||
const root = Fp.sqrt(elm); | ||
return Fp.isOdd(root) ? root : Fp.negate(root); | ||
return Fp.isOdd(root) ? root : Fp.neg(root); | ||
} | ||
@@ -337,3 +343,21 @@ export function FpSqrtEven(Fp, elm) { | ||
const root = Fp.sqrt(elm); | ||
return Fp.isOdd(root) ? Fp.negate(root) : root; | ||
return Fp.isOdd(root) ? Fp.neg(root) : root; | ||
} | ||
/** | ||
* FIPS 186 B.4.1-compliant "constant-time" private key generation utility. | ||
* Can take (n+8) or more bytes of uniform input e.g. from CSPRNG or KDF | ||
* and convert them into private scalar, with the modulo bias being neglible. | ||
* Needs at least 40 bytes of input for 32-byte private key. | ||
* https://research.kudelskisecurity.com/2020/07/28/the-definitive-guide-to-modulo-bias-and-how-to-avoid-it/ | ||
* @param hash hash output from SHA3 or a similar function | ||
* @returns valid private scalar | ||
*/ | ||
export function hashToPrivateScalar(hash, groupOrder, isLE = false) { | ||
hash = ensureBytes(hash); | ||
const hashLen = hash.length; | ||
const minLen = nLength(groupOrder).nByteLength + 8; | ||
if (minLen < 24 || hashLen < minLen || hashLen > 1024) | ||
throw new Error(`hashToPrivateScalar: expected ${minLen}-1024 bytes of input, got ${hashLen}`); | ||
const num = isLE ? bytesToNumberLE(hash) : bytesToNumberBE(hash); | ||
return mod(num, groupOrder - _1n) + _1n; | ||
} |
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */ | ||
import * as mod from './modular.js'; | ||
import { ensureBytes, numberToBytesLE, bytesToNumberLE, isPositiveInt } from './utils.js'; | ||
import { mod, pow } from './modular.js'; | ||
import { ensureBytes, numberToBytesLE, bytesToNumberLE } from './utils.js'; | ||
const _0n = BigInt(0); | ||
@@ -14,3 +14,3 @@ const _1n = BigInt(1); | ||
continue; // Optional | ||
if (!isPositiveInt(curve[i])) | ||
if (!Number.isSafeInteger(curve[i])) | ||
throw new Error(`Invalid curve param ${i}=${curve[i]} (${typeof curve[i]})`); | ||
@@ -31,3 +31,2 @@ } | ||
// Set defaults | ||
// ...nLength(curve.n, curve.nBitLength), | ||
return Object.freeze({ ...curve }); | ||
@@ -40,3 +39,3 @@ } | ||
const { P } = CURVE; | ||
const modP = (a) => mod.mod(a, P); | ||
const modP = (a) => mod(a, P); | ||
const montgomeryBits = CURVE.montgomeryBits; | ||
@@ -46,3 +45,3 @@ const montgomeryBytes = Math.ceil(montgomeryBits / 8); | ||
const adjustScalarBytes = CURVE.adjustScalarBytes || ((bytes) => bytes); | ||
const powPminus2 = CURVE.powPminus2 || ((x) => mod.pow(x, P - BigInt(2), P)); | ||
const powPminus2 = CURVE.powPminus2 || ((x) => pow(x, P - BigInt(2), P)); | ||
/** | ||
@@ -49,0 +48,0 @@ * Checks for num to be in range: |
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */ | ||
import * as mod from './modular.js'; | ||
const _0n = BigInt(0); | ||
const _1n = BigInt(1); | ||
const _2n = BigInt(2); | ||
// Bans floats and integers above 2^53-1 | ||
export function isPositiveInt(num) { | ||
return typeof num === 'number' && Number.isSafeInteger(num) && num > 0; | ||
} | ||
export function validateOpts(curve) { | ||
mod.validateField(curve.Fp); | ||
for (const i of ['n', 'h']) { | ||
const val = curve[i]; | ||
if (typeof val !== 'bigint') | ||
throw new Error(`Invalid curve param ${i}=${val} (${typeof val})`); | ||
} | ||
if (!curve.Fp.isValid(curve.Gx)) | ||
throw new Error('Invalid generator X coordinate Fp element'); | ||
if (!curve.Fp.isValid(curve.Gy)) | ||
throw new Error('Invalid generator Y coordinate Fp element'); | ||
for (const i of ['nBitLength', 'nByteLength']) { | ||
const val = curve[i]; | ||
if (val === undefined) | ||
continue; // Optional | ||
if (!isPositiveInt(val)) | ||
throw new Error(`Invalid curve param ${i}=${val} (${typeof val})`); | ||
} | ||
// Set defaults | ||
return Object.freeze({ ...nLength(curve.n, curve.nBitLength), ...curve }); | ||
} | ||
const u8a = (a) => a instanceof Uint8Array; | ||
const hexes = Array.from({ length: 256 }, (v, i) => i.toString(16).padStart(2, '0')); | ||
export function bytesToHex(uint8a) { | ||
if (!(uint8a instanceof Uint8Array)) | ||
export function bytesToHex(bytes) { | ||
if (!u8a(bytes)) | ||
throw new Error('Expected Uint8Array'); | ||
// pre-caching improves the speed 6x | ||
let hex = ''; | ||
for (let i = 0; i < uint8a.length; i++) { | ||
hex += hexes[uint8a[i]]; | ||
for (let i = 0; i < bytes.length; i++) { | ||
hex += hexes[bytes[i]]; | ||
} | ||
@@ -47,5 +22,4 @@ return hex; | ||
export function hexToNumber(hex) { | ||
if (typeof hex !== 'string') { | ||
throw new TypeError('hexToNumber: expected string, got ' + typeof hex); | ||
} | ||
if (typeof hex !== 'string') | ||
throw new Error('hexToNumber: expected string, got ' + typeof hex); | ||
// Big Endian | ||
@@ -56,5 +30,4 @@ return BigInt(`0x${hex}`); | ||
export function hexToBytes(hex) { | ||
if (typeof hex !== 'string') { | ||
throw new TypeError('hexToBytes: expected string, got ' + typeof hex); | ||
} | ||
if (typeof hex !== 'string') | ||
throw new Error('hexToBytes: expected string, got ' + typeof hex); | ||
if (hex.length % 2) | ||
@@ -77,13 +50,20 @@ throw new Error('hexToBytes: received invalid unpadded hex ' + hex.length); | ||
} | ||
export function bytesToNumberLE(uint8a) { | ||
if (!(uint8a instanceof Uint8Array)) | ||
export function bytesToNumberLE(bytes) { | ||
if (!u8a(bytes)) | ||
throw new Error('Expected Uint8Array'); | ||
return BigInt('0x' + bytesToHex(Uint8Array.from(uint8a).reverse())); | ||
return hexToNumber(bytesToHex(Uint8Array.from(bytes).reverse())); | ||
} | ||
export const numberToBytesBE = (n, len) => hexToBytes(n.toString(16).padStart(len * 2, '0')); | ||
export const numberToBytesLE = (n, len) => numberToBytesBE(n, len).reverse(); | ||
// Returns variable number bytes (minimal bigint encoding?) | ||
export const numberToVarBytesBE = (n) => { | ||
let hex = n.toString(16); | ||
if (hex.length & 1) | ||
hex = '0' + hex; | ||
return hexToBytes(hex); | ||
}; | ||
export function ensureBytes(hex, expectedLength) { | ||
// Uint8Array.from() instead of hash.slice() because node.js Buffer | ||
// is instance of Uint8Array, and its slice() creates **mutable** copy | ||
const bytes = hex instanceof Uint8Array ? Uint8Array.from(hex) : hexToBytes(hex); | ||
const bytes = u8a(hex) ? Uint8Array.from(hex) : hexToBytes(hex); | ||
if (typeof expectedLength === 'number' && bytes.length !== expectedLength) | ||
@@ -95,3 +75,3 @@ throw new Error(`Expected ${expectedLength} bytes`); | ||
export function concatBytes(...arrays) { | ||
if (!arrays.every((b) => b instanceof Uint8Array)) | ||
if (!arrays.every((b) => u8a(b))) | ||
throw new Error('Uint8Array list expected'); | ||
@@ -109,27 +89,2 @@ if (arrays.length === 1) | ||
} | ||
// CURVE.n lengths | ||
export function nLength(n, nBitLength) { | ||
// Bit size, byte size of CURVE.n | ||
const _nBitLength = nBitLength !== undefined ? nBitLength : n.toString(2).length; | ||
const nByteLength = Math.ceil(_nBitLength / 8); | ||
return { nBitLength: _nBitLength, nByteLength }; | ||
} | ||
/** | ||
* FIPS 186 B.4.1-compliant "constant-time" private key generation utility. | ||
* Can take (n+8) or more bytes of uniform input e.g. from CSPRNG or KDF | ||
* and convert them into private scalar, with the modulo bias being neglible. | ||
* Needs at least 40 bytes of input for 32-byte private key. | ||
* https://research.kudelskisecurity.com/2020/07/28/the-definitive-guide-to-modulo-bias-and-how-to-avoid-it/ | ||
* @param hash hash output from SHA3 or a similar function | ||
* @returns valid private scalar | ||
*/ | ||
export function hashToPrivateScalar(hash, groupOrder, isLE = false) { | ||
hash = ensureBytes(hash); | ||
const hashLen = hash.length; | ||
const minLen = nLength(groupOrder).nByteLength + 8; | ||
if (minLen < 24 || hashLen < minLen || hashLen > 1024) | ||
throw new Error(`hashToPrivateScalar: expected ${minLen}-1024 bytes of input, got ${hashLen}`); | ||
const num = isLE ? bytesToNumberLE(hash) : bytesToNumberBE(hash); | ||
return mod.mod(num, groupOrder - _1n) + _1n; | ||
} | ||
export function equalBytes(b1, b2) { | ||
@@ -136,0 +91,0 @@ // We don't care about timing attacks here |
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */ | ||
// Short Weierstrass curve. The formula is: y² = x³ + ax + b | ||
// Differences from @noble/secp256k1 1.7: | ||
// 1. Different double() formula (but same addition) | ||
// 2. Different sqrt() function | ||
// 3. truncateHash() truncateOnly mode | ||
// 4. DRBG supports outputLen bigger than outputLen of hmac | ||
// 5. Support for different hash functions | ||
import * as mod from './modular.js'; | ||
import * as ut from './utils.js'; | ||
import { bytesToHex } from './utils.js'; | ||
import { hash_to_field, validateHTFOpts } from './hash-to-curve.js'; | ||
import { wNAF } from './group.js'; | ||
// ASN.1 DER encoding utilities | ||
class DERError extends Error { | ||
constructor(message) { | ||
super(message); | ||
} | ||
} | ||
const DER = { | ||
slice(s) { | ||
// Proof: any([(i>=0x80) == (int(hex(i).replace('0x', '').zfill(2)[0], 16)>=8) for i in range(0, 256)]) | ||
// Padding done by numberToHex | ||
return Number.parseInt(s[0], 16) >= 8 ? '00' + s : s; | ||
}, | ||
parseInt(data) { | ||
if (data.length < 2 || data[0] !== 0x02) { | ||
throw new DERError(`Invalid signature integer tag: ${bytesToHex(data)}`); | ||
} | ||
const len = data[1]; | ||
const res = data.subarray(2, len + 2); | ||
if (!len || res.length !== len) { | ||
throw new DERError(`Invalid signature integer: wrong length`); | ||
} | ||
// Strange condition, its not about length, but about first bytes of number. | ||
if (res[0] === 0x00 && res[1] <= 0x7f) { | ||
throw new DERError('Invalid signature integer: trailing length'); | ||
} | ||
return { data: ut.bytesToNumberBE(res), left: data.subarray(len + 2) }; | ||
}, | ||
parseSig(data) { | ||
if (data.length < 2 || data[0] != 0x30) { | ||
throw new DERError(`Invalid signature tag: ${bytesToHex(data)}`); | ||
} | ||
if (data[1] !== data.length - 2) { | ||
throw new DERError('Invalid signature: incorrect length'); | ||
} | ||
const { data: r, left: sBytes } = DER.parseInt(data.subarray(2)); | ||
const { data: s, left: rBytesLeft } = DER.parseInt(sBytes); | ||
if (rBytesLeft.length) { | ||
throw new DERError(`Invalid signature: left bytes after parsing: ${bytesToHex(rBytesLeft)}`); | ||
} | ||
return { r, s }; | ||
}, | ||
}; | ||
import { ensureBytes } from './utils.js'; | ||
import { wNAF, validateAbsOpts, } from './curve.js'; | ||
function validatePointOpts(curve) { | ||
const opts = ut.validateOpts(curve); | ||
const opts = validateAbsOpts(curve); | ||
const Fp = opts.Fp; | ||
@@ -63,3 +14,3 @@ for (const i of ['a', 'b']) { | ||
} | ||
for (const i of ['isTorsionFree', 'clearCofactor', 'mapToCurve']) { | ||
for (const i of ['isTorsionFree', 'clearCofactor']) { | ||
if (curve[i] === undefined) | ||
@@ -72,3 +23,3 @@ continue; // Optional | ||
if (endo) { | ||
if (!Fp.equals(opts.a, Fp.ZERO)) { | ||
if (!Fp.eql(opts.a, Fp.ZERO)) { | ||
throw new Error('Endomorphism can only be defined for Koblitz curves that have a=0'); | ||
@@ -86,12 +37,62 @@ } | ||
throw new Error('Invalid fromBytes function'); | ||
// Requires including hashToCurve file | ||
if (opts.htfDefaults !== undefined) | ||
validateHTFOpts(opts.htfDefaults); | ||
// Set defaults | ||
return Object.freeze({ ...opts }); | ||
} | ||
// ASN.1 DER encoding utilities | ||
const { bytesToNumberBE: b2n, hexToBytes: h2b } = ut; | ||
const DER = { | ||
// asn.1 DER encoding utils | ||
Err: class DERErr extends Error { | ||
constructor(m = '') { | ||
super(m); | ||
} | ||
}, | ||
_parseInt(data) { | ||
const { Err: E } = DER; | ||
if (data.length < 2 || data[0] !== 0x02) | ||
throw new E('Invalid signature integer tag'); | ||
const len = data[1]; | ||
const res = data.subarray(2, len + 2); | ||
if (!len || res.length !== len) | ||
throw new E('Invalid signature integer: wrong length'); | ||
if (res[0] === 0x00 && res[1] <= 0x7f) | ||
throw new E('Invalid signature integer: trailing length'); | ||
// ^ Weird condition: not about length, but about first bytes of number. | ||
return { d: b2n(res), l: data.subarray(len + 2) }; // d is data, l is left | ||
}, | ||
toSig(hex) { | ||
// parse DER signature | ||
const { Err: E } = DER; | ||
const data = typeof hex === 'string' ? h2b(hex) : hex; | ||
if (!(data instanceof Uint8Array)) | ||
throw new Error('ui8a expected'); | ||
let l = data.length; | ||
if (l < 2 || data[0] != 0x30) | ||
throw new E('Invalid signature tag'); | ||
if (data[1] !== l - 2) | ||
throw new E('Invalid signature: incorrect length'); | ||
const { d: r, l: sBytes } = DER._parseInt(data.subarray(2)); | ||
const { d: s, l: rBytesLeft } = DER._parseInt(sBytes); | ||
if (rBytesLeft.length) | ||
throw new E('Invalid signature: left bytes after parsing'); | ||
return { r, s }; | ||
}, | ||
hexFromSig(sig) { | ||
const slice = (s) => (Number.parseInt(s[0], 16) >= 8 ? '00' + s : s); // slice DER | ||
const h = (num) => { | ||
const hex = num.toString(16); | ||
return hex.length & 1 ? `0${hex}` : hex; | ||
}; | ||
const s = slice(h(sig.s)); | ||
const r = slice(h(sig.r)); | ||
const shl = s.length / 2; | ||
const rhl = r.length / 2; | ||
const sl = h(shl); | ||
const rl = h(rhl); | ||
return `30${h(rhl + shl + 4)}02${rl}${r}02${sl}${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); | ||
export function weierstrassPoints(opts) { | ||
@@ -106,3 +107,3 @@ const CURVE = validatePointOpts(opts); | ||
const { a, b } = CURVE; | ||
const x2 = Fp.square(x); // x * x | ||
const x2 = Fp.sqr(x); // x * x | ||
const x3 = Fp.mul(x2, x); // x2 * x | ||
@@ -113,4 +114,8 @@ return Fp.add(Fp.add(x3, Fp.mul(x, a)), b); // x3 + a * x + b | ||
function isWithinCurveOrder(num) { | ||
return _0n < num && num < CURVE.n; | ||
return typeof num === 'bigint' && _0n < num && num < CURVE.n; | ||
} | ||
function assertGE(num) { | ||
if (!isWithinCurveOrder(num)) | ||
throw new Error('Expected valid bigint: 0 < bigint < curve.n'); | ||
} | ||
/** | ||
@@ -123,3 +128,3 @@ * Validates if a private key is valid and converts it to bigint form. | ||
function normalizePrivateKey(key) { | ||
const { normalizePrivateKey: custom, nByteLength: groupLen, wrapPrivateKey, n: order } = CURVE; | ||
const { normalizePrivateKey: custom, nByteLength: groupLen, wrapPrivateKey, n } = CURVE; | ||
if (typeof custom === 'function') | ||
@@ -132,36 +137,26 @@ key = custom(key); | ||
} | ||
else if (ut.isPositiveInt(key)) { | ||
num = BigInt(key); | ||
} | ||
else if (typeof key === 'string') { | ||
if (key.length !== 2 * groupLen) | ||
throw new Error(`Expected ${groupLen} bytes of private key`); | ||
throw new Error(`must be ${groupLen} bytes`); | ||
// Validates individual octets | ||
num = ut.hexToNumber(key); | ||
num = ut.bytesToNumberBE(ensureBytes(key)); | ||
} | ||
else if (key instanceof Uint8Array) { | ||
if (key.length !== groupLen) | ||
throw new Error(`Expected ${groupLen} bytes of private key`); | ||
throw new Error(`must be ${groupLen} bytes`); | ||
num = ut.bytesToNumberBE(key); | ||
} | ||
else { | ||
throw new TypeError('Expected valid private key'); | ||
throw new Error('private key must be bytes, hex or bigint, not ' + typeof key); | ||
} | ||
// 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'); | ||
num = mod.mod(num, n); | ||
assertGE(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) { | ||
if (ut.isPositiveInt(num)) | ||
return BigInt(num); | ||
if (typeof num === 'bigint' && isWithinCurveOrder(num)) | ||
return num; | ||
throw new TypeError('Expected valid private scalar: 0 < scalar < curve.n'); | ||
const pointPrecomputes = new Map(); | ||
function assertPrjPoint(other) { | ||
if (!(other instanceof Point)) | ||
throw new Error('ProjectivePoint expected'); | ||
} | ||
@@ -173,17 +168,32 @@ /** | ||
*/ | ||
class ProjectivePoint { | ||
constructor(x, y, z) { | ||
this.x = x; | ||
this.y = y; | ||
this.z = z; | ||
class Point { | ||
constructor(px, py, pz) { | ||
this.px = px; | ||
this.py = py; | ||
this.pz = pz; | ||
if (px == null || !Fp.isValid(px)) | ||
throw new Error('x required'); | ||
if (py == null || !Fp.isValid(py)) | ||
throw new Error('y required'); | ||
if (pz == null || !Fp.isValid(pz)) | ||
throw new Error('z required'); | ||
} | ||
static fromAffine(p) { | ||
if (!(p instanceof Point)) { | ||
throw new TypeError('ProjectivePoint#fromAffine: expected Point'); | ||
} | ||
const { x, y } = p || {}; | ||
if (!p || !Fp.isValid(x) || !Fp.isValid(y)) | ||
throw new Error('invalid affine point'); | ||
if (p instanceof Point) | ||
throw new Error('projective point not allowed'); | ||
const is0 = (i) => Fp.eql(i, Fp.ZERO); | ||
// fromAffine(x:0, y:0) would produce (x:0, y:0, z:1), but we need (x:0, y:1, z:0) | ||
if (p.equals(Point.ZERO)) | ||
return ProjectivePoint.ZERO; | ||
return new ProjectivePoint(p.x, p.y, Fp.ONE); | ||
if (is0(x) && is0(y)) | ||
return Point.ZERO; | ||
return new Point(x, y, Fp.ONE); | ||
} | ||
get x() { | ||
return this.toAffine().x; | ||
} | ||
get y() { | ||
return this.toAffine().y; | ||
} | ||
/** | ||
@@ -193,13 +203,52 @@ * Takes a bunch of Projective Points but executes only one | ||
* so this improves performance massively. | ||
* Optimization: converts a list of projective points to a list of identical points with Z=1. | ||
*/ | ||
static toAffineBatch(points) { | ||
const toInv = Fp.invertBatch(points.map((p) => p.z)); | ||
return points.map((p, i) => p.toAffine(toInv[i])); | ||
static normalizeZ(points) { | ||
const toInv = Fp.invertBatch(points.map((p) => p.pz)); | ||
return points.map((p, i) => p.toAffine(toInv[i])).map(Point.fromAffine); | ||
} | ||
/** | ||
* Optimization: converts a list of projective points to a list of identical points with Z=1. | ||
* Converts hash string or Uint8Array to Point. | ||
* @param hex short/long ECDSA hex | ||
*/ | ||
static normalizeZ(points) { | ||
return ProjectivePoint.toAffineBatch(points).map(ProjectivePoint.fromAffine); | ||
static fromHex(hex) { | ||
const P = Point.fromAffine(CURVE.fromBytes(ensureBytes(hex))); | ||
P.assertValidity(); | ||
return P; | ||
} | ||
// Multiplies generator point by privateKey. | ||
static fromPrivateKey(privateKey) { | ||
return Point.BASE.multiply(normalizePrivateKey(privateKey)); | ||
} | ||
// "Private method", don't use it directly | ||
_setWindowSize(windowSize) { | ||
this._WINDOW_SIZE = windowSize; | ||
pointPrecomputes.delete(this); | ||
} | ||
// A point on curve is valid if it conforms to equation. | ||
assertValidity() { | ||
// Zero is valid point too! | ||
if (this.is0()) { | ||
if (CURVE.allowInfinityPoint) | ||
return; | ||
throw new Error('bad point: ZERO'); | ||
} | ||
// Some 3rd-party test vectors require different wording between here & `fromCompressedHex` | ||
const { x, y } = this.toAffine(); | ||
// Check if x, y are valid field elements | ||
if (!Fp.isValid(x) || !Fp.isValid(y)) | ||
throw new Error('bad point: x or y not FE'); | ||
const left = Fp.sqr(y); // y² | ||
const right = weierstrassEquation(x); // x³ + ax + b | ||
if (!Fp.eql(left, right)) | ||
throw new Error('bad point: equation left != right'); | ||
if (!this.isTorsionFree()) | ||
throw new Error('bad point: not in prime-order subgroup'); | ||
} | ||
hasEvenY() { | ||
const { y } = this.toAffine(); | ||
if (Fp.isOdd) | ||
return !Fp.isOdd(y); | ||
throw new Error("Field doesn't support isOdd"); | ||
} | ||
/** | ||
@@ -210,6 +259,6 @@ * Compare one point to another. | ||
assertPrjPoint(other); | ||
const { x: X1, y: Y1, z: Z1 } = this; | ||
const { x: X2, y: Y2, z: Z2 } = other; | ||
const U1 = Fp.equals(Fp.mul(X1, Z2), Fp.mul(X2, Z1)); | ||
const U2 = Fp.equals(Fp.mul(Y1, Z2), Fp.mul(Y2, Z1)); | ||
const { px: X1, py: Y1, pz: Z1 } = this; | ||
const { px: X2, py: Y2, pz: Z2 } = other; | ||
const U1 = Fp.eql(Fp.mul(X1, Z2), Fp.mul(X2, Z1)); | ||
const U2 = Fp.eql(Fp.mul(Y1, Z2), Fp.mul(Y2, Z1)); | ||
return U1 && U2; | ||
@@ -221,3 +270,3 @@ } | ||
negate() { | ||
return new ProjectivePoint(this.x, Fp.negate(this.y), this.z); | ||
return new Point(this.px, Fp.neg(this.py), this.pz); | ||
} | ||
@@ -231,3 +280,3 @@ // Renes-Costello-Batina exception-free doubling formula. | ||
const b3 = Fp.mul(b, 3n); | ||
const { x: X1, y: Y1, z: Z1 } = this; | ||
const { px: X1, py: Y1, pz: Z1 } = this; | ||
let X3 = Fp.ZERO, Y3 = Fp.ZERO, Z3 = Fp.ZERO; // prettier-ignore | ||
@@ -265,3 +314,3 @@ let t0 = Fp.mul(X1, X1); // step 1 | ||
Z3 = Fp.add(Z3, Z3); | ||
return new ProjectivePoint(X3, Y3, Z3); | ||
return new Point(X3, Y3, Z3); | ||
} | ||
@@ -274,4 +323,4 @@ // Renes-Costello-Batina exception-free addition formula. | ||
assertPrjPoint(other); | ||
const { x: X1, y: Y1, z: Z1 } = this; | ||
const { x: X2, y: Y2, z: Z2 } = other; | ||
const { px: X1, py: Y1, pz: Z1 } = this; | ||
const { px: X2, py: Y2, pz: Z2 } = other; | ||
let X3 = Fp.ZERO, Y3 = Fp.ZERO, Z3 = Fp.ZERO; // prettier-ignore | ||
@@ -320,3 +369,3 @@ const a = CURVE.a; | ||
Z3 = Fp.add(Z3, t0); // step 40 | ||
return new ProjectivePoint(X3, Y3, Z3); | ||
return new Point(X3, Y3, Z3); | ||
} | ||
@@ -326,2 +375,11 @@ subtract(other) { | ||
} | ||
is0() { | ||
return this.equals(Point.ZERO); | ||
} | ||
wNAF(n) { | ||
return wnaf.wNAFCached(this, pointPrecomputes, n, (comp) => { | ||
const toInv = Fp.invertBatch(comp.map((p) => p.pz)); | ||
return comp.map((p, i) => p.toAffine(toInv[i])).map(Point.fromAffine); | ||
}); | ||
} | ||
/** | ||
@@ -332,16 +390,16 @@ * Non-constant-time multiplication. Uses double-and-add algorithm. | ||
*/ | ||
multiplyUnsafe(scalar) { | ||
const P0 = ProjectivePoint.ZERO; | ||
if (typeof scalar === 'bigint' && scalar === _0n) | ||
return P0; | ||
// Will throw on 0 | ||
let n = normalizeScalar(scalar); | ||
multiplyUnsafe(n) { | ||
const I = Point.ZERO; | ||
if (n === _0n) | ||
return I; | ||
assertGE(n); // Will throw on 0 | ||
if (n === _1n) | ||
return this; | ||
if (!CURVE.endo) | ||
const { endo } = CURVE; | ||
if (!endo) | ||
return wnaf.unsafeLadder(this, n); | ||
// Apply endomorphism | ||
let { k1neg, k1, k2neg, k2 } = CURVE.endo.splitScalar(n); | ||
let k1p = P0; | ||
let k2p = P0; | ||
let { k1neg, k1, k2neg, k2 } = endo.splitScalar(n); | ||
let k1p = I; | ||
let k2p = I; | ||
let d = this; | ||
@@ -361,24 +419,6 @@ while (k1 > _0n || k2 > _0n) { | ||
k2p = k2p.negate(); | ||
k2p = new ProjectivePoint(Fp.mul(k2p.x, CURVE.endo.beta), k2p.y, k2p.z); | ||
k2p = new Point(Fp.mul(k2p.px, endo.beta), k2p.py, k2p.pz); | ||
return k1p.add(k2p); | ||
} | ||
/** | ||
* Implements w-ary non-adjacent form for calculating ec multiplication. | ||
*/ | ||
wNAF(n, affinePoint) { | ||
if (!affinePoint && this.equals(ProjectivePoint.BASE)) | ||
affinePoint = Point.BASE; | ||
const W = (affinePoint && affinePoint._WINDOW_SIZE) || 1; | ||
// Calculate precomputes on a first run, reuse them after | ||
let precomputes = affinePoint && pointPrecomputes.get(affinePoint); | ||
if (!precomputes) { | ||
precomputes = wnaf.precomputeWindow(this, W); | ||
if (affinePoint && W !== 1) { | ||
precomputes = ProjectivePoint.normalizeZ(precomputes); | ||
pointPrecomputes.set(affinePoint, precomputes); | ||
} | ||
} | ||
return wnaf.wNAF(W, precomputes, n); | ||
} | ||
/** | ||
* Constant time multiplication. | ||
@@ -391,15 +431,14 @@ * Uses wNAF method. Windowed method may be 10% faster, | ||
*/ | ||
multiply(scalar, affinePoint) { | ||
let n = normalizeScalar(scalar); | ||
// Real point. | ||
let point; | ||
// Fake point, we use it to achieve constant-time multiplication. | ||
let fake; | ||
if (CURVE.endo) { | ||
const { k1neg, k1, k2neg, k2 } = CURVE.endo.splitScalar(n); | ||
let { p: k1p, f: f1p } = this.wNAF(k1, affinePoint); | ||
let { p: k2p, f: f2p } = this.wNAF(k2, affinePoint); | ||
multiply(scalar) { | ||
assertGE(scalar); | ||
let n = scalar; | ||
let point, fake; // Fake point is used to const-time mult | ||
const { endo } = CURVE; | ||
if (endo) { | ||
const { k1neg, k1, k2neg, k2 } = endo.splitScalar(n); | ||
let { p: k1p, f: f1p } = this.wNAF(k1); | ||
let { p: k2p, f: f2p } = this.wNAF(k2); | ||
k1p = wnaf.constTimeNegate(k1neg, k1p); | ||
k2p = wnaf.constTimeNegate(k2neg, k2p); | ||
k2p = new ProjectivePoint(Fp.mul(k2p.x, CURVE.endo.beta), k2p.y, k2p.z); | ||
k2p = new Point(Fp.mul(k2p.px, endo.beta), k2p.py, k2p.pz); | ||
point = k1p.add(k2p); | ||
@@ -409,3 +448,3 @@ fake = f1p.add(f2p); | ||
else { | ||
const { p, f } = this.wNAF(n, affinePoint); | ||
const { p, f } = this.wNAF(n); | ||
point = p; | ||
@@ -415,22 +454,33 @@ fake = f; | ||
// Normalize `z` for both points, but return only real one | ||
return ProjectivePoint.normalizeZ([point, fake])[0]; | ||
return Point.normalizeZ([point, fake])[0]; | ||
} | ||
/** | ||
* Efficiently calculate `aP + bQ`. Unsafe, can expose private key, if used incorrectly. | ||
* @returns non-zero affine point | ||
*/ | ||
multiplyAndAddUnsafe(Q, a, b) { | ||
const G = Point.BASE; // No Strauss-Shamir trick: we have 10% faster G precomputes | ||
const mul = (P, a // Select faster multiply() method | ||
) => (a === _0n || a === _1n || !P.equals(G) ? P.multiplyUnsafe(a) : P.multiply(a)); | ||
const sum = mul(this, a).add(mul(Q, b)); | ||
return sum.is0() ? undefined : sum; | ||
} | ||
// Converts Projective point to affine (x, y) coordinates. | ||
// Can accept precomputed Z^-1 - for example, from invertBatch. | ||
// (x, y, z) ∋ (x=x/z, y=y/z) | ||
toAffine(invZ) { | ||
const { x, y, z } = this; | ||
const is0 = this.equals(ProjectivePoint.ZERO); | ||
toAffine(iz) { | ||
const { px: x, py: y, pz: z } = this; | ||
const is0 = this.is0(); | ||
// If invZ was 0, we return zero point. However we still want to execute | ||
// all operations, so we replace invZ with a random number, 1. | ||
if (invZ == null) | ||
invZ = is0 ? Fp.ONE : Fp.invert(z); | ||
const ax = Fp.mul(x, invZ); | ||
const ay = Fp.mul(y, invZ); | ||
const zz = Fp.mul(z, invZ); | ||
if (iz == null) | ||
iz = is0 ? Fp.ONE : Fp.inv(z); | ||
const ax = Fp.mul(x, iz); | ||
const ay = Fp.mul(y, iz); | ||
const zz = Fp.mul(z, iz); | ||
if (is0) | ||
return Point.ZERO; | ||
if (!Fp.equals(zz, Fp.ONE)) | ||
return { x: Fp.ZERO, y: Fp.ZERO }; | ||
if (!Fp.eql(zz, Fp.ONE)) | ||
throw new Error('invZ was invalid'); | ||
return new Point(ax, ay); | ||
return { x: ax, y: ay }; | ||
} | ||
@@ -442,3 +492,3 @@ isTorsionFree() { | ||
if (isTorsionFree) | ||
return isTorsionFree(ProjectivePoint, this); | ||
return isTorsionFree(Point, this); | ||
throw new Error('isTorsionFree() has not been declared for the elliptic curve'); | ||
@@ -451,158 +501,19 @@ } | ||
if (clearCofactor) | ||
return clearCofactor(ProjectivePoint, this); | ||
return clearCofactor(Point, this); | ||
return this.multiplyUnsafe(CURVE.h); | ||
} | ||
} | ||
ProjectivePoint.BASE = new ProjectivePoint(CURVE.Gx, CURVE.Gy, Fp.ONE); | ||
ProjectivePoint.ZERO = new ProjectivePoint(Fp.ZERO, Fp.ONE, Fp.ZERO); | ||
const _bits = CURVE.nBitLength; | ||
const wnaf = wNAF(ProjectivePoint, CURVE.endo ? Math.ceil(_bits / 2) : _bits); | ||
function assertPrjPoint(other) { | ||
if (!(other instanceof ProjectivePoint)) | ||
throw new TypeError('ProjectivePoint expected'); | ||
} | ||
// Stores precomputed values for points. | ||
const pointPrecomputes = new WeakMap(); | ||
/** | ||
* Default Point works in default aka affine coordinates: (x, y) | ||
*/ | ||
class Point { | ||
constructor(x, y) { | ||
this.x = x; | ||
this.y = y; | ||
} | ||
// "Private method", don't use it directly | ||
_setWindowSize(windowSize) { | ||
this._WINDOW_SIZE = windowSize; | ||
pointPrecomputes.delete(this); | ||
} | ||
// Checks for y % 2 == 0 | ||
hasEvenY() { | ||
if (Fp.isOdd) | ||
return !Fp.isOdd(this.y); | ||
throw new Error("Field doesn't support isOdd"); | ||
} | ||
/** | ||
* Converts hash string or Uint8Array to Point. | ||
* @param hex short/long ECDSA hex | ||
*/ | ||
static fromHex(hex) { | ||
const { x, y } = CURVE.fromBytes(ut.ensureBytes(hex)); | ||
const point = new Point(x, y); | ||
point.assertValidity(); | ||
return point; | ||
} | ||
// Multiplies generator point by privateKey. | ||
static fromPrivateKey(privateKey) { | ||
return Point.BASE.multiply(normalizePrivateKey(privateKey)); | ||
} | ||
toRawBytes(isCompressed = false) { | ||
toRawBytes(isCompressed = true) { | ||
this.assertValidity(); | ||
return CURVE.toBytes(Point, this, isCompressed); | ||
} | ||
toHex(isCompressed = false) { | ||
return bytesToHex(this.toRawBytes(isCompressed)); | ||
toHex(isCompressed = true) { | ||
return ut.bytesToHex(this.toRawBytes(isCompressed)); | ||
} | ||
// A point on curve is valid if it conforms to equation. | ||
assertValidity() { | ||
// Zero is valid point too! | ||
if (this.equals(Point.ZERO)) { | ||
if (CURVE.allowInfinityPoint) | ||
return; | ||
throw new Error('Point at infinity'); | ||
} | ||
// Some 3rd-party test vectors require different wording between here & `fromCompressedHex` | ||
const msg = 'Point is not on elliptic curve'; | ||
const { x, y } = this; | ||
// Check if x, y are valid field elements | ||
if (!Fp.isValid(x) || !Fp.isValid(y)) | ||
throw new Error(msg); | ||
const left = Fp.square(y); // y² | ||
const right = weierstrassEquation(x); // x³ + ax + b | ||
if (!Fp.equals(left, right)) | ||
throw new Error(msg); | ||
if (!this.isTorsionFree()) | ||
throw new Error('Point must be of prime-order subgroup'); | ||
} | ||
equals(other) { | ||
if (!(other instanceof Point)) | ||
throw new TypeError('Point#equals: expected Point'); | ||
return Fp.equals(this.x, other.x) && Fp.equals(this.y, other.y); | ||
} | ||
// Returns the same point with inverted `y` | ||
negate() { | ||
return new Point(this.x, Fp.negate(this.y)); | ||
} | ||
toProj() { | ||
return ProjectivePoint.fromAffine(this); | ||
} | ||
// Adds point to itself | ||
double() { | ||
return this.toProj().double().toAffine(); | ||
} | ||
add(other) { | ||
return this.toProj().add(ProjectivePoint.fromAffine(other)).toAffine(); | ||
} | ||
subtract(other) { | ||
return this.add(other.negate()); | ||
} | ||
multiply(scalar) { | ||
return this.toProj().multiply(scalar, this).toAffine(); | ||
} | ||
multiplyUnsafe(scalar) { | ||
return this.toProj().multiplyUnsafe(scalar).toAffine(); | ||
} | ||
clearCofactor() { | ||
return this.toProj().clearCofactor().toAffine(); | ||
} | ||
isTorsionFree() { | ||
return this.toProj().isTorsionFree(); | ||
} | ||
/** | ||
* Efficiently calculate `aP + bQ`. | ||
* Unsafe, can expose private key, if used incorrectly. | ||
* TODO: Utilize Shamir's trick | ||
* @returns non-zero affine point | ||
*/ | ||
multiplyAndAddUnsafe(Q, a, b) { | ||
const P = this.toProj(); | ||
const aP = a === _0n || a === _1n || this !== Point.BASE ? P.multiplyUnsafe(a) : P.multiply(a); | ||
const bQ = ProjectivePoint.fromAffine(Q).multiplyUnsafe(b); | ||
const sum = aP.add(bQ); | ||
return sum.equals(ProjectivePoint.ZERO) ? undefined : sum.toAffine(); | ||
} | ||
// Encodes byte string to elliptic curve | ||
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-3 | ||
static hashToCurve(msg, options) { | ||
const { mapToCurve } = CURVE; | ||
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 }); | ||
const { x: x0, y: y0 } = mapToCurve(u[0]); | ||
const { x: x1, y: y1 } = mapToCurve(u[1]); | ||
return new Point(x0, y0).add(new Point(x1, y1)).clearCofactor(); | ||
} | ||
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-16#section-3 | ||
static encodeToCurve(msg, options) { | ||
const { mapToCurve } = CURVE; | ||
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 }); | ||
const { x, y } = mapToCurve(u[0]); | ||
return new Point(x, y).clearCofactor(); | ||
} | ||
} | ||
/** | ||
* Base point aka generator. Any public_key = Point.BASE * private_key | ||
*/ | ||
Point.BASE = new Point(CURVE.Gx, CURVE.Gy); | ||
/** | ||
* Identity point aka point at infinity. p - p = zero_p; p + zero_p = p | ||
*/ | ||
Point.ZERO = new Point(Fp.ZERO, Fp.ZERO); | ||
Point.BASE = new Point(CURVE.Gx, CURVE.Gy, Fp.ONE); | ||
Point.ZERO = new Point(Fp.ZERO, Fp.ONE, Fp.ZERO); | ||
const _bits = CURVE.nBitLength; | ||
const wnaf = wNAF(Point, CURVE.endo ? Math.ceil(_bits / 2) : _bits); | ||
return { | ||
Point: Point, | ||
ProjectivePoint: ProjectivePoint, | ||
ProjectivePoint: Point, | ||
normalizePrivateKey, | ||
@@ -614,4 +525,4 @@ weierstrassEquation, | ||
function validateOpts(curve) { | ||
const opts = ut.validateOpts(curve); | ||
if (typeof opts.hash !== 'function' || !ut.isPositiveInt(opts.hash.outputLen)) | ||
const opts = validateAbsOpts(curve); | ||
if (typeof opts.hash !== 'function' || !Number.isSafeInteger(opts.hash.outputLen)) | ||
throw new Error('Invalid hash function'); | ||
@@ -625,51 +536,54 @@ if (typeof opts.hmac !== 'function') | ||
} | ||
/** | ||
* Minimal HMAC-DRBG (NIST 800-90) for signatures. | ||
* Used only for RFC6979, does not fully implement DRBG spec. | ||
*/ | ||
class HmacDrbg { | ||
constructor(hashLen, qByteLen, hmacFn) { | ||
this.hashLen = hashLen; | ||
this.qByteLen = qByteLen; | ||
this.hmacFn = hmacFn; | ||
if (typeof hashLen !== 'number' || hashLen < 2) | ||
throw new Error('hashLen must be a number'); | ||
if (typeof qByteLen !== 'number' || qByteLen < 2) | ||
throw new Error('qByteLen must be a number'); | ||
if (typeof hmacFn !== 'function') | ||
throw new Error('hmacFn must be a function'); | ||
// Step B, Step C: set hashLen to 8*ceil(hlen/8) | ||
this.v = new Uint8Array(hashLen).fill(1); | ||
this.k = new Uint8Array(hashLen).fill(0); | ||
this.counter = 0; | ||
} | ||
hmacSync(...values) { | ||
return this.hmacFn(this.k, ...values); | ||
} | ||
incr() { | ||
if (this.counter >= 1000) | ||
throw new Error('Tried 1,000 k values for sign(), all were invalid'); | ||
this.counter += 1; | ||
} | ||
reseedSync(seed = new Uint8Array()) { | ||
this.k = this.hmacSync(this.v, Uint8Array.from([0x00]), seed); | ||
this.v = this.hmacSync(this.v); | ||
const u8n = (data) => new Uint8Array(data); // creates Uint8Array | ||
const u8fr = (arr) => Uint8Array.from(arr); // another shortcut | ||
function hmacDrbg(hashLen, qByteLen, hmacFn) { | ||
if (typeof hashLen !== 'number' || hashLen < 2) | ||
throw new Error('hashLen must be a number'); | ||
if (typeof qByteLen !== 'number' || qByteLen < 2) | ||
throw new Error('qByteLen must be a number'); | ||
if (typeof hmacFn !== 'function') | ||
throw new Error('hmacFn must be a function'); | ||
// Step B, Step C: set hashLen to 8*ceil(hlen/8) | ||
let v = u8n(hashLen); // Minimal non-full-spec HMAC-DRBG from NIST 800-90 for RFC6979 sigs. | ||
let k = u8n(hashLen); // Steps B and C of RFC6979 3.2: set hashLen, in our case always same | ||
let i = 0; // Iterations counter, will throw when over 1000 | ||
const reset = () => { | ||
v.fill(1); | ||
k.fill(0); | ||
i = 0; | ||
}; | ||
const h = (...b) => hmacFn(k, v, ...b); // hmac(k)(v, ...values) | ||
const reseed = (seed = u8n()) => { | ||
// HMAC-DRBG reseed() function. Steps D-G | ||
k = h(u8fr([0x00]), seed); // k = hmac(k || v || 0x00 || seed) | ||
v = h(); // v = hmac(k || v) | ||
if (seed.length === 0) | ||
return; | ||
this.k = this.hmacSync(this.v, Uint8Array.from([0x01]), seed); | ||
this.v = this.hmacSync(this.v); | ||
} | ||
// TODO: review | ||
generateSync() { | ||
this.incr(); | ||
k = h(u8fr([0x01]), seed); // k = hmac(k || v || 0x01 || seed) | ||
v = h(); // v = hmac(k || v) | ||
}; | ||
const gen = () => { | ||
// HMAC-DRBG generate() function | ||
if (i++ >= 1000) | ||
throw new Error('drbg: tried 1000 values'); | ||
let len = 0; | ||
const out = []; | ||
while (len < this.qByteLen) { | ||
this.v = this.hmacSync(this.v); | ||
const sl = this.v.slice(); | ||
while (len < qByteLen) { | ||
v = h(); | ||
const sl = v.slice(); | ||
out.push(sl); | ||
len += this.v.length; | ||
len += v.length; | ||
} | ||
return ut.concatBytes(...out); | ||
} | ||
}; | ||
const genUntil = (seed, pred) => { | ||
reset(); | ||
reseed(seed); // Steps D-G | ||
let res = undefined; // Step H: grind until k is in [1..n-1] | ||
while (!(res = pred(gen()))) | ||
reseed(); | ||
reset(); | ||
return res; | ||
}; | ||
return genUntil; | ||
} | ||
@@ -683,15 +597,22 @@ export function weierstrass(curveDef) { | ||
function isValidFieldElement(num) { | ||
// 0 is disallowed by arbitrary reasons. Probably because infinity point? | ||
return _0n < num && num < Fp.ORDER; | ||
return _0n < num && num < Fp.ORDER; // 0 is banned since it's not invertible FE | ||
} | ||
const { Point, ProjectivePoint, normalizePrivateKey, weierstrassEquation, isWithinCurveOrder } = weierstrassPoints({ | ||
function modN(a) { | ||
return mod.mod(a, CURVE_ORDER); | ||
} | ||
function invN(a) { | ||
return mod.invert(a, CURVE_ORDER); | ||
} | ||
const { ProjectivePoint: Point, normalizePrivateKey, weierstrassEquation, isWithinCurveOrder, } = weierstrassPoints({ | ||
...CURVE, | ||
toBytes(c, point, isCompressed) { | ||
const x = Fp.toBytes(point.x); | ||
const a = point.toAffine(); | ||
const x = Fp.toBytes(a.x); | ||
const cat = ut.concatBytes; | ||
if (isCompressed) { | ||
// TODO: hasEvenY | ||
return cat(Uint8Array.from([point.hasEvenY() ? 0x02 : 0x03]), x); | ||
} | ||
else { | ||
return cat(Uint8Array.from([0x04]), x, Fp.toBytes(point.y)); | ||
return cat(Uint8Array.from([0x04]), x, Fp.toBytes(a.y)); | ||
} | ||
@@ -701,6 +622,7 @@ }, | ||
const len = bytes.length; | ||
const header = bytes[0]; | ||
const head = bytes[0]; | ||
const tail = bytes.subarray(1); | ||
// this.assertValidity() is done inside of fromHex | ||
if (len === compressedLen && (header === 0x02 || header === 0x03)) { | ||
const x = ut.bytesToNumberBE(bytes.subarray(1)); | ||
if (len === compressedLen && (head === 0x02 || head === 0x03)) { | ||
const x = ut.bytesToNumberBE(tail); | ||
if (!isValidFieldElement(x)) | ||
@@ -712,10 +634,10 @@ throw new Error('Point is not on curve'); | ||
// ECDSA | ||
const isFirstByteOdd = (bytes[0] & 1) === 1; | ||
if (isFirstByteOdd !== isYOdd) | ||
y = Fp.negate(y); | ||
const isHeadOdd = (head & 1) === 1; | ||
if (isHeadOdd !== isYOdd) | ||
y = Fp.neg(y); | ||
return { x, y }; | ||
} | ||
else if (len === uncompressedLen && header === 0x04) { | ||
const x = Fp.fromBytes(bytes.subarray(1, Fp.BYTES + 1)); | ||
const y = Fp.fromBytes(bytes.subarray(Fp.BYTES + 1, 2 * Fp.BYTES + 1)); | ||
else if (len === uncompressedLen && head === 0x04) { | ||
const x = Fp.fromBytes(tail.subarray(0, Fp.BYTES)); | ||
const y = Fp.fromBytes(tail.subarray(Fp.BYTES, 2 * Fp.BYTES)); | ||
return { x, y }; | ||
@@ -728,26 +650,3 @@ } | ||
}); | ||
// Do we need these functions at all? | ||
function numToField(num) { | ||
if (typeof num !== 'bigint') | ||
throw new Error('Expected bigint'); | ||
if (!(_0n <= num && num < Fp.MASK)) | ||
throw new Error(`Expected number < 2^${Fp.BYTES * 8}`); | ||
return Fp.toBytes(num); | ||
} | ||
const numToFieldStr = (num) => bytesToHex(numToField(num)); | ||
/** | ||
* Normalizes hex, bytes, Point to Point. Checks for curve equation. | ||
*/ | ||
function normalizePublicKey(publicKey) { | ||
if (publicKey instanceof Point) { | ||
publicKey.assertValidity(); | ||
return publicKey; | ||
} | ||
else if (publicKey instanceof Uint8Array || typeof publicKey === 'string') { | ||
return Point.fromHex(publicKey); | ||
// This can happen because PointType can be instance of different class | ||
} | ||
else | ||
throw new Error(`Unknown type of public key: ${publicKey}`); | ||
} | ||
const numToNByteStr = (num) => ut.bytesToHex(ut.numberToBytesBE(num, CURVE.nByteLength)); | ||
function isBiggerThanHalfOrder(number) { | ||
@@ -758,18 +657,6 @@ const HALF = CURVE_ORDER >> _1n; | ||
function normalizeS(s) { | ||
return isBiggerThanHalfOrder(s) ? mod.mod(-s, CURVE_ORDER) : s; | ||
return isBiggerThanHalfOrder(s) ? modN(-s) : s; | ||
} | ||
function bits2int_2(bytes) { | ||
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 | ||
function _truncateHash(hash, truncateOnly = false) { | ||
const h = bits2int_2(hash); | ||
if (truncateOnly) | ||
return h; | ||
const { n } = CURVE; | ||
return h >= n ? h - n : h; | ||
} | ||
const truncateHash = CURVE.truncateHash || _truncateHash; | ||
// slice bytes num | ||
const slcNum = (b, from, to) => ut.bytesToNumberBE(b.slice(from, to)); | ||
/** | ||
@@ -787,12 +674,5 @@ * ECDSA signature with its (r, s) properties. Supports DER & compact representations. | ||
static fromCompact(hex) { | ||
const arr = hex instanceof Uint8Array; | ||
const name = 'Signature.fromCompact'; | ||
if (typeof hex !== 'string' && !arr) | ||
throw new TypeError(`${name}: Expected string or Uint8Array`); | ||
const str = arr ? bytesToHex(hex) : hex; | ||
const gl = CURVE.nByteLength * 2; // group length in hex, not ui8a | ||
if (str.length !== 2 * gl) | ||
throw new Error(`${name}: Expected ${gl / 2}-byte hex`); | ||
const slice = (from, to) => ut.hexToNumber(str.slice(from, to)); | ||
return new Signature(slice(0, gl), slice(gl, 2 * gl)); | ||
const gl = CURVE.nByteLength; | ||
hex = ensureBytes(hex, gl * 2); | ||
return new Signature(slcNum(hex, 0, gl), slcNum(hex, gl, 2 * gl)); | ||
} | ||
@@ -802,61 +682,38 @@ // DER encoded ECDSA signature | ||
static fromDER(hex) { | ||
const arr = hex instanceof Uint8Array; | ||
if (typeof hex !== 'string' && !arr) | ||
throw new TypeError(`Signature.fromDER: Expected string or Uint8Array`); | ||
const { r, s } = DER.parseSig(arr ? hex : ut.hexToBytes(hex)); | ||
if (typeof hex !== 'string' && !(hex instanceof Uint8Array)) | ||
throw new Error(`Signature.fromDER: Expected string or Uint8Array`); | ||
const { r, s } = DER.toSig(ensureBytes(hex)); | ||
return new Signature(r, s); | ||
} | ||
assertValidity() { | ||
const { r, s } = this; | ||
if (!isWithinCurveOrder(r)) | ||
throw new Error('Invalid Signature: r must be 0 < r < n'); | ||
if (!isWithinCurveOrder(s)) | ||
throw new Error('Invalid Signature: s must be 0 < s < n'); | ||
// can use assertGE here | ||
if (!isWithinCurveOrder(this.r)) | ||
throw new Error('r must be 0 < r < n'); | ||
if (!isWithinCurveOrder(this.s)) | ||
throw new Error('s must be 0 < s < n'); | ||
} | ||
copyWithRecoveryBit(recovery) { | ||
addRecoveryBit(recovery) { | ||
return new Signature(this.r, this.s, recovery); | ||
} | ||
/** | ||
* 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 | ||
* It's also possible to recover key without bit: try all 4 bit values and check for sig match. | ||
* | ||
* ``` | ||
* recover(r, s, h) where | ||
* u1 = hs^-1 mod n | ||
* u2 = sr^-1 mod n | ||
* Q = u1⋅G + u2⋅R | ||
* ``` | ||
* | ||
* @param msgHash message hash | ||
* @returns Point corresponding to public key | ||
*/ | ||
recoverPublicKey(msgHash) { | ||
const { r, s, recovery } = this; | ||
if (recovery == null) | ||
throw new Error('Cannot recover: recovery bit is not present'); | ||
if (![0, 1, 2, 3].includes(recovery)) | ||
throw new Error('Cannot recover: invalid recovery bit'); | ||
const h = truncateHash(ut.ensureBytes(msgHash)); | ||
const { n } = CURVE; | ||
const radj = recovery === 2 || recovery === 3 ? r + n : r; | ||
const { n: N } = CURVE; // ECDSA public key recovery secg.org/sec1-v2.pdf 4.1.6 | ||
const { r, s, recovery: rec } = this; | ||
const h = bits2int_modN(ensureBytes(msgHash)); // Truncate hash | ||
if (rec == null || ![0, 1, 2, 3].includes(rec)) | ||
throw new Error('recovery id invalid'); | ||
const radj = rec === 2 || rec === 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 | ||
const u1 = mod.mod(-h * rinv, n); | ||
const u2 = mod.mod(s * rinv, n); | ||
const prefix = recovery & 1 ? '03' : '02'; | ||
const R = Point.fromHex(prefix + numToFieldStr(radj)); | ||
const Q = Point.BASE.multiplyAndAddUnsafe(R, u1, u2); // unsafe is fine: no priv data leaked | ||
throw new Error('recovery id 2 or 3 invalid'); | ||
const prefix = (rec & 1) === 0 ? '02' : '03'; | ||
const R = Point.fromHex(prefix + numToNByteStr(radj)); | ||
const ir = invN(radj); // r^-1 | ||
const u1 = modN(-h * ir); // -hr^-1 | ||
const u2 = modN(s * ir); // sr^-1 | ||
const Q = Point.BASE.multiplyAndAddUnsafe(R, u1, u2); // (sr^-1)R-(hr^-1)G = -(hr^-1)G + (sr^-1) | ||
if (!Q) | ||
throw new Error('Cannot recover: point at infinify'); | ||
throw new Error('point at infinify'); // unsafe is fine: no priv data leaked | ||
Q.assertValidity(); | ||
return Q; | ||
} | ||
/** | ||
* Default signatures are always low-s, to prevent malleability. | ||
* `sign(lowS: true)` always produces low-s sigs. | ||
* `verify(lowS: true)` always fails for high-s. | ||
*/ | ||
// Signatures should be low-s, to prevent malleability. | ||
hasHighS() { | ||
@@ -866,5 +723,3 @@ return isBiggerThanHalfOrder(this.s); | ||
normalizeS() { | ||
return this.hasHighS() | ||
? new Signature(this.r, mod.mod(-this.s, CURVE_ORDER), this.recovery) | ||
: this; | ||
return this.hasHighS() ? new Signature(this.r, modN(-this.s), this.recovery) : this; | ||
} | ||
@@ -876,11 +731,3 @@ // DER-encoded | ||
toDERHex() { | ||
const { numberToHexUnpadded: toHex } = ut; | ||
const sHex = DER.slice(toHex(this.s)); | ||
const rHex = DER.slice(toHex(this.r)); | ||
const sHexL = sHex.length / 2; | ||
const rHexL = rHex.length / 2; | ||
const sLen = toHex(sHexL); | ||
const rLen = toHex(rHexL); | ||
const length = toHex(rHexL + sHexL + 4); | ||
return `30${length}02${rLen}${rHex}02${sLen}${sHex}`; | ||
return DER.hexFromSig({ r: this.r, s: this.s }); | ||
} | ||
@@ -892,3 +739,3 @@ // padded bytes of r, then padded bytes of s | ||
toCompactHex() { | ||
return numToFieldStr(this.r) + numToFieldStr(this.s); | ||
return numToNByteStr(this.r) + numToNByteStr(this.s); | ||
} | ||
@@ -906,13 +753,7 @@ } | ||
}, | ||
_bigintToBytes: numToField, | ||
_bigintToString: numToFieldStr, | ||
_normalizePrivateKey: normalizePrivateKey, | ||
_normalizePublicKey: normalizePublicKey, | ||
_isWithinCurveOrder: isWithinCurveOrder, | ||
_isValidFieldElement: isValidFieldElement, | ||
_weierstrassEquation: weierstrassEquation, | ||
/** | ||
* Converts some bytes to a valid private key. Needs at least (nBitLength+64) bytes. | ||
*/ | ||
hashToPrivateKey: (hash) => numToField(ut.hashToPrivateScalar(hash, CURVE_ORDER)), | ||
hashToPrivateKey: (hash) => ut.numberToBytesBE(mod.hashToPrivateScalar(hash, CURVE_ORDER), CURVE.nByteLength), | ||
/** | ||
@@ -932,6 +773,5 @@ * Produces cryptographically secure private key from random of size (nBitLength+64) | ||
precompute(windowSize = 8, point = Point.BASE) { | ||
const cached = point === Point.BASE ? point : new Point(point.x, point.y); | ||
cached._setWindowSize(windowSize); | ||
cached.multiply(_3n); | ||
return cached; | ||
point._setWindowSize(windowSize); | ||
point.multiply(BigInt(3)); | ||
return point; | ||
}, | ||
@@ -945,3 +785,3 @@ }; | ||
*/ | ||
function getPublicKey(privateKey, isCompressed = false) { | ||
function getPublicKey(privateKey, isCompressed = true) { | ||
return Point.fromPrivateKey(privateKey).toRawBytes(isCompressed); | ||
@@ -973,52 +813,68 @@ } | ||
*/ | ||
function getSharedSecret(privateA, publicB, isCompressed = false) { | ||
function getSharedSecret(privateA, publicB, isCompressed = true) { | ||
if (isProbPub(privateA)) | ||
throw new TypeError('getSharedSecret: first arg must be private key'); | ||
throw new Error('first arg must be private key'); | ||
if (!isProbPub(publicB)) | ||
throw new TypeError('getSharedSecret: second arg must be public key'); | ||
const b = normalizePublicKey(publicB); | ||
b.assertValidity(); | ||
throw new Error('second arg must be public key'); | ||
const b = Point.fromHex(publicB); // check for being on-curve | ||
return b.multiply(normalizePrivateKey(privateA)).toRawBytes(isCompressed); | ||
} | ||
// RFC6979 methods | ||
function bits2int(bytes) { | ||
const { nByteLength } = CURVE; | ||
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) { | ||
const z1 = bits2int(bytes); | ||
const z2 = mod.mod(z1, CURVE_ORDER); | ||
return int2octets(z2 < _0n ? z1 : z2); | ||
} | ||
// RFC6979: ensure ECDSA msg is X bytes and < N. RFC suggests optional truncating via bits2octets. | ||
// FIPS 186-4 4.6 suggests the leftmost min(nBitLen, outLen) bits, which matches bits2int. | ||
// bits2int can produce res>N, we can do mod(res, N) since the bitLen is the same. | ||
// int2octets can't be used; pads small msgs with 0: unacceptatble for trunc as per RFC vectors | ||
const bits2int = CURVE.bits2int || | ||
function (bytes) { | ||
// For curves with nBitLength % 8 !== 0: bits2octets(bits2octets(m)) !== bits2octets(m) | ||
// for some cases, since bytes.length * 8 is not actual bitLength. | ||
const delta = bytes.length * 8 - CURVE.nBitLength; // truncate to nBitLength leftmost bits | ||
const num = ut.bytesToNumberBE(bytes); // check for == u8 done here | ||
return delta > 0 ? num >> BigInt(delta) : num; | ||
}; | ||
const bits2int_modN = CURVE.bits2int_modN || | ||
function (bytes) { | ||
return modN(bits2int(bytes)); // can't use bytesToNumberBE here | ||
}; | ||
// NOTE: pads output with zero as per spec | ||
const ORDER_MASK = ut.bitMask(CURVE.nBitLength); | ||
function int2octets(num) { | ||
return numToField(num); // prohibits >nByteLength bytes | ||
if (typeof num !== 'bigint') | ||
throw new Error('Expected bigint'); | ||
if (!(_0n <= num && num < ORDER_MASK)) | ||
throw new Error(`Expected number < 2^${CURVE.nBitLength}`); | ||
// works with order, can have different size than numToField! | ||
return ut.numberToBytesBE(num, CURVE.nByteLength); | ||
} | ||
// Steps A, D of RFC6979 3.2 | ||
// Creates RFC6979 seed; converts msg/privKey to numbers. | ||
function initSigArgs(msgHash, privateKey, extraEntropy) { | ||
// Used only in sign, not in verify. | ||
// NOTE: we cannot assume here that msgHash has same amount of bytes as curve order, this will be wrong at least for P521. | ||
// Also it can be bigger for P224 + SHA256 | ||
function prepSig(msgHash, privateKey, opts = defaultSigOpts) { | ||
if (msgHash == null) | ||
throw new Error(`sign: expected valid message hash, not "${msgHash}"`); | ||
if (['recovered', 'canonical'].some((k) => k in opts)) | ||
// Ban legacy options | ||
throw new Error('sign() legacy options not supported'); | ||
let { lowS, prehash, extraEntropy: ent } = opts; // generates low-s sigs by default | ||
if (prehash) | ||
msgHash = CURVE.hash(ensureBytes(msgHash)); | ||
if (lowS == null) | ||
lowS = true; // RFC6979 3.2: we skip step A, because | ||
// Step A is ignored, since we already provide hash instead of msg | ||
const h1 = numToField(truncateHash(ut.ensureBytes(msgHash))); | ||
// NOTE: instead of bits2int, we calling here truncateHash, since we need | ||
// custom truncation for stark. For other curves it is essentially same as calling bits2int + mod | ||
// However, we cannot later call bits2octets (which is truncateHash + int2octets), since nested bits2int is broken | ||
// for curves where nBitLength % 8 !== 0, so we unwrap it here as int2octets call. | ||
// const bits2octets = (bits)=>int2octets(bytesToNumberBE(truncateHash(bits))) | ||
const h1int = bits2int_modN(ensureBytes(msgHash)); | ||
const h1octets = int2octets(h1int); | ||
const d = normalizePrivateKey(privateKey); | ||
// K = HMAC_K(V || 0x00 || int2octets(x) || bits2octets(h1) || k') | ||
const seedArgs = [int2octets(d), bits2octets(h1)]; | ||
// RFC6979 3.6: additional k' could be provided | ||
if (extraEntropy != null) { | ||
if (extraEntropy === true) | ||
extraEntropy = CURVE.randomBytes(Fp.BYTES); | ||
const e = ut.ensureBytes(extraEntropy); | ||
const seedArgs = [int2octets(d), h1octets]; | ||
if (ent != null) { | ||
// RFC6979 3.6: additional k' (optional) | ||
if (ent === true) | ||
ent = CURVE.randomBytes(Fp.BYTES); | ||
const e = ensureBytes(ent); | ||
if (e.length !== Fp.BYTES) | ||
@@ -1028,43 +884,30 @@ throw new Error(`sign: Expected ${Fp.BYTES} bytes of extra data`); | ||
} | ||
// seed is constructed from private key and message | ||
// Step D | ||
// V, 0x00 are done in HmacDRBG constructor. | ||
const seed = ut.concatBytes(...seedArgs); | ||
const m = bits2int(h1); | ||
return { seed, m, d }; | ||
} | ||
/** | ||
* Converts signature params into point & r/s, checks them for validity. | ||
* k must be in range [1, n-1] | ||
* @param k signature's k param: deterministic in our case, random in non-rfc6979 sigs | ||
* @param m message that would be signed | ||
* @param d private key | ||
* @returns Signature with its point on curve Q OR undefined if params were invalid | ||
*/ | ||
function kmdToSig(kBytes, m, d, lowS = true) { | ||
const { n } = CURVE; | ||
const k = truncateHash(kBytes, true); | ||
if (!isWithinCurveOrder(k)) | ||
return; | ||
// Important: all mod() calls in the function must be done over `n` | ||
const kinv = mod.invert(k, n); | ||
const q = Point.BASE.multiply(k); | ||
// r = x mod n | ||
const r = mod.mod(q.x, n); | ||
if (r === _0n) | ||
return; | ||
// 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); | ||
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 normS = s; | ||
if (lowS && isBiggerThanHalfOrder(s)) { | ||
normS = normalizeS(s); | ||
recovery ^= 1; | ||
const seed = ut.concatBytes(...seedArgs); // Step D of RFC6979 3.2 | ||
const m = h1int; // NOTE: no need to call bits2int second time here, it is inside truncateHash! | ||
// Converts signature params into point w r/s, checks result for validity. | ||
function k2sig(kBytes) { | ||
// RFC 6979 Section 3.2, step 3: k = bits2int(T) | ||
const k = bits2int(kBytes); // Cannot use fields methods, since it is group element | ||
if (!isWithinCurveOrder(k)) | ||
return; // Important: all mod() calls here must be done over N | ||
const ik = invN(k); // k^-1 mod n | ||
const q = Point.BASE.multiply(k).toAffine(); // q = Gk | ||
const r = modN(q.x); // r = q.x mod n | ||
if (r === _0n) | ||
return; | ||
const s = modN(ik * modN(m + modN(d * r))); // s = k^-1(m + rd) mod n | ||
if (s === _0n) | ||
return; | ||
let recovery = (q.x === r ? 0 : 2) | Number(q.y & _1n); // recovery bit (2 or 3, when q.x > n) | ||
let normS = s; | ||
if (lowS && isBiggerThanHalfOrder(s)) { | ||
normS = normalizeS(s); // if lowS was passed, ensure s is always | ||
recovery ^= 1; // // in the bottom half of N | ||
} | ||
return new Signature(r, normS, recovery); // use normS, not s | ||
} | ||
return new Signature(r, normS, recovery); | ||
return { seed, k2sig }; | ||
} | ||
const defaultSigOpts = { lowS: CURVE.lowS }; | ||
const defaultSigOpts = { lowS: CURVE.lowS, prehash: false }; | ||
const defaultVerOpts = { lowS: CURVE.lowS, prehash: false }; | ||
/** | ||
@@ -1078,24 +921,12 @@ * Signs message hash (not message: you need to hash it by yourself). | ||
* ``` | ||
* @param opts `lowS, extraEntropy` | ||
* @param opts `lowS, extraEntropy, prehash` | ||
*/ | ||
function sign(msgHash, privKey, opts = defaultSigOpts) { | ||
// Steps A, D of RFC6979 3.2. | ||
const { seed, m, d } = initSigArgs(msgHash, privKey, opts.extraEntropy); | ||
// Steps B, C, D, E, F, G | ||
const drbg = new HmacDrbg(CURVE.hash.outputLen, CURVE.nByteLength, CURVE.hmac); | ||
drbg.reseedSync(seed); | ||
// Step H3, repeat until k is in range [1, n-1] | ||
let sig; | ||
while (!(sig = kmdToSig(drbg.generateSync(), m, d, opts.lowS))) | ||
drbg.reseedSync(); | ||
return sig; | ||
const { seed, k2sig } = prepSig(msgHash, privKey, opts); // Steps A, D of RFC6979 3.2. | ||
const genUntil = hmacDrbg(CURVE.hash.outputLen, CURVE.nByteLength, CURVE.hmac); | ||
return genUntil(seed, k2sig); // Steps B, C, D, E, F, G | ||
} | ||
/** | ||
* Signs a message (not message hash). | ||
*/ | ||
function signUnhashed(msg, privKey, opts = defaultSigOpts) { | ||
return sign(CURVE.hash(ut.ensureBytes(msg)), privKey, opts); | ||
} | ||
// Enable precomputes. Slows down first publicKey computation by 20ms. | ||
Point.BASE._setWindowSize(8); | ||
// utils.precompute(8, ProjectivePoint.BASE) | ||
/** | ||
@@ -1114,20 +945,26 @@ * Verifies a signature against message hash and public key. | ||
*/ | ||
function verify(signature, msgHash, publicKey, opts = { lowS: CURVE.lowS }) { | ||
function verify(signature, msgHash, publicKey, opts = defaultVerOpts) { | ||
let P; | ||
let _sig = undefined; | ||
if (publicKey instanceof Point) | ||
throw new Error('publicKey must be hex'); | ||
try { | ||
if (signature instanceof Signature) { | ||
signature.assertValidity(); | ||
if (signature && typeof signature === 'object' && !(signature instanceof Uint8Array)) { | ||
const { r, s } = signature; | ||
_sig = new Signature(r, s); // assertValidity() is executed on creation | ||
} | ||
else { | ||
// Signature can be represented in 2 ways: compact (64-byte) & DER (variable-length). | ||
// Since DER can also be 64 bytes, we check for it first. | ||
// Signature can be represented in 2 ways: compact (2*nByteLength) & DER (variable-length). | ||
// Since DER can also be 2*nByteLength bytes, we check for it first. | ||
try { | ||
signature = Signature.fromDER(signature); | ||
_sig = Signature.fromDER(signature); | ||
} | ||
catch (derError) { | ||
if (!(derError instanceof DERError)) | ||
if (!(derError instanceof DER.Err)) | ||
throw derError; | ||
signature = Signature.fromCompact(signature); | ||
_sig = Signature.fromCompact(signature); | ||
} | ||
} | ||
msgHash = ut.ensureBytes(msgHash); | ||
msgHash = ensureBytes(msgHash); | ||
P = Point.fromHex(publicKey); | ||
} | ||
@@ -1137,24 +974,15 @@ catch (error) { | ||
} | ||
if (opts.lowS && signature.hasHighS()) | ||
if (opts.lowS && _sig.hasHighS()) | ||
return false; | ||
let P; | ||
try { | ||
P = normalizePublicKey(publicKey); | ||
} | ||
catch (error) { | ||
return false; | ||
} | ||
const { n } = CURVE; | ||
const { r, s } = signature; | ||
const h = truncateHash(msgHash); | ||
const sinv = mod.invert(s, n); // s^-1 | ||
// R = u1⋅G - u2⋅P | ||
const u1 = mod.mod(h * sinv, n); | ||
const u2 = mod.mod(r * sinv, n); | ||
// Some implementations compare R.x in projective, without inversion. | ||
// The speed-up is <5%, so we don't complicate the code. | ||
const R = Point.BASE.multiplyAndAddUnsafe(P, u1, u2); | ||
if (opts.prehash) | ||
msgHash = CURVE.hash(msgHash); | ||
const { r, s } = _sig; | ||
const h = bits2int_modN(msgHash); // Cannot use fields methods, since it is group element | ||
const is = invN(s); // s^-1 | ||
const u1 = modN(h * is); // u1 = hs^-1 mod n | ||
const u2 = modN(r * is); // u2 = rs^-1 mod n | ||
const R = Point.BASE.multiplyAndAddUnsafe(P, u1, u2)?.toAffine(); // R = u1⋅G + u2⋅P | ||
if (!R) | ||
return false; | ||
const v = mod.mod(R.x, n); | ||
const v = modN(R.x); | ||
return v === r; | ||
@@ -1167,6 +995,5 @@ } | ||
sign, | ||
signUnhashed, | ||
verify, | ||
Point, | ||
ProjectivePoint, | ||
// Point, | ||
ProjectivePoint: Point, | ||
Signature, | ||
@@ -1177,3 +1004,3 @@ utils, | ||
// Implementation of the Shallue and van de Woestijne method for any Weierstrass curve | ||
// TODO: check if there is a way to merge this with uvRation in Edwards && move to modular? | ||
// TODO: check if there is a way to merge this with uvRatio in Edwards && move to modular? | ||
// b = True and y = sqrt(u / v) if (u / v) is square in F, and | ||
@@ -1197,3 +1024,3 @@ // b = False and y = sqrt(Z * (u / v)) otherwise. | ||
let tv2 = Fp.pow(v, c4); // 2. tv2 = v^c4 | ||
let tv3 = Fp.square(tv2); // 3. tv3 = tv2^2 | ||
let tv3 = Fp.sqr(tv2); // 3. tv3 = tv2^2 | ||
tv3 = Fp.mul(tv3, v); // 4. tv3 = tv3 * v | ||
@@ -1207,3 +1034,3 @@ let tv5 = Fp.mul(u, tv3); // 5. tv5 = u * tv3 | ||
tv5 = Fp.pow(tv4, c5); // 11. tv5 = tv4^c5 | ||
let isQR = Fp.equals(tv5, Fp.ONE); // 12. isQR = tv5 == 1 | ||
let isQR = Fp.eql(tv5, Fp.ONE); // 12. isQR = tv5 == 1 | ||
tv2 = Fp.mul(tv3, c7); // 13. tv2 = tv3 * c7 | ||
@@ -1217,3 +1044,3 @@ tv5 = Fp.mul(tv4, tv1); // 14. tv5 = tv4 * tv1 | ||
let tvv5 = Fp.pow(tv4, tv5); // 20. tv5 = tv4^tv5 | ||
const e1 = Fp.equals(tvv5, Fp.ONE); // 21. e1 = tv5 == 1 | ||
const e1 = Fp.eql(tvv5, Fp.ONE); // 21. e1 = tv5 == 1 | ||
tv2 = Fp.mul(tv3, tv1); // 22. tv2 = tv3 * tv1 | ||
@@ -1230,5 +1057,5 @@ tv1 = Fp.mul(tv1, tv1); // 23. tv1 = tv1 * tv1 | ||
const c1 = (Fp.ORDER - 3n) / 4n; // 1. c1 = (q - 3) / 4 # Integer arithmetic | ||
const c2 = Fp.sqrt(Fp.negate(Z)); // 2. c2 = sqrt(-Z) | ||
const c2 = Fp.sqrt(Fp.neg(Z)); // 2. c2 = sqrt(-Z) | ||
sqrtRatio = (u, v) => { | ||
let tv1 = Fp.square(v); // 1. tv1 = v^2 | ||
let tv1 = Fp.sqr(v); // 1. tv1 = v^2 | ||
const tv2 = Fp.mul(u, v); // 2. tv2 = u * v | ||
@@ -1239,4 +1066,4 @@ tv1 = Fp.mul(tv1, tv2); // 3. tv1 = tv1 * tv2 | ||
const y2 = Fp.mul(y1, c2); // 6. y2 = y1 * c2 | ||
const tv3 = Fp.mul(Fp.square(y1), v); // 7. tv3 = y1^2; 8. tv3 = tv3 * v | ||
const isQR = Fp.equals(tv3, u); // 9. isQR = tv3 == u | ||
const tv3 = Fp.mul(Fp.sqr(y1), v); // 7. tv3 = y1^2; 8. tv3 = tv3 * v | ||
const isQR = Fp.eql(tv3, u); // 9. isQR = tv3 == u | ||
let y = Fp.cmov(y2, y1, isQR); // 10. y = CMOV(y2, y1, isQR) | ||
@@ -1263,12 +1090,12 @@ return { isValid: isQR, value: y }; // 11. return (isQR, y) isQR ? y : y*c2 | ||
let tv1, tv2, tv3, tv4, tv5, tv6, x, y; | ||
tv1 = Fp.square(u); // 1. tv1 = u^2 | ||
tv1 = Fp.sqr(u); // 1. tv1 = u^2 | ||
tv1 = Fp.mul(tv1, opts.Z); // 2. tv1 = Z * tv1 | ||
tv2 = Fp.square(tv1); // 3. tv2 = tv1^2 | ||
tv2 = Fp.sqr(tv1); // 3. tv2 = tv1^2 | ||
tv2 = Fp.add(tv2, tv1); // 4. tv2 = tv2 + tv1 | ||
tv3 = Fp.add(tv2, Fp.ONE); // 5. tv3 = tv2 + 1 | ||
tv3 = Fp.mul(tv3, opts.B); // 6. tv3 = B * tv3 | ||
tv4 = Fp.cmov(opts.Z, Fp.negate(tv2), !Fp.equals(tv2, Fp.ZERO)); // 7. tv4 = CMOV(Z, -tv2, tv2 != 0) | ||
tv4 = Fp.cmov(opts.Z, Fp.neg(tv2), !Fp.eql(tv2, Fp.ZERO)); // 7. tv4 = CMOV(Z, -tv2, tv2 != 0) | ||
tv4 = Fp.mul(tv4, opts.A); // 8. tv4 = A * tv4 | ||
tv2 = Fp.square(tv3); // 9. tv2 = tv3^2 | ||
tv6 = Fp.square(tv4); // 10. tv6 = tv4^2 | ||
tv2 = Fp.sqr(tv3); // 9. tv2 = tv3^2 | ||
tv6 = Fp.sqr(tv4); // 10. tv6 = tv4^2 | ||
tv5 = Fp.mul(tv6, opts.A); // 11. tv5 = A * tv6 | ||
@@ -1287,3 +1114,3 @@ tv2 = Fp.add(tv2, tv5); // 12. tv2 = tv2 + tv5 | ||
const e1 = Fp.isOdd(u) === Fp.isOdd(y); // 23. e1 = sgn0(u) == sgn0(y) | ||
y = Fp.cmov(Fp.negate(y), y, e1); // 24. y = CMOV(-y, y, e1) | ||
y = Fp.cmov(Fp.neg(y), y, e1); // 24. y = CMOV(-y, y, e1) | ||
x = Fp.div(x, tv4); // 25. x = x / tv4 | ||
@@ -1290,0 +1117,0 @@ return { x, y }; |
@@ -16,3 +16,3 @@ /*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */ | ||
import * as mod from './abstract/modular.js'; | ||
import { concatBytes, ensureBytes, numberToBytesBE, bytesToNumberBE, bitLen, bitSet, bitGet, bitMask, } from './abstract/utils.js'; | ||
import { concatBytes as concatB, ensureBytes, numberToBytesBE, bytesToNumberBE, bitLen, bitSet, bitGet, bitMask, } from './abstract/utils.js'; | ||
// Types | ||
@@ -69,5 +69,5 @@ import { mapToCurveSimpleSWU, } from './abstract/weierstrass.js'; | ||
isValid: ({ c0, c1 }) => typeof c0 === 'bigint' && typeof c1 === 'bigint', | ||
isZero: ({ c0, c1 }) => Fp.isZero(c0) && Fp.isZero(c1), | ||
equals: ({ c0, c1 }, { c0: r0, c1: r1 }) => Fp.equals(c0, r0) && Fp.equals(c1, r1), | ||
negate: ({ c0, c1 }) => ({ c0: Fp.negate(c0), c1: Fp.negate(c1) }), | ||
is0: ({ c0, c1 }) => Fp.is0(c0) && Fp.is0(c1), | ||
eql: ({ c0, c1 }, { c0: r0, c1: r1 }) => Fp.eql(c0, r0) && Fp.eql(c1, r1), | ||
neg: ({ c0, c1 }) => ({ c0: Fp.neg(c0), c1: Fp.neg(c1) }), | ||
pow: (num, power) => mod.FpPow(Fp2, num, power), | ||
@@ -79,3 +79,3 @@ invertBatch: (nums) => mod.FpInvertBatch(Fp2, nums), | ||
mul: Fp2Multiply, | ||
square: Fp2Square, | ||
sqr: Fp2Square, | ||
// NonNormalized stuff | ||
@@ -85,6 +85,6 @@ addN: Fp2Add, | ||
mulN: Fp2Multiply, | ||
squareN: Fp2Square, | ||
sqrN: Fp2Square, | ||
// Why inversion for bigint inside Fp instead of Fp2? it is even used in that context? | ||
div: (lhs, rhs) => Fp2.mul(lhs, typeof rhs === 'bigint' ? Fp.invert(Fp.create(rhs)) : Fp2.invert(rhs)), | ||
invert: ({ c0: a, c1: b }) => { | ||
div: (lhs, rhs) => Fp2.mul(lhs, typeof rhs === 'bigint' ? Fp.inv(Fp.create(rhs)) : Fp2.inv(rhs)), | ||
inv: ({ c0: a, c1: b }) => { | ||
// We wish to find the multiplicative inverse of a nonzero | ||
@@ -103,7 +103,7 @@ // element a + bu in Fp2. We leverage an identity | ||
// only a single inversion in Fp. | ||
const factor = Fp.invert(Fp.create(a * a + b * b)); | ||
const factor = Fp.inv(Fp.create(a * a + b * b)); | ||
return { c0: Fp.mul(factor, Fp.create(a)), c1: Fp.mul(factor, Fp.create(-b)) }; | ||
}, | ||
sqrt: (num) => { | ||
if (Fp2.equals(num, Fp2.ZERO)) | ||
if (Fp2.eql(num, Fp2.ZERO)) | ||
return Fp2.ZERO; // Algo doesn't handles this case | ||
@@ -117,5 +117,5 @@ // TODO: Optimize this line. It's extremely slow. | ||
const candidateSqrt = Fp2.pow(num, (Fp2.ORDER + 8n) / 16n); | ||
const check = Fp2.div(Fp2.square(candidateSqrt), num); // candidateSqrt.square().div(this); | ||
const check = Fp2.div(Fp2.sqr(candidateSqrt), num); // candidateSqrt.square().div(this); | ||
const R = FP2_ROOTS_OF_UNITY; | ||
const divisor = [R[0], R[2], R[4], R[6]].find((r) => Fp2.equals(r, check)); | ||
const divisor = [R[0], R[2], R[4], R[6]].find((r) => Fp2.eql(r, check)); | ||
if (!divisor) | ||
@@ -128,3 +128,3 @@ throw new Error('No root'); | ||
const x1 = Fp2.div(candidateSqrt, root); | ||
const x2 = Fp2.negate(x1); | ||
const x2 = Fp2.neg(x1); | ||
const { re: re1, im: im1 } = Fp2.reim(x1); | ||
@@ -150,3 +150,3 @@ const { re: re2, im: im2 } = Fp2.reim(x2); | ||
}, | ||
toBytes: ({ c0, c1 }) => concatBytes(Fp.toBytes(c0), Fp.toBytes(c1)), | ||
toBytes: ({ c0, c1 }) => concatB(Fp.toBytes(c0), Fp.toBytes(c1)), | ||
cmov: ({ c0, c1 }, { c0: r0, c1: r1 }, c) => ({ | ||
@@ -241,6 +241,6 @@ c0: Fp.cmov(c0, r0, c), | ||
const Fp6Square = ({ c0, c1, c2 }) => { | ||
let t0 = Fp2.square(c0); // c0² | ||
let t0 = Fp2.sqr(c0); // c0² | ||
let t1 = Fp2.mul(Fp2.mul(c0, c1), 2n); // 2 * c0 * c1 | ||
let t3 = Fp2.mul(Fp2.mul(c1, c2), 2n); // 2 * c1 * c2 | ||
let t4 = Fp2.square(c2); // c2² | ||
let t4 = Fp2.sqr(c2); // c2² | ||
return { | ||
@@ -250,3 +250,3 @@ c0: Fp2.add(Fp2.mulByNonresidue(t3), t0), | ||
// T1 + (c0 - c1 + c2)² + T3 - T0 - T4 | ||
c2: Fp2.sub(Fp2.sub(Fp2.add(Fp2.add(t1, Fp2.square(Fp2.add(Fp2.sub(c0, c1), c2))), t3), t0), t4), | ||
c2: Fp2.sub(Fp2.sub(Fp2.add(Fp2.add(t1, Fp2.sqr(Fp2.add(Fp2.sub(c0, c1), c2))), t3), t0), t4), | ||
}; | ||
@@ -263,5 +263,5 @@ }; | ||
isValid: ({ c0, c1, c2 }) => Fp2.isValid(c0) && Fp2.isValid(c1) && Fp2.isValid(c2), | ||
isZero: ({ c0, c1, c2 }) => Fp2.isZero(c0) && Fp2.isZero(c1) && Fp2.isZero(c2), | ||
negate: ({ c0, c1, c2 }) => ({ c0: Fp2.negate(c0), c1: Fp2.negate(c1), c2: Fp2.negate(c2) }), | ||
equals: ({ c0, c1, c2 }, { c0: r0, c1: r1, c2: r2 }) => Fp2.equals(c0, r0) && Fp2.equals(c1, r1) && Fp2.equals(c2, r2), | ||
is0: ({ c0, c1, c2 }) => Fp2.is0(c0) && Fp2.is0(c1) && Fp2.is0(c2), | ||
neg: ({ c0, c1, c2 }) => ({ c0: Fp2.neg(c0), c1: Fp2.neg(c1), c2: Fp2.neg(c2) }), | ||
eql: ({ c0, c1, c2 }, { c0: r0, c1: r1, c2: r2 }) => Fp2.eql(c0, r0) && Fp2.eql(c1, r1) && Fp2.eql(c2, r2), | ||
sqrt: () => { | ||
@@ -271,3 +271,3 @@ throw new Error('Not implemented'); | ||
// Do we need division by bigint at all? Should be done via order: | ||
div: (lhs, rhs) => Fp6.mul(lhs, typeof rhs === 'bigint' ? Fp.invert(Fp.create(rhs)) : Fp6.invert(rhs)), | ||
div: (lhs, rhs) => Fp6.mul(lhs, typeof rhs === 'bigint' ? Fp.inv(Fp.create(rhs)) : Fp6.inv(rhs)), | ||
pow: (num, power) => mod.FpPow(Fp6, num, power), | ||
@@ -279,3 +279,3 @@ invertBatch: (nums) => mod.FpInvertBatch(Fp6, nums), | ||
mul: Fp6Multiply, | ||
square: Fp6Square, | ||
sqr: Fp6Square, | ||
// NonNormalized stuff | ||
@@ -285,9 +285,9 @@ addN: Fp6Add, | ||
mulN: Fp6Multiply, | ||
squareN: Fp6Square, | ||
invert: ({ c0, c1, c2 }) => { | ||
let t0 = Fp2.sub(Fp2.square(c0), Fp2.mulByNonresidue(Fp2.mul(c2, c1))); // c0² - c2 * c1 * (u + 1) | ||
let t1 = Fp2.sub(Fp2.mulByNonresidue(Fp2.square(c2)), Fp2.mul(c0, c1)); // c2² * (u + 1) - c0 * c1 | ||
let t2 = Fp2.sub(Fp2.square(c1), Fp2.mul(c0, c2)); // c1² - c0 * c2 | ||
sqrN: Fp6Square, | ||
inv: ({ c0, c1, c2 }) => { | ||
let t0 = Fp2.sub(Fp2.sqr(c0), Fp2.mulByNonresidue(Fp2.mul(c2, c1))); // c0² - c2 * c1 * (u + 1) | ||
let t1 = Fp2.sub(Fp2.mulByNonresidue(Fp2.sqr(c2)), Fp2.mul(c0, c1)); // c2² * (u + 1) - c0 * c1 | ||
let t2 = Fp2.sub(Fp2.sqr(c1), Fp2.mul(c0, c2)); // c1² - c0 * c2 | ||
// 1/(((c2 * T1 + c1 * T2) * v) + c0 * T0) | ||
let t4 = Fp2.invert(Fp2.add(Fp2.mulByNonresidue(Fp2.add(Fp2.mul(c2, t1), Fp2.mul(c1, t2))), Fp2.mul(c0, t0))); | ||
let t4 = Fp2.inv(Fp2.add(Fp2.mulByNonresidue(Fp2.add(Fp2.mul(c2, t1), Fp2.mul(c1, t2))), Fp2.mul(c0, t0))); | ||
return { c0: Fp2.mul(t4, t0), c1: Fp2.mul(t4, t1), c2: Fp2.mul(t4, t2) }; | ||
@@ -305,3 +305,3 @@ }, | ||
}, | ||
toBytes: ({ c0, c1, c2 }) => concatBytes(Fp2.toBytes(c0), Fp2.toBytes(c1), Fp2.toBytes(c2)), | ||
toBytes: ({ c0, c1, c2 }) => concatB(Fp2.toBytes(c0), Fp2.toBytes(c1), Fp2.toBytes(c2)), | ||
cmov: ({ c0, c1, c2 }, { c0: r0, c1: r1, c2: r2 }, c) => ({ | ||
@@ -434,7 +434,7 @@ c0: Fp2.cmov(c0, r0, c), | ||
function Fp4Square(a, b) { | ||
const a2 = Fp2.square(a); | ||
const b2 = Fp2.square(b); | ||
const a2 = Fp2.sqr(a); | ||
const b2 = Fp2.sqr(b); | ||
return { | ||
first: Fp2.add(Fp2.mulByNonresidue(b2), a2), | ||
second: Fp2.sub(Fp2.sub(Fp2.square(Fp2.add(a, b)), a2), b2), // (a + b)² - a² - b² | ||
second: Fp2.sub(Fp2.sub(Fp2.sqr(Fp2.add(a, b)), a2), b2), // (a + b)² - a² - b² | ||
}; | ||
@@ -451,13 +451,13 @@ } | ||
isValid: ({ c0, c1 }) => Fp6.isValid(c0) && Fp6.isValid(c1), | ||
isZero: ({ c0, c1 }) => Fp6.isZero(c0) && Fp6.isZero(c1), | ||
negate: ({ c0, c1 }) => ({ c0: Fp6.negate(c0), c1: Fp6.negate(c1) }), | ||
equals: ({ c0, c1 }, { c0: r0, c1: r1 }) => Fp6.equals(c0, r0) && Fp6.equals(c1, r1), | ||
is0: ({ c0, c1 }) => Fp6.is0(c0) && Fp6.is0(c1), | ||
neg: ({ c0, c1 }) => ({ c0: Fp6.neg(c0), c1: Fp6.neg(c1) }), | ||
eql: ({ c0, c1 }, { c0: r0, c1: r1 }) => Fp6.eql(c0, r0) && Fp6.eql(c1, r1), | ||
sqrt: () => { | ||
throw new Error('Not implemented'); | ||
}, | ||
invert: ({ c0, c1 }) => { | ||
let t = Fp6.invert(Fp6.sub(Fp6.square(c0), Fp6.mulByNonresidue(Fp6.square(c1)))); // 1 / (c0² - c1² * v) | ||
return { c0: Fp6.mul(c0, t), c1: Fp6.negate(Fp6.mul(c1, t)) }; // ((C0 * T) * T) + (-C1 * T) * w | ||
inv: ({ c0, c1 }) => { | ||
let t = Fp6.inv(Fp6.sub(Fp6.sqr(c0), Fp6.mulByNonresidue(Fp6.sqr(c1)))); // 1 / (c0² - c1² * v) | ||
return { c0: Fp6.mul(c0, t), c1: Fp6.neg(Fp6.mul(c1, t)) }; // ((C0 * T) * T) + (-C1 * T) * w | ||
}, | ||
div: (lhs, rhs) => Fp12.mul(lhs, typeof rhs === 'bigint' ? Fp.invert(Fp.create(rhs)) : Fp12.invert(rhs)), | ||
div: (lhs, rhs) => Fp12.mul(lhs, typeof rhs === 'bigint' ? Fp.inv(Fp.create(rhs)) : Fp12.inv(rhs)), | ||
pow: (num, power) => mod.FpPow(Fp12, num, power), | ||
@@ -469,3 +469,3 @@ invertBatch: (nums) => mod.FpInvertBatch(Fp12, nums), | ||
mul: Fp12Multiply, | ||
square: Fp12Square, | ||
sqr: Fp12Square, | ||
// NonNormalized stuff | ||
@@ -475,3 +475,3 @@ addN: Fp12Add, | ||
mulN: Fp12Multiply, | ||
squareN: Fp12Square, | ||
sqrN: Fp12Square, | ||
// Bytes utils | ||
@@ -486,3 +486,3 @@ fromBytes: (b) => { | ||
}, | ||
toBytes: ({ c0, c1 }) => concatBytes(Fp6.toBytes(c0), Fp6.toBytes(c1)), | ||
toBytes: ({ c0, c1 }) => concatB(Fp6.toBytes(c0), Fp6.toBytes(c1)), | ||
cmov: ({ c0, c1 }, { c0: r0, c1: r1 }, c) => ({ | ||
@@ -531,3 +531,3 @@ c0: Fp6.cmov(c0, r0, c), | ||
}), | ||
conjugate: ({ c0, c1 }) => ({ c0, c1: Fp6.negate(c1) }), | ||
conjugate: ({ c0, c1 }) => ({ c0, c1: Fp6.neg(c1) }), | ||
// A cyclotomic group is a subgroup of Fp^n defined by | ||
@@ -811,3 +811,3 @@ // GΦₙ(p) = {α ∈ Fpⁿ : α^Φₙ(p) = 1} | ||
function psi2(x, y) { | ||
return [Fp2.mul(x, PSI2_C1), Fp2.negate(y)]; | ||
return [Fp2.mul(x, PSI2_C1), Fp2.neg(y)]; | ||
} | ||
@@ -834,2 +834,3 @@ function G2psi2(c, P) { | ||
DST: 'BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_NUL_', | ||
encodeDST: 'BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_NUL_', | ||
// p: the characteristic of F | ||
@@ -901,3 +902,3 @@ // where F is a finite field of characteristic p and order q = p^m | ||
const cubicRootOfUnityModP = 0x5f19672fdf76ce51ba69c6076a0f77eaddb3a93be6f89688de17d813620a00022e01fffffffefffen; | ||
const phi = new c(Fp.mul(point.x, cubicRootOfUnityModP), point.y, point.z); | ||
const phi = new c(Fp.mul(point.px, cubicRootOfUnityModP), point.py, point.pz); | ||
// todo: unroll | ||
@@ -944,3 +945,3 @@ const xP = point.multiplyUnsafe(bls12_381.CURVE.x).negate(); // [x]P | ||
if ((y * 2n) / P !== aflag) | ||
y = Fp.negate(y); | ||
y = Fp.neg(y); | ||
return { x: Fp.create(x), y: Fp.create(y) }; | ||
@@ -951,3 +952,3 @@ } | ||
if ((bytes[0] & (1 << 6)) !== 0) | ||
return bls12_381.G1.Point.ZERO; | ||
return bls12_381.G1.ProjectivePoint.ZERO.toAffine(); | ||
const x = bytesToNumberBE(bytes.slice(0, Fp.BYTES)); | ||
@@ -963,3 +964,3 @@ const y = bytesToNumberBE(bytes.slice(Fp.BYTES)); | ||
const isZero = point.equals(c.ZERO); | ||
const { x, y } = point; | ||
const { x, y } = point.toAffine(); | ||
if (isCompressed) { | ||
@@ -977,7 +978,7 @@ if (isZero) | ||
// 2x PUBLIC_KEY_LENGTH | ||
const x = concatBytes(new Uint8Array([0x40]), new Uint8Array(2 * Fp.BYTES - 1)); | ||
const x = concatB(new Uint8Array([0x40]), new Uint8Array(2 * Fp.BYTES - 1)); | ||
return x; | ||
} | ||
else { | ||
return concatBytes(numberToBytesBE(x, Fp.BYTES), numberToBytesBE(y, Fp.BYTES)); | ||
return concatB(numberToBytesBE(x, Fp.BYTES), numberToBytesBE(y, Fp.BYTES)); | ||
} | ||
@@ -1052,2 +1053,4 @@ } | ||
const bitS = m_byte & 0x20; // sign bit | ||
const L = Fp.BYTES; | ||
const slc = (b, from, to) => bytesToNumberBE(b.slice(from, to)); | ||
if (bytes.length === 96 && bitC) { | ||
@@ -1064,4 +1067,4 @@ const { b } = bls12_381.CURVE.G2; | ||
} | ||
const x_1 = bytesToNumberBE(bytes.slice(0, Fp.BYTES)); | ||
const x_0 = bytesToNumberBE(bytes.slice(Fp.BYTES)); | ||
const x_1 = slc(bytes, 0, L); | ||
const x_0 = slc(bytes, L, 2 * L); | ||
const x = Fp2.create({ c0: Fp.create(x_0), c1: Fp.create(x_1) }); | ||
@@ -1071,3 +1074,3 @@ const right = Fp2.add(Fp2.pow(x, 3n), b); // y² = x³ + 4 * (u+1) = x³ + b | ||
const Y_bit = y.c1 === 0n ? (y.c0 * 2n) / P : (y.c1 * 2n) / P ? 1n : 0n; | ||
y = bitS > 0 && Y_bit > 0 ? y : Fp2.negate(y); | ||
y = bitS > 0 && Y_bit > 0 ? y : Fp2.neg(y); | ||
return { x, y }; | ||
@@ -1080,6 +1083,6 @@ } | ||
} | ||
const x1 = bytesToNumberBE(bytes.slice(0, Fp.BYTES)); | ||
const x0 = bytesToNumberBE(bytes.slice(Fp.BYTES, 2 * Fp.BYTES)); | ||
const y1 = bytesToNumberBE(bytes.slice(2 * Fp.BYTES, 3 * Fp.BYTES)); | ||
const y0 = bytesToNumberBE(bytes.slice(3 * Fp.BYTES)); | ||
const x1 = slc(bytes, 0, L); | ||
const x0 = slc(bytes, L, 2 * L); | ||
const y1 = slc(bytes, 2 * L, 3 * L); | ||
const y0 = slc(bytes, 3 * L, 4 * L); | ||
return { x: Fp2.fromBigTuple([x0, x1]), y: Fp2.fromBigTuple([y0, y1]) }; | ||
@@ -1093,7 +1096,7 @@ } | ||
const isZero = point.equals(c.ZERO); | ||
const { x, y } = point; | ||
const { x, y } = point.toAffine(); | ||
if (isCompressed) { | ||
const P = Fp.ORDER; | ||
if (isZero) | ||
return concatBytes(COMPRESSED_ZERO, numberToBytesBE(0n, Fp.BYTES)); | ||
return concatB(COMPRESSED_ZERO, numberToBytesBE(0n, Fp.BYTES)); | ||
const flag = Boolean(y.c1 === 0n ? (y.c0 * 2n) / P : (y.c1 * 2n) / P); | ||
@@ -1103,10 +1106,10 @@ // set compressed & sign bits (looks like different offsets than for G1/Fp?) | ||
x_1 = bitSet(x_1, S_BIT_POS, true); | ||
return concatBytes(numberToBytesBE(x_1, Fp.BYTES), numberToBytesBE(x.c0, Fp.BYTES)); | ||
return concatB(numberToBytesBE(x_1, Fp.BYTES), numberToBytesBE(x.c0, Fp.BYTES)); | ||
} | ||
else { | ||
if (isZero) | ||
return concatBytes(new Uint8Array([0x40]), new Uint8Array(4 * Fp.BYTES - 1)); // bytes[0] |= 1 << 6; | ||
return concatB(new Uint8Array([0x40]), new Uint8Array(4 * Fp.BYTES - 1)); // bytes[0] |= 1 << 6; | ||
const { re: x0, im: x1 } = Fp2.reim(x); | ||
const { re: y0, im: y1 } = Fp2.reim(y); | ||
return concatBytes(numberToBytesBE(x1, Fp.BYTES), numberToBytesBE(x0, Fp.BYTES), numberToBytesBE(y1, Fp.BYTES), numberToBytesBE(y0, Fp.BYTES)); | ||
return concatB(numberToBytesBE(x1, Fp.BYTES), numberToBytesBE(x0, Fp.BYTES), numberToBytesBE(y1, Fp.BYTES), numberToBytesBE(y0, Fp.BYTES)); | ||
} | ||
@@ -1127,3 +1130,3 @@ }, | ||
if (bflag1 === 1n) | ||
return bls12_381.G2.Point.ZERO; | ||
return bls12_381.G2.ProjectivePoint.ZERO; | ||
const x1 = Fp.create(z1 & Fp.MASK); | ||
@@ -1144,4 +1147,5 @@ const x2 = Fp.create(z2); | ||
if (isGreater || isZero) | ||
y = Fp2.negate(y); | ||
const point = new bls12_381.G2.Point(x, y); | ||
y = Fp2.neg(y); | ||
const point = bls12_381.G2.ProjectivePoint.fromAffine({ x, y }); | ||
// console.log('Signature.decode', point); | ||
point.assertValidity(); | ||
@@ -1153,6 +1157,7 @@ return point; | ||
point.assertValidity(); | ||
if (point.equals(bls12_381.G2.Point.ZERO)) | ||
return concatBytes(COMPRESSED_ZERO, numberToBytesBE(0n, Fp.BYTES)); | ||
const { re: x0, im: x1 } = Fp2.reim(point.x); | ||
const { re: y0, im: y1 } = Fp2.reim(point.y); | ||
if (point.equals(bls12_381.G2.ProjectivePoint.ZERO)) | ||
return concatB(COMPRESSED_ZERO, numberToBytesBE(0n, Fp.BYTES)); | ||
const a = point.toAffine(); | ||
const { re: x0, im: x1 } = Fp2.reim(a.x); | ||
const { re: y0, im: y1 } = Fp2.reim(a.y); | ||
const tmp = y1 > 0n ? y1 * 2n : y0 * 2n; | ||
@@ -1162,3 +1167,3 @@ const aflag1 = Boolean((tmp / Fp.ORDER) & 1n); | ||
const z2 = x0; | ||
return concatBytes(numberToBytesBE(z1, Fp.BYTES), numberToBytesBE(z2, Fp.BYTES)); | ||
return concatB(numberToBytesBE(z1, Fp.BYTES), numberToBytesBE(z2, Fp.BYTES)); | ||
}, | ||
@@ -1165,0 +1170,0 @@ }, |
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */ | ||
import { sha256 } from '@noble/hashes/sha256'; | ||
import { weierstrass } from './abstract/weierstrass.js'; | ||
import { sha256 } from '@noble/hashes/sha256'; | ||
import { getHash } from './_shortw_utils.js'; | ||
@@ -5,0 +5,0 @@ import { Fp } from './abstract/modular.js'; |
@@ -8,2 +8,3 @@ /*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */ | ||
import { ensureBytes, equalBytes, bytesToHex, bytesToNumberLE, numberToBytesLE, } from './abstract/utils.js'; | ||
import * as htf from './abstract/hash-to-curve.js'; | ||
/** | ||
@@ -83,2 +84,52 @@ * ed25519 Twisted Edwards curve with following addons: | ||
const Fp = Field(ED25519_P, undefined, true); | ||
const ED25519_DEF = { | ||
// Param: a | ||
a: BigInt(-1), | ||
// Equal to -121665/121666 over finite field. | ||
// Negative number is P - number, and division is invert(number, P) | ||
d: BigInt('37095705934669439343138083508754565189542113879843219016388785533085940283555'), | ||
// Finite field 𝔽p over which we'll do calculations; 2n ** 255n - 19n | ||
Fp, | ||
// Subgroup order: how many points ed25519 has | ||
// 2n ** 252n + 27742317777372353535851937790883648493n; | ||
n: BigInt('7237005577332262213973186563042994240857116359379907606001950938285454250989'), | ||
// Cofactor | ||
h: BigInt(8), | ||
// Base point (x, y) aka generator point | ||
Gx: BigInt('15112221349535400772501151409588531511454012693041857206046113283949847762202'), | ||
Gy: BigInt('46316835694926478169428394003475163141307993866256225615783033603165251855960'), | ||
hash: sha512, | ||
randomBytes, | ||
adjustScalarBytes, | ||
// dom2 | ||
// Ratio of u to v. Allows us to combine inversion and square root. Uses algo from RFC8032 5.1.3. | ||
// Constant-time, u/√v | ||
uvRatio, | ||
}; | ||
export const ed25519 = twistedEdwards(ED25519_DEF); | ||
function ed25519_domain(data, ctx, phflag) { | ||
if (ctx.length > 255) | ||
throw new Error('Context is too big'); | ||
return concatBytes(utf8ToBytes('SigEd25519 no Ed25519 collisions'), new Uint8Array([phflag ? 1 : 0, ctx.length]), ctx, data); | ||
} | ||
export const ed25519ctx = twistedEdwards({ ...ED25519_DEF, domain: ed25519_domain }); | ||
export const ed25519ph = twistedEdwards({ | ||
...ED25519_DEF, | ||
domain: ed25519_domain, | ||
preHash: sha512, | ||
}); | ||
export const x25519 = montgomery({ | ||
P: ED25519_P, | ||
a24: BigInt('121665'), | ||
montgomeryBits: 255, | ||
nByteLength: 32, | ||
Gu: '0900000000000000000000000000000000000000000000000000000000000000', | ||
powPminus2: (x) => { | ||
const P = ED25519_P; | ||
// x^(p-2) aka x^(2^255-21) | ||
const { pow_p_5_8, b2 } = ed25519_pow_2_252_3(x); | ||
return mod(pow2(pow_p_5_8, BigInt(3), P) * b2, P); | ||
}, | ||
adjustScalarBytes, | ||
}); | ||
// Hash To Curve Elligator2 Map (NOTE: different from ristretto255 elligator) | ||
@@ -89,3 +140,3 @@ // NOTE: very important part is usage of FpSqrtEven for ELL2_C1_EDWARDS, since | ||
const ELL2_C2 = Fp.pow(_2n, ELL2_C1); // 2. c2 = 2^c1 | ||
const ELL2_C3 = Fp.sqrt(Fp.negate(Fp.ONE)); // 3. c3 = sqrt(-1) | ||
const ELL2_C3 = Fp.sqrt(Fp.neg(Fp.ONE)); // 3. c3 = sqrt(-1) | ||
const ELL2_C4 = (Fp.ORDER - BigInt(5)) / BigInt(8); // 4. c4 = (q - 5) / 8 # Integer arithmetic | ||
@@ -95,7 +146,7 @@ const ELL2_J = BigInt(486662); | ||
function map_to_curve_elligator2_curve25519(u) { | ||
let tv1 = Fp.square(u); // 1. tv1 = u^2 | ||
let tv1 = Fp.sqr(u); // 1. tv1 = u^2 | ||
tv1 = Fp.mul(tv1, _2n); // 2. tv1 = 2 * tv1 | ||
let xd = Fp.add(tv1, Fp.ONE); // 3. xd = tv1 + 1 # Nonzero: -1 is square (mod p), tv1 is not | ||
let x1n = Fp.negate(ELL2_J); // 4. x1n = -J # x1 = x1n / xd = -J / (1 + 2 * u^2) | ||
let tv2 = Fp.square(xd); // 5. tv2 = xd^2 | ||
let x1n = Fp.neg(ELL2_J); // 4. x1n = -J # x1 = x1n / xd = -J / (1 + 2 * u^2) | ||
let tv2 = Fp.sqr(xd); // 5. tv2 = xd^2 | ||
let gxd = Fp.mul(tv2, xd); // 6. gxd = tv2 * xd # gxd = xd^3 | ||
@@ -106,4 +157,4 @@ let gx1 = Fp.mul(tv1, ELL2_J); // 7. gx1 = J * tv1 # x1n + J * xd | ||
gx1 = Fp.mul(gx1, x1n); // 10. gx1 = gx1 * x1n # x1n^3 + J * x1n^2 * xd + x1n * xd^2 | ||
let tv3 = Fp.square(gxd); // 11. tv3 = gxd^2 | ||
tv2 = Fp.square(tv3); // 12. tv2 = tv3^2 # gxd^4 | ||
let tv3 = Fp.sqr(gxd); // 11. tv3 = gxd^2 | ||
tv2 = Fp.sqr(tv3); // 12. tv2 = tv3^2 # gxd^4 | ||
tv3 = Fp.mul(tv3, gxd); // 13. tv3 = tv3 * gxd # gxd^3 | ||
@@ -115,5 +166,5 @@ tv3 = Fp.mul(tv3, gx1); // 14. tv3 = tv3 * gx1 # gx1 * gxd^3 | ||
let y12 = Fp.mul(y11, ELL2_C3); // 18. y12 = y11 * c3 | ||
tv2 = Fp.square(y11); // 19. tv2 = y11^2 | ||
tv2 = Fp.sqr(y11); // 19. tv2 = y11^2 | ||
tv2 = Fp.mul(tv2, gxd); // 20. tv2 = tv2 * gxd | ||
let e1 = Fp.equals(tv2, gx1); // 21. e1 = tv2 == gx1 | ||
let e1 = Fp.eql(tv2, gx1); // 21. e1 = tv2 == gx1 | ||
let y1 = Fp.cmov(y12, y11, e1); // 22. y1 = CMOV(y12, y11, e1) # If g(x1) is square, this is its sqrt | ||
@@ -125,16 +176,16 @@ let x2n = Fp.mul(x1n, tv1); // 23. x2n = x1n * tv1 # x2 = x2n / xd = 2 * u^2 * x1n / xd | ||
let gx2 = Fp.mul(gx1, tv1); // 27. gx2 = gx1 * tv1 # g(x2) = gx2 / gxd = 2 * u^2 * g(x1) | ||
tv2 = Fp.square(y21); // 28. tv2 = y21^2 | ||
tv2 = Fp.sqr(y21); // 28. tv2 = y21^2 | ||
tv2 = Fp.mul(tv2, gxd); // 29. tv2 = tv2 * gxd | ||
let e2 = Fp.equals(tv2, gx2); // 30. e2 = tv2 == gx2 | ||
let e2 = Fp.eql(tv2, gx2); // 30. e2 = tv2 == gx2 | ||
let y2 = Fp.cmov(y22, y21, e2); // 31. y2 = CMOV(y22, y21, e2) # If g(x2) is square, this is its sqrt | ||
tv2 = Fp.square(y1); // 32. tv2 = y1^2 | ||
tv2 = Fp.sqr(y1); // 32. tv2 = y1^2 | ||
tv2 = Fp.mul(tv2, gxd); // 33. tv2 = tv2 * gxd | ||
let e3 = Fp.equals(tv2, gx1); // 34. e3 = tv2 == gx1 | ||
let e3 = Fp.eql(tv2, gx1); // 34. e3 = tv2 == gx1 | ||
let xn = Fp.cmov(x2n, x1n, e3); // 35. xn = CMOV(x2n, x1n, e3) # If e3, x = x1, else x = x2 | ||
let y = Fp.cmov(y2, y1, e3); // 36. y = CMOV(y2, y1, e3) # If e3, y = y1, else y = y2 | ||
let e4 = Fp.isOdd(y); // 37. e4 = sgn0(y) == 1 # Fix sign of y | ||
y = Fp.cmov(y, Fp.negate(y), e3 !== e4); // 38. y = CMOV(y, -y, e3 XOR e4) | ||
y = Fp.cmov(y, Fp.neg(y), e3 !== e4); // 38. y = CMOV(y, -y, e3 XOR e4) | ||
return { xMn: xn, xMd: xd, yMn: y, yMd: 1n }; // 39. return (xn, xd, y, 1) | ||
} | ||
const ELL2_C1_EDWARDS = FpSqrtEven(Fp, Fp.negate(BigInt(486664))); // sgn0(c1) MUST equal 0 | ||
const ELL2_C1_EDWARDS = FpSqrtEven(Fp, Fp.neg(BigInt(486664))); // sgn0(c1) MUST equal 0 | ||
function map_to_curve_elligator2_edwards25519(u) { | ||
@@ -148,3 +199,3 @@ const { xMn, xMd, yMn, yMd } = map_to_curve_elligator2_curve25519(u); // 1. (xMn, xMd, yMn, yMd) = map_to_curve_elligator2_curve25519(u) | ||
let tv1 = Fp.mul(xd, yd); // 7. tv1 = xd * yd | ||
let e = Fp.equals(tv1, Fp.ZERO); // 8. e = tv1 == 0 | ||
let e = Fp.eql(tv1, Fp.ZERO); // 8. e = tv1 == 0 | ||
xn = Fp.cmov(xn, Fp.ZERO, e); // 9. xn = CMOV(xn, 0, e) | ||
@@ -157,64 +208,15 @@ xd = Fp.cmov(xd, Fp.ONE, e); // 10. xd = CMOV(xd, 1, e) | ||
} | ||
const ED25519_DEF = { | ||
// Param: a | ||
a: BigInt(-1), | ||
// Equal to -121665/121666 over finite field. | ||
// Negative number is P - number, and division is invert(number, P) | ||
d: BigInt('37095705934669439343138083508754565189542113879843219016388785533085940283555'), | ||
// Finite field 𝔽p over which we'll do calculations; 2n ** 255n - 19n | ||
Fp, | ||
// Subgroup order: how many points ed25519 has | ||
// 2n ** 252n + 27742317777372353535851937790883648493n; | ||
n: BigInt('7237005577332262213973186563042994240857116359379907606001950938285454250989'), | ||
// Cofactor | ||
h: BigInt(8), | ||
// Base point (x, y) aka generator point | ||
Gx: BigInt('15112221349535400772501151409588531511454012693041857206046113283949847762202'), | ||
Gy: BigInt('46316835694926478169428394003475163141307993866256225615783033603165251855960'), | ||
const { hashToCurve, encodeToCurve } = htf.hashToCurve(ed25519.ExtendedPoint, (scalars) => map_to_curve_elligator2_edwards25519(scalars[0]), { | ||
DST: 'edwards25519_XMD:SHA-512_ELL2_RO_', | ||
encodeDST: 'edwards25519_XMD:SHA-512_ELL2_NU_', | ||
p: Fp.ORDER, | ||
m: 1, | ||
k: 128, | ||
expand: 'xmd', | ||
hash: sha512, | ||
randomBytes, | ||
adjustScalarBytes, | ||
// dom2 | ||
// Ratio of u to v. Allows us to combine inversion and square root. Uses algo from RFC8032 5.1.3. | ||
// Constant-time, u/√v | ||
uvRatio, | ||
htfDefaults: { | ||
DST: 'edwards25519_XMD:SHA-512_ELL2_RO_', | ||
p: Fp.ORDER, | ||
m: 1, | ||
k: 128, | ||
expand: 'xmd', | ||
hash: sha512, | ||
}, | ||
mapToCurve: (scalars) => map_to_curve_elligator2_edwards25519(scalars[0]), | ||
}; | ||
export const ed25519 = twistedEdwards(ED25519_DEF); | ||
function ed25519_domain(data, ctx, phflag) { | ||
if (ctx.length > 255) | ||
throw new Error('Context is too big'); | ||
return concatBytes(utf8ToBytes('SigEd25519 no Ed25519 collisions'), new Uint8Array([phflag ? 1 : 0, ctx.length]), ctx, data); | ||
} | ||
export const ed25519ctx = twistedEdwards({ ...ED25519_DEF, domain: ed25519_domain }); | ||
export const ed25519ph = twistedEdwards({ | ||
...ED25519_DEF, | ||
domain: ed25519_domain, | ||
preHash: sha512, | ||
}); | ||
export const x25519 = montgomery({ | ||
P: ED25519_P, | ||
a24: BigInt('121665'), | ||
montgomeryBits: 255, | ||
nByteLength: 32, | ||
Gu: '0900000000000000000000000000000000000000000000000000000000000000', | ||
powPminus2: (x) => { | ||
const P = ED25519_P; | ||
// x^(p-2) aka x^(2^255-21) | ||
const { pow_p_5_8, b2 } = ed25519_pow_2_252_3(x); | ||
return mod(pow2(pow_p_5_8, BigInt(3), P) * b2, P); | ||
}, | ||
adjustScalarBytes, | ||
}); | ||
export { hashToCurve, encodeToCurve }; | ||
function assertRstPoint(other) { | ||
if (!(other instanceof RistrettoPoint)) | ||
throw new TypeError('RistrettoPoint expected'); | ||
throw new Error('RistrettoPoint expected'); | ||
} | ||
@@ -328,3 +330,3 @@ // √(-1) aka √(a) aka 2^((p-1)/4) | ||
toRawBytes() { | ||
let { x, y, z, t } = this.ep; | ||
let { ex: x, ey: y, ez: z, et: t } = this.ep; | ||
const P = ed25519.CURVE.Fp.ORDER; | ||
@@ -367,8 +369,8 @@ const mod = ed25519.CURVE.Fp.create; | ||
assertRstPoint(other); | ||
const a = this.ep; | ||
const b = other.ep; | ||
const { ex: X1, ey: Y1 } = this.ep; | ||
const { ex: X2, ey: Y2 } = this.ep; | ||
const mod = ed25519.CURVE.Fp.create; | ||
// (x1 * y2 == y1 * x2) | (y1 * y2 == x1 * x2) | ||
const one = mod(a.x * b.y) === mod(a.y * b.x); | ||
const two = mod(a.y * b.y) === mod(a.x * b.x); | ||
const one = mod(X1 * Y2) === mod(Y1 * X2); | ||
const two = mod(Y1 * Y2) === mod(X1 * X2); | ||
return one || two; | ||
@@ -375,0 +377,0 @@ } |
@@ -7,2 +7,3 @@ /*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */ | ||
import { montgomery } from './abstract/montgomery.js'; | ||
import * as htf from './abstract/hash-to-curve.js'; | ||
/** | ||
@@ -50,75 +51,2 @@ * Edwards448 (not Ed448-Goldilocks) curve with following addons: | ||
const Fp = Field(ed448P, 456, true); | ||
// Hash To Curve Elligator2 Map | ||
const ELL2_C1 = (Fp.ORDER - BigInt(3)) / BigInt(4); // 1. c1 = (q - 3) / 4 # Integer arithmetic | ||
const ELL2_J = BigInt(156326); | ||
function map_to_curve_elligator2_curve448(u) { | ||
let tv1 = Fp.square(u); // 1. tv1 = u^2 | ||
let e1 = Fp.equals(tv1, Fp.ONE); // 2. e1 = tv1 == 1 | ||
tv1 = Fp.cmov(tv1, Fp.ZERO, e1); // 3. tv1 = CMOV(tv1, 0, e1) # If Z * u^2 == -1, set tv1 = 0 | ||
let xd = Fp.sub(Fp.ONE, tv1); // 4. xd = 1 - tv1 | ||
let x1n = Fp.negate(ELL2_J); // 5. x1n = -J | ||
let tv2 = Fp.square(xd); // 6. tv2 = xd^2 | ||
let gxd = Fp.mul(tv2, xd); // 7. gxd = tv2 * xd # gxd = xd^3 | ||
let gx1 = Fp.mul(tv1, Fp.negate(ELL2_J)); // 8. gx1 = -J * tv1 # x1n + J * xd | ||
gx1 = Fp.mul(gx1, x1n); // 9. gx1 = gx1 * x1n # x1n^2 + J * x1n * xd | ||
gx1 = Fp.add(gx1, tv2); // 10. gx1 = gx1 + tv2 # x1n^2 + J * x1n * xd + xd^2 | ||
gx1 = Fp.mul(gx1, x1n); // 11. gx1 = gx1 * x1n # x1n^3 + J * x1n^2 * xd + x1n * xd^2 | ||
let tv3 = Fp.square(gxd); // 12. tv3 = gxd^2 | ||
tv2 = Fp.mul(gx1, gxd); // 13. tv2 = gx1 * gxd # gx1 * gxd | ||
tv3 = Fp.mul(tv3, tv2); // 14. tv3 = tv3 * tv2 # gx1 * gxd^3 | ||
let y1 = Fp.pow(tv3, ELL2_C1); // 15. y1 = tv3^c1 # (gx1 * gxd^3)^((p - 3) / 4) | ||
y1 = Fp.mul(y1, tv2); // 16. y1 = y1 * tv2 # gx1 * gxd * (gx1 * gxd^3)^((p - 3) / 4) | ||
let x2n = Fp.mul(x1n, Fp.negate(tv1)); // 17. x2n = -tv1 * x1n # x2 = x2n / xd = -1 * u^2 * x1n / xd | ||
let y2 = Fp.mul(y1, u); // 18. y2 = y1 * u | ||
y2 = Fp.cmov(y2, Fp.ZERO, e1); // 19. y2 = CMOV(y2, 0, e1) | ||
tv2 = Fp.square(y1); // 20. tv2 = y1^2 | ||
tv2 = Fp.mul(tv2, gxd); // 21. tv2 = tv2 * gxd | ||
let e2 = Fp.equals(tv2, gx1); // 22. e2 = tv2 == gx1 | ||
let xn = Fp.cmov(x2n, x1n, e2); // 23. xn = CMOV(x2n, x1n, e2) # If e2, x = x1, else x = x2 | ||
let y = Fp.cmov(y2, y1, e2); // 24. y = CMOV(y2, y1, e2) # If e2, y = y1, else y = y2 | ||
let e3 = Fp.isOdd(y); // 25. e3 = sgn0(y) == 1 # Fix sign of y | ||
y = Fp.cmov(y, Fp.negate(y), e2 !== e3); // 26. y = CMOV(y, -y, e2 XOR e3) | ||
return { xn, xd, yn: y, yd: Fp.ONE }; // 27. return (xn, xd, y, 1) | ||
} | ||
function map_to_curve_elligator2_edwards448(u) { | ||
let { xn, xd, yn, yd } = map_to_curve_elligator2_curve448(u); // 1. (xn, xd, yn, yd) = map_to_curve_elligator2_curve448(u) | ||
let xn2 = Fp.square(xn); // 2. xn2 = xn^2 | ||
let xd2 = Fp.square(xd); // 3. xd2 = xd^2 | ||
let xd4 = Fp.square(xd2); // 4. xd4 = xd2^2 | ||
let yn2 = Fp.square(yn); // 5. yn2 = yn^2 | ||
let yd2 = Fp.square(yd); // 6. yd2 = yd^2 | ||
let xEn = Fp.sub(xn2, xd2); // 7. xEn = xn2 - xd2 | ||
let tv2 = Fp.sub(xEn, xd2); // 8. tv2 = xEn - xd2 | ||
xEn = Fp.mul(xEn, xd2); // 9. xEn = xEn * xd2 | ||
xEn = Fp.mul(xEn, yd); // 10. xEn = xEn * yd | ||
xEn = Fp.mul(xEn, yn); // 11. xEn = xEn * yn | ||
xEn = Fp.mul(xEn, 4n); // 12. xEn = xEn * 4 | ||
tv2 = Fp.mul(tv2, xn2); // 13. tv2 = tv2 * xn2 | ||
tv2 = Fp.mul(tv2, yd2); // 14. tv2 = tv2 * yd2 | ||
let tv3 = Fp.mul(yn2, 4n); // 15. tv3 = 4 * yn2 | ||
let tv1 = Fp.add(tv3, yd2); // 16. tv1 = tv3 + yd2 | ||
tv1 = Fp.mul(tv1, xd4); // 17. tv1 = tv1 * xd4 | ||
let xEd = Fp.add(tv1, tv2); // 18. xEd = tv1 + tv2 | ||
tv2 = Fp.mul(tv2, xn); // 19. tv2 = tv2 * xn | ||
let tv4 = Fp.mul(xn, xd4); // 20. tv4 = xn * xd4 | ||
let yEn = Fp.sub(tv3, yd2); // 21. yEn = tv3 - yd2 | ||
yEn = Fp.mul(yEn, tv4); // 22. yEn = yEn * tv4 | ||
yEn = Fp.sub(yEn, tv2); // 23. yEn = yEn - tv2 | ||
tv1 = Fp.add(xn2, xd2); // 24. tv1 = xn2 + xd2 | ||
tv1 = Fp.mul(tv1, xd2); // 25. tv1 = tv1 * xd2 | ||
tv1 = Fp.mul(tv1, xd); // 26. tv1 = tv1 * xd | ||
tv1 = Fp.mul(tv1, yn2); // 27. tv1 = tv1 * yn2 | ||
tv1 = Fp.mul(tv1, BigInt(-2)); // 28. tv1 = -2 * tv1 | ||
let yEd = Fp.add(tv2, tv1); // 29. yEd = tv2 + tv1 | ||
tv4 = Fp.mul(tv4, yd2); // 30. tv4 = tv4 * yd2 | ||
yEd = Fp.add(yEd, tv4); // 31. yEd = yEd + tv4 | ||
tv1 = Fp.mul(xEd, yEd); // 32. tv1 = xEd * yEd | ||
let e = Fp.equals(tv1, Fp.ZERO); // 33. e = tv1 == 0 | ||
xEn = Fp.cmov(xEn, Fp.ZERO, e); // 34. xEn = CMOV(xEn, 0, e) | ||
xEd = Fp.cmov(xEd, Fp.ONE, e); // 35. xEd = CMOV(xEd, 1, e) | ||
yEn = Fp.cmov(yEn, Fp.ONE, e); // 36. yEn = CMOV(yEn, 1, e) | ||
yEd = Fp.cmov(yEd, Fp.ONE, e); // 37. yEd = CMOV(yEd, 1, e) | ||
const inv = Fp.invertBatch([xEd, yEd]); // batch division | ||
return { x: Fp.mul(xEn, inv[0]), y: Fp.mul(yEn, inv[1]) }; // 38. return (xEn, xEd, yEn, yEd) | ||
} | ||
const ED448_DEF = { | ||
@@ -171,11 +99,2 @@ // Param: a | ||
}, | ||
htfDefaults: { | ||
DST: 'edwards448_XOF:SHAKE256_ELL2_RO_', | ||
p: Fp.ORDER, | ||
m: 1, | ||
k: 224, | ||
expand: 'xof', | ||
hash: shake256, | ||
}, | ||
mapToCurve: (scalars) => map_to_curve_elligator2_edwards448(scalars[0]), | ||
}; | ||
@@ -213,1 +132,84 @@ export const ed448 = twistedEdwards(ED448_DEF); | ||
}); | ||
// Hash To Curve Elligator2 Map | ||
const ELL2_C1 = (Fp.ORDER - BigInt(3)) / BigInt(4); // 1. c1 = (q - 3) / 4 # Integer arithmetic | ||
const ELL2_J = BigInt(156326); | ||
function map_to_curve_elligator2_curve448(u) { | ||
let tv1 = Fp.sqr(u); // 1. tv1 = u^2 | ||
let e1 = Fp.eql(tv1, Fp.ONE); // 2. e1 = tv1 == 1 | ||
tv1 = Fp.cmov(tv1, Fp.ZERO, e1); // 3. tv1 = CMOV(tv1, 0, e1) # If Z * u^2 == -1, set tv1 = 0 | ||
let xd = Fp.sub(Fp.ONE, tv1); // 4. xd = 1 - tv1 | ||
let x1n = Fp.neg(ELL2_J); // 5. x1n = -J | ||
let tv2 = Fp.sqr(xd); // 6. tv2 = xd^2 | ||
let gxd = Fp.mul(tv2, xd); // 7. gxd = tv2 * xd # gxd = xd^3 | ||
let gx1 = Fp.mul(tv1, Fp.neg(ELL2_J)); // 8. gx1 = -J * tv1 # x1n + J * xd | ||
gx1 = Fp.mul(gx1, x1n); // 9. gx1 = gx1 * x1n # x1n^2 + J * x1n * xd | ||
gx1 = Fp.add(gx1, tv2); // 10. gx1 = gx1 + tv2 # x1n^2 + J * x1n * xd + xd^2 | ||
gx1 = Fp.mul(gx1, x1n); // 11. gx1 = gx1 * x1n # x1n^3 + J * x1n^2 * xd + x1n * xd^2 | ||
let tv3 = Fp.sqr(gxd); // 12. tv3 = gxd^2 | ||
tv2 = Fp.mul(gx1, gxd); // 13. tv2 = gx1 * gxd # gx1 * gxd | ||
tv3 = Fp.mul(tv3, tv2); // 14. tv3 = tv3 * tv2 # gx1 * gxd^3 | ||
let y1 = Fp.pow(tv3, ELL2_C1); // 15. y1 = tv3^c1 # (gx1 * gxd^3)^((p - 3) / 4) | ||
y1 = Fp.mul(y1, tv2); // 16. y1 = y1 * tv2 # gx1 * gxd * (gx1 * gxd^3)^((p - 3) / 4) | ||
let x2n = Fp.mul(x1n, Fp.neg(tv1)); // 17. x2n = -tv1 * x1n # x2 = x2n / xd = -1 * u^2 * x1n / xd | ||
let y2 = Fp.mul(y1, u); // 18. y2 = y1 * u | ||
y2 = Fp.cmov(y2, Fp.ZERO, e1); // 19. y2 = CMOV(y2, 0, e1) | ||
tv2 = Fp.sqr(y1); // 20. tv2 = y1^2 | ||
tv2 = Fp.mul(tv2, gxd); // 21. tv2 = tv2 * gxd | ||
let e2 = Fp.eql(tv2, gx1); // 22. e2 = tv2 == gx1 | ||
let xn = Fp.cmov(x2n, x1n, e2); // 23. xn = CMOV(x2n, x1n, e2) # If e2, x = x1, else x = x2 | ||
let y = Fp.cmov(y2, y1, e2); // 24. y = CMOV(y2, y1, e2) # If e2, y = y1, else y = y2 | ||
let e3 = Fp.isOdd(y); // 25. e3 = sgn0(y) == 1 # Fix sign of y | ||
y = Fp.cmov(y, Fp.neg(y), e2 !== e3); // 26. y = CMOV(y, -y, e2 XOR e3) | ||
return { xn, xd, yn: y, yd: Fp.ONE }; // 27. return (xn, xd, y, 1) | ||
} | ||
function map_to_curve_elligator2_edwards448(u) { | ||
let { xn, xd, yn, yd } = map_to_curve_elligator2_curve448(u); // 1. (xn, xd, yn, yd) = map_to_curve_elligator2_curve448(u) | ||
let xn2 = Fp.sqr(xn); // 2. xn2 = xn^2 | ||
let xd2 = Fp.sqr(xd); // 3. xd2 = xd^2 | ||
let xd4 = Fp.sqr(xd2); // 4. xd4 = xd2^2 | ||
let yn2 = Fp.sqr(yn); // 5. yn2 = yn^2 | ||
let yd2 = Fp.sqr(yd); // 6. yd2 = yd^2 | ||
let xEn = Fp.sub(xn2, xd2); // 7. xEn = xn2 - xd2 | ||
let tv2 = Fp.sub(xEn, xd2); // 8. tv2 = xEn - xd2 | ||
xEn = Fp.mul(xEn, xd2); // 9. xEn = xEn * xd2 | ||
xEn = Fp.mul(xEn, yd); // 10. xEn = xEn * yd | ||
xEn = Fp.mul(xEn, yn); // 11. xEn = xEn * yn | ||
xEn = Fp.mul(xEn, 4n); // 12. xEn = xEn * 4 | ||
tv2 = Fp.mul(tv2, xn2); // 13. tv2 = tv2 * xn2 | ||
tv2 = Fp.mul(tv2, yd2); // 14. tv2 = tv2 * yd2 | ||
let tv3 = Fp.mul(yn2, 4n); // 15. tv3 = 4 * yn2 | ||
let tv1 = Fp.add(tv3, yd2); // 16. tv1 = tv3 + yd2 | ||
tv1 = Fp.mul(tv1, xd4); // 17. tv1 = tv1 * xd4 | ||
let xEd = Fp.add(tv1, tv2); // 18. xEd = tv1 + tv2 | ||
tv2 = Fp.mul(tv2, xn); // 19. tv2 = tv2 * xn | ||
let tv4 = Fp.mul(xn, xd4); // 20. tv4 = xn * xd4 | ||
let yEn = Fp.sub(tv3, yd2); // 21. yEn = tv3 - yd2 | ||
yEn = Fp.mul(yEn, tv4); // 22. yEn = yEn * tv4 | ||
yEn = Fp.sub(yEn, tv2); // 23. yEn = yEn - tv2 | ||
tv1 = Fp.add(xn2, xd2); // 24. tv1 = xn2 + xd2 | ||
tv1 = Fp.mul(tv1, xd2); // 25. tv1 = tv1 * xd2 | ||
tv1 = Fp.mul(tv1, xd); // 26. tv1 = tv1 * xd | ||
tv1 = Fp.mul(tv1, yn2); // 27. tv1 = tv1 * yn2 | ||
tv1 = Fp.mul(tv1, BigInt(-2)); // 28. tv1 = -2 * tv1 | ||
let yEd = Fp.add(tv2, tv1); // 29. yEd = tv2 + tv1 | ||
tv4 = Fp.mul(tv4, yd2); // 30. tv4 = tv4 * yd2 | ||
yEd = Fp.add(yEd, tv4); // 31. yEd = yEd + tv4 | ||
tv1 = Fp.mul(xEd, yEd); // 32. tv1 = xEd * yEd | ||
let e = Fp.eql(tv1, Fp.ZERO); // 33. e = tv1 == 0 | ||
xEn = Fp.cmov(xEn, Fp.ZERO, e); // 34. xEn = CMOV(xEn, 0, e) | ||
xEd = Fp.cmov(xEd, Fp.ONE, e); // 35. xEd = CMOV(xEd, 1, e) | ||
yEn = Fp.cmov(yEn, Fp.ONE, e); // 36. yEn = CMOV(yEn, 1, e) | ||
yEd = Fp.cmov(yEd, Fp.ONE, e); // 37. yEd = CMOV(yEd, 1, e) | ||
const inv = Fp.invertBatch([xEd, yEd]); // batch division | ||
return { x: Fp.mul(xEn, inv[0]), y: Fp.mul(yEn, inv[1]) }; // 38. return (xEn, xEd, yEn, yEd) | ||
} | ||
const { hashToCurve, encodeToCurve } = htf.hashToCurve(ed448.ExtendedPoint, (scalars) => map_to_curve_elligator2_edwards448(scalars[0]), { | ||
DST: 'edwards448_XOF:SHAKE256_ELL2_RO_', | ||
encodeDST: 'edwards448_XOF:SHAKE256_ELL2_NU_', | ||
p: Fp.ORDER, | ||
m: 1, | ||
k: 224, | ||
expand: 'xof', | ||
hash: shake256, | ||
}); | ||
export { hashToCurve, encodeToCurve }; |
@@ -36,3 +36,3 @@ /*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */ | ||
// NOTE: returns ExtendedPoint, in case it will be multiplied later | ||
let p = jubjub.ExtendedPoint.fromAffine(jubjub.Point.fromHex(h.digest())); | ||
let p = jubjub.ExtendedPoint.fromHex(h.digest()); | ||
// NOTE: cannot replace with isSmallOrder, returns Point*8 | ||
@@ -39,0 +39,0 @@ p = p.multiply(jubjub.CURVE.h); |
@@ -6,2 +6,3 @@ /*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */ | ||
import { mapToCurveSimpleSWU } from './abstract/weierstrass.js'; | ||
import * as htf from './abstract/hash-to-curve.js'; | ||
// NIST secp256r1 aka P256 | ||
@@ -30,12 +31,13 @@ // https://www.secg.org/sec2-v2.pdf, https://neuromancer.sk/std/nist/P-256 | ||
lowS: false, | ||
mapToCurve: (scalars) => mapSWU(scalars[0]), | ||
htfDefaults: { | ||
DST: 'P256_XMD:SHA-256_SSWU_RO_', | ||
p: Fp.ORDER, | ||
m: 1, | ||
k: 128, | ||
expand: 'xmd', | ||
hash: sha256, | ||
}, | ||
}, sha256); | ||
export const secp256r1 = P256; | ||
const { hashToCurve, encodeToCurve } = htf.hashToCurve(secp256r1.ProjectivePoint, (scalars) => mapSWU(scalars[0]), { | ||
DST: 'P256_XMD:SHA-256_SSWU_RO_', | ||
encodeDST: 'P256_XMD:SHA-256_SSWU_NU_', | ||
p: Fp.ORDER, | ||
m: 1, | ||
k: 128, | ||
expand: 'xmd', | ||
hash: sha256, | ||
}); | ||
export { hashToCurve, encodeToCurve }; |
@@ -6,2 +6,3 @@ /*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */ | ||
import { mapToCurveSimpleSWU } from './abstract/weierstrass.js'; | ||
import * as htf from './abstract/hash-to-curve.js'; | ||
// NIST secp384r1 aka P384 | ||
@@ -35,12 +36,13 @@ // https://www.secg.org/sec2-v2.pdf, https://neuromancer.sk/std/nist/P-384 | ||
lowS: false, | ||
mapToCurve: (scalars) => mapSWU(scalars[0]), | ||
htfDefaults: { | ||
DST: 'P384_XMD:SHA-384_SSWU_RO_', | ||
p: Fp.ORDER, | ||
m: 1, | ||
k: 192, | ||
expand: 'xmd', | ||
hash: sha384, | ||
}, | ||
}, sha384); | ||
export const secp384r1 = P384; | ||
const { hashToCurve, encodeToCurve } = htf.hashToCurve(secp384r1.ProjectivePoint, (scalars) => mapSWU(scalars[0]), { | ||
DST: 'P384_XMD:SHA-384_SSWU_RO_', | ||
encodeDST: 'P384_XMD:SHA-384_SSWU_NU_', | ||
p: Fp.ORDER, | ||
m: 1, | ||
k: 192, | ||
expand: 'xmd', | ||
hash: sha384, | ||
}); | ||
export { hashToCurve, encodeToCurve }; |
@@ -7,2 +7,3 @@ /*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */ | ||
import { mapToCurveSimpleSWU } from './abstract/weierstrass.js'; | ||
import * as htf from './abstract/hash-to-curve.js'; | ||
// NIST secp521r1 aka P521 | ||
@@ -36,4 +37,3 @@ // Note that it's 521, which differs from 512 of its hash function. | ||
lowS: false, | ||
// P521 keys could be 130, 131, 132 bytes - which doesn't play nicely. | ||
// We ensure all keys are 132 bytes. | ||
// P521 keys could be 130, 131, 132 bytes. We normalize to 132 bytes. | ||
// Does not replace validation; invalid keys would still be rejected. | ||
@@ -48,14 +48,15 @@ normalizePrivateKey(key) { | ||
} | ||
return key.padStart(66 * 2, '0'); | ||
return key.padStart(66 * 2, '0'); // ensure it's always 132 bytes | ||
}, | ||
mapToCurve: (scalars) => mapSWU(scalars[0]), | ||
htfDefaults: { | ||
DST: 'P521_XMD:SHA-512_SSWU_RO_', | ||
p: Fp.ORDER, | ||
m: 1, | ||
k: 256, | ||
expand: 'xmd', | ||
hash: sha512, | ||
}, | ||
}, sha512); | ||
export const secp521r1 = P521; | ||
const { hashToCurve, encodeToCurve } = htf.hashToCurve(secp521r1.ProjectivePoint, (scalars) => mapSWU(scalars[0]), { | ||
DST: 'P521_XMD:SHA-512_SSWU_RO_', | ||
encodeDST: 'P521_XMD:SHA-512_SSWU_NU_', | ||
p: Fp.ORDER, | ||
m: 1, | ||
k: 256, | ||
expand: 'xmd', | ||
hash: sha512, | ||
}); | ||
export { hashToCurve, encodeToCurve }; |
@@ -6,5 +6,5 @@ /*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */ | ||
import { mapToCurveSimpleSWU } from './abstract/weierstrass.js'; | ||
import { ensureBytes, concatBytes, hexToBytes, bytesToNumberBE, } from './abstract/utils.js'; | ||
import { ensureBytes, concatBytes, bytesToNumberBE as bytesToNum, numberToBytesBE, } from './abstract/utils.js'; | ||
import { randomBytes } from '@noble/hashes/utils'; | ||
import { isogenyMap } from './abstract/hash-to-curve.js'; | ||
import * as htf from './abstract/hash-to-curve.js'; | ||
/** | ||
@@ -23,6 +23,3 @@ * secp256k1 belongs to Koblitz curves: it has efficiently computable endomorphism. | ||
/** | ||
* Allows to compute square root √y 2x faster. | ||
* To calculate √y, we need to exponentiate it to a very big number: | ||
* `y² = x³ + ax + b; y = y² ^ (p+1)/4` | ||
* We are unwrapping the loop and multiplying it bit-by-bit. | ||
* √n = n^((p+1)/4) for fields p = 3 mod 4. We unwrap the loop and multiply bit-by-bit. | ||
* (P+1n/4n).toString(2) would produce bits [223x 1, 0, 22x 1, 4x 0, 11, 00] | ||
@@ -50,3 +47,3 @@ */ | ||
const root = pow2(t2, _2n, P); | ||
if (!Fp.equals(Fp.square(root), y)) | ||
if (!Fp.eql(Fp.sqr(root), y)) | ||
throw new Error('Cannot find square root'); | ||
@@ -56,36 +53,2 @@ return root; | ||
const Fp = Field(secp256k1P, undefined, undefined, { sqrt: sqrtMod }); | ||
const isoMap = isogenyMap(Fp, [ | ||
// xNum | ||
[ | ||
'0x8e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38daaaaa8c7', | ||
'0x7d3d4c80bc321d5b9f315cea7fd44c5d595d2fc0bf63b92dfff1044f17c6581', | ||
'0x534c328d23f234e6e2a413deca25caece4506144037c40314ecbd0b53d9dd262', | ||
'0x8e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38daaaaa88c', | ||
], | ||
// xDen | ||
[ | ||
'0xd35771193d94918a9ca34ccbb7b640dd86cd409542f8487d9fe6b745781eb49b', | ||
'0xedadc6f64383dc1df7c4b2d51b54225406d36b641f5e41bbc52a56612a8c6d14', | ||
'0x0000000000000000000000000000000000000000000000000000000000000001', // LAST 1 | ||
], | ||
// yNum | ||
[ | ||
'0x4bda12f684bda12f684bda12f684bda12f684bda12f684bda12f684b8e38e23c', | ||
'0xc75e0c32d5cb7c0fa9d0a54b12a0a6d5647ab046d686da6fdffc90fc201d71a3', | ||
'0x29a6194691f91a73715209ef6512e576722830a201be2018a765e85a9ecee931', | ||
'0x2f684bda12f684bda12f684bda12f684bda12f684bda12f684bda12f38e38d84', | ||
], | ||
// yDen | ||
[ | ||
'0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffff93b', | ||
'0x7a06534bb8bdb49fd5e9e6632722c2989467c1bfc8e8d978dfb425d2685c2573', | ||
'0x6484aa716545ca2cf3a70c3fa8fe337e0a3d21162f0d6299a7bf8192bfd2a76f', | ||
'0x0000000000000000000000000000000000000000000000000000000000000001', // LAST 1 | ||
], | ||
].map((i) => i.map((j) => BigInt(j)))); | ||
const mapSWU = mapToCurveSimpleSWU(Fp, { | ||
A: BigInt('0x3f8731abdd661adca08a5558f0f5d272e953d363cb6f0e5d405447c01a444533'), | ||
B: BigInt('1771'), | ||
Z: Fp.create(BigInt('-11')), | ||
}); | ||
export const secp256k1 = createCurve({ | ||
@@ -133,47 +96,9 @@ // Params: a, b | ||
}, | ||
mapToCurve: (scalars) => { | ||
const { x, y } = mapSWU(Fp.create(scalars[0])); | ||
return isoMap(x, y); | ||
}, | ||
htfDefaults: { | ||
DST: 'secp256k1_XMD:SHA-256_SSWU_RO_', | ||
p: Fp.ORDER, | ||
m: 1, | ||
k: 128, | ||
expand: 'xmd', | ||
hash: sha256, | ||
}, | ||
}, sha256); | ||
// Schnorr | ||
// Schnorr signatures are superior to ECDSA from above. | ||
// Below is Schnorr-specific code as per BIP0340. | ||
// https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki | ||
const _0n = BigInt(0); | ||
const numTo32b = secp256k1.utils._bigintToBytes; | ||
const numTo32bStr = secp256k1.utils._bigintToString; | ||
const normalizePrivateKey = secp256k1.utils._normalizePrivateKey; | ||
// TODO: export? | ||
function normalizePublicKey(publicKey) { | ||
if (publicKey instanceof secp256k1.Point) { | ||
publicKey.assertValidity(); | ||
return publicKey; | ||
} | ||
else { | ||
const bytes = ensureBytes(publicKey); | ||
// Schnorr is 32 bytes | ||
if (bytes.length !== 32) | ||
throw new Error('Schnorr pubkeys must be 32 bytes'); | ||
const x = bytesToNumberBE(bytes); | ||
if (!isValidFieldElement(x)) | ||
throw new Error('Point is not on curve'); | ||
const y2 = secp256k1.utils._weierstrassEquation(x); // y² = x³ + ax + b | ||
let y = sqrtMod(y2); // y = y² ^ (p+1)/4 | ||
const isYOdd = (y & _1n) === _1n; | ||
// Schnorr | ||
if (isYOdd) | ||
y = secp256k1.CURVE.Fp.negate(y); | ||
const point = new secp256k1.Point(x, y); | ||
point.assertValidity(); | ||
return point; | ||
} | ||
} | ||
const isWithinCurveOrder = secp256k1.utils._isWithinCurveOrder; | ||
const isValidFieldElement = secp256k1.utils._isValidFieldElement; | ||
const fe = (x) => typeof x === 'bigint' && _0n < x && x < secp256k1P; | ||
const ge = (x) => typeof x === 'bigint' && _0n < x && x < secp256k1N; | ||
const TAGS = { | ||
@@ -186,3 +111,3 @@ challenge: 'BIP0340/challenge', | ||
const TAGGED_HASH_PREFIXES = {}; | ||
export function taggedHash(tag, ...messages) { | ||
function taggedHash(tag, ...messages) { | ||
let tagP = TAGGED_HASH_PREFIXES[tag]; | ||
@@ -197,40 +122,33 @@ if (tagP === undefined) { | ||
const toRawX = (point) => point.toRawBytes(true).slice(1); | ||
// Schnorr signatures are superior to ECDSA from above. | ||
// Below is Schnorr-specific code as per BIP0340. | ||
function schnorrChallengeFinalize(ch) { | ||
return mod(bytesToNumberBE(ch), secp256k1.CURVE.n); | ||
} | ||
// Do we need this at all for Schnorr? | ||
class SchnorrSignature { | ||
constructor(r, s) { | ||
this.r = r; | ||
this.s = s; | ||
this.assertValidity(); | ||
} | ||
static fromHex(hex) { | ||
const bytes = ensureBytes(hex); | ||
const len = 32; // group length | ||
if (bytes.length !== 2 * len) | ||
throw new TypeError(`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); | ||
} | ||
assertValidity() { | ||
const { r, s } = this; | ||
if (!isValidFieldElement(r) || !isWithinCurveOrder(s)) | ||
throw new Error('Invalid signature'); | ||
} | ||
toHex() { | ||
return numTo32bStr(this.r) + numTo32bStr(this.s); | ||
} | ||
toRawBytes() { | ||
return hexToBytes(this.toHex()); | ||
} | ||
} | ||
const numTo32b = (n) => numberToBytesBE(n, 32); | ||
const modN = (x) => mod(x, secp256k1N); | ||
const _Point = secp256k1.ProjectivePoint; | ||
const Gmul = (priv) => _Point.fromPrivateKey(priv); | ||
const GmulAdd = (Q, a, b) => _Point.BASE.multiplyAndAddUnsafe(Q, a, b); | ||
function schnorrGetScalar(priv) { | ||
const point = secp256k1.Point.fromPrivateKey(priv); | ||
const scalar = point.hasEvenY() ? priv : secp256k1.CURVE.n - priv; | ||
// Let d' = int(sk) | ||
// Fail if d' = 0 or d' ≥ n | ||
// Let P = d'⋅G | ||
// Let d = d' if has_even_y(P), otherwise let d = n - d' . | ||
const point = Gmul(priv); | ||
const scalar = point.hasEvenY() ? priv : modN(-priv); | ||
return { point, scalar, x: toRawX(point) }; | ||
} | ||
function lift_x(x) { | ||
if (!fe(x)) | ||
throw new Error('bad x: need 0 < x < p'); // Fail if x ≥ p. | ||
const c = mod(x * x * x + BigInt(7), secp256k1P); // Let c = x³ + 7 mod p. | ||
let y = sqrtMod(c); // Let y = c^(p+1)/4 mod p. | ||
if (y % 2n !== 0n) | ||
y = mod(-y, secp256k1P); // Return the unique point P such that x(P) = x and | ||
const p = new _Point(x, y, _1n); // y(P) = y if y mod 2 = 0 or y(P) = p-y otherwise. | ||
p.assertValidity(); | ||
return p; | ||
} | ||
function challenge(...args) { | ||
return modN(bytesToNum(taggedHash(TAGS.challenge, ...args))); | ||
} | ||
function schnorrGetPublicKey(privateKey) { | ||
return toRawX(Gmul(privateKey)); // Let d' = int(sk). Fail if d' = 0 or d' ≥ n. Return bytes(d'⋅G) | ||
} | ||
/** | ||
@@ -245,19 +163,19 @@ * Synchronously creates Schnorr signature. Improved security: verifies itself before | ||
if (message == null) | ||
throw new TypeError(`sign: Expected valid message, not "${message}"`); | ||
throw new Error(`sign: Expected valid message, not "${message}"`); | ||
const m = ensureBytes(message); | ||
// checks for isWithinCurveOrder | ||
const { x: px, scalar: d } = schnorrGetScalar(normalizePrivateKey(privateKey)); | ||
const rand = ensureBytes(auxRand); | ||
if (rand.length !== 32) | ||
throw new TypeError('sign: Expected 32 bytes of aux randomness'); | ||
const tag = taggedHash; | ||
const t0h = tag(TAGS.aux, rand); | ||
const t = numTo32b(d ^ bytesToNumberBE(t0h)); | ||
const k0h = tag(TAGS.nonce, t, px, m); | ||
const k0 = mod(bytesToNumberBE(k0h), secp256k1.CURVE.n); | ||
if (k0 === _0n) | ||
throw new Error('sign: Creation of signature failed. k is zero'); | ||
const { point: R, x: rx, scalar: k } = schnorrGetScalar(k0); | ||
const e = schnorrChallengeFinalize(tag(TAGS.challenge, rx, px, m)); | ||
const sig = new SchnorrSignature(R.x, mod(k + e * d, secp256k1.CURVE.n)).toRawBytes(); | ||
const { x: px, scalar: d } = schnorrGetScalar(bytesToNum(ensureBytes(privateKey, 32))); | ||
const a = ensureBytes(auxRand, 32); // Auxiliary random data a: a 32-byte array | ||
// TODO: replace with proper xor? | ||
const t = numTo32b(d ^ bytesToNum(taggedHash(TAGS.aux, a))); // Let t be the byte-wise xor of bytes(d) and hash/aux(a) | ||
const rand = taggedHash(TAGS.nonce, t, px, m); // Let rand = hash/nonce(t || bytes(P) || m) | ||
const k_ = modN(bytesToNum(rand)); // Let k' = int(rand) mod n | ||
if (k_ === _0n) | ||
throw new Error('sign failed: k is zero'); // Fail if k' = 0. | ||
const { point: R, x: rx, scalar: k } = schnorrGetScalar(k_); // Let R = k'⋅G. | ||
const e = challenge(rx, px, m); // Let e = int(hash/challenge(bytes(R) || bytes(P) || m)) mod n. | ||
const sig = new Uint8Array(64); // Let sig = bytes(R) || bytes((k + ed) mod n). | ||
sig.set(numTo32b(R.px), 0); | ||
sig.set(numTo32b(modN(k + e * d)), 32); | ||
// If Verify(bytes(P), m, sig) (see below) returns failure, abort | ||
if (!schnorrVerify(sig, m, px)) | ||
@@ -272,17 +190,16 @@ throw new Error('sign: Invalid signature produced'); | ||
try { | ||
const raw = signature instanceof SchnorrSignature; | ||
const sig = raw ? signature : SchnorrSignature.fromHex(signature); | ||
if (raw) | ||
sig.assertValidity(); // just in case | ||
const { r, s } = sig; | ||
const P = lift_x(bytesToNum(ensureBytes(publicKey, 32))); // P = lift_x(int(pk)); fail if that fails | ||
const sig = ensureBytes(signature, 64); | ||
const r = bytesToNum(sig.subarray(0, 32)); // Let r = int(sig[0:32]); fail if r ≥ p. | ||
if (!fe(r)) | ||
return false; | ||
const s = bytesToNum(sig.subarray(32, 64)); // Let s = int(sig[32:64]); fail if s ≥ n. | ||
if (!ge(s)) | ||
return false; | ||
const m = ensureBytes(message); | ||
const P = normalizePublicKey(publicKey); | ||
const e = schnorrChallengeFinalize(taggedHash(TAGS.challenge, numTo32b(r), toRawX(P), m)); | ||
// Finalize | ||
// R = s⋅G - e⋅P | ||
// -eP == (n-e)P | ||
const R = secp256k1.Point.BASE.multiplyAndAddUnsafe(P, normalizePrivateKey(s), mod(-e, secp256k1.CURVE.n)); | ||
if (!R || !R.hasEvenY() || R.x !== r) | ||
return false; | ||
return true; | ||
const e = challenge(numTo32b(r), toRawX(P), m); // int(challenge(bytes(r)||bytes(P)||m)) mod n | ||
const R = GmulAdd(P, s, modN(-e)); // R = s⋅G - e⋅P | ||
if (!R || !R.hasEvenY() || R.toAffine().x !== r) | ||
return false; // -eP == (n-e)P | ||
return true; // Fail if is_infinite(R) / not has_even_y(R) / x(R) ≠ r. | ||
} | ||
@@ -294,7 +211,54 @@ catch (error) { | ||
export const schnorr = { | ||
Signature: SchnorrSignature, | ||
// Schnorr's pubkey is just `x` of Point (BIP340) | ||
getPublicKey: (privateKey) => toRawX(secp256k1.Point.fromPrivateKey(privateKey)), | ||
getPublicKey: schnorrGetPublicKey, | ||
sign: schnorrSign, | ||
verify: schnorrVerify, | ||
utils: { lift_x, int: bytesToNum, taggedHash }, | ||
}; | ||
const isoMap = htf.isogenyMap(Fp, [ | ||
// xNum | ||
[ | ||
'0x8e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38daaaaa8c7', | ||
'0x7d3d4c80bc321d5b9f315cea7fd44c5d595d2fc0bf63b92dfff1044f17c6581', | ||
'0x534c328d23f234e6e2a413deca25caece4506144037c40314ecbd0b53d9dd262', | ||
'0x8e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38daaaaa88c', | ||
], | ||
// xDen | ||
[ | ||
'0xd35771193d94918a9ca34ccbb7b640dd86cd409542f8487d9fe6b745781eb49b', | ||
'0xedadc6f64383dc1df7c4b2d51b54225406d36b641f5e41bbc52a56612a8c6d14', | ||
'0x0000000000000000000000000000000000000000000000000000000000000001', // LAST 1 | ||
], | ||
// yNum | ||
[ | ||
'0x4bda12f684bda12f684bda12f684bda12f684bda12f684bda12f684b8e38e23c', | ||
'0xc75e0c32d5cb7c0fa9d0a54b12a0a6d5647ab046d686da6fdffc90fc201d71a3', | ||
'0x29a6194691f91a73715209ef6512e576722830a201be2018a765e85a9ecee931', | ||
'0x2f684bda12f684bda12f684bda12f684bda12f684bda12f684bda12f38e38d84', | ||
], | ||
// yDen | ||
[ | ||
'0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffff93b', | ||
'0x7a06534bb8bdb49fd5e9e6632722c2989467c1bfc8e8d978dfb425d2685c2573', | ||
'0x6484aa716545ca2cf3a70c3fa8fe337e0a3d21162f0d6299a7bf8192bfd2a76f', | ||
'0x0000000000000000000000000000000000000000000000000000000000000001', // LAST 1 | ||
], | ||
].map((i) => i.map((j) => BigInt(j)))); | ||
const mapSWU = mapToCurveSimpleSWU(Fp, { | ||
A: BigInt('0x3f8731abdd661adca08a5558f0f5d272e953d363cb6f0e5d405447c01a444533'), | ||
B: BigInt('1771'), | ||
Z: Fp.create(BigInt('-11')), | ||
}); | ||
const { hashToCurve, encodeToCurve } = htf.hashToCurve(secp256k1.ProjectivePoint, (scalars) => { | ||
const { x, y } = mapSWU(Fp.create(scalars[0])); | ||
return isoMap(x, y); | ||
}, { | ||
DST: 'secp256k1_XMD:SHA-256_SSWU_RO_', | ||
encodeDST: 'secp256k1_XMD:SHA-256_SSWU_NU_', | ||
p: Fp.ORDER, | ||
m: 1, | ||
k: 128, | ||
expand: 'xmd', | ||
hash: sha256, | ||
}); | ||
export { hashToCurve, encodeToCurve }; |
@@ -6,4 +6,6 @@ /*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */ | ||
import * as cutils from './abstract/utils.js'; | ||
import { Fp } from './abstract/modular.js'; | ||
import { Fp, mod, validateField } from './abstract/modular.js'; | ||
import { getHash } from './_shortw_utils.js'; | ||
import * as poseidon from './abstract/poseidon.js'; | ||
import { utf8ToBytes } from '@noble/hashes/utils'; | ||
// Stark-friendly elliptic curve | ||
@@ -13,2 +15,11 @@ // https://docs.starkware.co/starkex/stark-curve.html | ||
const nBitLength = 252; | ||
// Copy-pasted from weierstrass.ts | ||
function bits2int(bytes) { | ||
const delta = bytes.length * 8 - nBitLength; | ||
const num = cutils.bytesToNumberBE(bytes); | ||
return delta > 0 ? num >> BigInt(delta) : num; | ||
} | ||
function bits2int_modN(bytes) { | ||
return mod(bits2int(bytes), CURVE_N); | ||
} | ||
export const starkCurve = weierstrass({ | ||
@@ -31,23 +42,18 @@ // Params: a, b | ||
...getHash(sha256), | ||
truncateHash: (hash, truncateOnly = false) => { | ||
// TODO: cleanup, ugly code | ||
// Fix truncation | ||
if (!truncateOnly) { | ||
let hashS = bytesToNumber0x(hash).toString(16); | ||
if (hashS.length === 63) { | ||
hashS += '0'; | ||
hash = hexToBytes0x(hashS); | ||
} | ||
// Custom truncation routines for stark curve | ||
bits2int: (bytes) => { | ||
while (bytes[0] === 0) | ||
bytes = bytes.subarray(1); | ||
return bits2int(bytes); | ||
}, | ||
bits2int_modN: (bytes) => { | ||
let hashS = cutils.bytesToNumberBE(bytes).toString(16); | ||
if (hashS.length === 63) { | ||
hashS += '0'; | ||
bytes = hexToBytes0x(hashS); | ||
} | ||
// Truncate zero bytes on left (compat with elliptic) | ||
while (hash[0] === 0) | ||
hash = hash.subarray(1); | ||
const byteLength = hash.length; | ||
const delta = byteLength * 8 - nBitLength; // size of curve.n (252 bits) | ||
let h = hash.length ? bytesToNumber0x(hash) : 0n; | ||
if (delta > 0) | ||
h = h >> BigInt(delta); | ||
if (!truncateOnly && h >= CURVE_N) | ||
h -= CURVE_N; | ||
return h; | ||
while (bytes[0] === 0) | ||
bytes = bytes.subarray(1); | ||
return bits2int_modN(bytes); | ||
}, | ||
@@ -58,3 +64,3 @@ }); | ||
if (typeof hex !== 'string') { | ||
throw new TypeError('hexToBytes: expected string, got ' + typeof hex); | ||
throw new Error('hexToBytes: expected string, got ' + typeof hex); | ||
} | ||
@@ -79,3 +85,3 @@ hex = strip0x(hex); | ||
if (typeof hex !== 'string') { | ||
throw new TypeError('hexToNumber: expected string, got ' + typeof hex); | ||
throw new Error('hexToNumber: expected string, got ' + typeof hex); | ||
} | ||
@@ -95,3 +101,3 @@ // Big Endian | ||
function normalizePrivateKey(privKey) { | ||
return cutils.bytesToHex(ensureBytes0x(privKey)).padStart(32 * 2, '0'); | ||
return cutils.bytesToHex(ensureBytes0x(privKey)).padStart(64, '0'); | ||
} | ||
@@ -113,5 +119,5 @@ function getPublicKey0x(privKey, isCompressed) { | ||
} | ||
const { CURVE, Point, ProjectivePoint, Signature } = starkCurve; | ||
const { CURVE, ProjectivePoint, Signature } = starkCurve; | ||
export const utils = starkCurve.utils; | ||
export { CURVE, Point, Signature, ProjectivePoint, getPublicKey0x as getPublicKey, getSharedSecret0x as getSharedSecret, sign0x as sign, verify0x as verify, }; | ||
export { CURVE, Signature, ProjectivePoint, getPublicKey0x as getPublicKey, getSharedSecret0x as getSharedSecret, sign0x as sign, verify0x as verify, }; | ||
const stripLeadingZeros = (s) => s.replace(/^0+/gm, ''); | ||
@@ -126,3 +132,3 @@ export const bytesToHexEth = (uint8a) => `0x${stripLeadingZeros(cutils.bytesToHex(uint8a))}`; | ||
indexHex = '0' + indexHex; | ||
return bytesToNumber0x(sha256(cutils.concatBytes(key, hexToBytes0x(indexHex)))); | ||
return sha256Num(cutils.concatBytes(key, hexToBytes0x(indexHex))); | ||
} | ||
@@ -132,4 +138,3 @@ export function grindKey(seed) { | ||
const sha256mask = 2n ** 256n; | ||
const Fn = Fp(CURVE.n); | ||
const limit = sha256mask - Fn.create(sha256mask); | ||
const limit = sha256mask - mod(sha256mask, CURVE_N); | ||
for (let i = 0;; i++) { | ||
@@ -139,3 +144,3 @@ const key = hashKeyWithIndex(_seed, i); | ||
if (key < limit) | ||
return Fn.create(key).toString(16); | ||
return mod(key, CURVE_N).toString(16); | ||
} | ||
@@ -155,4 +160,4 @@ } | ||
export function getAccountPath(layer, application, ethereumAddress, index) { | ||
const layerNum = int31(bytesToNumber0x(sha256(layer))); | ||
const applicationNum = int31(bytesToNumber0x(sha256(application))); | ||
const layerNum = int31(sha256Num(layer)); | ||
const applicationNum = int31(sha256Num(application)); | ||
const eth = hexToNumber0x(ethereumAddress); | ||
@@ -163,10 +168,10 @@ return `m/2645'/${layerNum}'/${applicationNum}'/${int31(eth)}'/${int31(eth >> 31n)}'/${index}`; | ||
const PEDERSEN_POINTS_AFFINE = [ | ||
new Point(2089986280348253421170679821480865132823066470938446095505822317253594081284n, 1713931329540660377023406109199410414810705867260802078187082345529207694986n), | ||
new Point(996781205833008774514500082376783249102396023663454813447423147977397232763n, 1668503676786377725805489344771023921079126552019160156920634619255970485781n), | ||
new Point(2251563274489750535117886426533222435294046428347329203627021249169616184184n, 1798716007562728905295480679789526322175868328062420237419143593021674992973n), | ||
new Point(2138414695194151160943305727036575959195309218611738193261179310511854807447n, 113410276730064486255102093846540133784865286929052426931474106396135072156n), | ||
new Point(2379962749567351885752724891227938183011949129833673362440656643086021394946n, 776496453633298175483985398648758586525933812536653089401905292063708816422n), | ||
new ProjectivePoint(2089986280348253421170679821480865132823066470938446095505822317253594081284n, 1713931329540660377023406109199410414810705867260802078187082345529207694986n, 1n), | ||
new ProjectivePoint(996781205833008774514500082376783249102396023663454813447423147977397232763n, 1668503676786377725805489344771023921079126552019160156920634619255970485781n, 1n), | ||
new ProjectivePoint(2251563274489750535117886426533222435294046428347329203627021249169616184184n, 1798716007562728905295480679789526322175868328062420237419143593021674992973n, 1n), | ||
new ProjectivePoint(2138414695194151160943305727036575959195309218611738193261179310511854807447n, 113410276730064486255102093846540133784865286929052426931474106396135072156n, 1n), | ||
new ProjectivePoint(2379962749567351885752724891227938183011949129833673362440656643086021394946n, 776496453633298175483985398648758586525933812536653089401905292063708816422n, 1n), | ||
]; | ||
// for (const p of PEDERSEN_POINTS) p._setWindowSize(8); | ||
const PEDERSEN_POINTS = PEDERSEN_POINTS_AFFINE.map(ProjectivePoint.fromAffine); | ||
const PEDERSEN_POINTS = PEDERSEN_POINTS_AFFINE; | ||
function pedersenPrecompute(p1, p2) { | ||
@@ -210,3 +215,3 @@ const out = []; | ||
const pt = constants[j]; | ||
if (pt.x === point.x) | ||
if (pt.px === point.px) | ||
throw new Error('Same point'); | ||
@@ -224,3 +229,3 @@ if ((x & 1n) !== 0n) | ||
point = pedersenSingle(point, y, PEDERSEN_POINTS2); | ||
return bytesToHexEth(point.toAffine().toRawBytes(true).slice(1)); | ||
return bytesToHexEth(point.toRawBytes(true).slice(1)); | ||
} | ||
@@ -238,3 +243,62 @@ export function hashChain(data, fn = pedersen) { | ||
export const computeHashOnElements = (data, fn = pedersen) => [0, ...data, data.length].reduce((x, y) => fn(x, y)); | ||
const MASK_250 = 2n ** 250n - 1n; | ||
const MASK_250 = cutils.bitMask(250); | ||
export const keccak = (data) => bytesToNumber0x(keccak_256(data)) & MASK_250; | ||
const sha256Num = (data) => cutils.bytesToNumberBE(sha256(data)); | ||
// Poseidon hash | ||
export const Fp253 = Fp(BigInt('14474011154664525231415395255581126252639794253786371766033694892385558855681')); // 2^253 + 2^199 + 1 | ||
export const Fp251 = Fp(BigInt('3618502788666131213697322783095070105623107215331596699973092056135872020481')); // 2^251 + 17 * 2^192 + 1 | ||
function poseidonRoundConstant(Fp, name, idx) { | ||
const val = Fp.fromBytes(sha256(utf8ToBytes(`${name}${idx}`))); | ||
return Fp.create(val); | ||
} | ||
// NOTE: doesn't check eiginvalues and possible can create unsafe matrix. But any filtration here will break compatibility with starknet | ||
// Please use only if you really know what you doing. | ||
// https://eprint.iacr.org/2019/458.pdf Section 2.3 (Avoiding Insecure Matrices) | ||
export function _poseidonMDS(Fp, name, m, attempt = 0) { | ||
const x_values = []; | ||
const y_values = []; | ||
for (let i = 0; i < m; i++) { | ||
x_values.push(poseidonRoundConstant(Fp, `${name}x`, attempt * m + i)); | ||
y_values.push(poseidonRoundConstant(Fp, `${name}y`, attempt * m + i)); | ||
} | ||
if (new Set([...x_values, ...y_values]).size !== 2 * m) | ||
throw new Error('X and Y values are not distinct'); | ||
return x_values.map((x) => y_values.map((y) => Fp.inv(Fp.sub(x, y)))); | ||
} | ||
const MDS_SMALL = [ | ||
[3, 1, 1], | ||
[1, -1, 1], | ||
[1, 1, -2], | ||
].map((i) => i.map(BigInt)); | ||
export function poseidonBasic(opts, mds) { | ||
validateField(opts.Fp); | ||
if (!Number.isSafeInteger(opts.rate) || !Number.isSafeInteger(opts.capacity)) | ||
throw new Error(`Wrong poseidon opts: ${opts}`); | ||
const m = opts.rate + opts.capacity; | ||
const rounds = opts.roundsFull + opts.roundsPartial; | ||
const roundConstants = []; | ||
for (let i = 0; i < rounds; i++) { | ||
const row = []; | ||
for (let j = 0; j < m; j++) | ||
row.push(poseidonRoundConstant(opts.Fp, 'Hades', m * i + j)); | ||
roundConstants.push(row); | ||
} | ||
return poseidon.poseidon({ | ||
...opts, | ||
t: m, | ||
sboxPower: 3, | ||
reversePartialPowIdx: true, | ||
mds, | ||
roundConstants, | ||
}); | ||
} | ||
export function poseidonCreate(opts, mdsAttempt = 0) { | ||
const m = opts.rate + opts.capacity; | ||
if (!Number.isSafeInteger(mdsAttempt)) | ||
throw new Error(`Wrong mdsAttempt=${mdsAttempt}`); | ||
return poseidonBasic(opts, _poseidonMDS(opts.Fp, 'HadesMDS', m, mdsAttempt)); | ||
} | ||
export const poseidonSmall = poseidonBasic({ Fp: Fp251, rate: 2, capacity: 1, roundsFull: 8, roundsPartial: 83 }, MDS_SMALL); | ||
export function poseidonHash(x, y, fn = poseidonSmall) { | ||
return fn([x, y, 2n])[0]; | ||
} |
@@ -7,3 +7,3 @@ /** | ||
export declare const jubjub: import("./abstract/edwards.js").CurveFn; | ||
export declare function groupHash(tag: Uint8Array, personalization: Uint8Array): import("./abstract/edwards.js").ExtendedPointType; | ||
export declare function findGroupHash(m: Uint8Array, personalization: Uint8Array): import("./abstract/edwards.js").ExtendedPointType; | ||
export declare function groupHash(tag: Uint8Array, personalization: Uint8Array): import("./abstract/edwards.js").ExtPointType; | ||
export declare function findGroupHash(m: Uint8Array, personalization: Uint8Array): import("./abstract/edwards.js").ExtPointType; |
@@ -39,3 +39,3 @@ "use strict"; | ||
// NOTE: returns ExtendedPoint, in case it will be multiplied later | ||
let p = exports.jubjub.ExtendedPoint.fromAffine(exports.jubjub.Point.fromHex(h.digest())); | ||
let p = exports.jubjub.ExtendedPoint.fromHex(h.digest()); | ||
// NOTE: cannot replace with isSmallOrder, returns Point*8 | ||
@@ -42,0 +42,0 @@ p = p.multiply(exports.jubjub.CURVE.h); |
@@ -26,9 +26,4 @@ export declare const P192: Readonly<{ | ||
} | undefined; | ||
readonly isTorsionFree?: ((c: import("./abstract/weierstrass.js").ProjectiveConstructor<bigint>, point: import("./abstract/weierstrass.js").ProjectivePointType<bigint>) => boolean) | undefined; | ||
readonly clearCofactor?: ((c: import("./abstract/weierstrass.js").ProjectiveConstructor<bigint>, point: import("./abstract/weierstrass.js").ProjectivePointType<bigint>) => import("./abstract/weierstrass.js").ProjectivePointType<bigint>) | undefined; | ||
readonly htfDefaults?: import("./abstract/hash-to-curve.js").htfOpts | undefined; | ||
readonly mapToCurve?: ((scalar: bigint[]) => { | ||
x: bigint; | ||
y: bigint; | ||
}) | undefined; | ||
readonly isTorsionFree?: ((c: import("./abstract/weierstrass.js").ProjConstructor<bigint>, point: import("./abstract/weierstrass.js").ProjPointType<bigint>) => boolean) | undefined; | ||
readonly clearCofactor?: ((c: import("./abstract/weierstrass.js").ProjConstructor<bigint>, point: import("./abstract/weierstrass.js").ProjPointType<bigint>) => import("./abstract/weierstrass.js").ProjPointType<bigint>) | undefined; | ||
lowS: boolean; | ||
@@ -38,22 +33,16 @@ readonly hash: import("./abstract/utils.js").CHash; | ||
readonly randomBytes: (bytesLength?: number | undefined) => Uint8Array; | ||
readonly truncateHash?: ((hash: Uint8Array, truncateOnly?: boolean | undefined) => bigint) | undefined; | ||
readonly bits2int?: ((bytes: Uint8Array) => bigint) | undefined; | ||
readonly bits2int_modN?: ((bytes: Uint8Array) => bigint) | undefined; | ||
}>; | ||
getPublicKey: (privateKey: import("./abstract/utils.js").PrivKey, isCompressed?: boolean | undefined) => Uint8Array; | ||
getSharedSecret: (privateA: import("./abstract/utils.js").PrivKey, publicB: import("./abstract/weierstrass.js").PubKey, isCompressed?: boolean | undefined) => Uint8Array; | ||
getSharedSecret: (privateA: import("./abstract/utils.js").PrivKey, publicB: import("./abstract/utils.js").Hex, isCompressed?: boolean | undefined) => Uint8Array; | ||
sign: (msgHash: import("./abstract/utils.js").Hex, privKey: import("./abstract/utils.js").PrivKey, opts?: import("./abstract/weierstrass.js").SignOpts | undefined) => import("./abstract/weierstrass.js").SignatureType; | ||
signUnhashed: (msg: Uint8Array, privKey: import("./abstract/utils.js").PrivKey, opts?: import("./abstract/weierstrass.js").SignOpts | undefined) => import("./abstract/weierstrass.js").SignatureType; | ||
verify: (signature: import("./abstract/utils.js").Hex | import("./abstract/weierstrass.js").SignatureType, msgHash: import("./abstract/utils.js").Hex, publicKey: import("./abstract/weierstrass.js").PubKey, opts?: { | ||
lowS?: boolean | undefined; | ||
} | undefined) => boolean; | ||
Point: import("./abstract/weierstrass.js").PointConstructor<bigint>; | ||
ProjectivePoint: import("./abstract/weierstrass.js").ProjectiveConstructor<bigint>; | ||
verify: (signature: import("./abstract/utils.js").Hex | { | ||
r: bigint; | ||
s: bigint; | ||
}, msgHash: import("./abstract/utils.js").Hex, publicKey: import("./abstract/utils.js").Hex, opts?: import("./abstract/weierstrass.js").VerOpts | undefined) => boolean; | ||
ProjectivePoint: import("./abstract/weierstrass.js").ProjConstructor<bigint>; | ||
Signature: import("./abstract/weierstrass.js").SignatureConstructor; | ||
utils: { | ||
_bigintToBytes: (num: bigint) => Uint8Array; | ||
_bigintToString: (num: bigint) => string; | ||
_normalizePrivateKey: (key: import("./abstract/utils.js").PrivKey) => bigint; | ||
_normalizePublicKey: (publicKey: import("./abstract/weierstrass.js").PubKey) => import("./abstract/weierstrass.js").PointType<bigint>; | ||
_isWithinCurveOrder: (num: bigint) => boolean; | ||
_isValidFieldElement: (num: bigint) => boolean; | ||
_weierstrassEquation: (x: bigint) => bigint; | ||
isValidPrivateKey(privateKey: import("./abstract/utils.js").PrivKey): boolean; | ||
@@ -89,9 +78,4 @@ hashToPrivateKey: (hash: import("./abstract/utils.js").Hex) => Uint8Array; | ||
} | undefined; | ||
readonly isTorsionFree?: ((c: import("./abstract/weierstrass.js").ProjectiveConstructor<bigint>, point: import("./abstract/weierstrass.js").ProjectivePointType<bigint>) => boolean) | undefined; | ||
readonly clearCofactor?: ((c: import("./abstract/weierstrass.js").ProjectiveConstructor<bigint>, point: import("./abstract/weierstrass.js").ProjectivePointType<bigint>) => import("./abstract/weierstrass.js").ProjectivePointType<bigint>) | undefined; | ||
readonly htfDefaults?: import("./abstract/hash-to-curve.js").htfOpts | undefined; | ||
readonly mapToCurve?: ((scalar: bigint[]) => { | ||
x: bigint; | ||
y: bigint; | ||
}) | undefined; | ||
readonly isTorsionFree?: ((c: import("./abstract/weierstrass.js").ProjConstructor<bigint>, point: import("./abstract/weierstrass.js").ProjPointType<bigint>) => boolean) | undefined; | ||
readonly clearCofactor?: ((c: import("./abstract/weierstrass.js").ProjConstructor<bigint>, point: import("./abstract/weierstrass.js").ProjPointType<bigint>) => import("./abstract/weierstrass.js").ProjPointType<bigint>) | undefined; | ||
lowS: boolean; | ||
@@ -101,22 +85,16 @@ readonly hash: import("./abstract/utils.js").CHash; | ||
readonly randomBytes: (bytesLength?: number | undefined) => Uint8Array; | ||
readonly truncateHash?: ((hash: Uint8Array, truncateOnly?: boolean | undefined) => bigint) | undefined; | ||
readonly bits2int?: ((bytes: Uint8Array) => bigint) | undefined; | ||
readonly bits2int_modN?: ((bytes: Uint8Array) => bigint) | undefined; | ||
}>; | ||
getPublicKey: (privateKey: import("./abstract/utils.js").PrivKey, isCompressed?: boolean | undefined) => Uint8Array; | ||
getSharedSecret: (privateA: import("./abstract/utils.js").PrivKey, publicB: import("./abstract/weierstrass.js").PubKey, isCompressed?: boolean | undefined) => Uint8Array; | ||
getSharedSecret: (privateA: import("./abstract/utils.js").PrivKey, publicB: import("./abstract/utils.js").Hex, isCompressed?: boolean | undefined) => Uint8Array; | ||
sign: (msgHash: import("./abstract/utils.js").Hex, privKey: import("./abstract/utils.js").PrivKey, opts?: import("./abstract/weierstrass.js").SignOpts | undefined) => import("./abstract/weierstrass.js").SignatureType; | ||
signUnhashed: (msg: Uint8Array, privKey: import("./abstract/utils.js").PrivKey, opts?: import("./abstract/weierstrass.js").SignOpts | undefined) => import("./abstract/weierstrass.js").SignatureType; | ||
verify: (signature: import("./abstract/utils.js").Hex | import("./abstract/weierstrass.js").SignatureType, msgHash: import("./abstract/utils.js").Hex, publicKey: import("./abstract/weierstrass.js").PubKey, opts?: { | ||
lowS?: boolean | undefined; | ||
} | undefined) => boolean; | ||
Point: import("./abstract/weierstrass.js").PointConstructor<bigint>; | ||
ProjectivePoint: import("./abstract/weierstrass.js").ProjectiveConstructor<bigint>; | ||
verify: (signature: import("./abstract/utils.js").Hex | { | ||
r: bigint; | ||
s: bigint; | ||
}, msgHash: import("./abstract/utils.js").Hex, publicKey: import("./abstract/utils.js").Hex, opts?: import("./abstract/weierstrass.js").VerOpts | undefined) => boolean; | ||
ProjectivePoint: import("./abstract/weierstrass.js").ProjConstructor<bigint>; | ||
Signature: import("./abstract/weierstrass.js").SignatureConstructor; | ||
utils: { | ||
_bigintToBytes: (num: bigint) => Uint8Array; | ||
_bigintToString: (num: bigint) => string; | ||
_normalizePrivateKey: (key: import("./abstract/utils.js").PrivKey) => bigint; | ||
_normalizePublicKey: (publicKey: import("./abstract/weierstrass.js").PubKey) => import("./abstract/weierstrass.js").PointType<bigint>; | ||
_isWithinCurveOrder: (num: bigint) => boolean; | ||
_isValidFieldElement: (num: bigint) => boolean; | ||
_weierstrassEquation: (x: bigint) => bigint; | ||
isValidPrivateKey(privateKey: import("./abstract/utils.js").PrivKey): boolean; | ||
@@ -123,0 +101,0 @@ hashToPrivateKey: (hash: import("./abstract/utils.js").Hex) => Uint8Array; |
@@ -26,9 +26,4 @@ export declare const P224: Readonly<{ | ||
} | undefined; | ||
readonly isTorsionFree?: ((c: import("./abstract/weierstrass.js").ProjectiveConstructor<bigint>, point: import("./abstract/weierstrass.js").ProjectivePointType<bigint>) => boolean) | undefined; | ||
readonly clearCofactor?: ((c: import("./abstract/weierstrass.js").ProjectiveConstructor<bigint>, point: import("./abstract/weierstrass.js").ProjectivePointType<bigint>) => import("./abstract/weierstrass.js").ProjectivePointType<bigint>) | undefined; | ||
readonly htfDefaults?: import("./abstract/hash-to-curve.js").htfOpts | undefined; | ||
readonly mapToCurve?: ((scalar: bigint[]) => { | ||
x: bigint; | ||
y: bigint; | ||
}) | undefined; | ||
readonly isTorsionFree?: ((c: import("./abstract/weierstrass.js").ProjConstructor<bigint>, point: import("./abstract/weierstrass.js").ProjPointType<bigint>) => boolean) | undefined; | ||
readonly clearCofactor?: ((c: import("./abstract/weierstrass.js").ProjConstructor<bigint>, point: import("./abstract/weierstrass.js").ProjPointType<bigint>) => import("./abstract/weierstrass.js").ProjPointType<bigint>) | undefined; | ||
lowS: boolean; | ||
@@ -38,22 +33,16 @@ readonly hash: import("./abstract/utils.js").CHash; | ||
readonly randomBytes: (bytesLength?: number | undefined) => Uint8Array; | ||
readonly truncateHash?: ((hash: Uint8Array, truncateOnly?: boolean | undefined) => bigint) | undefined; | ||
readonly bits2int?: ((bytes: Uint8Array) => bigint) | undefined; | ||
readonly bits2int_modN?: ((bytes: Uint8Array) => bigint) | undefined; | ||
}>; | ||
getPublicKey: (privateKey: import("./abstract/utils.js").PrivKey, isCompressed?: boolean | undefined) => Uint8Array; | ||
getSharedSecret: (privateA: import("./abstract/utils.js").PrivKey, publicB: import("./abstract/weierstrass.js").PubKey, isCompressed?: boolean | undefined) => Uint8Array; | ||
getSharedSecret: (privateA: import("./abstract/utils.js").PrivKey, publicB: import("./abstract/utils.js").Hex, isCompressed?: boolean | undefined) => Uint8Array; | ||
sign: (msgHash: import("./abstract/utils.js").Hex, privKey: import("./abstract/utils.js").PrivKey, opts?: import("./abstract/weierstrass.js").SignOpts | undefined) => import("./abstract/weierstrass.js").SignatureType; | ||
signUnhashed: (msg: Uint8Array, privKey: import("./abstract/utils.js").PrivKey, opts?: import("./abstract/weierstrass.js").SignOpts | undefined) => import("./abstract/weierstrass.js").SignatureType; | ||
verify: (signature: import("./abstract/utils.js").Hex | import("./abstract/weierstrass.js").SignatureType, msgHash: import("./abstract/utils.js").Hex, publicKey: import("./abstract/weierstrass.js").PubKey, opts?: { | ||
lowS?: boolean | undefined; | ||
} | undefined) => boolean; | ||
Point: import("./abstract/weierstrass.js").PointConstructor<bigint>; | ||
ProjectivePoint: import("./abstract/weierstrass.js").ProjectiveConstructor<bigint>; | ||
verify: (signature: import("./abstract/utils.js").Hex | { | ||
r: bigint; | ||
s: bigint; | ||
}, msgHash: import("./abstract/utils.js").Hex, publicKey: import("./abstract/utils.js").Hex, opts?: import("./abstract/weierstrass.js").VerOpts | undefined) => boolean; | ||
ProjectivePoint: import("./abstract/weierstrass.js").ProjConstructor<bigint>; | ||
Signature: import("./abstract/weierstrass.js").SignatureConstructor; | ||
utils: { | ||
_bigintToBytes: (num: bigint) => Uint8Array; | ||
_bigintToString: (num: bigint) => string; | ||
_normalizePrivateKey: (key: import("./abstract/utils.js").PrivKey) => bigint; | ||
_normalizePublicKey: (publicKey: import("./abstract/weierstrass.js").PubKey) => import("./abstract/weierstrass.js").PointType<bigint>; | ||
_isWithinCurveOrder: (num: bigint) => boolean; | ||
_isValidFieldElement: (num: bigint) => boolean; | ||
_weierstrassEquation: (x: bigint) => bigint; | ||
isValidPrivateKey(privateKey: import("./abstract/utils.js").PrivKey): boolean; | ||
@@ -89,9 +78,4 @@ hashToPrivateKey: (hash: import("./abstract/utils.js").Hex) => Uint8Array; | ||
} | undefined; | ||
readonly isTorsionFree?: ((c: import("./abstract/weierstrass.js").ProjectiveConstructor<bigint>, point: import("./abstract/weierstrass.js").ProjectivePointType<bigint>) => boolean) | undefined; | ||
readonly clearCofactor?: ((c: import("./abstract/weierstrass.js").ProjectiveConstructor<bigint>, point: import("./abstract/weierstrass.js").ProjectivePointType<bigint>) => import("./abstract/weierstrass.js").ProjectivePointType<bigint>) | undefined; | ||
readonly htfDefaults?: import("./abstract/hash-to-curve.js").htfOpts | undefined; | ||
readonly mapToCurve?: ((scalar: bigint[]) => { | ||
x: bigint; | ||
y: bigint; | ||
}) | undefined; | ||
readonly isTorsionFree?: ((c: import("./abstract/weierstrass.js").ProjConstructor<bigint>, point: import("./abstract/weierstrass.js").ProjPointType<bigint>) => boolean) | undefined; | ||
readonly clearCofactor?: ((c: import("./abstract/weierstrass.js").ProjConstructor<bigint>, point: import("./abstract/weierstrass.js").ProjPointType<bigint>) => import("./abstract/weierstrass.js").ProjPointType<bigint>) | undefined; | ||
lowS: boolean; | ||
@@ -101,22 +85,16 @@ readonly hash: import("./abstract/utils.js").CHash; | ||
readonly randomBytes: (bytesLength?: number | undefined) => Uint8Array; | ||
readonly truncateHash?: ((hash: Uint8Array, truncateOnly?: boolean | undefined) => bigint) | undefined; | ||
readonly bits2int?: ((bytes: Uint8Array) => bigint) | undefined; | ||
readonly bits2int_modN?: ((bytes: Uint8Array) => bigint) | undefined; | ||
}>; | ||
getPublicKey: (privateKey: import("./abstract/utils.js").PrivKey, isCompressed?: boolean | undefined) => Uint8Array; | ||
getSharedSecret: (privateA: import("./abstract/utils.js").PrivKey, publicB: import("./abstract/weierstrass.js").PubKey, isCompressed?: boolean | undefined) => Uint8Array; | ||
getSharedSecret: (privateA: import("./abstract/utils.js").PrivKey, publicB: import("./abstract/utils.js").Hex, isCompressed?: boolean | undefined) => Uint8Array; | ||
sign: (msgHash: import("./abstract/utils.js").Hex, privKey: import("./abstract/utils.js").PrivKey, opts?: import("./abstract/weierstrass.js").SignOpts | undefined) => import("./abstract/weierstrass.js").SignatureType; | ||
signUnhashed: (msg: Uint8Array, privKey: import("./abstract/utils.js").PrivKey, opts?: import("./abstract/weierstrass.js").SignOpts | undefined) => import("./abstract/weierstrass.js").SignatureType; | ||
verify: (signature: import("./abstract/utils.js").Hex | import("./abstract/weierstrass.js").SignatureType, msgHash: import("./abstract/utils.js").Hex, publicKey: import("./abstract/weierstrass.js").PubKey, opts?: { | ||
lowS?: boolean | undefined; | ||
} | undefined) => boolean; | ||
Point: import("./abstract/weierstrass.js").PointConstructor<bigint>; | ||
ProjectivePoint: import("./abstract/weierstrass.js").ProjectiveConstructor<bigint>; | ||
verify: (signature: import("./abstract/utils.js").Hex | { | ||
r: bigint; | ||
s: bigint; | ||
}, msgHash: import("./abstract/utils.js").Hex, publicKey: import("./abstract/utils.js").Hex, opts?: import("./abstract/weierstrass.js").VerOpts | undefined) => boolean; | ||
ProjectivePoint: import("./abstract/weierstrass.js").ProjConstructor<bigint>; | ||
Signature: import("./abstract/weierstrass.js").SignatureConstructor; | ||
utils: { | ||
_bigintToBytes: (num: bigint) => Uint8Array; | ||
_bigintToString: (num: bigint) => string; | ||
_normalizePrivateKey: (key: import("./abstract/utils.js").PrivKey) => bigint; | ||
_normalizePublicKey: (publicKey: import("./abstract/weierstrass.js").PubKey) => import("./abstract/weierstrass.js").PointType<bigint>; | ||
_isWithinCurveOrder: (num: bigint) => boolean; | ||
_isValidFieldElement: (num: bigint) => boolean; | ||
_weierstrassEquation: (x: bigint) => bigint; | ||
isValidPrivateKey(privateKey: import("./abstract/utils.js").PrivKey): boolean; | ||
@@ -123,0 +101,0 @@ hashToPrivateKey: (hash: import("./abstract/utils.js").Hex) => Uint8Array; |
@@ -0,1 +1,2 @@ | ||
import * as htf from './abstract/hash-to-curve.js'; | ||
export declare const P256: Readonly<{ | ||
@@ -26,9 +27,4 @@ create: (hash: import("./abstract/utils.js").CHash) => import("./abstract/weierstrass.js").CurveFn; | ||
} | undefined; | ||
readonly isTorsionFree?: ((c: import("./abstract/weierstrass.js").ProjectiveConstructor<bigint>, point: import("./abstract/weierstrass.js").ProjectivePointType<bigint>) => boolean) | undefined; | ||
readonly clearCofactor?: ((c: import("./abstract/weierstrass.js").ProjectiveConstructor<bigint>, point: import("./abstract/weierstrass.js").ProjectivePointType<bigint>) => import("./abstract/weierstrass.js").ProjectivePointType<bigint>) | undefined; | ||
readonly htfDefaults?: import("./abstract/hash-to-curve.js").htfOpts | undefined; | ||
readonly mapToCurve?: ((scalar: bigint[]) => { | ||
x: bigint; | ||
y: bigint; | ||
}) | undefined; | ||
readonly isTorsionFree?: ((c: import("./abstract/weierstrass.js").ProjConstructor<bigint>, point: import("./abstract/weierstrass.js").ProjPointType<bigint>) => boolean) | undefined; | ||
readonly clearCofactor?: ((c: import("./abstract/weierstrass.js").ProjConstructor<bigint>, point: import("./abstract/weierstrass.js").ProjPointType<bigint>) => import("./abstract/weierstrass.js").ProjPointType<bigint>) | undefined; | ||
lowS: boolean; | ||
@@ -38,22 +34,16 @@ readonly hash: import("./abstract/utils.js").CHash; | ||
readonly randomBytes: (bytesLength?: number | undefined) => Uint8Array; | ||
readonly truncateHash?: ((hash: Uint8Array, truncateOnly?: boolean | undefined) => bigint) | undefined; | ||
readonly bits2int?: ((bytes: Uint8Array) => bigint) | undefined; | ||
readonly bits2int_modN?: ((bytes: Uint8Array) => bigint) | undefined; | ||
}>; | ||
getPublicKey: (privateKey: import("./abstract/utils.js").PrivKey, isCompressed?: boolean | undefined) => Uint8Array; | ||
getSharedSecret: (privateA: import("./abstract/utils.js").PrivKey, publicB: import("./abstract/weierstrass.js").PubKey, isCompressed?: boolean | undefined) => Uint8Array; | ||
getSharedSecret: (privateA: import("./abstract/utils.js").PrivKey, publicB: import("./abstract/utils.js").Hex, isCompressed?: boolean | undefined) => Uint8Array; | ||
sign: (msgHash: import("./abstract/utils.js").Hex, privKey: import("./abstract/utils.js").PrivKey, opts?: import("./abstract/weierstrass.js").SignOpts | undefined) => import("./abstract/weierstrass.js").SignatureType; | ||
signUnhashed: (msg: Uint8Array, privKey: import("./abstract/utils.js").PrivKey, opts?: import("./abstract/weierstrass.js").SignOpts | undefined) => import("./abstract/weierstrass.js").SignatureType; | ||
verify: (signature: import("./abstract/utils.js").Hex | import("./abstract/weierstrass.js").SignatureType, msgHash: import("./abstract/utils.js").Hex, publicKey: import("./abstract/weierstrass.js").PubKey, opts?: { | ||
lowS?: boolean | undefined; | ||
} | undefined) => boolean; | ||
Point: import("./abstract/weierstrass.js").PointConstructor<bigint>; | ||
ProjectivePoint: import("./abstract/weierstrass.js").ProjectiveConstructor<bigint>; | ||
verify: (signature: import("./abstract/utils.js").Hex | { | ||
r: bigint; | ||
s: bigint; | ||
}, msgHash: import("./abstract/utils.js").Hex, publicKey: import("./abstract/utils.js").Hex, opts?: import("./abstract/weierstrass.js").VerOpts | undefined) => boolean; | ||
ProjectivePoint: import("./abstract/weierstrass.js").ProjConstructor<bigint>; | ||
Signature: import("./abstract/weierstrass.js").SignatureConstructor; | ||
utils: { | ||
_bigintToBytes: (num: bigint) => Uint8Array; | ||
_bigintToString: (num: bigint) => string; | ||
_normalizePrivateKey: (key: import("./abstract/utils.js").PrivKey) => bigint; | ||
_normalizePublicKey: (publicKey: import("./abstract/weierstrass.js").PubKey) => import("./abstract/weierstrass.js").PointType<bigint>; | ||
_isWithinCurveOrder: (num: bigint) => boolean; | ||
_isValidFieldElement: (num: bigint) => boolean; | ||
_weierstrassEquation: (x: bigint) => bigint; | ||
isValidPrivateKey(privateKey: import("./abstract/utils.js").PrivKey): boolean; | ||
@@ -89,9 +79,4 @@ hashToPrivateKey: (hash: import("./abstract/utils.js").Hex) => Uint8Array; | ||
} | undefined; | ||
readonly isTorsionFree?: ((c: import("./abstract/weierstrass.js").ProjectiveConstructor<bigint>, point: import("./abstract/weierstrass.js").ProjectivePointType<bigint>) => boolean) | undefined; | ||
readonly clearCofactor?: ((c: import("./abstract/weierstrass.js").ProjectiveConstructor<bigint>, point: import("./abstract/weierstrass.js").ProjectivePointType<bigint>) => import("./abstract/weierstrass.js").ProjectivePointType<bigint>) | undefined; | ||
readonly htfDefaults?: import("./abstract/hash-to-curve.js").htfOpts | undefined; | ||
readonly mapToCurve?: ((scalar: bigint[]) => { | ||
x: bigint; | ||
y: bigint; | ||
}) | undefined; | ||
readonly isTorsionFree?: ((c: import("./abstract/weierstrass.js").ProjConstructor<bigint>, point: import("./abstract/weierstrass.js").ProjPointType<bigint>) => boolean) | undefined; | ||
readonly clearCofactor?: ((c: import("./abstract/weierstrass.js").ProjConstructor<bigint>, point: import("./abstract/weierstrass.js").ProjPointType<bigint>) => import("./abstract/weierstrass.js").ProjPointType<bigint>) | undefined; | ||
lowS: boolean; | ||
@@ -101,22 +86,16 @@ readonly hash: import("./abstract/utils.js").CHash; | ||
readonly randomBytes: (bytesLength?: number | undefined) => Uint8Array; | ||
readonly truncateHash?: ((hash: Uint8Array, truncateOnly?: boolean | undefined) => bigint) | undefined; | ||
readonly bits2int?: ((bytes: Uint8Array) => bigint) | undefined; | ||
readonly bits2int_modN?: ((bytes: Uint8Array) => bigint) | undefined; | ||
}>; | ||
getPublicKey: (privateKey: import("./abstract/utils.js").PrivKey, isCompressed?: boolean | undefined) => Uint8Array; | ||
getSharedSecret: (privateA: import("./abstract/utils.js").PrivKey, publicB: import("./abstract/weierstrass.js").PubKey, isCompressed?: boolean | undefined) => Uint8Array; | ||
getSharedSecret: (privateA: import("./abstract/utils.js").PrivKey, publicB: import("./abstract/utils.js").Hex, isCompressed?: boolean | undefined) => Uint8Array; | ||
sign: (msgHash: import("./abstract/utils.js").Hex, privKey: import("./abstract/utils.js").PrivKey, opts?: import("./abstract/weierstrass.js").SignOpts | undefined) => import("./abstract/weierstrass.js").SignatureType; | ||
signUnhashed: (msg: Uint8Array, privKey: import("./abstract/utils.js").PrivKey, opts?: import("./abstract/weierstrass.js").SignOpts | undefined) => import("./abstract/weierstrass.js").SignatureType; | ||
verify: (signature: import("./abstract/utils.js").Hex | import("./abstract/weierstrass.js").SignatureType, msgHash: import("./abstract/utils.js").Hex, publicKey: import("./abstract/weierstrass.js").PubKey, opts?: { | ||
lowS?: boolean | undefined; | ||
} | undefined) => boolean; | ||
Point: import("./abstract/weierstrass.js").PointConstructor<bigint>; | ||
ProjectivePoint: import("./abstract/weierstrass.js").ProjectiveConstructor<bigint>; | ||
verify: (signature: import("./abstract/utils.js").Hex | { | ||
r: bigint; | ||
s: bigint; | ||
}, msgHash: import("./abstract/utils.js").Hex, publicKey: import("./abstract/utils.js").Hex, opts?: import("./abstract/weierstrass.js").VerOpts | undefined) => boolean; | ||
ProjectivePoint: import("./abstract/weierstrass.js").ProjConstructor<bigint>; | ||
Signature: import("./abstract/weierstrass.js").SignatureConstructor; | ||
utils: { | ||
_bigintToBytes: (num: bigint) => Uint8Array; | ||
_bigintToString: (num: bigint) => string; | ||
_normalizePrivateKey: (key: import("./abstract/utils.js").PrivKey) => bigint; | ||
_normalizePublicKey: (publicKey: import("./abstract/weierstrass.js").PubKey) => import("./abstract/weierstrass.js").PointType<bigint>; | ||
_isWithinCurveOrder: (num: bigint) => boolean; | ||
_isValidFieldElement: (num: bigint) => boolean; | ||
_weierstrassEquation: (x: bigint) => bigint; | ||
isValidPrivateKey(privateKey: import("./abstract/utils.js").PrivKey): boolean; | ||
@@ -127,1 +106,3 @@ hashToPrivateKey: (hash: import("./abstract/utils.js").Hex) => Uint8Array; | ||
}>; | ||
declare const hashToCurve: (msg: import("./abstract/utils.js").Hex, options?: htf.htfBasicOpts | undefined) => htf.H2CPoint<bigint>, encodeToCurve: (msg: import("./abstract/utils.js").Hex, options?: htf.htfBasicOpts | undefined) => htf.H2CPoint<bigint>; | ||
export { hashToCurve, encodeToCurve }; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.secp256r1 = exports.P256 = void 0; | ||
exports.encodeToCurve = exports.hashToCurve = exports.secp256r1 = exports.P256 = void 0; | ||
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */ | ||
@@ -9,2 +9,3 @@ const _shortw_utils_js_1 = require("./_shortw_utils.js"); | ||
const weierstrass_js_1 = require("./abstract/weierstrass.js"); | ||
const htf = require("./abstract/hash-to-curve.js"); | ||
// NIST secp256r1 aka P256 | ||
@@ -33,12 +34,14 @@ // https://www.secg.org/sec2-v2.pdf, https://neuromancer.sk/std/nist/P-256 | ||
lowS: false, | ||
mapToCurve: (scalars) => mapSWU(scalars[0]), | ||
htfDefaults: { | ||
DST: 'P256_XMD:SHA-256_SSWU_RO_', | ||
p: Fp.ORDER, | ||
m: 1, | ||
k: 128, | ||
expand: 'xmd', | ||
hash: sha256_1.sha256, | ||
}, | ||
}, sha256_1.sha256); | ||
exports.secp256r1 = exports.P256; | ||
const { hashToCurve, encodeToCurve } = htf.hashToCurve(exports.secp256r1.ProjectivePoint, (scalars) => mapSWU(scalars[0]), { | ||
DST: 'P256_XMD:SHA-256_SSWU_RO_', | ||
encodeDST: 'P256_XMD:SHA-256_SSWU_NU_', | ||
p: Fp.ORDER, | ||
m: 1, | ||
k: 128, | ||
expand: 'xmd', | ||
hash: sha256_1.sha256, | ||
}); | ||
exports.hashToCurve = hashToCurve; | ||
exports.encodeToCurve = encodeToCurve; |
@@ -0,1 +1,2 @@ | ||
import * as htf from './abstract/hash-to-curve.js'; | ||
export declare const P384: Readonly<{ | ||
@@ -26,9 +27,4 @@ create: (hash: import("./abstract/utils.js").CHash) => import("./abstract/weierstrass.js").CurveFn; | ||
} | undefined; | ||
readonly isTorsionFree?: ((c: import("./abstract/weierstrass.js").ProjectiveConstructor<bigint>, point: import("./abstract/weierstrass.js").ProjectivePointType<bigint>) => boolean) | undefined; | ||
readonly clearCofactor?: ((c: import("./abstract/weierstrass.js").ProjectiveConstructor<bigint>, point: import("./abstract/weierstrass.js").ProjectivePointType<bigint>) => import("./abstract/weierstrass.js").ProjectivePointType<bigint>) | undefined; | ||
readonly htfDefaults?: import("./abstract/hash-to-curve.js").htfOpts | undefined; | ||
readonly mapToCurve?: ((scalar: bigint[]) => { | ||
x: bigint; | ||
y: bigint; | ||
}) | undefined; | ||
readonly isTorsionFree?: ((c: import("./abstract/weierstrass.js").ProjConstructor<bigint>, point: import("./abstract/weierstrass.js").ProjPointType<bigint>) => boolean) | undefined; | ||
readonly clearCofactor?: ((c: import("./abstract/weierstrass.js").ProjConstructor<bigint>, point: import("./abstract/weierstrass.js").ProjPointType<bigint>) => import("./abstract/weierstrass.js").ProjPointType<bigint>) | undefined; | ||
lowS: boolean; | ||
@@ -38,22 +34,16 @@ readonly hash: import("./abstract/utils.js").CHash; | ||
readonly randomBytes: (bytesLength?: number | undefined) => Uint8Array; | ||
readonly truncateHash?: ((hash: Uint8Array, truncateOnly?: boolean | undefined) => bigint) | undefined; | ||
readonly bits2int?: ((bytes: Uint8Array) => bigint) | undefined; | ||
readonly bits2int_modN?: ((bytes: Uint8Array) => bigint) | undefined; | ||
}>; | ||
getPublicKey: (privateKey: import("./abstract/utils.js").PrivKey, isCompressed?: boolean | undefined) => Uint8Array; | ||
getSharedSecret: (privateA: import("./abstract/utils.js").PrivKey, publicB: import("./abstract/weierstrass.js").PubKey, isCompressed?: boolean | undefined) => Uint8Array; | ||
getSharedSecret: (privateA: import("./abstract/utils.js").PrivKey, publicB: import("./abstract/utils.js").Hex, isCompressed?: boolean | undefined) => Uint8Array; | ||
sign: (msgHash: import("./abstract/utils.js").Hex, privKey: import("./abstract/utils.js").PrivKey, opts?: import("./abstract/weierstrass.js").SignOpts | undefined) => import("./abstract/weierstrass.js").SignatureType; | ||
signUnhashed: (msg: Uint8Array, privKey: import("./abstract/utils.js").PrivKey, opts?: import("./abstract/weierstrass.js").SignOpts | undefined) => import("./abstract/weierstrass.js").SignatureType; | ||
verify: (signature: import("./abstract/utils.js").Hex | import("./abstract/weierstrass.js").SignatureType, msgHash: import("./abstract/utils.js").Hex, publicKey: import("./abstract/weierstrass.js").PubKey, opts?: { | ||
lowS?: boolean | undefined; | ||
} | undefined) => boolean; | ||
Point: import("./abstract/weierstrass.js").PointConstructor<bigint>; | ||
ProjectivePoint: import("./abstract/weierstrass.js").ProjectiveConstructor<bigint>; | ||
verify: (signature: import("./abstract/utils.js").Hex | { | ||
r: bigint; | ||
s: bigint; | ||
}, msgHash: import("./abstract/utils.js").Hex, publicKey: import("./abstract/utils.js").Hex, opts?: import("./abstract/weierstrass.js").VerOpts | undefined) => boolean; | ||
ProjectivePoint: import("./abstract/weierstrass.js").ProjConstructor<bigint>; | ||
Signature: import("./abstract/weierstrass.js").SignatureConstructor; | ||
utils: { | ||
_bigintToBytes: (num: bigint) => Uint8Array; | ||
_bigintToString: (num: bigint) => string; | ||
_normalizePrivateKey: (key: import("./abstract/utils.js").PrivKey) => bigint; | ||
_normalizePublicKey: (publicKey: import("./abstract/weierstrass.js").PubKey) => import("./abstract/weierstrass.js").PointType<bigint>; | ||
_isWithinCurveOrder: (num: bigint) => boolean; | ||
_isValidFieldElement: (num: bigint) => boolean; | ||
_weierstrassEquation: (x: bigint) => bigint; | ||
isValidPrivateKey(privateKey: import("./abstract/utils.js").PrivKey): boolean; | ||
@@ -89,9 +79,4 @@ hashToPrivateKey: (hash: import("./abstract/utils.js").Hex) => Uint8Array; | ||
} | undefined; | ||
readonly isTorsionFree?: ((c: import("./abstract/weierstrass.js").ProjectiveConstructor<bigint>, point: import("./abstract/weierstrass.js").ProjectivePointType<bigint>) => boolean) | undefined; | ||
readonly clearCofactor?: ((c: import("./abstract/weierstrass.js").ProjectiveConstructor<bigint>, point: import("./abstract/weierstrass.js").ProjectivePointType<bigint>) => import("./abstract/weierstrass.js").ProjectivePointType<bigint>) | undefined; | ||
readonly htfDefaults?: import("./abstract/hash-to-curve.js").htfOpts | undefined; | ||
readonly mapToCurve?: ((scalar: bigint[]) => { | ||
x: bigint; | ||
y: bigint; | ||
}) | undefined; | ||
readonly isTorsionFree?: ((c: import("./abstract/weierstrass.js").ProjConstructor<bigint>, point: import("./abstract/weierstrass.js").ProjPointType<bigint>) => boolean) | undefined; | ||
readonly clearCofactor?: ((c: import("./abstract/weierstrass.js").ProjConstructor<bigint>, point: import("./abstract/weierstrass.js").ProjPointType<bigint>) => import("./abstract/weierstrass.js").ProjPointType<bigint>) | undefined; | ||
lowS: boolean; | ||
@@ -101,22 +86,16 @@ readonly hash: import("./abstract/utils.js").CHash; | ||
readonly randomBytes: (bytesLength?: number | undefined) => Uint8Array; | ||
readonly truncateHash?: ((hash: Uint8Array, truncateOnly?: boolean | undefined) => bigint) | undefined; | ||
readonly bits2int?: ((bytes: Uint8Array) => bigint) | undefined; | ||
readonly bits2int_modN?: ((bytes: Uint8Array) => bigint) | undefined; | ||
}>; | ||
getPublicKey: (privateKey: import("./abstract/utils.js").PrivKey, isCompressed?: boolean | undefined) => Uint8Array; | ||
getSharedSecret: (privateA: import("./abstract/utils.js").PrivKey, publicB: import("./abstract/weierstrass.js").PubKey, isCompressed?: boolean | undefined) => Uint8Array; | ||
getSharedSecret: (privateA: import("./abstract/utils.js").PrivKey, publicB: import("./abstract/utils.js").Hex, isCompressed?: boolean | undefined) => Uint8Array; | ||
sign: (msgHash: import("./abstract/utils.js").Hex, privKey: import("./abstract/utils.js").PrivKey, opts?: import("./abstract/weierstrass.js").SignOpts | undefined) => import("./abstract/weierstrass.js").SignatureType; | ||
signUnhashed: (msg: Uint8Array, privKey: import("./abstract/utils.js").PrivKey, opts?: import("./abstract/weierstrass.js").SignOpts | undefined) => import("./abstract/weierstrass.js").SignatureType; | ||
verify: (signature: import("./abstract/utils.js").Hex | import("./abstract/weierstrass.js").SignatureType, msgHash: import("./abstract/utils.js").Hex, publicKey: import("./abstract/weierstrass.js").PubKey, opts?: { | ||
lowS?: boolean | undefined; | ||
} | undefined) => boolean; | ||
Point: import("./abstract/weierstrass.js").PointConstructor<bigint>; | ||
ProjectivePoint: import("./abstract/weierstrass.js").ProjectiveConstructor<bigint>; | ||
verify: (signature: import("./abstract/utils.js").Hex | { | ||
r: bigint; | ||
s: bigint; | ||
}, msgHash: import("./abstract/utils.js").Hex, publicKey: import("./abstract/utils.js").Hex, opts?: import("./abstract/weierstrass.js").VerOpts | undefined) => boolean; | ||
ProjectivePoint: import("./abstract/weierstrass.js").ProjConstructor<bigint>; | ||
Signature: import("./abstract/weierstrass.js").SignatureConstructor; | ||
utils: { | ||
_bigintToBytes: (num: bigint) => Uint8Array; | ||
_bigintToString: (num: bigint) => string; | ||
_normalizePrivateKey: (key: import("./abstract/utils.js").PrivKey) => bigint; | ||
_normalizePublicKey: (publicKey: import("./abstract/weierstrass.js").PubKey) => import("./abstract/weierstrass.js").PointType<bigint>; | ||
_isWithinCurveOrder: (num: bigint) => boolean; | ||
_isValidFieldElement: (num: bigint) => boolean; | ||
_weierstrassEquation: (x: bigint) => bigint; | ||
isValidPrivateKey(privateKey: import("./abstract/utils.js").PrivKey): boolean; | ||
@@ -127,1 +106,3 @@ hashToPrivateKey: (hash: import("./abstract/utils.js").Hex) => Uint8Array; | ||
}>; | ||
declare const hashToCurve: (msg: import("./abstract/utils.js").Hex, options?: htf.htfBasicOpts | undefined) => htf.H2CPoint<bigint>, encodeToCurve: (msg: import("./abstract/utils.js").Hex, options?: htf.htfBasicOpts | undefined) => htf.H2CPoint<bigint>; | ||
export { hashToCurve, encodeToCurve }; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.secp384r1 = exports.P384 = void 0; | ||
exports.encodeToCurve = exports.hashToCurve = exports.secp384r1 = exports.P384 = void 0; | ||
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */ | ||
@@ -9,2 +9,3 @@ const _shortw_utils_js_1 = require("./_shortw_utils.js"); | ||
const weierstrass_js_1 = require("./abstract/weierstrass.js"); | ||
const htf = require("./abstract/hash-to-curve.js"); | ||
// NIST secp384r1 aka P384 | ||
@@ -38,12 +39,14 @@ // https://www.secg.org/sec2-v2.pdf, https://neuromancer.sk/std/nist/P-384 | ||
lowS: false, | ||
mapToCurve: (scalars) => mapSWU(scalars[0]), | ||
htfDefaults: { | ||
DST: 'P384_XMD:SHA-384_SSWU_RO_', | ||
p: Fp.ORDER, | ||
m: 1, | ||
k: 192, | ||
expand: 'xmd', | ||
hash: sha512_1.sha384, | ||
}, | ||
}, sha512_1.sha384); | ||
exports.secp384r1 = exports.P384; | ||
const { hashToCurve, encodeToCurve } = htf.hashToCurve(exports.secp384r1.ProjectivePoint, (scalars) => mapSWU(scalars[0]), { | ||
DST: 'P384_XMD:SHA-384_SSWU_RO_', | ||
encodeDST: 'P384_XMD:SHA-384_SSWU_NU_', | ||
p: Fp.ORDER, | ||
m: 1, | ||
k: 192, | ||
expand: 'xmd', | ||
hash: sha512_1.sha384, | ||
}); | ||
exports.hashToCurve = hashToCurve; | ||
exports.encodeToCurve = encodeToCurve; |
import { PrivKey } from './abstract/utils.js'; | ||
import * as htf from './abstract/hash-to-curve.js'; | ||
export declare const P521: Readonly<{ | ||
@@ -27,9 +28,4 @@ create: (hash: import("./abstract/utils.js").CHash) => import("./abstract/weierstrass.js").CurveFn; | ||
} | undefined; | ||
readonly isTorsionFree?: ((c: import("./abstract/weierstrass.js").ProjectiveConstructor<bigint>, point: import("./abstract/weierstrass.js").ProjectivePointType<bigint>) => boolean) | undefined; | ||
readonly clearCofactor?: ((c: import("./abstract/weierstrass.js").ProjectiveConstructor<bigint>, point: import("./abstract/weierstrass.js").ProjectivePointType<bigint>) => import("./abstract/weierstrass.js").ProjectivePointType<bigint>) | undefined; | ||
readonly htfDefaults?: import("./abstract/hash-to-curve.js").htfOpts | undefined; | ||
readonly mapToCurve?: ((scalar: bigint[]) => { | ||
x: bigint; | ||
y: bigint; | ||
}) | undefined; | ||
readonly isTorsionFree?: ((c: import("./abstract/weierstrass.js").ProjConstructor<bigint>, point: import("./abstract/weierstrass.js").ProjPointType<bigint>) => boolean) | undefined; | ||
readonly clearCofactor?: ((c: import("./abstract/weierstrass.js").ProjConstructor<bigint>, point: import("./abstract/weierstrass.js").ProjPointType<bigint>) => import("./abstract/weierstrass.js").ProjPointType<bigint>) | undefined; | ||
lowS: boolean; | ||
@@ -39,22 +35,16 @@ readonly hash: import("./abstract/utils.js").CHash; | ||
readonly randomBytes: (bytesLength?: number | undefined) => Uint8Array; | ||
readonly truncateHash?: ((hash: Uint8Array, truncateOnly?: boolean | undefined) => bigint) | undefined; | ||
readonly bits2int?: ((bytes: Uint8Array) => bigint) | undefined; | ||
readonly bits2int_modN?: ((bytes: Uint8Array) => bigint) | undefined; | ||
}>; | ||
getPublicKey: (privateKey: PrivKey, isCompressed?: boolean | undefined) => Uint8Array; | ||
getSharedSecret: (privateA: PrivKey, publicB: import("./abstract/weierstrass.js").PubKey, isCompressed?: boolean | undefined) => Uint8Array; | ||
getSharedSecret: (privateA: PrivKey, publicB: import("./abstract/utils.js").Hex, isCompressed?: boolean | undefined) => Uint8Array; | ||
sign: (msgHash: import("./abstract/utils.js").Hex, privKey: PrivKey, opts?: import("./abstract/weierstrass.js").SignOpts | undefined) => import("./abstract/weierstrass.js").SignatureType; | ||
signUnhashed: (msg: Uint8Array, privKey: PrivKey, opts?: import("./abstract/weierstrass.js").SignOpts | undefined) => import("./abstract/weierstrass.js").SignatureType; | ||
verify: (signature: import("./abstract/utils.js").Hex | import("./abstract/weierstrass.js").SignatureType, msgHash: import("./abstract/utils.js").Hex, publicKey: import("./abstract/weierstrass.js").PubKey, opts?: { | ||
lowS?: boolean | undefined; | ||
} | undefined) => boolean; | ||
Point: import("./abstract/weierstrass.js").PointConstructor<bigint>; | ||
ProjectivePoint: import("./abstract/weierstrass.js").ProjectiveConstructor<bigint>; | ||
verify: (signature: import("./abstract/utils.js").Hex | { | ||
r: bigint; | ||
s: bigint; | ||
}, msgHash: import("./abstract/utils.js").Hex, publicKey: import("./abstract/utils.js").Hex, opts?: import("./abstract/weierstrass.js").VerOpts | undefined) => boolean; | ||
ProjectivePoint: import("./abstract/weierstrass.js").ProjConstructor<bigint>; | ||
Signature: import("./abstract/weierstrass.js").SignatureConstructor; | ||
utils: { | ||
_bigintToBytes: (num: bigint) => Uint8Array; | ||
_bigintToString: (num: bigint) => string; | ||
_normalizePrivateKey: (key: PrivKey) => bigint; | ||
_normalizePublicKey: (publicKey: import("./abstract/weierstrass.js").PubKey) => import("./abstract/weierstrass.js").PointType<bigint>; | ||
_isWithinCurveOrder: (num: bigint) => boolean; | ||
_isValidFieldElement: (num: bigint) => boolean; | ||
_weierstrassEquation: (x: bigint) => bigint; | ||
isValidPrivateKey(privateKey: PrivKey): boolean; | ||
@@ -90,9 +80,4 @@ hashToPrivateKey: (hash: import("./abstract/utils.js").Hex) => Uint8Array; | ||
} | undefined; | ||
readonly isTorsionFree?: ((c: import("./abstract/weierstrass.js").ProjectiveConstructor<bigint>, point: import("./abstract/weierstrass.js").ProjectivePointType<bigint>) => boolean) | undefined; | ||
readonly clearCofactor?: ((c: import("./abstract/weierstrass.js").ProjectiveConstructor<bigint>, point: import("./abstract/weierstrass.js").ProjectivePointType<bigint>) => import("./abstract/weierstrass.js").ProjectivePointType<bigint>) | undefined; | ||
readonly htfDefaults?: import("./abstract/hash-to-curve.js").htfOpts | undefined; | ||
readonly mapToCurve?: ((scalar: bigint[]) => { | ||
x: bigint; | ||
y: bigint; | ||
}) | undefined; | ||
readonly isTorsionFree?: ((c: import("./abstract/weierstrass.js").ProjConstructor<bigint>, point: import("./abstract/weierstrass.js").ProjPointType<bigint>) => boolean) | undefined; | ||
readonly clearCofactor?: ((c: import("./abstract/weierstrass.js").ProjConstructor<bigint>, point: import("./abstract/weierstrass.js").ProjPointType<bigint>) => import("./abstract/weierstrass.js").ProjPointType<bigint>) | undefined; | ||
lowS: boolean; | ||
@@ -102,22 +87,16 @@ readonly hash: import("./abstract/utils.js").CHash; | ||
readonly randomBytes: (bytesLength?: number | undefined) => Uint8Array; | ||
readonly truncateHash?: ((hash: Uint8Array, truncateOnly?: boolean | undefined) => bigint) | undefined; | ||
readonly bits2int?: ((bytes: Uint8Array) => bigint) | undefined; | ||
readonly bits2int_modN?: ((bytes: Uint8Array) => bigint) | undefined; | ||
}>; | ||
getPublicKey: (privateKey: PrivKey, isCompressed?: boolean | undefined) => Uint8Array; | ||
getSharedSecret: (privateA: PrivKey, publicB: import("./abstract/weierstrass.js").PubKey, isCompressed?: boolean | undefined) => Uint8Array; | ||
getSharedSecret: (privateA: PrivKey, publicB: import("./abstract/utils.js").Hex, isCompressed?: boolean | undefined) => Uint8Array; | ||
sign: (msgHash: import("./abstract/utils.js").Hex, privKey: PrivKey, opts?: import("./abstract/weierstrass.js").SignOpts | undefined) => import("./abstract/weierstrass.js").SignatureType; | ||
signUnhashed: (msg: Uint8Array, privKey: PrivKey, opts?: import("./abstract/weierstrass.js").SignOpts | undefined) => import("./abstract/weierstrass.js").SignatureType; | ||
verify: (signature: import("./abstract/utils.js").Hex | import("./abstract/weierstrass.js").SignatureType, msgHash: import("./abstract/utils.js").Hex, publicKey: import("./abstract/weierstrass.js").PubKey, opts?: { | ||
lowS?: boolean | undefined; | ||
} | undefined) => boolean; | ||
Point: import("./abstract/weierstrass.js").PointConstructor<bigint>; | ||
ProjectivePoint: import("./abstract/weierstrass.js").ProjectiveConstructor<bigint>; | ||
verify: (signature: import("./abstract/utils.js").Hex | { | ||
r: bigint; | ||
s: bigint; | ||
}, msgHash: import("./abstract/utils.js").Hex, publicKey: import("./abstract/utils.js").Hex, opts?: import("./abstract/weierstrass.js").VerOpts | undefined) => boolean; | ||
ProjectivePoint: import("./abstract/weierstrass.js").ProjConstructor<bigint>; | ||
Signature: import("./abstract/weierstrass.js").SignatureConstructor; | ||
utils: { | ||
_bigintToBytes: (num: bigint) => Uint8Array; | ||
_bigintToString: (num: bigint) => string; | ||
_normalizePrivateKey: (key: PrivKey) => bigint; | ||
_normalizePublicKey: (publicKey: import("./abstract/weierstrass.js").PubKey) => import("./abstract/weierstrass.js").PointType<bigint>; | ||
_isWithinCurveOrder: (num: bigint) => boolean; | ||
_isValidFieldElement: (num: bigint) => boolean; | ||
_weierstrassEquation: (x: bigint) => bigint; | ||
isValidPrivateKey(privateKey: PrivKey): boolean; | ||
@@ -128,1 +107,3 @@ hashToPrivateKey: (hash: import("./abstract/utils.js").Hex) => Uint8Array; | ||
}>; | ||
declare const hashToCurve: (msg: import("./abstract/utils.js").Hex, options?: htf.htfBasicOpts | undefined) => htf.H2CPoint<bigint>, encodeToCurve: (msg: import("./abstract/utils.js").Hex, options?: htf.htfBasicOpts | undefined) => htf.H2CPoint<bigint>; | ||
export { hashToCurve, encodeToCurve }; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.secp521r1 = exports.P521 = void 0; | ||
exports.encodeToCurve = exports.hashToCurve = exports.secp521r1 = exports.P521 = void 0; | ||
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */ | ||
@@ -10,2 +10,3 @@ const _shortw_utils_js_1 = require("./_shortw_utils.js"); | ||
const weierstrass_js_1 = require("./abstract/weierstrass.js"); | ||
const htf = require("./abstract/hash-to-curve.js"); | ||
// NIST secp521r1 aka P521 | ||
@@ -39,4 +40,3 @@ // Note that it's 521, which differs from 512 of its hash function. | ||
lowS: false, | ||
// P521 keys could be 130, 131, 132 bytes - which doesn't play nicely. | ||
// We ensure all keys are 132 bytes. | ||
// P521 keys could be 130, 131, 132 bytes. We normalize to 132 bytes. | ||
// Does not replace validation; invalid keys would still be rejected. | ||
@@ -51,14 +51,16 @@ normalizePrivateKey(key) { | ||
} | ||
return key.padStart(66 * 2, '0'); | ||
return key.padStart(66 * 2, '0'); // ensure it's always 132 bytes | ||
}, | ||
mapToCurve: (scalars) => mapSWU(scalars[0]), | ||
htfDefaults: { | ||
DST: 'P521_XMD:SHA-512_SSWU_RO_', | ||
p: Fp.ORDER, | ||
m: 1, | ||
k: 256, | ||
expand: 'xmd', | ||
hash: sha512_1.sha512, | ||
}, | ||
}, sha512_1.sha512); | ||
exports.secp521r1 = exports.P521; | ||
const { hashToCurve, encodeToCurve } = htf.hashToCurve(exports.secp521r1.ProjectivePoint, (scalars) => mapSWU(scalars[0]), { | ||
DST: 'P521_XMD:SHA-512_SSWU_RO_', | ||
encodeDST: 'P521_XMD:SHA-512_SSWU_NU_', | ||
p: Fp.ORDER, | ||
m: 1, | ||
k: 256, | ||
expand: 'xmd', | ||
hash: sha512_1.sha512, | ||
}); | ||
exports.hashToCurve = hashToCurve; | ||
exports.encodeToCurve = encodeToCurve; |
@@ -1,3 +0,4 @@ | ||
import { PointType } from './abstract/weierstrass.js'; | ||
import { Hex, PrivKey } from './abstract/utils.js'; | ||
import { ProjPointType as PointType } from './abstract/weierstrass.js'; | ||
import { Hex, bytesToNumberBE as bytesToNum, PrivKey } from './abstract/utils.js'; | ||
import * as htf from './abstract/hash-to-curve.js'; | ||
export declare const secp256k1: Readonly<{ | ||
@@ -28,9 +29,4 @@ create: (hash: import("./abstract/utils.js").CHash) => import("./abstract/weierstrass.js").CurveFn; | ||
} | undefined; | ||
readonly isTorsionFree?: ((c: import("./abstract/weierstrass.js").ProjectiveConstructor<bigint>, point: import("./abstract/weierstrass.js").ProjectivePointType<bigint>) => boolean) | undefined; | ||
readonly clearCofactor?: ((c: import("./abstract/weierstrass.js").ProjectiveConstructor<bigint>, point: import("./abstract/weierstrass.js").ProjectivePointType<bigint>) => import("./abstract/weierstrass.js").ProjectivePointType<bigint>) | undefined; | ||
readonly htfDefaults?: import("./abstract/hash-to-curve.js").htfOpts | undefined; | ||
readonly mapToCurve?: ((scalar: bigint[]) => { | ||
x: bigint; | ||
y: bigint; | ||
}) | undefined; | ||
readonly isTorsionFree?: ((c: import("./abstract/weierstrass.js").ProjConstructor<bigint>, point: PointType<bigint>) => boolean) | undefined; | ||
readonly clearCofactor?: ((c: import("./abstract/weierstrass.js").ProjConstructor<bigint>, point: PointType<bigint>) => PointType<bigint>) | undefined; | ||
lowS: boolean; | ||
@@ -40,22 +36,16 @@ readonly hash: import("./abstract/utils.js").CHash; | ||
readonly randomBytes: (bytesLength?: number | undefined) => Uint8Array; | ||
readonly truncateHash?: ((hash: Uint8Array, truncateOnly?: boolean | undefined) => bigint) | undefined; | ||
readonly bits2int?: ((bytes: Uint8Array) => bigint) | undefined; | ||
readonly bits2int_modN?: ((bytes: Uint8Array) => bigint) | undefined; | ||
}>; | ||
getPublicKey: (privateKey: PrivKey, isCompressed?: boolean | undefined) => Uint8Array; | ||
getSharedSecret: (privateA: PrivKey, publicB: import("./abstract/weierstrass.js").PubKey, isCompressed?: boolean | undefined) => Uint8Array; | ||
getSharedSecret: (privateA: PrivKey, publicB: Hex, isCompressed?: boolean | undefined) => Uint8Array; | ||
sign: (msgHash: Hex, privKey: PrivKey, opts?: import("./abstract/weierstrass.js").SignOpts | undefined) => import("./abstract/weierstrass.js").SignatureType; | ||
signUnhashed: (msg: Uint8Array, privKey: PrivKey, opts?: import("./abstract/weierstrass.js").SignOpts | undefined) => import("./abstract/weierstrass.js").SignatureType; | ||
verify: (signature: Hex | import("./abstract/weierstrass.js").SignatureType, msgHash: Hex, publicKey: import("./abstract/weierstrass.js").PubKey, opts?: { | ||
lowS?: boolean | undefined; | ||
} | undefined) => boolean; | ||
Point: import("./abstract/weierstrass.js").PointConstructor<bigint>; | ||
ProjectivePoint: import("./abstract/weierstrass.js").ProjectiveConstructor<bigint>; | ||
verify: (signature: Hex | { | ||
r: bigint; | ||
s: bigint; | ||
}, msgHash: Hex, publicKey: Hex, opts?: import("./abstract/weierstrass.js").VerOpts | undefined) => boolean; | ||
ProjectivePoint: import("./abstract/weierstrass.js").ProjConstructor<bigint>; | ||
Signature: import("./abstract/weierstrass.js").SignatureConstructor; | ||
utils: { | ||
_bigintToBytes: (num: bigint) => Uint8Array; | ||
_bigintToString: (num: bigint) => string; | ||
_normalizePrivateKey: (key: PrivKey) => bigint; | ||
_normalizePublicKey: (publicKey: import("./abstract/weierstrass.js").PubKey) => PointType<bigint>; | ||
_isWithinCurveOrder: (num: bigint) => boolean; | ||
_isValidFieldElement: (num: bigint) => boolean; | ||
_weierstrassEquation: (x: bigint) => bigint; | ||
isValidPrivateKey(privateKey: PrivKey): boolean; | ||
@@ -66,12 +56,5 @@ hashToPrivateKey: (hash: Hex) => Uint8Array; | ||
}>; | ||
export declare function taggedHash(tag: string, ...messages: Uint8Array[]): Uint8Array; | ||
declare class SchnorrSignature { | ||
readonly r: bigint; | ||
readonly s: bigint; | ||
constructor(r: bigint, s: bigint); | ||
static fromHex(hex: Hex): SchnorrSignature; | ||
assertValidity(): void; | ||
toHex(): string; | ||
toRawBytes(): Uint8Array; | ||
} | ||
declare function taggedHash(tag: string, ...messages: Uint8Array[]): Uint8Array; | ||
declare function lift_x(x: bigint): PointType<bigint>; | ||
declare function schnorrGetPublicKey(privateKey: PrivKey): Uint8Array; | ||
/** | ||
@@ -84,3 +67,3 @@ * Synchronously creates Schnorr signature. Improved security: verifies itself before | ||
*/ | ||
declare function schnorrSign(message: Hex, privateKey: PrivKey, auxRand?: Hex): Uint8Array; | ||
declare function schnorrSign(message: Hex, privateKey: Hex, auxRand?: Hex): Uint8Array; | ||
/** | ||
@@ -91,7 +74,12 @@ * Verifies Schnorr signature synchronously. | ||
export declare const schnorr: { | ||
Signature: typeof SchnorrSignature; | ||
getPublicKey: (privateKey: PrivKey) => Uint8Array; | ||
getPublicKey: typeof schnorrGetPublicKey; | ||
sign: typeof schnorrSign; | ||
verify: typeof schnorrVerify; | ||
utils: { | ||
lift_x: typeof lift_x; | ||
int: typeof bytesToNum; | ||
taggedHash: typeof taggedHash; | ||
}; | ||
}; | ||
export {}; | ||
declare const hashToCurve: (msg: Hex, options?: htf.htfBasicOpts | undefined) => htf.H2CPoint<bigint>, encodeToCurve: (msg: Hex, options?: htf.htfBasicOpts | undefined) => htf.H2CPoint<bigint>; | ||
export { hashToCurve, encodeToCurve }; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.schnorr = exports.taggedHash = exports.secp256k1 = void 0; | ||
exports.encodeToCurve = exports.hashToCurve = exports.schnorr = exports.secp256k1 = void 0; | ||
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */ | ||
@@ -11,3 +11,3 @@ const sha256_1 = require("@noble/hashes/sha256"); | ||
const utils_1 = require("@noble/hashes/utils"); | ||
const hash_to_curve_js_1 = require("./abstract/hash-to-curve.js"); | ||
const htf = require("./abstract/hash-to-curve.js"); | ||
/** | ||
@@ -26,6 +26,3 @@ * secp256k1 belongs to Koblitz curves: it has efficiently computable endomorphism. | ||
/** | ||
* Allows to compute square root √y 2x faster. | ||
* To calculate √y, we need to exponentiate it to a very big number: | ||
* `y² = x³ + ax + b; y = y² ^ (p+1)/4` | ||
* We are unwrapping the loop and multiplying it bit-by-bit. | ||
* √n = n^((p+1)/4) for fields p = 3 mod 4. We unwrap the loop and multiply bit-by-bit. | ||
* (P+1n/4n).toString(2) would produce bits [223x 1, 0, 22x 1, 4x 0, 11, 00] | ||
@@ -53,3 +50,3 @@ */ | ||
const root = (0, modular_js_1.pow2)(t2, _2n, P); | ||
if (!Fp.equals(Fp.square(root), y)) | ||
if (!Fp.eql(Fp.sqr(root), y)) | ||
throw new Error('Cannot find square root'); | ||
@@ -59,36 +56,2 @@ return root; | ||
const Fp = (0, modular_js_1.Fp)(secp256k1P, undefined, undefined, { sqrt: sqrtMod }); | ||
const isoMap = (0, hash_to_curve_js_1.isogenyMap)(Fp, [ | ||
// xNum | ||
[ | ||
'0x8e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38daaaaa8c7', | ||
'0x7d3d4c80bc321d5b9f315cea7fd44c5d595d2fc0bf63b92dfff1044f17c6581', | ||
'0x534c328d23f234e6e2a413deca25caece4506144037c40314ecbd0b53d9dd262', | ||
'0x8e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38daaaaa88c', | ||
], | ||
// xDen | ||
[ | ||
'0xd35771193d94918a9ca34ccbb7b640dd86cd409542f8487d9fe6b745781eb49b', | ||
'0xedadc6f64383dc1df7c4b2d51b54225406d36b641f5e41bbc52a56612a8c6d14', | ||
'0x0000000000000000000000000000000000000000000000000000000000000001', // LAST 1 | ||
], | ||
// yNum | ||
[ | ||
'0x4bda12f684bda12f684bda12f684bda12f684bda12f684bda12f684b8e38e23c', | ||
'0xc75e0c32d5cb7c0fa9d0a54b12a0a6d5647ab046d686da6fdffc90fc201d71a3', | ||
'0x29a6194691f91a73715209ef6512e576722830a201be2018a765e85a9ecee931', | ||
'0x2f684bda12f684bda12f684bda12f684bda12f684bda12f684bda12f38e38d84', | ||
], | ||
// yDen | ||
[ | ||
'0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffff93b', | ||
'0x7a06534bb8bdb49fd5e9e6632722c2989467c1bfc8e8d978dfb425d2685c2573', | ||
'0x6484aa716545ca2cf3a70c3fa8fe337e0a3d21162f0d6299a7bf8192bfd2a76f', | ||
'0x0000000000000000000000000000000000000000000000000000000000000001', // LAST 1 | ||
], | ||
].map((i) => i.map((j) => BigInt(j)))); | ||
const mapSWU = (0, weierstrass_js_1.mapToCurveSimpleSWU)(Fp, { | ||
A: BigInt('0x3f8731abdd661adca08a5558f0f5d272e953d363cb6f0e5d405447c01a444533'), | ||
B: BigInt('1771'), | ||
Z: Fp.create(BigInt('-11')), | ||
}); | ||
exports.secp256k1 = (0, _shortw_utils_js_1.createCurve)({ | ||
@@ -136,47 +99,9 @@ // Params: a, b | ||
}, | ||
mapToCurve: (scalars) => { | ||
const { x, y } = mapSWU(Fp.create(scalars[0])); | ||
return isoMap(x, y); | ||
}, | ||
htfDefaults: { | ||
DST: 'secp256k1_XMD:SHA-256_SSWU_RO_', | ||
p: Fp.ORDER, | ||
m: 1, | ||
k: 128, | ||
expand: 'xmd', | ||
hash: sha256_1.sha256, | ||
}, | ||
}, sha256_1.sha256); | ||
// Schnorr | ||
// Schnorr signatures are superior to ECDSA from above. | ||
// Below is Schnorr-specific code as per BIP0340. | ||
// https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki | ||
const _0n = BigInt(0); | ||
const numTo32b = exports.secp256k1.utils._bigintToBytes; | ||
const numTo32bStr = exports.secp256k1.utils._bigintToString; | ||
const normalizePrivateKey = exports.secp256k1.utils._normalizePrivateKey; | ||
// TODO: export? | ||
function normalizePublicKey(publicKey) { | ||
if (publicKey instanceof exports.secp256k1.Point) { | ||
publicKey.assertValidity(); | ||
return publicKey; | ||
} | ||
else { | ||
const bytes = (0, utils_js_1.ensureBytes)(publicKey); | ||
// Schnorr is 32 bytes | ||
if (bytes.length !== 32) | ||
throw new Error('Schnorr pubkeys must be 32 bytes'); | ||
const x = (0, utils_js_1.bytesToNumberBE)(bytes); | ||
if (!isValidFieldElement(x)) | ||
throw new Error('Point is not on curve'); | ||
const y2 = exports.secp256k1.utils._weierstrassEquation(x); // y² = x³ + ax + b | ||
let y = sqrtMod(y2); // y = y² ^ (p+1)/4 | ||
const isYOdd = (y & _1n) === _1n; | ||
// Schnorr | ||
if (isYOdd) | ||
y = exports.secp256k1.CURVE.Fp.negate(y); | ||
const point = new exports.secp256k1.Point(x, y); | ||
point.assertValidity(); | ||
return point; | ||
} | ||
} | ||
const isWithinCurveOrder = exports.secp256k1.utils._isWithinCurveOrder; | ||
const isValidFieldElement = exports.secp256k1.utils._isValidFieldElement; | ||
const fe = (x) => typeof x === 'bigint' && _0n < x && x < secp256k1P; | ||
const ge = (x) => typeof x === 'bigint' && _0n < x && x < secp256k1N; | ||
const TAGS = { | ||
@@ -198,42 +123,34 @@ challenge: 'BIP0340/challenge', | ||
} | ||
exports.taggedHash = taggedHash; | ||
const toRawX = (point) => point.toRawBytes(true).slice(1); | ||
// Schnorr signatures are superior to ECDSA from above. | ||
// Below is Schnorr-specific code as per BIP0340. | ||
function schnorrChallengeFinalize(ch) { | ||
return (0, modular_js_1.mod)((0, utils_js_1.bytesToNumberBE)(ch), exports.secp256k1.CURVE.n); | ||
} | ||
// Do we need this at all for Schnorr? | ||
class SchnorrSignature { | ||
constructor(r, s) { | ||
this.r = r; | ||
this.s = s; | ||
this.assertValidity(); | ||
} | ||
static fromHex(hex) { | ||
const bytes = (0, utils_js_1.ensureBytes)(hex); | ||
const len = 32; // group length | ||
if (bytes.length !== 2 * len) | ||
throw new TypeError(`SchnorrSignature.fromHex: expected ${2 * len} bytes, not ${bytes.length}`); | ||
const r = (0, utils_js_1.bytesToNumberBE)(bytes.subarray(0, len)); | ||
const s = (0, utils_js_1.bytesToNumberBE)(bytes.subarray(len, 2 * len)); | ||
return new SchnorrSignature(r, s); | ||
} | ||
assertValidity() { | ||
const { r, s } = this; | ||
if (!isValidFieldElement(r) || !isWithinCurveOrder(s)) | ||
throw new Error('Invalid signature'); | ||
} | ||
toHex() { | ||
return numTo32bStr(this.r) + numTo32bStr(this.s); | ||
} | ||
toRawBytes() { | ||
return (0, utils_js_1.hexToBytes)(this.toHex()); | ||
} | ||
} | ||
const numTo32b = (n) => (0, utils_js_1.numberToBytesBE)(n, 32); | ||
const modN = (x) => (0, modular_js_1.mod)(x, secp256k1N); | ||
const _Point = exports.secp256k1.ProjectivePoint; | ||
const Gmul = (priv) => _Point.fromPrivateKey(priv); | ||
const GmulAdd = (Q, a, b) => _Point.BASE.multiplyAndAddUnsafe(Q, a, b); | ||
function schnorrGetScalar(priv) { | ||
const point = exports.secp256k1.Point.fromPrivateKey(priv); | ||
const scalar = point.hasEvenY() ? priv : exports.secp256k1.CURVE.n - priv; | ||
// Let d' = int(sk) | ||
// Fail if d' = 0 or d' ≥ n | ||
// Let P = d'⋅G | ||
// Let d = d' if has_even_y(P), otherwise let d = n - d' . | ||
const point = Gmul(priv); | ||
const scalar = point.hasEvenY() ? priv : modN(-priv); | ||
return { point, scalar, x: toRawX(point) }; | ||
} | ||
function lift_x(x) { | ||
if (!fe(x)) | ||
throw new Error('bad x: need 0 < x < p'); // Fail if x ≥ p. | ||
const c = (0, modular_js_1.mod)(x * x * x + BigInt(7), secp256k1P); // Let c = x³ + 7 mod p. | ||
let y = sqrtMod(c); // Let y = c^(p+1)/4 mod p. | ||
if (y % 2n !== 0n) | ||
y = (0, modular_js_1.mod)(-y, secp256k1P); // Return the unique point P such that x(P) = x and | ||
const p = new _Point(x, y, _1n); // y(P) = y if y mod 2 = 0 or y(P) = p-y otherwise. | ||
p.assertValidity(); | ||
return p; | ||
} | ||
function challenge(...args) { | ||
return modN((0, utils_js_1.bytesToNumberBE)(taggedHash(TAGS.challenge, ...args))); | ||
} | ||
function schnorrGetPublicKey(privateKey) { | ||
return toRawX(Gmul(privateKey)); // Let d' = int(sk). Fail if d' = 0 or d' ≥ n. Return bytes(d'⋅G) | ||
} | ||
/** | ||
@@ -248,19 +165,19 @@ * Synchronously creates Schnorr signature. Improved security: verifies itself before | ||
if (message == null) | ||
throw new TypeError(`sign: Expected valid message, not "${message}"`); | ||
throw new Error(`sign: Expected valid message, not "${message}"`); | ||
const m = (0, utils_js_1.ensureBytes)(message); | ||
// checks for isWithinCurveOrder | ||
const { x: px, scalar: d } = schnorrGetScalar(normalizePrivateKey(privateKey)); | ||
const rand = (0, utils_js_1.ensureBytes)(auxRand); | ||
if (rand.length !== 32) | ||
throw new TypeError('sign: Expected 32 bytes of aux randomness'); | ||
const tag = taggedHash; | ||
const t0h = tag(TAGS.aux, rand); | ||
const t = numTo32b(d ^ (0, utils_js_1.bytesToNumberBE)(t0h)); | ||
const k0h = tag(TAGS.nonce, t, px, m); | ||
const k0 = (0, modular_js_1.mod)((0, utils_js_1.bytesToNumberBE)(k0h), exports.secp256k1.CURVE.n); | ||
if (k0 === _0n) | ||
throw new Error('sign: Creation of signature failed. k is zero'); | ||
const { point: R, x: rx, scalar: k } = schnorrGetScalar(k0); | ||
const e = schnorrChallengeFinalize(tag(TAGS.challenge, rx, px, m)); | ||
const sig = new SchnorrSignature(R.x, (0, modular_js_1.mod)(k + e * d, exports.secp256k1.CURVE.n)).toRawBytes(); | ||
const { x: px, scalar: d } = schnorrGetScalar((0, utils_js_1.bytesToNumberBE)((0, utils_js_1.ensureBytes)(privateKey, 32))); | ||
const a = (0, utils_js_1.ensureBytes)(auxRand, 32); // Auxiliary random data a: a 32-byte array | ||
// TODO: replace with proper xor? | ||
const t = numTo32b(d ^ (0, utils_js_1.bytesToNumberBE)(taggedHash(TAGS.aux, a))); // Let t be the byte-wise xor of bytes(d) and hash/aux(a) | ||
const rand = taggedHash(TAGS.nonce, t, px, m); // Let rand = hash/nonce(t || bytes(P) || m) | ||
const k_ = modN((0, utils_js_1.bytesToNumberBE)(rand)); // Let k' = int(rand) mod n | ||
if (k_ === _0n) | ||
throw new Error('sign failed: k is zero'); // Fail if k' = 0. | ||
const { point: R, x: rx, scalar: k } = schnorrGetScalar(k_); // Let R = k'⋅G. | ||
const e = challenge(rx, px, m); // Let e = int(hash/challenge(bytes(R) || bytes(P) || m)) mod n. | ||
const sig = new Uint8Array(64); // Let sig = bytes(R) || bytes((k + ed) mod n). | ||
sig.set(numTo32b(R.px), 0); | ||
sig.set(numTo32b(modN(k + e * d)), 32); | ||
// If Verify(bytes(P), m, sig) (see below) returns failure, abort | ||
if (!schnorrVerify(sig, m, px)) | ||
@@ -275,17 +192,16 @@ throw new Error('sign: Invalid signature produced'); | ||
try { | ||
const raw = signature instanceof SchnorrSignature; | ||
const sig = raw ? signature : SchnorrSignature.fromHex(signature); | ||
if (raw) | ||
sig.assertValidity(); // just in case | ||
const { r, s } = sig; | ||
const P = lift_x((0, utils_js_1.bytesToNumberBE)((0, utils_js_1.ensureBytes)(publicKey, 32))); // P = lift_x(int(pk)); fail if that fails | ||
const sig = (0, utils_js_1.ensureBytes)(signature, 64); | ||
const r = (0, utils_js_1.bytesToNumberBE)(sig.subarray(0, 32)); // Let r = int(sig[0:32]); fail if r ≥ p. | ||
if (!fe(r)) | ||
return false; | ||
const s = (0, utils_js_1.bytesToNumberBE)(sig.subarray(32, 64)); // Let s = int(sig[32:64]); fail if s ≥ n. | ||
if (!ge(s)) | ||
return false; | ||
const m = (0, utils_js_1.ensureBytes)(message); | ||
const P = normalizePublicKey(publicKey); | ||
const e = schnorrChallengeFinalize(taggedHash(TAGS.challenge, numTo32b(r), toRawX(P), m)); | ||
// Finalize | ||
// R = s⋅G - e⋅P | ||
// -eP == (n-e)P | ||
const R = exports.secp256k1.Point.BASE.multiplyAndAddUnsafe(P, normalizePrivateKey(s), (0, modular_js_1.mod)(-e, exports.secp256k1.CURVE.n)); | ||
if (!R || !R.hasEvenY() || R.x !== r) | ||
return false; | ||
return true; | ||
const e = challenge(numTo32b(r), toRawX(P), m); // int(challenge(bytes(r)||bytes(P)||m)) mod n | ||
const R = GmulAdd(P, s, modN(-e)); // R = s⋅G - e⋅P | ||
if (!R || !R.hasEvenY() || R.toAffine().x !== r) | ||
return false; // -eP == (n-e)P | ||
return true; // Fail if is_infinite(R) / not has_even_y(R) / x(R) ≠ r. | ||
} | ||
@@ -297,7 +213,55 @@ catch (error) { | ||
exports.schnorr = { | ||
Signature: SchnorrSignature, | ||
// Schnorr's pubkey is just `x` of Point (BIP340) | ||
getPublicKey: (privateKey) => toRawX(exports.secp256k1.Point.fromPrivateKey(privateKey)), | ||
getPublicKey: schnorrGetPublicKey, | ||
sign: schnorrSign, | ||
verify: schnorrVerify, | ||
utils: { lift_x, int: utils_js_1.bytesToNumberBE, taggedHash }, | ||
}; | ||
const isoMap = htf.isogenyMap(Fp, [ | ||
// xNum | ||
[ | ||
'0x8e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38daaaaa8c7', | ||
'0x7d3d4c80bc321d5b9f315cea7fd44c5d595d2fc0bf63b92dfff1044f17c6581', | ||
'0x534c328d23f234e6e2a413deca25caece4506144037c40314ecbd0b53d9dd262', | ||
'0x8e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38daaaaa88c', | ||
], | ||
// xDen | ||
[ | ||
'0xd35771193d94918a9ca34ccbb7b640dd86cd409542f8487d9fe6b745781eb49b', | ||
'0xedadc6f64383dc1df7c4b2d51b54225406d36b641f5e41bbc52a56612a8c6d14', | ||
'0x0000000000000000000000000000000000000000000000000000000000000001', // LAST 1 | ||
], | ||
// yNum | ||
[ | ||
'0x4bda12f684bda12f684bda12f684bda12f684bda12f684bda12f684b8e38e23c', | ||
'0xc75e0c32d5cb7c0fa9d0a54b12a0a6d5647ab046d686da6fdffc90fc201d71a3', | ||
'0x29a6194691f91a73715209ef6512e576722830a201be2018a765e85a9ecee931', | ||
'0x2f684bda12f684bda12f684bda12f684bda12f684bda12f684bda12f38e38d84', | ||
], | ||
// yDen | ||
[ | ||
'0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffff93b', | ||
'0x7a06534bb8bdb49fd5e9e6632722c2989467c1bfc8e8d978dfb425d2685c2573', | ||
'0x6484aa716545ca2cf3a70c3fa8fe337e0a3d21162f0d6299a7bf8192bfd2a76f', | ||
'0x0000000000000000000000000000000000000000000000000000000000000001', // LAST 1 | ||
], | ||
].map((i) => i.map((j) => BigInt(j)))); | ||
const mapSWU = (0, weierstrass_js_1.mapToCurveSimpleSWU)(Fp, { | ||
A: BigInt('0x3f8731abdd661adca08a5558f0f5d272e953d363cb6f0e5d405447c01a444533'), | ||
B: BigInt('1771'), | ||
Z: Fp.create(BigInt('-11')), | ||
}); | ||
const { hashToCurve, encodeToCurve } = htf.hashToCurve(exports.secp256k1.ProjectivePoint, (scalars) => { | ||
const { x, y } = mapSWU(Fp.create(scalars[0])); | ||
return isoMap(x, y); | ||
}, { | ||
DST: 'secp256k1_XMD:SHA-256_SSWU_RO_', | ||
encodeDST: 'secp256k1_XMD:SHA-256_SSWU_NU_', | ||
p: Fp.ORDER, | ||
m: 1, | ||
k: 128, | ||
expand: 'xmd', | ||
hash: sha256_1.sha256, | ||
}); | ||
exports.hashToCurve = hashToCurve; | ||
exports.encodeToCurve = encodeToCurve; |
@@ -1,4 +0,5 @@ | ||
import { ProjectivePointType } from './abstract/weierstrass.js'; | ||
import { ProjPointType } from './abstract/weierstrass.js'; | ||
import * as cutils from './abstract/utils.js'; | ||
declare type ProjectivePoint = ProjectivePointType<bigint>; | ||
import { Field } from './abstract/modular.js'; | ||
declare type ProjectivePoint = ProjPointType<bigint>; | ||
export declare const starkCurve: import("./abstract/weierstrass.js").CurveFn; | ||
@@ -12,3 +13,3 @@ declare function getPublicKey0x(privKey: Hex, isCompressed?: boolean): Uint8Array; | ||
readonly nByteLength: number; | ||
readonly Fp: import("./abstract/modular.js").Field<bigint>; | ||
readonly Fp: Field<bigint>; | ||
readonly n: bigint; | ||
@@ -33,9 +34,4 @@ readonly h: bigint; | ||
} | undefined; | ||
readonly isTorsionFree?: ((c: import("./abstract/weierstrass.js").ProjectiveConstructor<bigint>, point: ProjectivePointType<bigint>) => boolean) | undefined; | ||
readonly clearCofactor?: ((c: import("./abstract/weierstrass.js").ProjectiveConstructor<bigint>, point: ProjectivePointType<bigint>) => ProjectivePointType<bigint>) | undefined; | ||
readonly htfDefaults?: import("./abstract/hash-to-curve.js").htfOpts | undefined; | ||
readonly mapToCurve?: ((scalar: bigint[]) => { | ||
x: bigint; | ||
y: bigint; | ||
}) | undefined; | ||
readonly isTorsionFree?: ((c: import("./abstract/weierstrass.js").ProjConstructor<bigint>, point: ProjPointType<bigint>) => boolean) | undefined; | ||
readonly clearCofactor?: ((c: import("./abstract/weierstrass.js").ProjConstructor<bigint>, point: ProjPointType<bigint>) => ProjPointType<bigint>) | undefined; | ||
lowS: boolean; | ||
@@ -45,12 +41,7 @@ readonly hash: cutils.CHash; | ||
readonly randomBytes: (bytesLength?: number | undefined) => Uint8Array; | ||
readonly truncateHash?: ((hash: Uint8Array, truncateOnly?: boolean | undefined) => bigint) | undefined; | ||
}>, Point: import("./abstract/weierstrass.js").PointConstructor<bigint>, ProjectivePoint: import("./abstract/weierstrass.js").ProjectiveConstructor<bigint>, Signature: import("./abstract/weierstrass.js").SignatureConstructor; | ||
readonly bits2int?: ((bytes: Uint8Array) => bigint) | undefined; | ||
readonly bits2int_modN?: ((bytes: Uint8Array) => bigint) | undefined; | ||
}>, ProjectivePoint: import("./abstract/weierstrass.js").ProjConstructor<bigint>, Signature: import("./abstract/weierstrass.js").SignatureConstructor; | ||
export declare const utils: { | ||
_bigintToBytes: (num: bigint) => Uint8Array; | ||
_bigintToString: (num: bigint) => string; | ||
_normalizePrivateKey: (key: cutils.PrivKey) => bigint; | ||
_normalizePublicKey: (publicKey: import("./abstract/weierstrass.js").PubKey) => import("./abstract/weierstrass.js").PointType<bigint>; | ||
_isWithinCurveOrder: (num: bigint) => boolean; | ||
_isValidFieldElement: (num: bigint) => boolean; | ||
_weierstrassEquation: (x: bigint) => bigint; | ||
isValidPrivateKey(privateKey: cutils.PrivKey): boolean; | ||
@@ -60,3 +51,3 @@ hashToPrivateKey: (hash: cutils.Hex) => Uint8Array; | ||
}; | ||
export { CURVE, Point, Signature, ProjectivePoint, getPublicKey0x as getPublicKey, getSharedSecret0x as getSharedSecret, sign0x as sign, verify0x as verify, }; | ||
export { CURVE, Signature, ProjectivePoint, getPublicKey0x as getPublicKey, getSharedSecret0x as getSharedSecret, sign0x as sign, verify0x as verify, }; | ||
export declare const bytesToHexEth: (uint8a: Uint8Array) => string; | ||
@@ -75,1 +66,27 @@ export declare const strip0x: (hex: string) => string; | ||
export declare const keccak: (data: Uint8Array) => bigint; | ||
export declare const Fp253: Readonly<Field<bigint> & Required<Pick<Field<bigint>, "isOdd">>>; | ||
export declare const Fp251: Readonly<Field<bigint> & Required<Pick<Field<bigint>, "isOdd">>>; | ||
export declare function _poseidonMDS(Fp: Field<bigint>, name: string, m: number, attempt?: number): bigint[][]; | ||
export declare type PoseidonOpts = { | ||
Fp: Field<bigint>; | ||
rate: number; | ||
capacity: number; | ||
roundsFull: number; | ||
roundsPartial: number; | ||
}; | ||
export declare function poseidonBasic(opts: PoseidonOpts, mds: bigint[][]): { | ||
(values: bigint[]): bigint[]; | ||
roundConstants: bigint[][]; | ||
}; | ||
export declare function poseidonCreate(opts: PoseidonOpts, mdsAttempt?: number): { | ||
(values: bigint[]): bigint[]; | ||
roundConstants: bigint[][]; | ||
}; | ||
export declare const poseidonSmall: { | ||
(values: bigint[]): bigint[]; | ||
roundConstants: bigint[][]; | ||
}; | ||
export declare function poseidonHash(x: bigint, y: bigint, fn?: { | ||
(values: bigint[]): bigint[]; | ||
roundConstants: bigint[][]; | ||
}): bigint; |
147
lib/stark.js
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.keccak = exports.computeHashOnElements = exports.hashChain = exports.pedersen = exports.getAccountPath = exports.ethSigToPrivate = exports.getStarkKey = exports.grindKey = exports.numberToHexEth = exports.strip0x = exports.bytesToHexEth = exports.verify = exports.sign = exports.getSharedSecret = exports.getPublicKey = exports.ProjectivePoint = exports.Signature = exports.Point = exports.CURVE = exports.utils = exports.starkCurve = void 0; | ||
exports.poseidonHash = exports.poseidonSmall = exports.poseidonCreate = exports.poseidonBasic = exports._poseidonMDS = exports.Fp251 = exports.Fp253 = exports.keccak = exports.computeHashOnElements = exports.hashChain = exports.pedersen = exports.getAccountPath = exports.ethSigToPrivate = exports.getStarkKey = exports.grindKey = exports.numberToHexEth = exports.strip0x = exports.bytesToHexEth = exports.verify = exports.sign = exports.getSharedSecret = exports.getPublicKey = exports.ProjectivePoint = exports.Signature = exports.CURVE = exports.utils = exports.starkCurve = void 0; | ||
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */ | ||
@@ -11,2 +11,4 @@ const sha3_1 = require("@noble/hashes/sha3"); | ||
const _shortw_utils_js_1 = require("./_shortw_utils.js"); | ||
const poseidon = require("./abstract/poseidon.js"); | ||
const utils_1 = require("@noble/hashes/utils"); | ||
// Stark-friendly elliptic curve | ||
@@ -16,2 +18,11 @@ // https://docs.starkware.co/starkex/stark-curve.html | ||
const nBitLength = 252; | ||
// Copy-pasted from weierstrass.ts | ||
function bits2int(bytes) { | ||
const delta = bytes.length * 8 - nBitLength; | ||
const num = cutils.bytesToNumberBE(bytes); | ||
return delta > 0 ? num >> BigInt(delta) : num; | ||
} | ||
function bits2int_modN(bytes) { | ||
return (0, modular_js_1.mod)(bits2int(bytes), CURVE_N); | ||
} | ||
exports.starkCurve = (0, weierstrass_js_1.weierstrass)({ | ||
@@ -34,23 +45,18 @@ // Params: a, b | ||
...(0, _shortw_utils_js_1.getHash)(sha256_1.sha256), | ||
truncateHash: (hash, truncateOnly = false) => { | ||
// TODO: cleanup, ugly code | ||
// Fix truncation | ||
if (!truncateOnly) { | ||
let hashS = bytesToNumber0x(hash).toString(16); | ||
if (hashS.length === 63) { | ||
hashS += '0'; | ||
hash = hexToBytes0x(hashS); | ||
} | ||
// Custom truncation routines for stark curve | ||
bits2int: (bytes) => { | ||
while (bytes[0] === 0) | ||
bytes = bytes.subarray(1); | ||
return bits2int(bytes); | ||
}, | ||
bits2int_modN: (bytes) => { | ||
let hashS = cutils.bytesToNumberBE(bytes).toString(16); | ||
if (hashS.length === 63) { | ||
hashS += '0'; | ||
bytes = hexToBytes0x(hashS); | ||
} | ||
// Truncate zero bytes on left (compat with elliptic) | ||
while (hash[0] === 0) | ||
hash = hash.subarray(1); | ||
const byteLength = hash.length; | ||
const delta = byteLength * 8 - nBitLength; // size of curve.n (252 bits) | ||
let h = hash.length ? bytesToNumber0x(hash) : 0n; | ||
if (delta > 0) | ||
h = h >> BigInt(delta); | ||
if (!truncateOnly && h >= CURVE_N) | ||
h -= CURVE_N; | ||
return h; | ||
while (bytes[0] === 0) | ||
bytes = bytes.subarray(1); | ||
return bits2int_modN(bytes); | ||
}, | ||
@@ -61,3 +67,3 @@ }); | ||
if (typeof hex !== 'string') { | ||
throw new TypeError('hexToBytes: expected string, got ' + typeof hex); | ||
throw new Error('hexToBytes: expected string, got ' + typeof hex); | ||
} | ||
@@ -82,3 +88,3 @@ hex = (0, exports.strip0x)(hex); | ||
if (typeof hex !== 'string') { | ||
throw new TypeError('hexToNumber: expected string, got ' + typeof hex); | ||
throw new Error('hexToNumber: expected string, got ' + typeof hex); | ||
} | ||
@@ -98,3 +104,3 @@ // Big Endian | ||
function normalizePrivateKey(privKey) { | ||
return cutils.bytesToHex(ensureBytes0x(privKey)).padStart(32 * 2, '0'); | ||
return cutils.bytesToHex(ensureBytes0x(privKey)).padStart(64, '0'); | ||
} | ||
@@ -120,5 +126,4 @@ function getPublicKey0x(privKey, isCompressed) { | ||
exports.verify = verify0x; | ||
const { CURVE, Point, ProjectivePoint, Signature } = exports.starkCurve; | ||
const { CURVE, ProjectivePoint, Signature } = exports.starkCurve; | ||
exports.CURVE = CURVE; | ||
exports.Point = Point; | ||
exports.ProjectivePoint = ProjectivePoint; | ||
@@ -139,3 +144,3 @@ exports.Signature = Signature; | ||
indexHex = '0' + indexHex; | ||
return bytesToNumber0x((0, sha256_1.sha256)(cutils.concatBytes(key, hexToBytes0x(indexHex)))); | ||
return sha256Num(cutils.concatBytes(key, hexToBytes0x(indexHex))); | ||
} | ||
@@ -145,4 +150,3 @@ function grindKey(seed) { | ||
const sha256mask = 2n ** 256n; | ||
const Fn = (0, modular_js_1.Fp)(CURVE.n); | ||
const limit = sha256mask - Fn.create(sha256mask); | ||
const limit = sha256mask - (0, modular_js_1.mod)(sha256mask, CURVE_N); | ||
for (let i = 0;; i++) { | ||
@@ -152,3 +156,3 @@ const key = hashKeyWithIndex(_seed, i); | ||
if (key < limit) | ||
return Fn.create(key).toString(16); | ||
return (0, modular_js_1.mod)(key, CURVE_N).toString(16); | ||
} | ||
@@ -171,4 +175,4 @@ } | ||
function getAccountPath(layer, application, ethereumAddress, index) { | ||
const layerNum = int31(bytesToNumber0x((0, sha256_1.sha256)(layer))); | ||
const applicationNum = int31(bytesToNumber0x((0, sha256_1.sha256)(application))); | ||
const layerNum = int31(sha256Num(layer)); | ||
const applicationNum = int31(sha256Num(application)); | ||
const eth = hexToNumber0x(ethereumAddress); | ||
@@ -180,10 +184,10 @@ return `m/2645'/${layerNum}'/${applicationNum}'/${int31(eth)}'/${int31(eth >> 31n)}'/${index}`; | ||
const PEDERSEN_POINTS_AFFINE = [ | ||
new Point(2089986280348253421170679821480865132823066470938446095505822317253594081284n, 1713931329540660377023406109199410414810705867260802078187082345529207694986n), | ||
new Point(996781205833008774514500082376783249102396023663454813447423147977397232763n, 1668503676786377725805489344771023921079126552019160156920634619255970485781n), | ||
new Point(2251563274489750535117886426533222435294046428347329203627021249169616184184n, 1798716007562728905295480679789526322175868328062420237419143593021674992973n), | ||
new Point(2138414695194151160943305727036575959195309218611738193261179310511854807447n, 113410276730064486255102093846540133784865286929052426931474106396135072156n), | ||
new Point(2379962749567351885752724891227938183011949129833673362440656643086021394946n, 776496453633298175483985398648758586525933812536653089401905292063708816422n), | ||
new ProjectivePoint(2089986280348253421170679821480865132823066470938446095505822317253594081284n, 1713931329540660377023406109199410414810705867260802078187082345529207694986n, 1n), | ||
new ProjectivePoint(996781205833008774514500082376783249102396023663454813447423147977397232763n, 1668503676786377725805489344771023921079126552019160156920634619255970485781n, 1n), | ||
new ProjectivePoint(2251563274489750535117886426533222435294046428347329203627021249169616184184n, 1798716007562728905295480679789526322175868328062420237419143593021674992973n, 1n), | ||
new ProjectivePoint(2138414695194151160943305727036575959195309218611738193261179310511854807447n, 113410276730064486255102093846540133784865286929052426931474106396135072156n, 1n), | ||
new ProjectivePoint(2379962749567351885752724891227938183011949129833673362440656643086021394946n, 776496453633298175483985398648758586525933812536653089401905292063708816422n, 1n), | ||
]; | ||
// for (const p of PEDERSEN_POINTS) p._setWindowSize(8); | ||
const PEDERSEN_POINTS = PEDERSEN_POINTS_AFFINE.map(ProjectivePoint.fromAffine); | ||
const PEDERSEN_POINTS = PEDERSEN_POINTS_AFFINE; | ||
function pedersenPrecompute(p1, p2) { | ||
@@ -227,3 +231,3 @@ const out = []; | ||
const pt = constants[j]; | ||
if (pt.x === point.x) | ||
if (pt.px === point.px) | ||
throw new Error('Same point'); | ||
@@ -241,3 +245,3 @@ if ((x & 1n) !== 0n) | ||
point = pedersenSingle(point, y, PEDERSEN_POINTS2); | ||
return (0, exports.bytesToHexEth)(point.toAffine().toRawBytes(true).slice(1)); | ||
return (0, exports.bytesToHexEth)(point.toRawBytes(true).slice(1)); | ||
} | ||
@@ -258,4 +262,67 @@ exports.pedersen = pedersen; | ||
exports.computeHashOnElements = computeHashOnElements; | ||
const MASK_250 = 2n ** 250n - 1n; | ||
const MASK_250 = cutils.bitMask(250); | ||
const keccak = (data) => bytesToNumber0x((0, sha3_1.keccak_256)(data)) & MASK_250; | ||
exports.keccak = keccak; | ||
const sha256Num = (data) => cutils.bytesToNumberBE((0, sha256_1.sha256)(data)); | ||
// Poseidon hash | ||
exports.Fp253 = (0, modular_js_1.Fp)(BigInt('14474011154664525231415395255581126252639794253786371766033694892385558855681')); // 2^253 + 2^199 + 1 | ||
exports.Fp251 = (0, modular_js_1.Fp)(BigInt('3618502788666131213697322783095070105623107215331596699973092056135872020481')); // 2^251 + 17 * 2^192 + 1 | ||
function poseidonRoundConstant(Fp, name, idx) { | ||
const val = Fp.fromBytes((0, sha256_1.sha256)((0, utils_1.utf8ToBytes)(`${name}${idx}`))); | ||
return Fp.create(val); | ||
} | ||
// NOTE: doesn't check eiginvalues and possible can create unsafe matrix. But any filtration here will break compatibility with starknet | ||
// Please use only if you really know what you doing. | ||
// https://eprint.iacr.org/2019/458.pdf Section 2.3 (Avoiding Insecure Matrices) | ||
function _poseidonMDS(Fp, name, m, attempt = 0) { | ||
const x_values = []; | ||
const y_values = []; | ||
for (let i = 0; i < m; i++) { | ||
x_values.push(poseidonRoundConstant(Fp, `${name}x`, attempt * m + i)); | ||
y_values.push(poseidonRoundConstant(Fp, `${name}y`, attempt * m + i)); | ||
} | ||
if (new Set([...x_values, ...y_values]).size !== 2 * m) | ||
throw new Error('X and Y values are not distinct'); | ||
return x_values.map((x) => y_values.map((y) => Fp.inv(Fp.sub(x, y)))); | ||
} | ||
exports._poseidonMDS = _poseidonMDS; | ||
const MDS_SMALL = [ | ||
[3, 1, 1], | ||
[1, -1, 1], | ||
[1, 1, -2], | ||
].map((i) => i.map(BigInt)); | ||
function poseidonBasic(opts, mds) { | ||
(0, modular_js_1.validateField)(opts.Fp); | ||
if (!Number.isSafeInteger(opts.rate) || !Number.isSafeInteger(opts.capacity)) | ||
throw new Error(`Wrong poseidon opts: ${opts}`); | ||
const m = opts.rate + opts.capacity; | ||
const rounds = opts.roundsFull + opts.roundsPartial; | ||
const roundConstants = []; | ||
for (let i = 0; i < rounds; i++) { | ||
const row = []; | ||
for (let j = 0; j < m; j++) | ||
row.push(poseidonRoundConstant(opts.Fp, 'Hades', m * i + j)); | ||
roundConstants.push(row); | ||
} | ||
return poseidon.poseidon({ | ||
...opts, | ||
t: m, | ||
sboxPower: 3, | ||
reversePartialPowIdx: true, | ||
mds, | ||
roundConstants, | ||
}); | ||
} | ||
exports.poseidonBasic = poseidonBasic; | ||
function poseidonCreate(opts, mdsAttempt = 0) { | ||
const m = opts.rate + opts.capacity; | ||
if (!Number.isSafeInteger(mdsAttempt)) | ||
throw new Error(`Wrong mdsAttempt=${mdsAttempt}`); | ||
return poseidonBasic(opts, _poseidonMDS(opts.Fp, 'HadesMDS', m, mdsAttempt)); | ||
} | ||
exports.poseidonCreate = poseidonCreate; | ||
exports.poseidonSmall = poseidonBasic({ Fp: exports.Fp251, rate: 2, capacity: 1, roundsFull: 8, roundsPartial: 83 }, MDS_SMALL); | ||
function poseidonHash(x, y, fn = exports.poseidonSmall) { | ||
return fn([x, y, 2n])[0]; | ||
} | ||
exports.poseidonHash = poseidonHash; |
{ | ||
"name": "@noble/curves", | ||
"version": "0.5.2", | ||
"version": "0.6.0", | ||
"description": "Minimal, auditable JS implementation of elliptic curve cryptography", | ||
@@ -9,3 +9,3 @@ "files": [ | ||
"scripts": { | ||
"bench": "node benchmark/index.js", | ||
"bench": "cd benchmark; node index.js", | ||
"build": "tsc && tsc -p tsconfig.esm.json", | ||
@@ -36,3 +36,3 @@ "build:release": "rollup -c rollup.config.js", | ||
"micro-should": "0.3.0", | ||
"prettier": "2.6.2", | ||
"prettier": "2.8.3", | ||
"rollup": "2.75.5", | ||
@@ -78,6 +78,6 @@ "typescript": "4.7.3" | ||
}, | ||
"./abstract/group": { | ||
"types": "./lib/abstract/group.d.ts", | ||
"import": "./lib/esm/abstract/group.js", | ||
"default": "./lib/abstract/group.js" | ||
"./abstract/curve": { | ||
"types": "./lib/abstract/curve.d.ts", | ||
"import": "./lib/esm/abstract/curve.js", | ||
"default": "./lib/abstract/curve.js" | ||
}, | ||
@@ -89,2 +89,7 @@ "./abstract/utils": { | ||
}, | ||
"./abstract/poseidon": { | ||
"types": "./lib/abstract/poseidon.d.ts", | ||
"import": "./lib/esm/abstract/poseidon.js", | ||
"default": "./lib/abstract/poseidon.js" | ||
}, | ||
"./_shortw_utils": { | ||
@@ -196,2 +201,2 @@ "types": "./lib/_shortw_utils.d.ts", | ||
] | ||
} | ||
} |
@@ -9,3 +9,5 @@ # noble-curves | ||
for encoding or hashing an arbitrary string to a point on an elliptic curve | ||
- Auditable, [fast](#speed) | ||
- [Poseidon](https://www.poseidon-hash.info) ZK-friendly hash | ||
- Auditable | ||
- 🏎 [Ultra-fast](#speed), hand-optimized for caveats of JS engines | ||
- 🔍 Unique tests ensure correctness. Wycheproof vectors included | ||
@@ -28,3 +30,2 @@ - 🔻 Tree-shaking-friendly: there is no entry point, which ensures small size of your app | ||
which had security audits and were developed from 2019 to 2022. | ||
The goal is to replace them with lean UMD builds based on single-codebase noble-curves. | ||
@@ -93,2 +94,3 @@ ### This library belongs to _noble_ crypto | ||
- [abstract/hash-to-curve: Hashing strings to curve points](#abstracthash-to-curve-hashing-strings-to-curve-points) | ||
- [abstract/poseidon: Poseidon hash](#abstractposeidon-poseidon-hash) | ||
- [abstract/modular](#abstractmodular) | ||
@@ -308,9 +310,8 @@ - [abstract/utils](#abstractutils) | ||
getPublicKey: (privateKey: PrivKey, isCompressed?: boolean) => Uint8Array; | ||
getSharedSecret: (privateA: PrivKey, publicB: PubKey, isCompressed?: boolean) => Uint8Array; | ||
getSharedSecret: (privateA: PrivKey, publicB: Hex, isCompressed?: boolean) => Uint8Array; | ||
sign: (msgHash: Hex, privKey: PrivKey, opts?: SignOpts) => SignatureType; | ||
signUnhashed: (msg: Uint8Array, privKey: PrivKey, opts?: SignOpts) => SignatureType; | ||
verify: ( | ||
signature: Hex | SignatureType, | ||
msgHash: Hex, | ||
publicKey: PubKey, | ||
publicKey: Hex, | ||
opts?: { lowS?: boolean } | ||
@@ -377,2 +378,26 @@ ) => boolean; | ||
### abstract/poseidon: Poseidon hash | ||
Implements [Poseidon](https://www.poseidon-hash.info) ZK-friendly hash. | ||
There are many poseidon instances with different constants. We don't provide them, | ||
but we provide ability to specify them manually. For actual usage, check out | ||
stark curve source code. | ||
```ts | ||
import { poseidon } from '@noble/curves/abstract/poseidon'; | ||
type PoseidonOpts = { | ||
Fp: Field<bigint>; | ||
t: number; | ||
roundsFull: number; | ||
roundsPartial: number; | ||
sboxPower?: number; | ||
reversePartialPowIdx?: boolean; // Hack for stark | ||
mds: bigint[][]; | ||
roundConstants: bigint[][]; | ||
}; | ||
const instance = poseidon(opts: PoseidonOpts); | ||
``` | ||
### abstract/modular | ||
@@ -467,2 +492,21 @@ | ||
## Upgrading | ||
Differences from @noble/secp256k1 1.7: | ||
1. Different double() formula (but same addition) | ||
2. Different sqrt() function | ||
3. DRBG supports outputLen bigger than outputLen of hmac | ||
4. Support for different hash functions | ||
Differences from @noble/ed25519 1.7: | ||
1. Variable field element lengths between EDDSA/ECDH: | ||
EDDSA (RFC8032) is 456 bits / 57 bytes, ECDH (RFC7748) is 448 bits / 56 bytes | ||
2. Different addition formula (doubling is same) | ||
3. uvRatio differs between curves (half-expected, not only pow fn changes) | ||
4. Point decompression code is different (unexpected), now using generalized formula | ||
5. Domain function was no-op for ed25519, but adds some data even with empty context for ed448 | ||
## Contributing & testing | ||
@@ -469,0 +513,0 @@ |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
76
520
587374
12355