forked from tornado-packages/noble-curves
p.x, p.y are now getters executing toAffine()
This commit is contained in:
parent
21d2438a33
commit
0422e6ef38
@ -6,7 +6,7 @@
|
|||||||
"lib"
|
"lib"
|
||||||
],
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"bench": "node benchmark/index.js",
|
"bench": "cd benchmark; node index.js",
|
||||||
"build": "tsc && tsc -p tsconfig.esm.json",
|
"build": "tsc && tsc -p tsconfig.esm.json",
|
||||||
"build:release": "rollup -c rollup.config.js",
|
"build:release": "rollup -c rollup.config.js",
|
||||||
"lint": "prettier --check 'src/**/*.{js,ts}' 'test/*.js'",
|
"lint": "prettier --check 'src/**/*.{js,ts}' 'test/*.js'",
|
||||||
|
@ -68,10 +68,10 @@ export type AffinePoint = {
|
|||||||
|
|
||||||
// Instance of Extended Point with coordinates in X, Y, Z, T
|
// Instance of Extended Point with coordinates in X, Y, Z, T
|
||||||
export interface ExtendedPointType extends Group<ExtendedPointType> {
|
export interface ExtendedPointType extends Group<ExtendedPointType> {
|
||||||
readonly x: bigint;
|
readonly ex: bigint;
|
||||||
readonly y: bigint;
|
readonly ey: bigint;
|
||||||
readonly z: bigint;
|
readonly ez: bigint;
|
||||||
readonly t: bigint;
|
readonly et: bigint;
|
||||||
multiply(scalar: bigint): ExtendedPointType;
|
multiply(scalar: bigint): ExtendedPointType;
|
||||||
multiplyUnsafe(scalar: bigint): ExtendedPointType;
|
multiplyUnsafe(scalar: bigint): ExtendedPointType;
|
||||||
isSmallOrder(): boolean;
|
isSmallOrder(): boolean;
|
||||||
@ -85,7 +85,6 @@ export interface ExtendedPointConstructor extends GroupConstructor<ExtendedPoint
|
|||||||
fromAffine(p: AffinePoint): ExtendedPointType;
|
fromAffine(p: AffinePoint): ExtendedPointType;
|
||||||
fromHex(hex: Hex): ExtendedPointType;
|
fromHex(hex: Hex): ExtendedPointType;
|
||||||
fromPrivateKey(privateKey: PrivKey): ExtendedPointType; // TODO: remove
|
fromPrivateKey(privateKey: PrivKey): ExtendedPointType; // TODO: remove
|
||||||
toAffineBatch(points: ExtendedPointType[]): AffinePoint[];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type CurveFn = {
|
export type CurveFn = {
|
||||||
@ -154,6 +153,11 @@ export function twistedEdwards(curveDef: CurveType): CurveFn {
|
|||||||
// GE = subgroup element, not full group
|
// GE = subgroup element, not full group
|
||||||
return n === _0n ? n : assertGE(n);
|
return n === _0n ? n : assertGE(n);
|
||||||
}
|
}
|
||||||
|
function badc(a: any) {
|
||||||
|
return a == null || !ut.big(a);
|
||||||
|
}
|
||||||
|
|
||||||
|
const pointPrecomputes = new Map<ExtendedPoint, ExtendedPoint[]>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extended Point works in extended coordinates: (x, y, z, t) ∋ (x=x/z, y=y/z, t=xy).
|
* Extended Point works in extended coordinates: (x, y, z, t) ∋ (x=x/z, y=y/z, t=xy).
|
||||||
@ -161,51 +165,68 @@ export function twistedEdwards(curveDef: CurveType): CurveFn {
|
|||||||
* https://en.wikipedia.org/wiki/Twisted_Edwards_curve#Extended_coordinates
|
* https://en.wikipedia.org/wiki/Twisted_Edwards_curve#Extended_coordinates
|
||||||
*/
|
*/
|
||||||
class ExtendedPoint implements ExtendedPointType {
|
class ExtendedPoint implements ExtendedPointType {
|
||||||
constructor(readonly x: bigint, readonly y: bigint, readonly z: bigint, readonly t: bigint) {
|
|
||||||
if (y == null || !ut.big(y)) throw new Error('y required');
|
|
||||||
if (z == null || !ut.big(z)) throw new Error('z required');
|
|
||||||
if (t == null || !ut.big(t)) throw new Error('t required');
|
|
||||||
}
|
|
||||||
|
|
||||||
static BASE = new ExtendedPoint(CURVE.Gx, CURVE.Gy, _1n, modP(CURVE.Gx * CURVE.Gy));
|
static BASE = new ExtendedPoint(CURVE.Gx, CURVE.Gy, _1n, modP(CURVE.Gx * CURVE.Gy));
|
||||||
static ZERO = new ExtendedPoint(_0n, _1n, _1n, _0n); // 0, 1, 1, 0
|
static ZERO = new ExtendedPoint(_0n, _1n, _1n, _0n); // 0, 1, 1, 0
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
readonly ex: bigint,
|
||||||
|
readonly ey: bigint,
|
||||||
|
readonly ez: bigint,
|
||||||
|
readonly et: bigint
|
||||||
|
) {
|
||||||
|
if (badc(ey)) throw new Error('y required');
|
||||||
|
if (badc(ez)) throw new Error('z required');
|
||||||
|
if (badc(et)) throw new Error('t required');
|
||||||
|
}
|
||||||
|
|
||||||
|
get x(): bigint {
|
||||||
|
return this.toAffine().x;
|
||||||
|
}
|
||||||
|
get y(): bigint {
|
||||||
|
return this.toAffine().y;
|
||||||
|
}
|
||||||
|
|
||||||
static fromAffine(p: AffinePoint): ExtendedPoint {
|
static fromAffine(p: AffinePoint): ExtendedPoint {
|
||||||
const { x, y } = p || {};
|
const { x, y } = p || {};
|
||||||
if (p instanceof ExtendedPoint) throw new Error('fromAffine: extended point not allowed');
|
if (p instanceof ExtendedPoint) throw new Error('fromAffine: extended point not allowed');
|
||||||
if (!ut.big(x) || !ut.big(y)) throw new Error('fromAffine: invalid affine point');
|
if (!ut.big(x) || !ut.big(y)) throw new Error('fromAffine: invalid affine point');
|
||||||
if (p.x === _0n && p.y === _1n) return ExtendedPoint.ZERO;
|
return new ExtendedPoint(x, y, _1n, modP(x * y));
|
||||||
return new ExtendedPoint(p.x, p.y, _1n, modP(x * y));
|
|
||||||
}
|
}
|
||||||
// Takes a bunch of Jacobian Points but executes only one
|
static normalizeZ(points: ExtendedPoint[]): ExtendedPoint[] {
|
||||||
// invert on all of them. invert is very slow operation,
|
const toInv = Fp.invertBatch(points.map((p) => p.ez));
|
||||||
// so this improves performance massively.
|
return points.map((p, i) => p.toAffine(toInv[i])).map(ExtendedPoint.fromAffine);
|
||||||
static toAffineBatch(points: ExtendedPoint[]): AffinePoint[] {
|
|
||||||
const toInv = Fp.invertBatch(points.map((p) => p.z));
|
|
||||||
return points.map((p, i) => p.toAffine(toInv[i]));
|
|
||||||
}
|
}
|
||||||
static normalizeZ(denorm: ExtendedPoint[]): ExtendedPoint[] {
|
|
||||||
return ExtendedPoint.toAffineBatch(denorm).map(ExtendedPoint.fromAffine);
|
// We calculate precomputes for elliptic curve point multiplication
|
||||||
|
// using windowed method. This specifies window size and
|
||||||
|
// stores precomputed values. Usually only base point would be precomputed.
|
||||||
|
_WINDOW_SIZE?: number;
|
||||||
|
|
||||||
|
// "Private method", don't use it directly
|
||||||
|
_setWindowSize(windowSize: number) {
|
||||||
|
this._WINDOW_SIZE = windowSize;
|
||||||
|
pointPrecomputes.delete(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compare one point to another.
|
// Compare one point to another.
|
||||||
equals(other: ExtendedPoint): boolean {
|
equals(other: ExtendedPoint): boolean {
|
||||||
assertExtPoint(other);
|
assertExtPoint(other);
|
||||||
const { x: X1, y: Y1, z: Z1 } = this;
|
const { ex: X1, ey: Y1, ez: Z1 } = this;
|
||||||
const { x: X2, y: Y2, z: Z2 } = other;
|
const { ex: X2, ey: Y2, ez: Z2 } = other;
|
||||||
const X1Z2 = modP(X1 * Z2);
|
const X1Z2 = modP(X1 * Z2);
|
||||||
const X2Z1 = modP(X2 * Z1);
|
const X2Z1 = modP(X2 * Z1);
|
||||||
const Y1Z2 = modP(Y1 * Z2);
|
const Y1Z2 = modP(Y1 * Z2);
|
||||||
const Y2Z1 = modP(Y2 * Z1);
|
const Y2Z1 = modP(Y2 * Z1);
|
||||||
return X1Z2 === X2Z1 && Y1Z2 === Y2Z1;
|
return X1Z2 === X2Z1 && Y1Z2 === Y2Z1;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected is0(): boolean {
|
protected is0(): boolean {
|
||||||
return this.equals(ExtendedPoint.ZERO);
|
return this.equals(ExtendedPoint.ZERO);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Inverses point to one corresponding to (x, -y) in Affine coordinates.
|
// Inverses point to one corresponding to (x, -y) in Affine coordinates.
|
||||||
negate(): ExtendedPoint {
|
negate(): ExtendedPoint {
|
||||||
return new ExtendedPoint(modP(-this.x), this.y, this.z, modP(-this.t));
|
return new ExtendedPoint(modP(-this.ex), this.ey, this.ez, modP(-this.et));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fast algo for doubling Extended Point.
|
// Fast algo for doubling Extended Point.
|
||||||
@ -213,7 +234,7 @@ export function twistedEdwards(curveDef: CurveType): CurveFn {
|
|||||||
// Cost: 4M + 4S + 1*a + 6add + 1*2.
|
// Cost: 4M + 4S + 1*a + 6add + 1*2.
|
||||||
double(): ExtendedPoint {
|
double(): ExtendedPoint {
|
||||||
const { a } = CURVE;
|
const { a } = CURVE;
|
||||||
const { x: X1, y: Y1, z: Z1 } = this;
|
const { ex: X1, ey: Y1, ez: Z1 } = this;
|
||||||
const A = modP(X1 * X1); // A = X12
|
const A = modP(X1 * X1); // A = X12
|
||||||
const B = modP(Y1 * Y1); // B = Y12
|
const B = modP(Y1 * Y1); // B = Y12
|
||||||
const C = modP(_2n * modP(Z1 * Z1)); // C = 2*Z12
|
const C = modP(_2n * modP(Z1 * Z1)); // C = 2*Z12
|
||||||
@ -236,8 +257,8 @@ export function twistedEdwards(curveDef: CurveType): CurveFn {
|
|||||||
add(other: ExtendedPoint) {
|
add(other: ExtendedPoint) {
|
||||||
assertExtPoint(other);
|
assertExtPoint(other);
|
||||||
const { a, d } = CURVE;
|
const { a, d } = CURVE;
|
||||||
const { x: X1, y: Y1, z: Z1, t: T1 } = this;
|
const { ex: X1, ey: Y1, ez: Z1, et: T1 } = this;
|
||||||
const { x: X2, y: Y2, z: Z2, t: T2 } = other;
|
const { ex: X2, ey: Y2, ez: Z2, et: T2 } = other;
|
||||||
// Faster algo for adding 2 Extended Points when curve's a=-1.
|
// Faster algo for adding 2 Extended Points when curve's a=-1.
|
||||||
// http://hyperelliptic.org/EFD/g1p/auto-twisted-extended-1.html#addition-add-2008-hwcd-4
|
// http://hyperelliptic.org/EFD/g1p/auto-twisted-extended-1.html#addition-add-2008-hwcd-4
|
||||||
// Cost: 8M + 8add + 2*2.
|
// Cost: 8M + 8add + 2*2.
|
||||||
@ -278,11 +299,16 @@ export function twistedEdwards(curveDef: CurveType): CurveFn {
|
|||||||
return this.add(other.negate());
|
return this.add(other.negate());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private wNAF(n: bigint): { p: ExtendedPoint; f: ExtendedPoint } {
|
||||||
|
return wnaf.wNAFCached(this, pointPrecomputes, n, ExtendedPoint.normalizeZ);
|
||||||
|
}
|
||||||
|
|
||||||
// Constant time multiplication.
|
// Constant time multiplication.
|
||||||
// Uses wNAF method. Windowed method may be 10% faster,
|
// Uses wNAF method. Windowed method may be 10% faster,
|
||||||
// but takes 2x longer to generate and consumes 2x memory.
|
// but takes 2x longer to generate and consumes 2x memory.
|
||||||
multiply(scalar: bigint): ExtendedPoint {
|
multiply(scalar: bigint): ExtendedPoint {
|
||||||
return wNAF_TMP_FN(this, assertGE(scalar));
|
const { p, f } = this.wNAF(assertGE(scalar));
|
||||||
|
return ExtendedPoint.normalizeZ([p, f])[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Non-constant-time multiplication. Uses double-and-add algorithm.
|
// Non-constant-time multiplication. Uses double-and-add algorithm.
|
||||||
@ -292,7 +318,7 @@ export function twistedEdwards(curveDef: CurveType): CurveFn {
|
|||||||
let n = assertGE0(scalar);
|
let n = assertGE0(scalar);
|
||||||
if (n === _0n) return I;
|
if (n === _0n) return I;
|
||||||
if (this.equals(I) || n === _1n) return this;
|
if (this.equals(I) || n === _1n) return this;
|
||||||
if (this.equals(G)) return wNAF_TMP_FN(this, n);
|
if (this.equals(G)) return this.wNAF(n).p;
|
||||||
return wnaf.unsafeLadder(this, n);
|
return wnaf.unsafeLadder(this, n);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -313,7 +339,7 @@ export function twistedEdwards(curveDef: CurveType): CurveFn {
|
|||||||
// Converts Extended point to default (x, y) coordinates.
|
// Converts Extended point to default (x, y) coordinates.
|
||||||
// Can accept precomputed Z^-1 - for example, from invertBatch.
|
// Can accept precomputed Z^-1 - for example, from invertBatch.
|
||||||
toAffine(iz?: bigint): AffinePoint {
|
toAffine(iz?: bigint): AffinePoint {
|
||||||
const { x, y, z } = this;
|
const { ex: x, ey: y, ez: z } = this;
|
||||||
const is0 = this.is0();
|
const is0 = this.is0();
|
||||||
if (iz == null) iz = is0 ? _8n : (Fp.invert(z) as bigint); // 8 was chosen arbitrarily
|
if (iz == null) iz = is0 ? _8n : (Fp.invert(z) as bigint); // 8 was chosen arbitrarily
|
||||||
const ax = modP(x * iz);
|
const ax = modP(x * iz);
|
||||||
@ -390,26 +416,8 @@ export function twistedEdwards(curveDef: CurveType): CurveFn {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
const { BASE: G, ZERO: I } = ExtendedPoint;
|
const { BASE: G, ZERO: I } = ExtendedPoint;
|
||||||
let Gpows: ExtendedPoint[] | undefined = undefined; // precomputes for base point G
|
|
||||||
const wnaf = wNAF(ExtendedPoint, CURVE.nByteLength * 8);
|
const wnaf = wNAF(ExtendedPoint, CURVE.nByteLength * 8);
|
||||||
function wNAF_TMP_FN(P: ExtendedPoint, n: bigint): ExtendedPoint {
|
|
||||||
if (P.equals(G)) {
|
|
||||||
const W = 8;
|
|
||||||
if (!Gpows) {
|
|
||||||
const denorm = wnaf.precomputeWindow(P, W) as ExtendedPoint[];
|
|
||||||
const norm = ExtendedPoint.toAffineBatch(denorm).map(ExtendedPoint.fromAffine);
|
|
||||||
Gpows = norm;
|
|
||||||
}
|
|
||||||
const comp = Gpows;
|
|
||||||
const { p, f } = wnaf.wNAF(W, comp, n);
|
|
||||||
return ExtendedPoint.normalizeZ([p, f])[0];
|
|
||||||
}
|
|
||||||
const W = 1;
|
|
||||||
const denorm = wnaf.precomputeWindow(P, W) as ExtendedPoint[];
|
|
||||||
const norm = ExtendedPoint.toAffineBatch(denorm).map(ExtendedPoint.fromAffine);
|
|
||||||
const { p, f } = wnaf.wNAF(W, norm, n);
|
|
||||||
return ExtendedPoint.normalizeZ([p, f])[0];
|
|
||||||
}
|
|
||||||
function assertExtPoint(other: unknown) {
|
function assertExtPoint(other: unknown) {
|
||||||
if (!(other instanceof ExtendedPoint)) throw new TypeError('ExtendedPoint expected');
|
if (!(other instanceof ExtendedPoint)) throw new TypeError('ExtendedPoint expected');
|
||||||
}
|
}
|
||||||
@ -494,7 +502,7 @@ export function twistedEdwards(curveDef: CurveType): CurveFn {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Enable precomputes. Slows down first publicKey computation by 20ms.
|
// Enable precomputes. Slows down first publicKey computation by 20ms.
|
||||||
// G._setWindowSize(8);
|
G._setWindowSize(8);
|
||||||
|
|
||||||
const utils = {
|
const utils = {
|
||||||
getExtendedPublicKey,
|
getExtendedPublicKey,
|
||||||
@ -515,12 +523,10 @@ export function twistedEdwards(curveDef: CurveType): CurveFn {
|
|||||||
* but allows to speed-up subsequent getPublicKey() calls up to 20x.
|
* but allows to speed-up subsequent getPublicKey() calls up to 20x.
|
||||||
* @param windowSize 2, 4, 8, 16
|
* @param windowSize 2, 4, 8, 16
|
||||||
*/
|
*/
|
||||||
precompute(windowSize = 8, point = G): ExtendedPoint {
|
precompute(windowSize = 8, point = ExtendedPoint.BASE): typeof ExtendedPoint.BASE {
|
||||||
return G.multiply(2n);
|
point._setWindowSize(windowSize);
|
||||||
// const cached = point.equals(Point.BASE) ? point : new Point(point.x, point.y);
|
point.multiply(BigInt(3));
|
||||||
// cached._setWindowSize(windowSize);
|
return point;
|
||||||
// cached.multiply(_2n);
|
|
||||||
// return cached;
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -16,6 +16,7 @@ export type GroupConstructor<T> = {
|
|||||||
BASE: T;
|
BASE: T;
|
||||||
ZERO: T;
|
ZERO: T;
|
||||||
};
|
};
|
||||||
|
export type Mapper<T> = (i: T[]) => T[];
|
||||||
// Not big, but pretty complex and it is easy to break stuff. To avoid too much copy paste
|
// Not big, but pretty complex and it is easy to break stuff. To avoid too much copy paste
|
||||||
export function wNAF<T extends Group<T>>(c: GroupConstructor<T>, bits: number) {
|
export function wNAF<T extends Group<T>>(c: GroupConstructor<T>, bits: number) {
|
||||||
const constTimeNegate = (condition: boolean, item: T): T => {
|
const constTimeNegate = (condition: boolean, item: T): T => {
|
||||||
@ -125,5 +126,19 @@ export function wNAF<T extends Group<T>>(c: GroupConstructor<T>, bits: number) {
|
|||||||
// which makes it less const-time: around 1 bigint multiply.
|
// which makes it less const-time: around 1 bigint multiply.
|
||||||
return { p, f };
|
return { p, f };
|
||||||
},
|
},
|
||||||
|
|
||||||
|
wNAFCached(P: T, precomputesMap: Map<T, T[]>, n: bigint, transform: Mapper<T>): { p: T; f: T } {
|
||||||
|
// @ts-ignore
|
||||||
|
const W: number = '_WINDOW_SIZE' in P ? P._WINDOW_SIZE : 1;
|
||||||
|
// Calculate precomputes on a first run, reuse them after
|
||||||
|
let comp = precomputesMap.get(P);
|
||||||
|
if (!comp) {
|
||||||
|
comp = this.precomputeWindow(P, W) as T[];
|
||||||
|
if (W !== 1) {
|
||||||
|
precomputesMap.set(P, transform(comp));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this.wNAF(W, comp, n);
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -178,19 +178,18 @@ export function isogenyMap<T, F extends mod.Field<T>>(field: F, map: [T[], T[],
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type AffinePoint<T> = { x: T; y: T };
|
||||||
export interface H2CPoint<T> extends Group<H2CPoint<T>> {
|
export interface H2CPoint<T> extends Group<H2CPoint<T>> {
|
||||||
readonly x: T;
|
|
||||||
readonly y: T;
|
|
||||||
add(rhs: H2CPoint<T>): H2CPoint<T>;
|
add(rhs: H2CPoint<T>): H2CPoint<T>;
|
||||||
toAffine(iz?: bigint): { x: T; y: T };
|
toAffine(iz?: bigint): AffinePoint<T>;
|
||||||
clearCofactor(): H2CPoint<T>;
|
clearCofactor(): H2CPoint<T>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface H2CPointConstructor<T> extends GroupConstructor<H2CPoint<T>> {
|
export interface H2CPointConstructor<T> extends GroupConstructor<H2CPoint<T>> {
|
||||||
fromAffine(ap: { x: T; y: T }): H2CPoint<T>;
|
fromAffine(ap: AffinePoint<T>): H2CPoint<T>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type MapToCurve<T> = (scalar: bigint[]) => { x: T; y: T };
|
export type MapToCurve<T> = (scalar: bigint[]) => AffinePoint<T>;
|
||||||
|
|
||||||
// Separated from initialization opts, so users won't accidentally change per-curve parameters (changing DST is ok!)
|
// Separated from initialization opts, so users won't accidentally change per-curve parameters (changing DST is ok!)
|
||||||
export type htfBasicOpts = {
|
export type htfBasicOpts = {
|
||||||
|
@ -332,7 +332,7 @@ export function Fp(
|
|||||||
isValid: (num) => {
|
isValid: (num) => {
|
||||||
if (typeof num !== 'bigint')
|
if (typeof num !== 'bigint')
|
||||||
throw new Error(`Invalid field element: expected bigint, got ${typeof num}`);
|
throw new Error(`Invalid field element: expected bigint, got ${typeof num}`);
|
||||||
return _0n <= num && num < ORDER;
|
return _0n <= num && num < ORDER; // 0 is valid element, but it's not invertible
|
||||||
},
|
},
|
||||||
isZero: (num) => num === _0n,
|
isZero: (num) => num === _0n,
|
||||||
isOdd: (num) => (num & _1n) === _1n,
|
isOdd: (num) => (num & _1n) === _1n,
|
||||||
@ -360,7 +360,6 @@ export function Fp(
|
|||||||
cmov: (a, b, c) => (c ? b : a),
|
cmov: (a, b, c) => (c ? b : a),
|
||||||
toBytes: (num) =>
|
toBytes: (num) =>
|
||||||
isLE ? utils.numberToBytesLE(num, BYTES) : utils.numberToBytesBE(num, BYTES),
|
isLE ? utils.numberToBytesLE(num, BYTES) : utils.numberToBytesBE(num, BYTES),
|
||||||
|
|
||||||
fromBytes: (bytes) => {
|
fromBytes: (bytes) => {
|
||||||
if (bytes.length !== BYTES)
|
if (bytes.length !== BYTES)
|
||||||
throw new Error(`Fp.fromBytes: expected ${BYTES}, got ${bytes.length}`);
|
throw new Error(`Fp.fromBytes: expected ${BYTES}, got ${bytes.length}`);
|
||||||
|
@ -115,9 +115,9 @@ export type AffinePoint<T> = {
|
|||||||
} & { z?: never };
|
} & { z?: never };
|
||||||
// Instance for 3d XYZ points
|
// Instance for 3d XYZ points
|
||||||
export interface ProjectivePointType<T> extends Group<ProjectivePointType<T>> {
|
export interface ProjectivePointType<T> extends Group<ProjectivePointType<T>> {
|
||||||
readonly x: T;
|
readonly px: T;
|
||||||
readonly y: T;
|
readonly py: T;
|
||||||
readonly z: T;
|
readonly pz: T;
|
||||||
multiply(scalar: bigint): ProjectivePointType<T>;
|
multiply(scalar: bigint): ProjectivePointType<T>;
|
||||||
multiplyUnsafe(scalar: bigint): ProjectivePointType<T>;
|
multiplyUnsafe(scalar: bigint): ProjectivePointType<T>;
|
||||||
multiplyAndAddUnsafe(
|
multiplyAndAddUnsafe(
|
||||||
@ -140,7 +140,6 @@ export interface ProjectiveConstructor<T> extends GroupConstructor<ProjectivePoi
|
|||||||
fromAffine(p: AffinePoint<T>): ProjectivePointType<T>;
|
fromAffine(p: AffinePoint<T>): ProjectivePointType<T>;
|
||||||
fromHex(hex: Hex): ProjectivePointType<T>;
|
fromHex(hex: Hex): ProjectivePointType<T>;
|
||||||
fromPrivateKey(privateKey: PrivKey): ProjectivePointType<T>;
|
fromPrivateKey(privateKey: PrivKey): ProjectivePointType<T>;
|
||||||
toAffineBatch(points: ProjectivePointType<T>[]): AffinePoint<T>[];
|
|
||||||
normalizeZ(points: ProjectivePointType<T>[]): ProjectivePointType<T>[];
|
normalizeZ(points: ProjectivePointType<T>[]): ProjectivePointType<T>[];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -212,9 +211,12 @@ export function weierstrassPoints<T>(opts: CurvePointsType<T>) {
|
|||||||
|
|
||||||
// Valid group elements reside in range 1..n-1
|
// Valid group elements reside in range 1..n-1
|
||||||
function isWithinCurveOrder(num: bigint): boolean {
|
function isWithinCurveOrder(num: bigint): boolean {
|
||||||
return _0n < num && num < CURVE.n;
|
return typeof num === 'bigint' && _0n < num && num < CURVE.n;
|
||||||
|
}
|
||||||
|
function assertGE(num: bigint) {
|
||||||
|
if (!isWithinCurveOrder(num))
|
||||||
|
throw new TypeError('Expected valid bigint: 0 < bigint < curve.n');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validates if a private key is valid and converts it to bigint form.
|
* Validates if a private key is valid and converts it to bigint form.
|
||||||
* Supports two options, that are passed when CURVE is initialized:
|
* Supports two options, that are passed when CURVE is initialized:
|
||||||
@ -231,31 +233,21 @@ export function weierstrassPoints<T>(opts: CurvePointsType<T>) {
|
|||||||
} else if (ut.isPositiveInt(key)) {
|
} else if (ut.isPositiveInt(key)) {
|
||||||
num = BigInt(key);
|
num = BigInt(key);
|
||||||
} else if (typeof key === 'string') {
|
} else if (typeof key === 'string') {
|
||||||
if (key.length !== 2 * groupLen) throw new Error(`Expected ${groupLen} bytes of private key`);
|
if (key.length !== 2 * groupLen) throw new Error(`Private key must be ${groupLen} bytes`);
|
||||||
// Validates individual octets
|
// Validates individual octets
|
||||||
num = ut.hexToNumber(key);
|
num = ut.hexToNumber(key);
|
||||||
} else if (key instanceof Uint8Array) {
|
} else if (key instanceof Uint8Array) {
|
||||||
if (key.length !== groupLen) throw new Error(`Expected ${groupLen} bytes of private key`);
|
if (key.length !== groupLen) throw new Error(`Private key must be ${groupLen} bytes`);
|
||||||
num = ut.bytesToNumberBE(key);
|
num = ut.bytesToNumberBE(key);
|
||||||
} else {
|
} else {
|
||||||
throw new TypeError('Expected valid private key');
|
throw new TypeError('Private key was invalid');
|
||||||
}
|
}
|
||||||
// Useful for curves with cofactor != 1
|
// Useful for curves with cofactor != 1
|
||||||
if (wrapPrivateKey) num = mod.mod(num, order);
|
if (wrapPrivateKey) num = mod.mod(num, order);
|
||||||
if (!isWithinCurveOrder(num)) throw new Error('Expected private key: 0 < key < n');
|
assertGE(num);
|
||||||
return num;
|
return num;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Validates if a scalar ("private number") is valid.
|
|
||||||
* Scalars are valid only if they are less than curve order.
|
|
||||||
*/
|
|
||||||
function normalizeScalar(num: bigint): bigint {
|
|
||||||
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<ProjectivePoint, ProjectivePoint[]>();
|
const pointPrecomputes = new Map<ProjectivePoint, ProjectivePoint[]>();
|
||||||
/**
|
/**
|
||||||
* Projective Point works in 3d / projective (homogeneous) coordinates: (x, y, z) ∋ (x=x/z, y=y/z)
|
* Projective Point works in 3d / projective (homogeneous) coordinates: (x, y, z) ∋ (x=x/z, y=y/z)
|
||||||
@ -263,13 +255,14 @@ export function weierstrassPoints<T>(opts: CurvePointsType<T>) {
|
|||||||
* We're doing calculations in projective, because its operations don't require costly inversion.
|
* We're doing calculations in projective, because its operations don't require costly inversion.
|
||||||
*/
|
*/
|
||||||
class ProjectivePoint implements ProjectivePointType<T> {
|
class ProjectivePoint implements ProjectivePointType<T> {
|
||||||
constructor(readonly x: T, readonly y: T, readonly z: T) {
|
|
||||||
if (y == null || !Fp.isValid(y)) throw new Error('ProjectivePoint: y required');
|
|
||||||
if (z == null || !Fp.isValid(z)) throw new Error('ProjectivePoint: z required');
|
|
||||||
}
|
|
||||||
static readonly BASE = new ProjectivePoint(CURVE.Gx, CURVE.Gy, Fp.ONE);
|
static readonly BASE = new ProjectivePoint(CURVE.Gx, CURVE.Gy, Fp.ONE);
|
||||||
static readonly ZERO = new ProjectivePoint(Fp.ZERO, Fp.ONE, Fp.ZERO);
|
static readonly ZERO = new ProjectivePoint(Fp.ZERO, Fp.ONE, Fp.ZERO);
|
||||||
|
|
||||||
|
constructor(readonly px: T, readonly py: T, readonly pz: T) {
|
||||||
|
if (py == null || !Fp.isValid(py)) throw new Error('ProjectivePoint: y required');
|
||||||
|
if (pz == null || !Fp.isValid(pz)) throw new Error('ProjectivePoint: z required');
|
||||||
|
}
|
||||||
|
|
||||||
static fromAffine(p: AffinePoint<T>): ProjectivePoint {
|
static fromAffine(p: AffinePoint<T>): ProjectivePoint {
|
||||||
const { x, y } = p || {};
|
const { x, y } = p || {};
|
||||||
if (!p || !Fp.isValid(x) || !Fp.isValid(y))
|
if (!p || !Fp.isValid(x) || !Fp.isValid(y))
|
||||||
@ -281,6 +274,39 @@ export function weierstrassPoints<T>(opts: CurvePointsType<T>) {
|
|||||||
return new ProjectivePoint(x, y, Fp.ONE);
|
return new ProjectivePoint(x, y, Fp.ONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get x(): T {
|
||||||
|
return this.toAffine().x;
|
||||||
|
}
|
||||||
|
get y(): T {
|
||||||
|
return this.toAffine().y;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Takes a bunch of Projective Points but executes only one
|
||||||
|
* inversion on all of them. Inversion is very slow operation,
|
||||||
|
* so this improves performance massively.
|
||||||
|
* Optimization: converts a list of projective points to a list of identical points with Z=1.
|
||||||
|
*/
|
||||||
|
static normalizeZ(points: ProjectivePoint[]): ProjectivePoint[] {
|
||||||
|
const toInv = Fp.invertBatch(points.map((p) => p.pz));
|
||||||
|
return points.map((p, i) => p.toAffine(toInv[i])).map(ProjectivePoint.fromAffine);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts hash string or Uint8Array to Point.
|
||||||
|
* @param hex short/long ECDSA hex
|
||||||
|
*/
|
||||||
|
static fromHex(hex: Hex): ProjectivePoint {
|
||||||
|
const P = ProjectivePoint.fromAffine(CURVE.fromBytes(ut.ensureBytes(hex)));
|
||||||
|
P.assertValidity();
|
||||||
|
return P;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Multiplies generator point by privateKey.
|
||||||
|
static fromPrivateKey(privateKey: PrivKey) {
|
||||||
|
return ProjectivePoint.BASE.multiply(normalizePrivateKey(privateKey));
|
||||||
|
}
|
||||||
|
|
||||||
// We calculate precomputes for elliptic curve point multiplication
|
// We calculate precomputes for elliptic curve point multiplication
|
||||||
// using windowed method. This specifies window size and
|
// using windowed method. This specifies window size and
|
||||||
// stores precomputed values. Usually only base point would be precomputed.
|
// stores precomputed values. Usually only base point would be precomputed.
|
||||||
@ -291,38 +317,27 @@ export function weierstrassPoints<T>(opts: CurvePointsType<T>) {
|
|||||||
this._WINDOW_SIZE = windowSize;
|
this._WINDOW_SIZE = windowSize;
|
||||||
pointPrecomputes.delete(this);
|
pointPrecomputes.delete(this);
|
||||||
}
|
}
|
||||||
protected is0() {
|
|
||||||
return this.equals(ProjectivePoint.ZERO);
|
|
||||||
}
|
|
||||||
private wNAF(n: bigint): { p: ProjectivePoint; f: ProjectivePoint } {
|
|
||||||
const W = this._WINDOW_SIZE || 1;
|
|
||||||
// Calculate precomputes on a first run, reuse them after
|
|
||||||
let comp = pointPrecomputes.get(this);
|
|
||||||
if (!comp) {
|
|
||||||
comp = wnaf.precomputeWindow(this, W) as ProjectivePoint[];
|
|
||||||
if (W !== 1) {
|
|
||||||
comp = ProjectivePoint.normalizeZ(comp);
|
|
||||||
pointPrecomputes.set(this, comp);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return wnaf.wNAF(W, comp, n);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
// A point on curve is valid if it conforms to equation.
|
||||||
* Takes a bunch of Projective Points but executes only one
|
assertValidity(): void {
|
||||||
* inversion on all of them. Inversion is very slow operation,
|
// Zero is valid point too!
|
||||||
* so this improves performance massively.
|
if (this.is0()) {
|
||||||
*/
|
if (CURVE.allowInfinityPoint) return;
|
||||||
static toAffineBatch(points: ProjectivePoint[]): AffinePoint<T>[] {
|
throw new Error('bad point: ZERO');
|
||||||
const toInv = Fp.invertBatch(points.map((p) => p.z));
|
|
||||||
return points.map((p, i) => p.toAffine(toInv[i]));
|
|
||||||
}
|
}
|
||||||
|
// Some 3rd-party test vectors require different wording between here & `fromCompressedHex`
|
||||||
/**
|
const { x, y } = this.toAffine();
|
||||||
* Optimization: converts a list of projective points to a list of identical points with Z=1.
|
// 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');
|
||||||
static normalizeZ(points: ProjectivePoint[]): ProjectivePoint[] {
|
const left = Fp.square(y); // y²
|
||||||
return ProjectivePoint.toAffineBatch(points).map(ProjectivePoint.fromAffine);
|
const right = weierstrassEquation(x); // x³ + ax + b
|
||||||
|
if (!Fp.equals(left, right)) throw new Error('bad point: equation left != right');
|
||||||
|
if (!this.isTorsionFree()) throw new Error('bad point: not in prime-order subgroup');
|
||||||
|
}
|
||||||
|
hasEvenY(): boolean {
|
||||||
|
const { y } = this.toAffine();
|
||||||
|
if (Fp.isOdd) return !Fp.isOdd(y);
|
||||||
|
throw new Error("Field doesn't support isOdd");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -330,8 +345,8 @@ export function weierstrassPoints<T>(opts: CurvePointsType<T>) {
|
|||||||
*/
|
*/
|
||||||
equals(other: ProjectivePoint): boolean {
|
equals(other: ProjectivePoint): boolean {
|
||||||
assertPrjPoint(other);
|
assertPrjPoint(other);
|
||||||
const { x: X1, y: Y1, z: Z1 } = this;
|
const { px: X1, py: Y1, pz: Z1 } = this;
|
||||||
const { x: X2, y: Y2, z: Z2 } = other;
|
const { px: X2, py: Y2, pz: Z2 } = other;
|
||||||
const U1 = Fp.equals(Fp.mul(X1, Z2), Fp.mul(X2, Z1));
|
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 U2 = Fp.equals(Fp.mul(Y1, Z2), Fp.mul(Y2, Z1));
|
||||||
return U1 && U2;
|
return U1 && U2;
|
||||||
@ -341,7 +356,7 @@ export function weierstrassPoints<T>(opts: CurvePointsType<T>) {
|
|||||||
* Flips point to one corresponding to (x, -y) in Affine coordinates.
|
* Flips point to one corresponding to (x, -y) in Affine coordinates.
|
||||||
*/
|
*/
|
||||||
negate(): ProjectivePoint {
|
negate(): ProjectivePoint {
|
||||||
return new ProjectivePoint(this.x, Fp.negate(this.y), this.z);
|
return new ProjectivePoint(this.px, Fp.negate(this.py), this.pz);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Renes-Costello-Batina exception-free doubling formula.
|
// Renes-Costello-Batina exception-free doubling formula.
|
||||||
@ -351,7 +366,7 @@ export function weierstrassPoints<T>(opts: CurvePointsType<T>) {
|
|||||||
double() {
|
double() {
|
||||||
const { a, b } = CURVE;
|
const { a, b } = CURVE;
|
||||||
const b3 = Fp.mul(b, 3n);
|
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
|
let X3 = Fp.ZERO, Y3 = Fp.ZERO, Z3 = Fp.ZERO; // prettier-ignore
|
||||||
let t0 = Fp.mul(X1, X1); // step 1
|
let t0 = Fp.mul(X1, X1); // step 1
|
||||||
let t1 = Fp.mul(Y1, Y1);
|
let t1 = Fp.mul(Y1, Y1);
|
||||||
@ -393,8 +408,8 @@ export function weierstrassPoints<T>(opts: CurvePointsType<T>) {
|
|||||||
// Cost: 12M + 0S + 3*a + 3*b3 + 23add.
|
// Cost: 12M + 0S + 3*a + 3*b3 + 23add.
|
||||||
add(other: ProjectivePoint): ProjectivePoint {
|
add(other: ProjectivePoint): ProjectivePoint {
|
||||||
assertPrjPoint(other);
|
assertPrjPoint(other);
|
||||||
const { x: X1, y: Y1, z: Z1 } = this;
|
const { px: X1, py: Y1, pz: Z1 } = this;
|
||||||
const { x: X2, y: Y2, z: Z2 } = other;
|
const { px: X2, py: Y2, pz: Z2 } = other;
|
||||||
let X3 = Fp.ZERO, Y3 = Fp.ZERO, Z3 = Fp.ZERO; // prettier-ignore
|
let X3 = Fp.ZERO, Y3 = Fp.ZERO, Z3 = Fp.ZERO; // prettier-ignore
|
||||||
const a = CURVE.a;
|
const a = CURVE.a;
|
||||||
const b3 = Fp.mul(CURVE.b, 3n);
|
const b3 = Fp.mul(CURVE.b, 3n);
|
||||||
@ -445,24 +460,33 @@ export function weierstrassPoints<T>(opts: CurvePointsType<T>) {
|
|||||||
return this.add(other.negate());
|
return this.add(other.negate());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private is0() {
|
||||||
|
return this.equals(ProjectivePoint.ZERO);
|
||||||
|
}
|
||||||
|
private wNAF(n: bigint): { p: ProjectivePoint; f: ProjectivePoint } {
|
||||||
|
return wnaf.wNAFCached(this, pointPrecomputes, n, (comp: ProjectivePoint[]) => {
|
||||||
|
const toInv = Fp.invertBatch(comp.map((p) => p.pz));
|
||||||
|
return comp.map((p, i) => p.toAffine(toInv[i])).map(ProjectivePoint.fromAffine);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Non-constant-time multiplication. Uses double-and-add algorithm.
|
* Non-constant-time multiplication. Uses double-and-add algorithm.
|
||||||
* It's faster, but should only be used when you don't care about
|
* It's faster, but should only be used when you don't care about
|
||||||
* an exposed private key e.g. sig verification, which works over *public* keys.
|
* an exposed private key e.g. sig verification, which works over *public* keys.
|
||||||
*/
|
*/
|
||||||
multiplyUnsafe(scalar: bigint): ProjectivePoint {
|
multiplyUnsafe(n: bigint): ProjectivePoint {
|
||||||
const P0 = ProjectivePoint.ZERO;
|
const I = ProjectivePoint.ZERO;
|
||||||
if (typeof scalar === 'bigint' && scalar === _0n) return P0;
|
if (n === _0n) return I;
|
||||||
// Will throw on 0
|
assertGE(n); // Will throw on 0
|
||||||
let n = normalizeScalar(scalar);
|
|
||||||
if (n === _1n) return this;
|
if (n === _1n) return this;
|
||||||
|
const { endo } = CURVE;
|
||||||
if (!CURVE.endo) return wnaf.unsafeLadder(this, n);
|
if (!endo) return wnaf.unsafeLadder(this, n);
|
||||||
|
|
||||||
// Apply endomorphism
|
// Apply endomorphism
|
||||||
let { k1neg, k1, k2neg, k2 } = CURVE.endo.splitScalar(n);
|
let { k1neg, k1, k2neg, k2 } = endo.splitScalar(n);
|
||||||
let k1p = P0;
|
let k1p = I;
|
||||||
let k2p = P0;
|
let k2p = I;
|
||||||
let d: ProjectivePoint = this;
|
let d: ProjectivePoint = this;
|
||||||
while (k1 > _0n || k2 > _0n) {
|
while (k1 > _0n || k2 > _0n) {
|
||||||
if (k1 & _1n) k1p = k1p.add(d);
|
if (k1 & _1n) k1p = k1p.add(d);
|
||||||
@ -473,9 +497,10 @@ export function weierstrassPoints<T>(opts: CurvePointsType<T>) {
|
|||||||
}
|
}
|
||||||
if (k1neg) k1p = k1p.negate();
|
if (k1neg) k1p = k1p.negate();
|
||||||
if (k2neg) k2p = k2p.negate();
|
if (k2neg) k2p = k2p.negate();
|
||||||
k2p = new ProjectivePoint(Fp.mul(k2p.x, CURVE.endo.beta), k2p.y, k2p.z);
|
k2p = new ProjectivePoint(Fp.mul(k2p.px, endo.beta), k2p.py, k2p.pz);
|
||||||
return k1p.add(k2p);
|
return k1p.add(k2p);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constant time multiplication.
|
* Constant time multiplication.
|
||||||
* Uses wNAF method. Windowed method may be 10% faster,
|
* Uses wNAF method. Windowed method may be 10% faster,
|
||||||
@ -485,19 +510,17 @@ export function weierstrassPoints<T>(opts: CurvePointsType<T>) {
|
|||||||
* @returns New point
|
* @returns New point
|
||||||
*/
|
*/
|
||||||
multiply(scalar: bigint): ProjectivePoint {
|
multiply(scalar: bigint): ProjectivePoint {
|
||||||
let n = normalizeScalar(scalar);
|
assertGE(scalar);
|
||||||
|
let n = scalar;
|
||||||
// Real point.
|
let point: ProjectivePoint, fake: ProjectivePoint; // Fake point is used to const-time mult
|
||||||
let point: ProjectivePoint;
|
const { endo } = CURVE;
|
||||||
// Fake point, we use it to achieve constant-time multiplication.
|
if (endo) {
|
||||||
let fake: ProjectivePoint;
|
const { k1neg, k1, k2neg, k2 } = endo.splitScalar(n);
|
||||||
if (CURVE.endo) {
|
|
||||||
const { k1neg, k1, k2neg, k2 } = CURVE.endo.splitScalar(n);
|
|
||||||
let { p: k1p, f: f1p } = this.wNAF(k1);
|
let { p: k1p, f: f1p } = this.wNAF(k1);
|
||||||
let { p: k2p, f: f2p } = this.wNAF(k2);
|
let { p: k2p, f: f2p } = this.wNAF(k2);
|
||||||
k1p = wnaf.constTimeNegate(k1neg, k1p);
|
k1p = wnaf.constTimeNegate(k1neg, k1p);
|
||||||
k2p = wnaf.constTimeNegate(k2neg, k2p);
|
k2p = wnaf.constTimeNegate(k2neg, k2p);
|
||||||
k2p = new ProjectivePoint(Fp.mul(k2p.x, CURVE.endo.beta), k2p.y, k2p.z);
|
k2p = new ProjectivePoint(Fp.mul(k2p.px, endo.beta), k2p.py, k2p.pz);
|
||||||
point = k1p.add(k2p);
|
point = k1p.add(k2p);
|
||||||
fake = f1p.add(f2p);
|
fake = f1p.add(f2p);
|
||||||
} else {
|
} else {
|
||||||
@ -509,11 +532,25 @@ export function weierstrassPoints<T>(opts: CurvePointsType<T>) {
|
|||||||
return ProjectivePoint.normalizeZ([point, fake])[0];
|
return ProjectivePoint.normalizeZ([point, fake])[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Efficiently calculate `aP + bQ`. Unsafe, can expose private key, if used incorrectly.
|
||||||
|
* @returns non-zero affine point
|
||||||
|
*/
|
||||||
|
multiplyAndAddUnsafe(Q: ProjectivePoint, a: bigint, b: bigint): ProjectivePoint | undefined {
|
||||||
|
const G = ProjectivePoint.BASE; // No Strauss-Shamir trick: we have 10% faster G precomputes
|
||||||
|
const mul = (
|
||||||
|
P: ProjectivePoint,
|
||||||
|
a: bigint // 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.
|
// Converts Projective point to affine (x, y) coordinates.
|
||||||
// Can accept precomputed Z^-1 - for example, from invertBatch.
|
// Can accept precomputed Z^-1 - for example, from invertBatch.
|
||||||
// (x, y, z) ∋ (x=x/z, y=y/z)
|
// (x, y, z) ∋ (x=x/z, y=y/z)
|
||||||
toAffine(iz?: T): AffinePoint<T> {
|
toAffine(iz?: T): AffinePoint<T> {
|
||||||
const { x, y, z } = this;
|
const { px: x, py: y, pz: z } = this;
|
||||||
const is0 = this.is0();
|
const is0 = this.is0();
|
||||||
// If invZ was 0, we return zero point. However we still want to execute
|
// 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.
|
// all operations, so we replace invZ with a random number, 1.
|
||||||
@ -537,45 +574,7 @@ export function weierstrassPoints<T>(opts: CurvePointsType<T>) {
|
|||||||
if (clearCofactor) return clearCofactor(ProjectivePoint, this) as ProjectivePoint;
|
if (clearCofactor) return clearCofactor(ProjectivePoint, this) as ProjectivePoint;
|
||||||
return this.multiplyUnsafe(CURVE.h);
|
return this.multiplyUnsafe(CURVE.h);
|
||||||
}
|
}
|
||||||
/**
|
|
||||||
* Efficiently calculate `aP + bQ`.
|
|
||||||
* Unsafe, can expose private key, if used incorrectly.
|
|
||||||
* TODO: Utilize Shamir's trick
|
|
||||||
* @returns non-zero affine point
|
|
||||||
*/
|
|
||||||
multiplyAndAddUnsafe(Q: ProjectivePoint, a: bigint, b: bigint): ProjectivePoint | undefined {
|
|
||||||
const P = this;
|
|
||||||
const aP =
|
|
||||||
a === _0n || a === _1n || !P.equals(ProjectivePoint.BASE)
|
|
||||||
? P.multiplyUnsafe(a)
|
|
||||||
: P.multiply(a);
|
|
||||||
const bQ = Q.multiplyUnsafe(b);
|
|
||||||
const sum = aP.add(bQ);
|
|
||||||
return sum.is0() ? undefined : sum;
|
|
||||||
}
|
|
||||||
|
|
||||||
// A point on curve is valid if it conforms to equation.
|
|
||||||
assertValidity(): void {
|
|
||||||
const err = 'Point invalid:';
|
|
||||||
// Zero is valid point too!
|
|
||||||
if (this.is0()) {
|
|
||||||
if (CURVE.allowInfinityPoint) return;
|
|
||||||
throw new Error(`${err} 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(`${err} x or y not FE`);
|
|
||||||
const left = Fp.square(y); // y²
|
|
||||||
const right = weierstrassEquation(x); // x³ + ax + b
|
|
||||||
if (!Fp.equals(left, right)) throw new Error(`${err} equation left != right`);
|
|
||||||
if (!this.isTorsionFree()) throw new Error(`${err} not in prime-order subgroup`);
|
|
||||||
}
|
|
||||||
hasEvenY(): boolean {
|
|
||||||
const { y } = this.toAffine();
|
|
||||||
if (Fp.isOdd) return !Fp.isOdd(y);
|
|
||||||
throw new Error("Field doesn't support isOdd");
|
|
||||||
}
|
|
||||||
toRawBytes(isCompressed = true): Uint8Array {
|
toRawBytes(isCompressed = true): Uint8Array {
|
||||||
this.assertValidity();
|
this.assertValidity();
|
||||||
return CURVE.toBytes(ProjectivePoint, this, isCompressed);
|
return CURVE.toBytes(ProjectivePoint, this, isCompressed);
|
||||||
@ -584,21 +583,6 @@ export function weierstrassPoints<T>(opts: CurvePointsType<T>) {
|
|||||||
toHex(isCompressed = true): string {
|
toHex(isCompressed = true): string {
|
||||||
return bytesToHex(this.toRawBytes(isCompressed));
|
return bytesToHex(this.toRawBytes(isCompressed));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts hash string or Uint8Array to Point.
|
|
||||||
* @param hex short/long ECDSA hex
|
|
||||||
*/
|
|
||||||
static fromHex(hex: Hex): ProjectivePoint {
|
|
||||||
const P = ProjectivePoint.fromAffine(CURVE.fromBytes(ut.ensureBytes(hex)));
|
|
||||||
P.assertValidity();
|
|
||||||
return P;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Multiplies generator point by privateKey.
|
|
||||||
static fromPrivateKey(privateKey: PrivKey) {
|
|
||||||
return ProjectivePoint.BASE.multiply(normalizePrivateKey(privateKey));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
const _bits = CURVE.nBitLength;
|
const _bits = CURVE.nBitLength;
|
||||||
const wnaf = wNAF(ProjectivePoint, CURVE.endo ? Math.ceil(_bits / 2) : _bits);
|
const wnaf = wNAF(ProjectivePoint, CURVE.endo ? Math.ceil(_bits / 2) : _bits);
|
||||||
@ -620,7 +604,7 @@ export interface SignatureType {
|
|||||||
readonly s: bigint;
|
readonly s: bigint;
|
||||||
readonly recovery?: number;
|
readonly recovery?: number;
|
||||||
assertValidity(): void;
|
assertValidity(): void;
|
||||||
copyWithRecoveryBit(recovery: number): SignatureType;
|
addRecoveryBit(recovery: number): SignatureType;
|
||||||
hasHighS(): boolean;
|
hasHighS(): boolean;
|
||||||
normalizeS(): SignatureType;
|
normalizeS(): SignatureType;
|
||||||
recoverPublicKey(msgHash: Hex): ProjectivePointType<bigint>;
|
recoverPublicKey(msgHash: Hex): ProjectivePointType<bigint>;
|
||||||
@ -752,12 +736,15 @@ export function weierstrass(curveDef: CurveType): CurveFn {
|
|||||||
const uncompressedLen = 2 * Fp.BYTES + 1; // e.g. 65 for 32
|
const uncompressedLen = 2 * Fp.BYTES + 1; // e.g. 65 for 32
|
||||||
|
|
||||||
function isValidFieldElement(num: bigint): boolean {
|
function isValidFieldElement(num: bigint): boolean {
|
||||||
// 0 is disallowed by arbitrary reasons. Probably because infinity point?
|
return _0n < num && num < Fp.ORDER; // 0 is banned since it's not invertible FE
|
||||||
return _0n < num && num < Fp.ORDER;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const { ProjectivePoint, normalizePrivateKey, weierstrassEquation, isWithinCurveOrder } =
|
const {
|
||||||
weierstrassPoints({
|
ProjectivePoint: Point,
|
||||||
|
normalizePrivateKey,
|
||||||
|
weierstrassEquation,
|
||||||
|
isWithinCurveOrder,
|
||||||
|
} = weierstrassPoints({
|
||||||
...CURVE,
|
...CURVE,
|
||||||
toBytes(c, point, isCompressed: boolean): Uint8Array {
|
toBytes(c, point, isCompressed: boolean): Uint8Array {
|
||||||
const a = point.toAffine();
|
const a = point.toAffine();
|
||||||
@ -772,21 +759,22 @@ export function weierstrass(curveDef: CurveType): CurveFn {
|
|||||||
},
|
},
|
||||||
fromBytes(bytes: Uint8Array) {
|
fromBytes(bytes: Uint8Array) {
|
||||||
const len = bytes.length;
|
const len = bytes.length;
|
||||||
const header = bytes[0];
|
const head = bytes[0];
|
||||||
|
const tail = bytes.subarray(1);
|
||||||
// this.assertValidity() is done inside of fromHex
|
// this.assertValidity() is done inside of fromHex
|
||||||
if (len === compressedLen && (header === 0x02 || header === 0x03)) {
|
if (len === compressedLen && (head === 0x02 || head === 0x03)) {
|
||||||
const x = ut.bytesToNumberBE(bytes.subarray(1));
|
const x = ut.bytesToNumberBE(tail);
|
||||||
if (!isValidFieldElement(x)) throw new Error('Point is not on curve');
|
if (!isValidFieldElement(x)) throw new Error('Point is not on curve');
|
||||||
const y2 = weierstrassEquation(x); // y² = x³ + ax + b
|
const y2 = weierstrassEquation(x); // y² = x³ + ax + b
|
||||||
let y = Fp.sqrt(y2); // y = y² ^ (p+1)/4
|
let y = Fp.sqrt(y2); // y = y² ^ (p+1)/4
|
||||||
const isYOdd = (y & _1n) === _1n;
|
const isYOdd = (y & _1n) === _1n;
|
||||||
// ECDSA
|
// ECDSA
|
||||||
const isFirstByteOdd = (bytes[0] & 1) === 1;
|
const isHeadOdd = (head & 1) === 1;
|
||||||
if (isFirstByteOdd !== isYOdd) y = Fp.negate(y);
|
if (isHeadOdd !== isYOdd) y = Fp.negate(y);
|
||||||
return { x, y };
|
return { x, y };
|
||||||
} else if (len === uncompressedLen && header === 0x04) {
|
} else if (len === uncompressedLen && head === 0x04) {
|
||||||
const x = Fp.fromBytes(bytes.subarray(1, Fp.BYTES + 1));
|
const x = Fp.fromBytes(tail.subarray(0, Fp.BYTES));
|
||||||
const y = Fp.fromBytes(bytes.subarray(Fp.BYTES + 1, 2 * Fp.BYTES + 1));
|
const y = Fp.fromBytes(tail.subarray(Fp.BYTES, 2 * Fp.BYTES));
|
||||||
return { x, y };
|
return { x, y };
|
||||||
} else {
|
} else {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
@ -809,11 +797,11 @@ export function weierstrass(curveDef: CurveType): CurveFn {
|
|||||||
* Normalizes hex, bytes, Point to Point. Checks for curve equation.
|
* Normalizes hex, bytes, Point to Point. Checks for curve equation.
|
||||||
*/
|
*/
|
||||||
function normalizePublicKey(publicKey: PubKey): ProjectivePointType<bigint> {
|
function normalizePublicKey(publicKey: PubKey): ProjectivePointType<bigint> {
|
||||||
if (publicKey instanceof ProjectivePoint) {
|
if (publicKey instanceof Point) {
|
||||||
publicKey.assertValidity();
|
publicKey.assertValidity();
|
||||||
return publicKey;
|
return publicKey;
|
||||||
} else if (publicKey instanceof Uint8Array || typeof publicKey === 'string') {
|
} else if (publicKey instanceof Uint8Array || typeof publicKey === 'string') {
|
||||||
return ProjectivePoint.fromHex(publicKey);
|
return Point.fromHex(publicKey);
|
||||||
// This can happen because PointType can be instance of different class
|
// This can happen because PointType can be instance of different class
|
||||||
} else throw new Error(`Unknown type of public key: ${publicKey}`);
|
} else throw new Error(`Unknown type of public key: ${publicKey}`);
|
||||||
}
|
}
|
||||||
@ -826,6 +814,8 @@ export function weierstrass(curveDef: CurveType): CurveFn {
|
|||||||
function normalizeS(s: bigint) {
|
function normalizeS(s: bigint) {
|
||||||
return isBiggerThanHalfOrder(s) ? mod.mod(-s, CURVE_ORDER) : s;
|
return isBiggerThanHalfOrder(s) ? mod.mod(-s, CURVE_ORDER) : s;
|
||||||
}
|
}
|
||||||
|
// slice bytes num
|
||||||
|
const slcNum = (b: Uint8Array, from: number, to: number) => ut.bytesToNumberBE(b.slice(from, to));
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ECDSA signature with its (r, s) properties. Supports DER & compact representations.
|
* ECDSA signature with its (r, s) properties. Supports DER & compact representations.
|
||||||
@ -837,15 +827,9 @@ export function weierstrass(curveDef: CurveType): CurveFn {
|
|||||||
|
|
||||||
// pair (bytes of r, bytes of s)
|
// pair (bytes of r, bytes of s)
|
||||||
static fromCompact(hex: Hex) {
|
static fromCompact(hex: Hex) {
|
||||||
const arr = hex instanceof Uint8Array;
|
const gl = CURVE.nByteLength;
|
||||||
const name = 'Signature.fromCompact';
|
hex = ut.ensureBytes(hex, gl * 2);
|
||||||
if (typeof hex !== 'string' && !arr)
|
return new Signature(slcNum(hex, 0, gl), slcNum(hex, gl, 2 * gl));
|
||||||
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: number, to: number) => ut.hexToNumber(str.slice(from, to));
|
|
||||||
return new Signature(slice(0, gl), slice(gl, 2 * gl));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// DER encoded ECDSA signature
|
// DER encoded ECDSA signature
|
||||||
@ -859,12 +843,12 @@ export function weierstrass(curveDef: CurveType): CurveFn {
|
|||||||
}
|
}
|
||||||
|
|
||||||
assertValidity(): void {
|
assertValidity(): void {
|
||||||
const { r, s } = this;
|
// can use assertGE here
|
||||||
if (!isWithinCurveOrder(r)) throw new Error('Invalid Signature: r must be 0 < r < n');
|
if (!isWithinCurveOrder(this.r)) throw new Error('r must be 0 < r < n');
|
||||||
if (!isWithinCurveOrder(s)) throw new Error('Invalid Signature: s must be 0 < s < n');
|
if (!isWithinCurveOrder(this.s)) throw new Error('s must be 0 < s < n');
|
||||||
}
|
}
|
||||||
|
|
||||||
copyWithRecoveryBit(recovery: number) {
|
addRecoveryBit(recovery: number) {
|
||||||
return new Signature(this.r, this.s, recovery);
|
return new Signature(this.r, this.s, recovery);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -883,23 +867,21 @@ export function weierstrass(curveDef: CurveType): CurveFn {
|
|||||||
* @param msgHash message hash
|
* @param msgHash message hash
|
||||||
* @returns Point corresponding to public key
|
* @returns Point corresponding to public key
|
||||||
*/
|
*/
|
||||||
recoverPublicKey(msgHash: Hex): typeof ProjectivePoint.BASE {
|
recoverPublicKey(msgHash: Hex): typeof Point.BASE {
|
||||||
const { r, s, recovery } = this;
|
const { n: N } = CURVE;
|
||||||
if (recovery == null) throw new Error('Cannot recover: recovery bit is not present');
|
const { r, s, recovery: rec } = this;
|
||||||
if (![0, 1, 2, 3].includes(recovery)) throw new Error('Cannot recover: invalid recovery bit');
|
|
||||||
const h = bits2int_modN(ut.ensureBytes(msgHash));
|
const h = bits2int_modN(ut.ensureBytes(msgHash));
|
||||||
|
if (rec == null || ![0, 1, 2, 3].includes(rec)) throw new Error('recovery id invalid');
|
||||||
const { n } = CURVE;
|
const radj = rec === 2 || rec === 3 ? r + N : r;
|
||||||
const radj = recovery === 2 || recovery === 3 ? r + n : r;
|
if (radj >= Fp.ORDER) throw new Error('recovery id 2 or 3 currently invalid');
|
||||||
if (radj >= Fp.ORDER) throw new Error('Cannot recover: bit 2/3 is invalid with current r');
|
const prefix = (rec & 1) === 0 ? '02' : '03';
|
||||||
const rinv = mod.invert(radj, n);
|
const R = Point.fromHex(prefix + numToFieldStr(radj));
|
||||||
// Q = u1⋅G + u2⋅R
|
const ir = mod.invert(radj, N); // r^-1
|
||||||
const u1 = mod.mod(-h * rinv, n);
|
const u1 = mod.mod(-h * ir, N); // -hr^-1
|
||||||
const u2 = mod.mod(s * rinv, n);
|
const u2 = mod.mod(s * ir, N); // sr^-1
|
||||||
const prefix = recovery & 1 ? '03' : '02';
|
// (sr^-1)R-(hr^-1)G = -(hr^-1)G + (sr^-1)
|
||||||
const R = ProjectivePoint.fromHex(prefix + numToFieldStr(radj));
|
const Q = Point.BASE.multiplyAndAddUnsafe(R, u1, u2);
|
||||||
const Q = ProjectivePoint.BASE.multiplyAndAddUnsafe(R, u1, u2); // unsafe is fine: no priv data leaked
|
if (!Q) throw new Error('point at infinify'); // unsafe is fine: no priv data leaked
|
||||||
if (!Q) throw new Error('Cannot recover: point at infinify');
|
|
||||||
Q.assertValidity();
|
Q.assertValidity();
|
||||||
return Q;
|
return Q;
|
||||||
}
|
}
|
||||||
@ -981,8 +963,7 @@ export function weierstrass(curveDef: CurveType): CurveFn {
|
|||||||
* @param windowSize 2, 4, 8, 16
|
* @param windowSize 2, 4, 8, 16
|
||||||
* @returns cached point
|
* @returns cached point
|
||||||
*/
|
*/
|
||||||
precompute(windowSize = 8, point = ProjectivePoint.BASE): typeof ProjectivePoint.BASE {
|
precompute(windowSize = 8, point = Point.BASE): typeof Point.BASE {
|
||||||
// const cached = point === ProjectivePoint.BASE ? point : ProjectivePoint.fromAffine({x, y});
|
|
||||||
point._setWindowSize(windowSize);
|
point._setWindowSize(windowSize);
|
||||||
point.multiply(BigInt(3));
|
point.multiply(BigInt(3));
|
||||||
return point;
|
return point;
|
||||||
@ -996,7 +977,7 @@ export function weierstrass(curveDef: CurveType): CurveFn {
|
|||||||
* @returns Public key, full when isCompressed=false; short when isCompressed=true
|
* @returns Public key, full when isCompressed=false; short when isCompressed=true
|
||||||
*/
|
*/
|
||||||
function getPublicKey(privateKey: PrivKey, isCompressed = true): Uint8Array {
|
function getPublicKey(privateKey: PrivKey, isCompressed = true): Uint8Array {
|
||||||
return ProjectivePoint.fromPrivateKey(privateKey).toRawBytes(isCompressed);
|
return Point.fromPrivateKey(privateKey).toRawBytes(isCompressed);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1008,7 +989,7 @@ export function weierstrass(curveDef: CurveType): CurveFn {
|
|||||||
const len = (arr || str) && (item as Hex).length;
|
const len = (arr || str) && (item as Hex).length;
|
||||||
if (arr) return len === compressedLen || len === uncompressedLen;
|
if (arr) return len === compressedLen || len === uncompressedLen;
|
||||||
if (str) return len === 2 * compressedLen || len === 2 * uncompressedLen;
|
if (str) return len === 2 * compressedLen || len === 2 * uncompressedLen;
|
||||||
if (item instanceof ProjectivePoint) return true;
|
if (item instanceof Point) return true;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1101,7 +1082,7 @@ export function weierstrass(curveDef: CurveType): CurveFn {
|
|||||||
if (!isWithinCurveOrder(k)) return;
|
if (!isWithinCurveOrder(k)) return;
|
||||||
// Important: all mod() calls in the function must be done over `n`
|
// Important: all mod() calls in the function must be done over `n`
|
||||||
const ik = mod.invert(k, n);
|
const ik = mod.invert(k, n);
|
||||||
const q = ProjectivePoint.BASE.multiply(k).toAffine();
|
const q = Point.BASE.multiply(k).toAffine();
|
||||||
// r = x mod n
|
// r = x mod n
|
||||||
const r = mod.mod(q.x, n);
|
const r = mod.mod(q.x, n);
|
||||||
if (r === _0n) return;
|
if (r === _0n) return;
|
||||||
@ -1146,7 +1127,7 @@ export function weierstrass(curveDef: CurveType): CurveFn {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Enable precomputes. Slows down first publicKey computation by 20ms.
|
// Enable precomputes. Slows down first publicKey computation by 20ms.
|
||||||
ProjectivePoint.BASE._setWindowSize(8);
|
Point.BASE._setWindowSize(8);
|
||||||
// utils.precompute(8, ProjectivePoint.BASE)
|
// utils.precompute(8, ProjectivePoint.BASE)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1203,9 +1184,7 @@ export function weierstrass(curveDef: CurveType): CurveFn {
|
|||||||
const u1 = mod.mod(h * sinv, n);
|
const u1 = mod.mod(h * sinv, n);
|
||||||
const u2 = mod.mod(r * sinv, n);
|
const u2 = mod.mod(r * sinv, n);
|
||||||
|
|
||||||
// Some implementations compare R.x in projective, without inversion.
|
const R = Point.BASE.multiplyAndAddUnsafe(P, u1, u2)?.toAffine();
|
||||||
// The speed-up is <5%, so we don't complicate the code.
|
|
||||||
const R = ProjectivePoint.BASE.multiplyAndAddUnsafe(P, u1, u2)?.toAffine();
|
|
||||||
if (!R) return false;
|
if (!R) return false;
|
||||||
const v = mod.mod(R.x, n);
|
const v = mod.mod(R.x, n);
|
||||||
return v === r;
|
return v === r;
|
||||||
@ -1218,7 +1197,7 @@ export function weierstrass(curveDef: CurveType): CurveFn {
|
|||||||
signUnhashed,
|
signUnhashed,
|
||||||
verify,
|
verify,
|
||||||
// Point,
|
// Point,
|
||||||
ProjectivePoint,
|
ProjectivePoint: Point,
|
||||||
Signature,
|
Signature,
|
||||||
utils,
|
utils,
|
||||||
};
|
};
|
||||||
|
@ -990,7 +990,7 @@ export const bls12_381: CurveFn<Fp, Fp2, Fp6, Fp12> = bls({
|
|||||||
// φ endomorphism
|
// φ endomorphism
|
||||||
const cubicRootOfUnityModP =
|
const cubicRootOfUnityModP =
|
||||||
0x5f19672fdf76ce51ba69c6076a0f77eaddb3a93be6f89688de17d813620a00022e01fffffffefffen;
|
0x5f19672fdf76ce51ba69c6076a0f77eaddb3a93be6f89688de17d813620a00022e01fffffffefffen;
|
||||||
const phi = new c(Fp.mul(point.x, cubicRootOfUnityModP), point.y, point.z);
|
const phi = new c(Fp.mul(point.px, cubicRootOfUnityModP), point.py, point.pz);
|
||||||
|
|
||||||
// todo: unroll
|
// todo: unroll
|
||||||
const xP = point.multiplyUnsafe(bls12_381.CURVE.x).negate(); // [x]P
|
const xP = point.multiplyUnsafe(bls12_381.CURVE.x).negate(); // [x]P
|
||||||
|
@ -362,7 +362,7 @@ export class RistrettoPoint {
|
|||||||
* https://ristretto.group/formulas/encoding.html
|
* https://ristretto.group/formulas/encoding.html
|
||||||
*/
|
*/
|
||||||
toRawBytes(): Uint8Array {
|
toRawBytes(): Uint8Array {
|
||||||
let { x, y, z, t } = this.ep;
|
let { ex: x, ey: y, ez: z, et: t } = this.ep;
|
||||||
const P = ed25519.CURVE.Fp.ORDER;
|
const P = ed25519.CURVE.Fp.ORDER;
|
||||||
const mod = ed25519.CURVE.Fp.create;
|
const mod = ed25519.CURVE.Fp.create;
|
||||||
const u1 = mod(mod(z + y) * mod(z - y)); // 1
|
const u1 = mod(mod(z + y) * mod(z - y)); // 1
|
||||||
@ -400,12 +400,12 @@ export class RistrettoPoint {
|
|||||||
// Compare one point to another.
|
// Compare one point to another.
|
||||||
equals(other: RistrettoPoint): boolean {
|
equals(other: RistrettoPoint): boolean {
|
||||||
assertRstPoint(other);
|
assertRstPoint(other);
|
||||||
const a = this.ep;
|
const { ex: X1, ey: Y1 } = this.ep;
|
||||||
const b = other.ep;
|
const { ex: X2, ey: Y2 } = this.ep;
|
||||||
const mod = ed25519.CURVE.Fp.create;
|
const mod = ed25519.CURVE.Fp.create;
|
||||||
// (x1 * y2 == y1 * x2) | (y1 * y2 == x1 * x2)
|
// (x1 * y2 == y1 * x2) | (y1 * y2 == x1 * x2)
|
||||||
const one = mod(a.x * b.y) === mod(a.y * b.x);
|
const one = mod(X1 * Y2) === mod(Y1 * X2);
|
||||||
const two = mod(a.y * b.y) === mod(a.x * b.x);
|
const two = mod(Y1 * Y2) === mod(X1 * X2);
|
||||||
return one || two;
|
return one || two;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -280,7 +280,7 @@ function schnorrSign(
|
|||||||
if (k0 === _0n) throw new Error('sign: Creation of signature failed. k is zero');
|
if (k0 === _0n) throw new Error('sign: Creation of signature failed. k is zero');
|
||||||
const { point: R, x: rx, scalar: k } = schnorrGetScalar(k0);
|
const { point: R, x: rx, scalar: k } = schnorrGetScalar(k0);
|
||||||
const e = schnorrChallengeFinalize(tag(TAGS.challenge, rx, px, m));
|
const e = schnorrChallengeFinalize(tag(TAGS.challenge, rx, px, m));
|
||||||
const sig = new SchnorrSignature(R.x, mod(k + e * d, secp256k1.CURVE.n)).toRawBytes();
|
const sig = new SchnorrSignature(R.px, mod(k + e * d, secp256k1.CURVE.n)).toRawBytes();
|
||||||
if (!schnorrVerify(sig, m, px)) throw new Error('sign: Invalid signature produced');
|
if (!schnorrVerify(sig, m, px)) throw new Error('sign: Invalid signature produced');
|
||||||
return sig;
|
return sig;
|
||||||
}
|
}
|
||||||
|
@ -245,7 +245,7 @@ function pedersenSingle(point: ProjectivePoint, value: PedersenArg, constants: P
|
|||||||
let x = pedersenArg(value);
|
let x = pedersenArg(value);
|
||||||
for (let j = 0; j < 252; j++) {
|
for (let j = 0; j < 252; j++) {
|
||||||
const pt = constants[j];
|
const pt = constants[j];
|
||||||
if (pt.x === point.x) throw new Error('Same point');
|
if (pt.px === point.px) throw new Error('Same point');
|
||||||
if ((x & 1n) !== 0n) point = point.add(pt);
|
if ((x & 1n) !== 0n) point = point.add(pt);
|
||||||
x >>= 1n;
|
x >>= 1n;
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,12 @@
|
|||||||
import { deepStrictEqual, throws } from 'assert';
|
import { deepStrictEqual, throws } from 'assert';
|
||||||
import { should } from 'micro-should';
|
import { describe, should } from 'micro-should';
|
||||||
import * as starknet from '../../lib/esm/stark.js';
|
import * as starknet from '../../lib/esm/stark.js';
|
||||||
import { default as issue2 } from './fixtures/issue2.json' assert { type: 'json' };
|
import { default as issue2 } from './fixtures/issue2.json' assert { type: 'json' };
|
||||||
|
import * as bip32 from '@scure/bip32';
|
||||||
|
import * as bip39 from '@scure/bip39';
|
||||||
|
|
||||||
should('Basic elliptic sanity check', () => {
|
describe('starknet basic', () => {
|
||||||
|
should('Basic elliptic sanity check', () => {
|
||||||
const g1 = starknet.ProjectivePoint.BASE;
|
const g1 = starknet.ProjectivePoint.BASE;
|
||||||
deepStrictEqual(
|
deepStrictEqual(
|
||||||
g1.toAffine().x.toString(16),
|
g1.toAffine().x.toString(16),
|
||||||
@ -31,7 +34,7 @@ should('Basic elliptic sanity check', () => {
|
|||||||
g3.toAffine().y.toString(16),
|
g3.toAffine().y.toString(16),
|
||||||
'7e1b3ebac08924d2c26f409549191fcf94f3bf6f301ed3553e22dfb802f0686'
|
'7e1b3ebac08924d2c26f409549191fcf94f3bf6f301ed3553e22dfb802f0686'
|
||||||
);
|
);
|
||||||
const g32 = g1.multiply(3);
|
const g32 = g1.multiply(3n);
|
||||||
deepStrictEqual(
|
deepStrictEqual(
|
||||||
g32.toAffine().x.toString(16),
|
g32.toAffine().x.toString(16),
|
||||||
'411494b501a98abd8262b0da1351e17899a0c4ef23dd2f96fec5ba847310b20'
|
'411494b501a98abd8262b0da1351e17899a0c4ef23dd2f96fec5ba847310b20'
|
||||||
@ -49,9 +52,9 @@ should('Basic elliptic sanity check', () => {
|
|||||||
minus1.toAffine().y.toString(16),
|
minus1.toAffine().y.toString(16),
|
||||||
'7a997f9f55b68e04841b7fe20b9139d21ac132ee541bc5cd78cfff3c91723e2'
|
'7a997f9f55b68e04841b7fe20b9139d21ac132ee541bc5cd78cfff3c91723e2'
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
should('Pedersen', () => {
|
should('Pedersen', () => {
|
||||||
deepStrictEqual(
|
deepStrictEqual(
|
||||||
starknet.pedersen(2, 3),
|
starknet.pedersen(2, 3),
|
||||||
'0x5774fa77b3d843ae9167abd61cf80365a9b2b02218fc2f628494b5bdc9b33b8'
|
'0x5774fa77b3d843ae9167abd61cf80365a9b2b02218fc2f628494b5bdc9b33b8'
|
||||||
@ -64,16 +67,16 @@ should('Pedersen', () => {
|
|||||||
starknet.pedersen(3, 4),
|
starknet.pedersen(3, 4),
|
||||||
'0x262697b88544f733e5c6907c3e1763131e9f14c51ee7951258abbfb29415fbf'
|
'0x262697b88544f733e5c6907c3e1763131e9f14c51ee7951258abbfb29415fbf'
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
should('Hash chain', () => {
|
should('Hash chain', () => {
|
||||||
deepStrictEqual(
|
deepStrictEqual(
|
||||||
starknet.hashChain([1, 2, 3]),
|
starknet.hashChain([1, 2, 3]),
|
||||||
'0x5d9d62d4040b977c3f8d2389d494e4e89a96a8b45c44b1368f1cc6ec5418915'
|
'0x5d9d62d4040b977c3f8d2389d494e4e89a96a8b45c44b1368f1cc6ec5418915'
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
should('Pedersen hash edgecases', () => {
|
should('Pedersen hash edgecases', () => {
|
||||||
// >>> pedersen_hash(0,0)
|
// >>> pedersen_hash(0,0)
|
||||||
const zero = '0x49ee3eba8c1600700ee1b87eb599f16716b0b1022947733551fde4050ca6804';
|
const zero = '0x49ee3eba8c1600700ee1b87eb599f16716b0b1022947733551fde4050ca6804';
|
||||||
deepStrictEqual(starknet.pedersen(0, 0), zero);
|
deepStrictEqual(starknet.pedersen(0, 0), zero);
|
||||||
@ -100,9 +103,9 @@ should('Pedersen hash edgecases', () => {
|
|||||||
throws(() => starknet.pedersen(false, false), 'false');
|
throws(() => starknet.pedersen(false, false), 'false');
|
||||||
throws(() => starknet.pedersen(true, true), 'true');
|
throws(() => starknet.pedersen(true, true), 'true');
|
||||||
throws(() => starknet.pedersen(10.1, 10.1), 'float');
|
throws(() => starknet.pedersen(10.1, 10.1), 'float');
|
||||||
});
|
});
|
||||||
|
|
||||||
should('hashChain edgecases', () => {
|
should('hashChain edgecases', () => {
|
||||||
deepStrictEqual(starknet.hashChain([32312321312321312312312321n]), '0x1aba6672c014b4838cc201');
|
deepStrictEqual(starknet.hashChain([32312321312321312312312321n]), '0x1aba6672c014b4838cc201');
|
||||||
deepStrictEqual(
|
deepStrictEqual(
|
||||||
starknet.hashChain([1n, 2n]),
|
starknet.hashChain([1n, 2n]),
|
||||||
@ -118,9 +121,9 @@ should('hashChain edgecases', () => {
|
|||||||
starknet.hashChain([1, 2]),
|
starknet.hashChain([1, 2]),
|
||||||
'0x5bb9440e27889a364bcb678b1f679ecd1347acdedcbf36e83494f857cc58026'
|
'0x5bb9440e27889a364bcb678b1f679ecd1347acdedcbf36e83494f857cc58026'
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
should('Pedersen hash, issue #2', () => {
|
should('Pedersen hash, issue #2', () => {
|
||||||
// Verified with starnet.js
|
// Verified with starnet.js
|
||||||
deepStrictEqual(
|
deepStrictEqual(
|
||||||
starknet.computeHashOnElements(issue2),
|
starknet.computeHashOnElements(issue2),
|
||||||
@ -134,12 +137,10 @@ should('Pedersen hash, issue #2', () => {
|
|||||||
starknet.computeHashOnElements([1]),
|
starknet.computeHashOnElements([1]),
|
||||||
'0x78d74f61aeaa8286418fd34b3a12a610445eba11d00ecc82ecac2542d55f7a4'
|
'0x78d74f61aeaa8286418fd34b3a12a610445eba11d00ecc82ecac2542d55f7a4'
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
import * as bip32 from '@scure/bip32';
|
|
||||||
import * as bip39 from '@scure/bip39';
|
|
||||||
|
|
||||||
should('Seed derivation (example)', () => {
|
should('Seed derivation (example)', () => {
|
||||||
const layer = 'starkex';
|
const layer = 'starkex';
|
||||||
const application = 'starkdeployement';
|
const application = 'starkdeployement';
|
||||||
const mnemonic =
|
const mnemonic =
|
||||||
@ -153,18 +154,18 @@ should('Seed derivation (example)', () => {
|
|||||||
starknet.grindKey(hdKey.privateKey),
|
starknet.grindKey(hdKey.privateKey),
|
||||||
'6cf0a8bf113352eb863157a45c5e5567abb34f8d32cddafd2c22aa803f4892c'
|
'6cf0a8bf113352eb863157a45c5e5567abb34f8d32cddafd2c22aa803f4892c'
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
should('Compressed keys', () => {
|
should('Compressed keys', () => {
|
||||||
const G = starknet.ProjectivePoint.BASE;
|
const G = starknet.ProjectivePoint.BASE;
|
||||||
const half = starknet.CURVE.n / 2n;
|
const half = starknet.CURVE.n / 2n;
|
||||||
const last = starknet.CURVE.n;
|
const last = starknet.CURVE.n;
|
||||||
const vectors = [
|
const vectors = [
|
||||||
1,
|
1n,
|
||||||
2,
|
2n,
|
||||||
3,
|
3n,
|
||||||
4,
|
4n,
|
||||||
5,
|
5n,
|
||||||
half - 5n,
|
half - 5n,
|
||||||
half - 4n,
|
half - 4n,
|
||||||
half - 3n,
|
half - 3n,
|
||||||
@ -191,8 +192,8 @@ should('Compressed keys', () => {
|
|||||||
deepStrictEqual(fixPoint(starknet.ProjectivePoint.fromHex(compressed)), exp);
|
deepStrictEqual(fixPoint(starknet.ProjectivePoint.fromHex(compressed)), exp);
|
||||||
deepStrictEqual(starknet.ProjectivePoint.fromHex(compressed).toHex(), uncompressed);
|
deepStrictEqual(starknet.ProjectivePoint.fromHex(compressed).toHex(), uncompressed);
|
||||||
}
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// ESM is broken.
|
// ESM is broken.
|
||||||
import url from 'url';
|
import url from 'url';
|
||||||
if (import.meta.url === url.pathToFileURL(process.argv[1]).href) {
|
if (import.meta.url === url.pathToFileURL(process.argv[1]).href) {
|
||||||
|
Loading…
Reference in New Issue
Block a user