Schnorr, weierstrass: refactor

This commit is contained in:
Paul Miller 2023-01-25 06:48:53 +00:00
parent 5fc38fc0e7
commit cffea91061
No known key found for this signature in database
GPG Key ID: 697079DA6878B89B
7 changed files with 215 additions and 283 deletions

View File

@ -18,7 +18,7 @@ import { Hex, PrivKey } from './utils.js';
import * as htf from './hash-to-curve.js'; import * as htf from './hash-to-curve.js';
import { import {
CurvePointsType, CurvePointsType,
ProjectivePointType as PPointType, ProjPointType as ProjPointType,
CurvePointsRes, CurvePointsRes,
weierstrassPoints, weierstrassPoints,
AffinePoint, AffinePoint,
@ -27,8 +27,8 @@ import {
type Fp = bigint; // Can be different field? type Fp = bigint; // Can be different field?
export type SignatureCoder<Fp2> = { export type SignatureCoder<Fp2> = {
decode(hex: Hex): PPointType<Fp2>; decode(hex: Hex): ProjPointType<Fp2>;
encode(point: PPointType<Fp2>): Uint8Array; encode(point: ProjPointType<Fp2>): Uint8Array;
}; };
export type CurveType<Fp, Fp2, Fp6, Fp12> = { export type CurveType<Fp, Fp2, Fp6, Fp12> = {
@ -79,29 +79,29 @@ export type CurveFn<Fp, Fp2, Fp6, Fp12> = {
G1: ReturnType<(typeof htf.hashToCurve<Fp>)>, G1: ReturnType<(typeof htf.hashToCurve<Fp>)>,
G2: ReturnType<(typeof htf.hashToCurve<Fp2>)>, G2: ReturnType<(typeof htf.hashToCurve<Fp2>)>,
}, },
pairing: (P: PPointType<Fp>, Q: PPointType<Fp2>, withFinalExponent?: boolean) => Fp12; pairing: (P: ProjPointType<Fp>, Q: ProjPointType<Fp2>, withFinalExponent?: boolean) => Fp12;
getPublicKey: (privateKey: PrivKey) => Uint8Array; getPublicKey: (privateKey: PrivKey) => Uint8Array;
sign: { sign: {
(message: Hex, privateKey: PrivKey): Uint8Array; (message: Hex, privateKey: PrivKey): Uint8Array;
(message: PPointType<Fp2>, privateKey: PrivKey): PPointType<Fp2>; (message: ProjPointType<Fp2>, privateKey: PrivKey): ProjPointType<Fp2>;
}; };
verify: ( verify: (
signature: Hex | PPointType<Fp2>, signature: Hex | ProjPointType<Fp2>,
message: Hex | PPointType<Fp2>, message: Hex | ProjPointType<Fp2>,
publicKey: Hex | PPointType<Fp> publicKey: Hex | ProjPointType<Fp>
) => boolean; ) => boolean;
aggregatePublicKeys: { aggregatePublicKeys: {
(publicKeys: Hex[]): Uint8Array; (publicKeys: Hex[]): Uint8Array;
(publicKeys: PPointType<Fp>[]): PPointType<Fp>; (publicKeys: ProjPointType<Fp>[]): ProjPointType<Fp>;
}; };
aggregateSignatures: { aggregateSignatures: {
(signatures: Hex[]): Uint8Array; (signatures: Hex[]): Uint8Array;
(signatures: PPointType<Fp2>[]): PPointType<Fp2>; (signatures: ProjPointType<Fp2>[]): ProjPointType<Fp2>;
}; };
verifyBatch: ( verifyBatch: (
signature: Hex | PPointType<Fp2>, signature: Hex | ProjPointType<Fp2>,
messages: (Hex | PPointType<Fp2>)[], messages: (Hex | ProjPointType<Fp2>)[],
publicKeys: (Hex | PPointType<Fp>)[] publicKeys: (Hex | ProjPointType<Fp>)[]
) => boolean; ) => boolean;
utils: { utils: {
stringToBytes: typeof htf.stringToBytes; stringToBytes: typeof htf.stringToBytes;

View File

@ -67,39 +67,39 @@ export type AffinePoint = {
} & { z?: never; t?: never }; } & { z?: never; t?: never };
// Instance of Extended Point with coordinates in X, Y, Z, T // Instance of Extended Point with coordinates in X, Y, Z, T
export interface ExtendedPointType extends Group<ExtendedPointType> { export interface ExtPointType extends Group<ExtPointType> {
readonly ex: bigint; readonly ex: bigint;
readonly ey: bigint; readonly ey: bigint;
readonly ez: bigint; readonly ez: bigint;
readonly et: bigint; readonly et: bigint;
multiply(scalar: bigint): ExtendedPointType; multiply(scalar: bigint): ExtPointType;
multiplyUnsafe(scalar: bigint): ExtendedPointType; multiplyUnsafe(scalar: bigint): ExtPointType;
isSmallOrder(): boolean; isSmallOrder(): boolean;
isTorsionFree(): boolean; isTorsionFree(): boolean;
toAffine(iz?: bigint): AffinePoint; toAffine(iz?: bigint): AffinePoint;
clearCofactor(): ExtendedPointType; clearCofactor(): ExtPointType;
} }
// Static methods of Extended Point with coordinates in X, Y, Z, T // Static methods of Extended Point with coordinates in X, Y, Z, T
export interface ExtendedPointConstructor extends GroupConstructor<ExtendedPointType> { export interface ExtPointConstructor extends GroupConstructor<ExtPointType> {
new (x: bigint, y: bigint, z: bigint, t: bigint): ExtendedPointType; new (x: bigint, y: bigint, z: bigint, t: bigint): ExtPointType;
fromAffine(p: AffinePoint): ExtendedPointType; fromAffine(p: AffinePoint): ExtPointType;
fromHex(hex: Hex): ExtendedPointType; fromHex(hex: Hex): ExtPointType;
fromPrivateKey(privateKey: PrivKey): ExtendedPointType; // TODO: remove fromPrivateKey(privateKey: PrivKey): ExtPointType; // TODO: remove
} }
export type CurveFn = { export type CurveFn = {
CURVE: ReturnType<typeof validateOpts>; CURVE: ReturnType<typeof validateOpts>;
getPublicKey: (privateKey: PrivKey, isCompressed?: boolean) => Uint8Array; getPublicKey: (privateKey: Hex) => Uint8Array;
sign: (message: Hex, privateKey: Hex) => Uint8Array; sign: (message: Hex, privateKey: Hex) => Uint8Array;
verify: (sig: Hex, message: Hex, publicKey: Hex) => boolean; verify: (sig: Hex, message: Hex, publicKey: Hex) => boolean;
ExtendedPoint: ExtendedPointConstructor; ExtendedPoint: ExtPointConstructor;
utils: { utils: {
randomPrivateKey: () => Uint8Array; randomPrivateKey: () => Uint8Array;
getExtendedPublicKey: (key: PrivKey) => { getExtendedPublicKey: (key: Hex) => {
head: Uint8Array; head: Uint8Array;
prefix: Uint8Array; prefix: Uint8Array;
scalar: bigint; scalar: bigint;
point: ExtendedPointType; point: ExtPointType;
pointBytes: Uint8Array; pointBytes: Uint8Array;
}; };
}; };
@ -153,20 +153,18 @@ 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) { const coord = (n: bigint) => _0n <= n && n < MASK; // not < P because of ZIP215
return a == null || !ut.big(a);
}
const pointPrecomputes = new Map<ExtendedPoint, ExtendedPoint[]>(); const pointPrecomputes = new Map<Point, Point[]>();
/** /**
* 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).
* Default Point works in affine coordinates: (x, y) * Default Point works in affine coordinates: (x, y)
* 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 Point implements ExtPointType {
static BASE = new ExtendedPoint(CURVE.Gx, CURVE.Gy, _1n, modP(CURVE.Gx * CURVE.Gy)); static readonly BASE = new Point(CURVE.Gx, CURVE.Gy, _1n, modP(CURVE.Gx * CURVE.Gy));
static ZERO = new ExtendedPoint(_0n, _1n, _1n, _0n); // 0, 1, 1, 0 static readonly ZERO = new Point(_0n, _1n, _1n, _0n); // 0, 1, 1, 0
constructor( constructor(
readonly ex: bigint, readonly ex: bigint,
@ -174,9 +172,10 @@ export function twistedEdwards(curveDef: CurveType): CurveFn {
readonly ez: bigint, readonly ez: bigint,
readonly et: bigint readonly et: bigint
) { ) {
if (badc(ey)) throw new Error('y required'); if (!coord(ex)) throw new Error('x required');
if (badc(ez)) throw new Error('z required'); if (!coord(ey)) throw new Error('y required');
if (badc(et)) throw new Error('t required'); if (!coord(ez)) throw new Error('z required');
if (!coord(et)) throw new Error('t required');
} }
get x(): bigint { get x(): bigint {
@ -186,15 +185,15 @@ export function twistedEdwards(curveDef: CurveType): CurveFn {
return this.toAffine().y; return this.toAffine().y;
} }
static fromAffine(p: AffinePoint): ExtendedPoint { static fromAffine(p: AffinePoint): Point {
const { x, y } = p || {}; const { x, y } = p || {};
if (p instanceof ExtendedPoint) throw new Error('fromAffine: extended point not allowed'); if (p instanceof Point) 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');
return new ExtendedPoint(x, y, _1n, modP(x * y)); return new Point(x, y, _1n, modP(x * y));
} }
static normalizeZ(points: ExtendedPoint[]): ExtendedPoint[] { static normalizeZ(points: Point[]): Point[] {
const toInv = Fp.invertBatch(points.map((p) => p.ez)); const toInv = Fp.invertBatch(points.map((p) => p.ez));
return points.map((p, i) => p.toAffine(toInv[i])).map(ExtendedPoint.fromAffine); return points.map((p, i) => p.toAffine(toInv[i])).map(Point.fromAffine);
} }
// We calculate precomputes for elliptic curve point multiplication // We calculate precomputes for elliptic curve point multiplication
@ -209,7 +208,7 @@ export function twistedEdwards(curveDef: CurveType): CurveFn {
} }
// Compare one point to another. // Compare one point to another.
equals(other: ExtendedPoint): boolean { equals(other: Point): boolean {
assertExtPoint(other); assertExtPoint(other);
const { ex: X1, ey: Y1, ez: Z1 } = this; const { ex: X1, ey: Y1, ez: Z1 } = this;
const { ex: X2, ey: Y2, ez: Z2 } = other; const { ex: X2, ey: Y2, ez: Z2 } = other;
@ -221,18 +220,18 @@ export function twistedEdwards(curveDef: CurveType): CurveFn {
} }
protected is0(): boolean { protected is0(): boolean {
return this.equals(ExtendedPoint.ZERO); return this.equals(Point.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(): Point {
return new ExtendedPoint(modP(-this.ex), this.ey, this.ez, modP(-this.et)); return new Point(modP(-this.ex), this.ey, this.ez, modP(-this.et));
} }
// Fast algo for doubling Extended Point. // Fast algo for doubling Extended Point.
// https://hyperelliptic.org/EFD/g1p/auto-twisted-extended.html#doubling-dbl-2008-hwcd // https://hyperelliptic.org/EFD/g1p/auto-twisted-extended.html#doubling-dbl-2008-hwcd
// Cost: 4M + 4S + 1*a + 6add + 1*2. // Cost: 4M + 4S + 1*a + 6add + 1*2.
double(): ExtendedPoint { double(): Point {
const { a } = CURVE; const { a } = CURVE;
const { ex: X1, ey: Y1, ez: Z1 } = this; const { ex: X1, ey: Y1, ez: Z1 } = this;
const A = modP(X1 * X1); // A = X12 const A = modP(X1 * X1); // A = X12
@ -248,13 +247,13 @@ export function twistedEdwards(curveDef: CurveType): CurveFn {
const Y3 = modP(G * H); // Y3 = G*H const Y3 = modP(G * H); // Y3 = G*H
const T3 = modP(E * H); // T3 = E*H const T3 = modP(E * H); // T3 = E*H
const Z3 = modP(F * G); // Z3 = F*G const Z3 = modP(F * G); // Z3 = F*G
return new ExtendedPoint(X3, Y3, Z3, T3); return new Point(X3, Y3, Z3, T3);
} }
// Fast algo for adding 2 Extended Points. // Fast algo for adding 2 Extended Points.
// https://hyperelliptic.org/EFD/g1p/auto-twisted-extended.html#addition-add-2008-hwcd // https://hyperelliptic.org/EFD/g1p/auto-twisted-extended.html#addition-add-2008-hwcd
// Cost: 9M + 1*a + 1*d + 7add. // Cost: 9M + 1*a + 1*d + 7add.
add(other: ExtendedPoint) { add(other: Point) {
assertExtPoint(other); assertExtPoint(other);
const { a, d } = CURVE; const { a, d } = CURVE;
const { ex: X1, ey: Y1, ez: Z1, et: T1 } = this; const { ex: X1, ey: Y1, ez: Z1, et: T1 } = this;
@ -277,7 +276,7 @@ export function twistedEdwards(curveDef: CurveType): CurveFn {
const Y3 = modP(G * H); const Y3 = modP(G * H);
const T3 = modP(E * H); const T3 = modP(E * H);
const Z3 = modP(F * G); const Z3 = modP(F * G);
return new ExtendedPoint(X3, Y3, Z3, T3); return new Point(X3, Y3, Z3, T3);
} }
const A = modP(X1 * X2); // A = X1*X2 const A = modP(X1 * X2); // A = X1*X2
const B = modP(Y1 * Y2); // B = Y1*Y2 const B = modP(Y1 * Y2); // B = Y1*Y2
@ -292,29 +291,29 @@ export function twistedEdwards(curveDef: CurveType): CurveFn {
const T3 = modP(E * H); // T3 = E*H const T3 = modP(E * H); // T3 = E*H
const Z3 = modP(F * G); // Z3 = F*G const Z3 = modP(F * G); // Z3 = F*G
return new ExtendedPoint(X3, Y3, Z3, T3); return new Point(X3, Y3, Z3, T3);
} }
subtract(other: ExtendedPoint): ExtendedPoint { subtract(other: Point): Point {
return this.add(other.negate()); return this.add(other.negate());
} }
private wNAF(n: bigint): { p: ExtendedPoint; f: ExtendedPoint } { private wNAF(n: bigint): { p: Point; f: Point } {
return wnaf.wNAFCached(this, pointPrecomputes, n, ExtendedPoint.normalizeZ); return wnaf.wNAFCached(this, pointPrecomputes, n, Point.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): Point {
const { p, f } = this.wNAF(assertGE(scalar)); const { p, f } = this.wNAF(assertGE(scalar));
return ExtendedPoint.normalizeZ([p, f])[0]; return Point.normalizeZ([p, f])[0];
} }
// Non-constant-time multiplication. Uses double-and-add algorithm. // Non-constant-time multiplication. Uses double-and-add algorithm.
// It's faster, but should only be used when you don't care about // It's faster, but should only be used when you don't care about
// an exposed private key e.g. sig verification. // an exposed private key e.g. sig verification.
multiplyUnsafe(scalar: bigint): ExtendedPoint { multiplyUnsafe(scalar: bigint): Point {
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;
@ -349,7 +348,7 @@ export function twistedEdwards(curveDef: CurveType): CurveFn {
if (zz !== _1n) throw new Error('invZ was invalid'); if (zz !== _1n) throw new Error('invZ was invalid');
return { x: ax, y: ay }; return { x: ax, y: ay };
} }
clearCofactor(): ExtendedPoint { clearCofactor(): Point {
const { h: cofactor } = CURVE; const { h: cofactor } = CURVE;
if (cofactor === _1n) return this; if (cofactor === _1n) return this;
return this.multiplyUnsafe(cofactor); return this.multiplyUnsafe(cofactor);
@ -399,7 +398,7 @@ export function twistedEdwards(curveDef: CurveType): CurveFn {
const isXOdd = (x & _1n) === _1n; const isXOdd = (x & _1n) === _1n;
const isLastByteOdd = (lastByte & 0x80) !== 0; const isLastByteOdd = (lastByte & 0x80) !== 0;
if (isLastByteOdd !== isXOdd) x = modP(-x); if (isLastByteOdd !== isXOdd) x = modP(-x);
return ExtendedPoint.fromAffine({ x, y }); return Point.fromAffine({ x, y });
} }
static fromPrivateKey(privateKey: PrivKey) { static fromPrivateKey(privateKey: PrivKey) {
return getExtendedPublicKey(privateKey).point; return getExtendedPublicKey(privateKey).point;
@ -415,11 +414,11 @@ export function twistedEdwards(curveDef: CurveType): CurveFn {
return ut.bytesToHex(this.toRawBytes()); // Same as toRawBytes, but returns string. return ut.bytesToHex(this.toRawBytes()); // Same as toRawBytes, but returns string.
} }
} }
const { BASE: G, ZERO: I } = ExtendedPoint; const { BASE: G, ZERO: I } = Point;
const wnaf = wNAF(ExtendedPoint, CURVE.nByteLength * 8); const wnaf = wNAF(Point, CURVE.nByteLength * 8);
function assertExtPoint(other: unknown) { function assertExtPoint(other: unknown) {
if (!(other instanceof ExtendedPoint)) throw new Error('ExtendedPoint expected'); if (!(other instanceof Point)) throw new Error('ExtendedPoint expected');
} }
// Little-endian SHA512 with modulo n // Little-endian SHA512 with modulo n
function modnLE(hash: Uint8Array): bigint { function modnLE(hash: Uint8Array): bigint {
@ -490,15 +489,15 @@ export function twistedEdwards(curveDef: CurveType): CurveFn {
sig = ensureBytes(sig, 2 * len); sig = ensureBytes(sig, 2 * len);
message = ensureBytes(message); message = ensureBytes(message);
if (CURVE.preHash) message = CURVE.preHash(message); if (CURVE.preHash) message = CURVE.preHash(message);
const R = ExtendedPoint.fromHex(sig.slice(0, len), false); // non-strict; allows 0..MASK const R = Point.fromHex(sig.slice(0, len), false); // non-strict; allows 0..MASK
const s = ut.bytesToNumberLE(sig.slice(len, 2 * len)); const s = ut.bytesToNumberLE(sig.slice(len, 2 * len));
const A = ExtendedPoint.fromHex(publicKey, false); // Check for s bounds, hex validity const A = Point.fromHex(publicKey, false); // Check for s bounds, hex validity
const SB = G.multiplyUnsafe(s); const SB = G.multiplyUnsafe(s);
const k = hashDomainToScalar(ut.concatBytes(R.toRawBytes(), A.toRawBytes(), message), context); const k = hashDomainToScalar(ut.concatBytes(R.toRawBytes(), A.toRawBytes(), message), context);
const kA = A.multiplyUnsafe(k); const kA = A.multiplyUnsafe(k);
const RkA = R.add(kA); const RkA = R.add(kA);
// [8][S]B = [8]R + [8][k]A' // [8][S]B = [8]R + [8][k]A'
return RkA.subtract(SB).clearCofactor().equals(ExtendedPoint.ZERO); return RkA.subtract(SB).clearCofactor().equals(Point.ZERO);
} }
// Enable precomputes. Slows down first publicKey computation by 20ms. // Enable precomputes. Slows down first publicKey computation by 20ms.
@ -523,7 +522,7 @@ 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 = ExtendedPoint.BASE): typeof ExtendedPoint.BASE { precompute(windowSize = 8, point = Point.BASE): typeof Point.BASE {
point._setWindowSize(windowSize); point._setWindowSize(windowSize);
point.multiply(BigInt(3)); point.multiply(BigInt(3));
return point; return point;
@ -535,7 +534,7 @@ export function twistedEdwards(curveDef: CurveType): CurveFn {
getPublicKey, getPublicKey,
sign, sign,
verify, verify,
ExtendedPoint, ExtendedPoint: Point,
utils, utils,
}; };
} }

View File

@ -32,12 +32,9 @@ export type BasicCurve<T> = ut.BasicCurve<T> & {
endo?: EndomorphismOpts; endo?: EndomorphismOpts;
// When a cofactor != 1, there can be an effective methods to: // When a cofactor != 1, there can be an effective methods to:
// 1. Determine whether a point is torsion-free // 1. Determine whether a point is torsion-free
isTorsionFree?: (c: ProjectiveConstructor<T>, point: ProjectivePointType<T>) => boolean; isTorsionFree?: (c: ProjConstructor<T>, point: ProjPointType<T>) => boolean;
// 2. Clear torsion component // 2. Clear torsion component
clearCofactor?: ( clearCofactor?: (c: ProjConstructor<T>, point: ProjPointType<T>) => ProjPointType<T>;
c: ProjectiveConstructor<T>,
point: ProjectivePointType<T>
) => ProjectivePointType<T>;
}; };
// ASN.1 DER encoding utilities // ASN.1 DER encoding utilities
@ -115,43 +112,35 @@ export type AffinePoint<T> = {
y: T; y: 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 ProjPointType<T> extends Group<ProjPointType<T>> {
readonly px: T; readonly px: T;
readonly py: T; readonly py: T;
readonly pz: T; readonly pz: T;
multiply(scalar: bigint): ProjectivePointType<T>; multiply(scalar: bigint): ProjPointType<T>;
multiplyUnsafe(scalar: bigint): ProjectivePointType<T>; multiplyUnsafe(scalar: bigint): ProjPointType<T>;
multiplyAndAddUnsafe( multiplyAndAddUnsafe(Q: ProjPointType<T>, a: bigint, b: bigint): ProjPointType<T> | undefined;
Q: ProjectivePointType<T>,
a: bigint,
b: bigint
): ProjectivePointType<T> | undefined;
_setWindowSize(windowSize: number): void; _setWindowSize(windowSize: number): void;
toAffine(iz?: T): AffinePoint<T>; toAffine(iz?: T): AffinePoint<T>;
isTorsionFree(): boolean; isTorsionFree(): boolean;
clearCofactor(): ProjectivePointType<T>; clearCofactor(): ProjPointType<T>;
assertValidity(): void; assertValidity(): void;
hasEvenY(): boolean; hasEvenY(): boolean;
toRawBytes(isCompressed?: boolean): Uint8Array; toRawBytes(isCompressed?: boolean): Uint8Array;
toHex(isCompressed?: boolean): string; toHex(isCompressed?: boolean): string;
} }
// Static methods for 3d XYZ points // Static methods for 3d XYZ points
export interface ProjectiveConstructor<T> extends GroupConstructor<ProjectivePointType<T>> { export interface ProjConstructor<T> extends GroupConstructor<ProjPointType<T>> {
new (x: T, y: T, z: T): ProjectivePointType<T>; new (x: T, y: T, z: T): ProjPointType<T>;
fromAffine(p: AffinePoint<T>): ProjectivePointType<T>; fromAffine(p: AffinePoint<T>): ProjPointType<T>;
fromHex(hex: Hex): ProjectivePointType<T>; fromHex(hex: Hex): ProjPointType<T>;
fromPrivateKey(privateKey: PrivKey): ProjectivePointType<T>; fromPrivateKey(privateKey: PrivKey): ProjPointType<T>;
normalizeZ(points: ProjectivePointType<T>[]): ProjectivePointType<T>[]; normalizeZ(points: ProjPointType<T>[]): ProjPointType<T>[];
} }
export type CurvePointsType<T> = BasicCurve<T> & { export type CurvePointsType<T> = BasicCurve<T> & {
// Bytes // Bytes
fromBytes: (bytes: Uint8Array) => AffinePoint<T>; fromBytes: (bytes: Uint8Array) => AffinePoint<T>;
toBytes: ( toBytes: (c: ProjConstructor<T>, point: ProjPointType<T>, compressed: boolean) => Uint8Array;
c: ProjectiveConstructor<T>,
point: ProjectivePointType<T>,
compressed: boolean
) => Uint8Array;
}; };
function validatePointOpts<T>(curve: CurvePointsType<T>) { function validatePointOpts<T>(curve: CurvePointsType<T>) {
@ -185,7 +174,7 @@ function validatePointOpts<T>(curve: CurvePointsType<T>) {
} }
export type CurvePointsRes<T> = { export type CurvePointsRes<T> = {
ProjectivePoint: ProjectiveConstructor<T>; ProjectivePoint: ProjConstructor<T>;
normalizePrivateKey: (key: PrivKey) => bigint; normalizePrivateKey: (key: PrivKey) => bigint;
weierstrassEquation: (x: T) => T; weierstrassEquation: (x: T) => T;
isWithinCurveOrder: (num: bigint) => boolean; isWithinCurveOrder: (num: bigint) => boolean;
@ -230,17 +219,15 @@ export function weierstrassPoints<T>(opts: CurvePointsType<T>) {
if (typeof key === 'bigint') { if (typeof key === 'bigint') {
// Curve order check is done below // Curve order check is done below
num = key; num = key;
} else if (ut.isPositiveInt(key)) {
num = BigInt(key);
} else if (typeof key === 'string') { } else if (typeof key === 'string') {
if (key.length !== 2 * groupLen) throw new Error(`Private key must be ${groupLen} bytes`); if (key.length !== 2 * groupLen) throw new Error(`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(`Private key must be ${groupLen} bytes`); if (key.length !== groupLen) throw new Error(`must be ${groupLen} bytes`);
num = ut.bytesToNumberBE(key); num = ut.bytesToNumberBE(key);
} else { } else {
throw new Error('Private key was invalid'); throw new Error('private key must be bytes, hex or bigint, not ' + typeof key);
} }
// 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);
@ -249,25 +236,28 @@ export function weierstrassPoints<T>(opts: CurvePointsType<T>) {
} }
const pointPrecomputes = new Map<ProjectivePoint, ProjectivePoint[]>(); const pointPrecomputes = new Map<ProjectivePoint, ProjectivePoint[]>();
function assertPrjPoint(other: unknown) {
if (!(other instanceof ProjectivePoint)) throw new Error('ProjectivePoint expected');
}
/** /**
* 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)
* Default Point works in 2d / affine coordinates: (x, y) * Default Point works in 2d / affine coordinates: (x, y)
* 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 ProjPointType<T> {
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) { constructor(readonly px: T, readonly py: T, readonly pz: T) {
if (py == null || !Fp.isValid(py)) throw new Error('ProjectivePoint: y required'); if (px == null || !Fp.isValid(px)) throw new Error('x required');
if (pz == null || !Fp.isValid(pz)) throw new Error('ProjectivePoint: z 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: 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)) throw new Error('invalid affine point');
throw new Error('fromAffine: invalid affine point'); if (p instanceof ProjectivePoint) throw new Error('projective point not allowed');
if (p instanceof ProjectivePoint) throw new Error('fromAffine: projective point not allowed');
const is0 = (i: T) => Fp.equals(i, Fp.ZERO); const is0 = (i: T) => Fp.equals(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) // fromAffine(x:0, y:0) would produce (x:0, y:0, z:1), but we need (x:0, y:1, z:0)
if (is0(x) && is0(y)) return ProjectivePoint.ZERO; if (is0(x) && is0(y)) return ProjectivePoint.ZERO;
@ -587,11 +577,8 @@ export function weierstrassPoints<T>(opts: CurvePointsType<T>) {
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);
function assertPrjPoint(other: unknown) {
if (!(other instanceof ProjectivePoint)) throw new Error('ProjectivePoint expected');
}
return { return {
ProjectivePoint: ProjectivePoint as ProjectiveConstructor<T>, ProjectivePoint: ProjectivePoint as ProjConstructor<T>,
normalizePrivateKey, normalizePrivateKey,
weierstrassEquation, weierstrassEquation,
isWithinCurveOrder, isWithinCurveOrder,
@ -607,7 +594,7 @@ export interface SignatureType {
addRecoveryBit(recovery: number): SignatureType; addRecoveryBit(recovery: number): SignatureType;
hasHighS(): boolean; hasHighS(): boolean;
normalizeS(): SignatureType; normalizeS(): SignatureType;
recoverPublicKey(msgHash: Hex): ProjectivePointType<bigint>; recoverPublicKey(msgHash: Hex): ProjPointType<bigint>;
// DER-encoded // DER-encoded
toDERRawBytes(isCompressed?: boolean): Uint8Array; toDERRawBytes(isCompressed?: boolean): Uint8Array;
toDERHex(isCompressed?: boolean): string; toDERHex(isCompressed?: boolean): string;
@ -621,7 +608,7 @@ export type SignatureConstructor = {
fromDER(hex: Hex): SignatureType; fromDER(hex: Hex): SignatureType;
}; };
export type PubKey = Hex | ProjectivePointType<bigint>; export type PubKey = Hex | ProjPointType<bigint>;
export type CurveType = BasicCurve<bigint> & { export type CurveType = BasicCurve<bigint> & {
// Default options // Default options
@ -651,7 +638,7 @@ export type CurveFn = {
getSharedSecret: (privateA: PrivKey, publicB: Hex, isCompressed?: boolean) => Uint8Array; getSharedSecret: (privateA: PrivKey, publicB: Hex, isCompressed?: boolean) => Uint8Array;
sign: (msgHash: Hex, privKey: PrivKey, opts?: SignOpts) => SignatureType; sign: (msgHash: Hex, privKey: PrivKey, opts?: SignOpts) => SignatureType;
verify: (signature: Hex | SignatureType, msgHash: Hex, publicKey: Hex, opts?: VerOpts) => boolean; verify: (signature: Hex | SignatureType, msgHash: Hex, publicKey: Hex, opts?: VerOpts) => boolean;
ProjectivePoint: ProjectiveConstructor<bigint>; ProjectivePoint: ProjConstructor<bigint>;
Signature: SignatureConstructor; Signature: SignatureConstructor;
utils: { utils: {
_bigintToBytes: (num: bigint) => Uint8Array; _bigintToBytes: (num: bigint) => Uint8Array;
@ -1104,7 +1091,7 @@ export function weierstrass(curveDef: CurveType): CurveFn {
publicKey: Hex, publicKey: Hex,
opts = defaultVerOpts opts = defaultVerOpts
): boolean { ): boolean {
let P: ProjectivePointType<bigint>; let P: ProjPointType<bigint>;
let _sig: Signature | undefined = undefined; let _sig: Signature | undefined = undefined;
if (publicKey instanceof Point) throw new Error('publicKey must be hex'); if (publicKey instanceof Point) throw new Error('publicKey must be hex');
try { try {

View File

@ -28,8 +28,8 @@ import {
} from './abstract/utils.js'; } from './abstract/utils.js';
// Types // Types
import { import {
ProjectivePointType, ProjPointType,
ProjectiveConstructor, ProjConstructor,
mapToCurveSimpleSWU, mapToCurveSimpleSWU,
AffinePoint, AffinePoint,
} from './abstract/weierstrass.js'; } from './abstract/weierstrass.js';
@ -886,7 +886,7 @@ function psi(x: Fp2, y: Fp2): [Fp2, Fp2] {
return [x2, y2]; return [x2, y2];
} }
// Ψ endomorphism // Ψ endomorphism
function G2psi(c: ProjectiveConstructor<Fp2>, P: ProjectivePointType<Fp2>) { function G2psi(c: ProjConstructor<Fp2>, P: ProjPointType<Fp2>) {
const affine = P.toAffine(); const affine = P.toAffine();
const p = psi(affine.x, affine.y); const p = psi(affine.x, affine.y);
return new c(p[0], p[1], Fp2.ONE); return new c(p[0], p[1], Fp2.ONE);
@ -899,7 +899,7 @@ const PSI2_C1 =
function psi2(x: Fp2, y: Fp2): [Fp2, Fp2] { function psi2(x: Fp2, y: Fp2): [Fp2, Fp2] {
return [Fp2.mul(x, PSI2_C1), Fp2.negate(y)]; return [Fp2.mul(x, PSI2_C1), Fp2.negate(y)];
} }
function G2psi2(c: ProjectiveConstructor<Fp2>, P: ProjectivePointType<Fp2>) { function G2psi2(c: ProjConstructor<Fp2>, P: ProjPointType<Fp2>) {
const affine = P.toAffine(); const affine = P.toAffine();
const p = psi2(affine.x, affine.y); const p = psi2(affine.x, affine.y);
return new c(p[0], p[1], Fp2.ONE); return new c(p[0], p[1], Fp2.ONE);
@ -1190,7 +1190,7 @@ export const bls12_381: CurveFn<Fp, Fp2, Fp6, Fp12> = bls({
}, },
Signature: { Signature: {
// TODO: Optimize, it's very slow because of sqrt. // TODO: Optimize, it's very slow because of sqrt.
decode(hex: Hex): ProjectivePointType<Fp2> { decode(hex: Hex): ProjPointType<Fp2> {
hex = ensureBytes(hex); hex = ensureBytes(hex);
const P = Fp.ORDER; const P = Fp.ORDER;
const half = hex.length / 2; const half = hex.length / 2;
@ -1222,7 +1222,7 @@ export const bls12_381: CurveFn<Fp, Fp2, Fp6, Fp12> = bls({
point.assertValidity(); point.assertValidity();
return point; return point;
}, },
encode(point: ProjectivePointType<Fp2>) { encode(point: ProjPointType<Fp2>) {
// NOTE: by some reasons it was missed in bls12-381, looks like bug // NOTE: by some reasons it was missed in bls12-381, looks like bug
point.assertValidity(); point.assertValidity();
if (point.equals(bls12_381.G2.ProjectivePoint.ZERO)) if (point.equals(bls12_381.G2.ProjectivePoint.ZERO))

View File

@ -1,7 +1,7 @@
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */ /*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
import { sha512 } from '@noble/hashes/sha512'; import { sha512 } from '@noble/hashes/sha512';
import { concatBytes, randomBytes, utf8ToBytes } from '@noble/hashes/utils'; import { concatBytes, randomBytes, utf8ToBytes } from '@noble/hashes/utils';
import { twistedEdwards, ExtendedPointType } from './abstract/edwards.js'; import { twistedEdwards, ExtPointType } from './abstract/edwards.js';
import { montgomery } from './abstract/montgomery.js'; import { montgomery } from './abstract/montgomery.js';
import { mod, pow2, isNegativeLE, Fp as Field, FpSqrtEven } from './abstract/modular.js'; import { mod, pow2, isNegativeLE, Fp as Field, FpSqrtEven } from './abstract/modular.js';
import { import {
@ -269,7 +269,7 @@ const MAX_255B = BigInt('0x7ffffffffffffffffffffffffffffffffffffffffffffffffffff
const bytes255ToNumberLE = (bytes: Uint8Array) => const bytes255ToNumberLE = (bytes: Uint8Array) =>
ed25519.CURVE.Fp.create(bytesToNumberLE(bytes) & MAX_255B); ed25519.CURVE.Fp.create(bytesToNumberLE(bytes) & MAX_255B);
type ExtendedPoint = ExtendedPointType; type ExtendedPoint = ExtPointType;
// Computes Elligator map for Ristretto // Computes Elligator map for Ristretto
// https://ristretto.group/formulas/elligator.html // https://ristretto.group/formulas/elligator.html

View File

@ -2,15 +2,8 @@
import { sha256 } from '@noble/hashes/sha256'; import { sha256 } from '@noble/hashes/sha256';
import { Fp as Field, mod, pow2 } from './abstract/modular.js'; import { Fp as Field, mod, pow2 } from './abstract/modular.js';
import { createCurve } from './_shortw_utils.js'; import { createCurve } from './_shortw_utils.js';
import { ProjectivePointType as PointType, mapToCurveSimpleSWU } from './abstract/weierstrass.js'; import { ProjPointType as PointType, mapToCurveSimpleSWU } from './abstract/weierstrass.js';
import { import { ensureBytes, concatBytes, Hex, bytesToNumberBE, PrivKey } from './abstract/utils.js';
ensureBytes,
concatBytes,
Hex,
hexToBytes,
bytesToNumberBE,
PrivKey,
} from './abstract/utils.js';
import { randomBytes } from '@noble/hashes/utils'; import { randomBytes } from '@noble/hashes/utils';
import * as htf from './abstract/hash-to-curve.js'; import * as htf from './abstract/hash-to-curve.js';
@ -62,45 +55,6 @@ function sqrtMod(y: bigint): bigint {
const Fp = Field(secp256k1P, undefined, undefined, { sqrt: sqrtMod }); const Fp = Field(secp256k1P, undefined, undefined, { sqrt: sqrtMod });
type Fp = bigint; type Fp = bigint;
const isoMap = htf.isogenyMap(
Fp,
[
// xNum
[
'0x8e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38daaaaa8c7',
'0x7d3d4c80bc321d5b9f315cea7fd44c5d595d2fc0bf63b92dfff1044f17c6581',
'0x534c328d23f234e6e2a413deca25caece4506144037c40314ecbd0b53d9dd262',
'0x8e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38daaaaa88c',
],
// xDen
[
'0xd35771193d94918a9ca34ccbb7b640dd86cd409542f8487d9fe6b745781eb49b',
'0xedadc6f64383dc1df7c4b2d51b54225406d36b641f5e41bbc52a56612a8c6d14',
'0x0000000000000000000000000000000000000000000000000000000000000001', // LAST 1
],
// yNum
[
'0x4bda12f684bda12f684bda12f684bda12f684bda12f684bda12f684b8e38e23c',
'0xc75e0c32d5cb7c0fa9d0a54b12a0a6d5647ab046d686da6fdffc90fc201d71a3',
'0x29a6194691f91a73715209ef6512e576722830a201be2018a765e85a9ecee931',
'0x2f684bda12f684bda12f684bda12f684bda12f684bda12f684bda12f38e38d84',
],
// yDen
[
'0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffff93b',
'0x7a06534bb8bdb49fd5e9e6632722c2989467c1bfc8e8d978dfb425d2685c2573',
'0x6484aa716545ca2cf3a70c3fa8fe337e0a3d21162f0d6299a7bf8192bfd2a76f',
'0x0000000000000000000000000000000000000000000000000000000000000001', // LAST 1
],
].map((i) => i.map((j) => BigInt(j))) as [Fp[], Fp[], Fp[], Fp[]]
);
const mapSWU = mapToCurveSimpleSWU(Fp, {
A: BigInt('0x3f8731abdd661adca08a5558f0f5d272e953d363cb6f0e5d405447c01a444533'),
B: BigInt('1771'),
Z: Fp.create(BigInt('-11')),
});
export const secp256k1 = createCurve( export const secp256k1 = createCurve(
{ {
// Params: a, b // Params: a, b
@ -147,54 +101,11 @@ export const secp256k1 = createCurve(
sha256 sha256
); );
const { hashToCurve, encodeToCurve } = htf.hashToCurve(
secp256k1.ProjectivePoint,
(scalars: bigint[]) => {
const { x, y } = mapSWU(Fp.create(scalars[0]));
return isoMap(x, y);
},
{
DST: 'secp256k1_XMD:SHA-256_SSWU_RO_',
encodeDST: 'secp256k1_XMD:SHA-256_SSWU_NU_',
p: Fp.ORDER,
m: 1,
k: 128,
expand: 'xmd',
hash: sha256,
}
);
export { hashToCurve, encodeToCurve };
// Schnorr // Schnorr
const _0n = BigInt(0); const _0n = BigInt(0);
const numTo32b = secp256k1.utils._bigintToBytes; const numTo32b = secp256k1.utils._bigintToBytes;
const numTo32bStr = secp256k1.utils._bigintToString; const fe = (x: bigint) => typeof x === 'bigint' && _0n < x && x < secp256k1P;
const normalizePrivateKey = secp256k1.utils._normalizePrivateKey; const ge = (x: bigint) => typeof x === 'bigint' && _0n < x && x < secp256k1N;
// TODO: export?
function normalizePublicKey(publicKey: Hex | PointType<bigint>): PointType<bigint> {
if (publicKey instanceof secp256k1.ProjectivePoint) {
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 = secp256k1.ProjectivePoint.fromAffine({ x, y });
point.assertValidity();
return point;
}
}
const isWithinCurveOrder = secp256k1.utils._isWithinCurveOrder;
const isValidFieldElement = secp256k1.utils._isValidFieldElement;
const TAGS = { const TAGS = {
challenge: 'BIP0340/challenge', challenge: 'BIP0340/challenge',
@ -213,45 +124,44 @@ export function taggedHash(tag: string, ...messages: Uint8Array[]): Uint8Array {
} }
return sha256(concatBytes(tagP, ...messages)); return sha256(concatBytes(tagP, ...messages));
} }
const toRawX = (point: PointType<bigint>) => point.toRawBytes(true).slice(1);
// Schnorr signatures are superior to ECDSA from above. // Schnorr signatures are superior to ECDSA from above.
// Below is Schnorr-specific code as per BIP0340. // Below is Schnorr-specific code as per BIP0340.
function schnorrChallengeFinalize(ch: Uint8Array): bigint {
return mod(bytesToNumberBE(ch), secp256k1.CURVE.n);
}
// Do we need this at all for Schnorr?
class SchnorrSignature {
constructor(readonly r: bigint, readonly s: bigint) {
this.assertValidity();
}
static fromHex(hex: Hex) {
const bytes = ensureBytes(hex);
const len = 32; // group length
if (bytes.length !== 2 * len)
throw new Error(`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(): string {
return numTo32bStr(this.r) + numTo32bStr(this.s);
}
toRawBytes(): Uint8Array {
return hexToBytes(this.toHex());
}
}
const tag = taggedHash;
const toRawX = (point: PointType<bigint>) => point.toRawBytes(true).slice(1);
const b2num = bytesToNumberBE;
const modN = (x: bigint) => mod(x, secp256k1N);
function validateRS(r: bigint, s: bigint) {
if (!fe(r) || !ge(s)) throw new Error('Invalid signature');
}
const PPoint = secp256k1.ProjectivePoint;
function schnorrGetScalar(priv: bigint) { function schnorrGetScalar(priv: bigint) {
const point = secp256k1.ProjectivePoint.fromPrivateKey(priv); const point = PPoint.fromPrivateKey(priv);
const scalar = point.hasEvenY() ? priv : secp256k1.CURVE.n - priv; const scalar = point.hasEvenY() ? priv : modN(-priv);
return { point, scalar, x: toRawX(point) }; return { point, scalar, x: toRawX(point) };
} }
function lift_x(x: bigint) {
if (!fe(x)) throw new Error('not fe'); // Fail if x ≥ p.
const c = mod(x * x * x + BigInt(7), secp256k1P); // Let c = x3 + 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 PPoint(x, y, _1n); // y(P) = y if y mod 2 = 0 or y(P) = p-y otherwise.
p.assertValidity();
return p;
}
function packSig(r: bigint, s: bigint): Uint8Array {
validateRS(r, s);
const sig = new Uint8Array(64);
sig.set(numTo32b(r), 0);
sig.set(numTo32b(s), 32);
return sig;
}
function unpackSig(sig: Uint8Array): { r: bigint; s: bigint } {
const r = b2num(sig.subarray(0, 32)); // Let r = int(sig[0:32]); fail if r ≥ p.
const s = b2num(sig.subarray(32, 64)); // Let s = int(sig[32:64]); fail if s ≥ n.
validateRS(r, s);
return { r, s };
}
/** /**
* Synchronously creates Schnorr signature. Improved security: verifies itself before * Synchronously creates Schnorr signature. Improved security: verifies itself before
* producing an output. * producing an output.
@ -259,26 +169,20 @@ function schnorrGetScalar(priv: bigint) {
* @param privateKey private key * @param privateKey private key
* @param auxRand random bytes that would be added to k. Bad RNG won't break it. * @param auxRand random bytes that would be added to k. Bad RNG won't break it.
*/ */
function schnorrSign( function schnorrSign(message: Hex, privateKey: Hex, auxRand: Hex = randomBytes(32)): Uint8Array {
message: Hex,
privateKey: PrivKey,
auxRand: Hex = randomBytes(32)
): Uint8Array {
if (message == null) throw new Error(`sign: Expected valid message, not "${message}"`); if (message == null) throw new Error(`sign: Expected valid message, not "${message}"`);
const m = ensureBytes(message); const m = ensureBytes(message);
// checks for isWithinCurveOrder // checks for isWithinCurveOrder
const { x: px, scalar: d } = schnorrGetScalar(normalizePrivateKey(privateKey)); const { x: px, scalar: d } = schnorrGetScalar(b2num(ensureBytes(privateKey, 32)));
const rand = ensureBytes(auxRand); const a = ensureBytes(auxRand, 32); // Auxiliary random data a: a 32-byte array
if (rand.length !== 32) throw new Error('sign: Expected 32 bytes of aux randomness'); // TODO: replace with proper xor?
const tag = taggedHash; const t = numTo32b(d ^ bytesToNumberBE(tag(TAGS.aux, a))); // Let t be the byte-wise xor of bytes(d) and hashBIP0340/aux(a)
const t0h = tag(TAGS.aux, rand); const rand = tag(TAGS.nonce, t, px, m); // Let rand = hashBIP0340/nonce(t || bytes(P) || m)
const t = numTo32b(d ^ bytesToNumberBE(t0h)); const k_ = modN(bytesToNumberBE(rand)); // Let k' = int(rand) mod n
const k0h = tag(TAGS.nonce, t, px, m); if (k_ === _0n) throw new Error('sign failed: k is zero'); // Fail if k' = 0.
const k0 = mod(bytesToNumberBE(k0h), secp256k1.CURVE.n); const { point: R, x: rx, scalar: k } = schnorrGetScalar(k_);
if (k0 === _0n) throw new Error('sign: Creation of signature failed. k is zero'); const e = modN(b2num(tag(TAGS.challenge, rx, px, m)));
const { point: R, x: rx, scalar: k } = schnorrGetScalar(k0); const sig = packSig(R.px, modN(k + e * d));
const e = schnorrChallengeFinalize(tag(TAGS.challenge, rx, px, m));
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;
} }
@ -288,23 +192,12 @@ function schnorrSign(
*/ */
function schnorrVerify(signature: Hex, message: Hex, publicKey: Hex): boolean { function schnorrVerify(signature: Hex, message: Hex, publicKey: Hex): boolean {
try { try {
const raw = signature instanceof SchnorrSignature; const P = lift_x(b2num(ensureBytes(publicKey, 32))); // P = lift_x(int(pk)); fail if that fails
const sig: SchnorrSignature = raw ? signature : SchnorrSignature.fromHex(signature); const { r, s } = unpackSig(ensureBytes(signature, 64));
if (raw) sig.assertValidity(); // just in case
const { r, s } = sig;
const m = ensureBytes(message); const m = ensureBytes(message);
const P = normalizePublicKey(publicKey); const e = modN(b2num(tag(TAGS.challenge, numTo32b(r), toRawX(P), m)));
const e = schnorrChallengeFinalize(taggedHash(TAGS.challenge, numTo32b(r), toRawX(P), m)); const R = PPoint.BASE.multiplyAndAddUnsafe(P, s, modN(-e)); // R = s⋅G - e⋅P
// Finalize if (!R || !R.hasEvenY() || R.toAffine().x !== r) return false; // -eP == (n-e)P
// R = s⋅G - e⋅P
// -eP == (n-e)P
const R = secp256k1.ProjectivePoint.BASE.multiplyAndAddUnsafe(
P,
normalizePrivateKey(s),
mod(-e, secp256k1.CURVE.n)
);
if (!R || !R.hasEvenY() || R.toAffine().x !== r) return false;
return true; return true;
} catch (error) { } catch (error) {
return false; return false;
@ -312,10 +205,63 @@ function schnorrVerify(signature: Hex, message: Hex, publicKey: Hex): boolean {
} }
export const schnorr = { export const schnorr = {
Signature: SchnorrSignature,
// Schnorr's pubkey is just `x` of Point (BIP340) // Schnorr's pubkey is just `x` of Point (BIP340)
getPublicKey: (privateKey: PrivKey): Uint8Array => getPublicKey: (privateKey: PrivKey): Uint8Array => toRawX(PPoint.fromPrivateKey(privateKey)),
toRawX(secp256k1.ProjectivePoint.fromPrivateKey(privateKey)),
sign: schnorrSign, sign: schnorrSign,
verify: schnorrVerify, verify: schnorrVerify,
}; };
const isoMap = htf.isogenyMap(
Fp,
[
// xNum
[
'0x8e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38daaaaa8c7',
'0x7d3d4c80bc321d5b9f315cea7fd44c5d595d2fc0bf63b92dfff1044f17c6581',
'0x534c328d23f234e6e2a413deca25caece4506144037c40314ecbd0b53d9dd262',
'0x8e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38daaaaa88c',
],
// xDen
[
'0xd35771193d94918a9ca34ccbb7b640dd86cd409542f8487d9fe6b745781eb49b',
'0xedadc6f64383dc1df7c4b2d51b54225406d36b641f5e41bbc52a56612a8c6d14',
'0x0000000000000000000000000000000000000000000000000000000000000001', // LAST 1
],
// yNum
[
'0x4bda12f684bda12f684bda12f684bda12f684bda12f684bda12f684b8e38e23c',
'0xc75e0c32d5cb7c0fa9d0a54b12a0a6d5647ab046d686da6fdffc90fc201d71a3',
'0x29a6194691f91a73715209ef6512e576722830a201be2018a765e85a9ecee931',
'0x2f684bda12f684bda12f684bda12f684bda12f684bda12f684bda12f38e38d84',
],
// yDen
[
'0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffff93b',
'0x7a06534bb8bdb49fd5e9e6632722c2989467c1bfc8e8d978dfb425d2685c2573',
'0x6484aa716545ca2cf3a70c3fa8fe337e0a3d21162f0d6299a7bf8192bfd2a76f',
'0x0000000000000000000000000000000000000000000000000000000000000001', // LAST 1
],
].map((i) => i.map((j) => BigInt(j))) as [Fp[], Fp[], Fp[], Fp[]]
);
const mapSWU = mapToCurveSimpleSWU(Fp, {
A: BigInt('0x3f8731abdd661adca08a5558f0f5d272e953d363cb6f0e5d405447c01a444533'),
B: BigInt('1771'),
Z: Fp.create(BigInt('-11')),
});
const { hashToCurve, encodeToCurve } = htf.hashToCurve(
secp256k1.ProjectivePoint,
(scalars: bigint[]) => {
const { x, y } = mapSWU(Fp.create(scalars[0]));
return isoMap(x, y);
},
{
DST: 'secp256k1_XMD:SHA-256_SSWU_RO_',
encodeDST: 'secp256k1_XMD:SHA-256_SSWU_NU_',
p: Fp.ORDER,
m: 1,
k: 128,
expand: 'xmd',
hash: sha256,
}
);
export { hashToCurve, encodeToCurve };

View File

@ -1,14 +1,14 @@
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */ /*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
import { keccak_256 } from '@noble/hashes/sha3'; import { keccak_256 } from '@noble/hashes/sha3';
import { sha256 } from '@noble/hashes/sha256'; import { sha256 } from '@noble/hashes/sha256';
import { weierstrass, ProjectivePointType } from './abstract/weierstrass.js'; import { weierstrass, ProjPointType } from './abstract/weierstrass.js';
import * as cutils from './abstract/utils.js'; import * as cutils from './abstract/utils.js';
import { Fp, mod, Field, validateField } from './abstract/modular.js'; import { Fp, mod, Field, validateField } from './abstract/modular.js';
import { getHash } from './_shortw_utils.js'; import { getHash } from './_shortw_utils.js';
import * as poseidon from './abstract/poseidon.js'; import * as poseidon from './abstract/poseidon.js';
import { utf8ToBytes } from '@noble/hashes/utils'; import { utf8ToBytes } from '@noble/hashes/utils';
type ProjectivePoint = ProjectivePointType<bigint>; type ProjectivePoint = ProjPointType<bigint>;
// Stark-friendly elliptic curve // Stark-friendly elliptic curve
// https://docs.starkware.co/starkex/stark-curve.html // https://docs.starkware.co/starkex/stark-curve.html