forked from tornado-packages/noble-curves
hash-to-curve: decrease coupling, improve tree shaking support
This commit is contained in:
parent
b9482bb17d
commit
40530eae0c
@ -15,12 +15,7 @@ import * as mod from './modular.js';
|
|||||||
import * as ut from './utils.js';
|
import * as ut from './utils.js';
|
||||||
// Types require separate import
|
// Types require separate import
|
||||||
import { Hex, PrivKey } from './utils.js';
|
import { Hex, PrivKey } from './utils.js';
|
||||||
import {
|
import * as htf from './hash-to-curve.js';
|
||||||
htfOpts,
|
|
||||||
stringToBytes,
|
|
||||||
hash_to_field as hashToField,
|
|
||||||
expand_message_xmd as expandMessageXMD,
|
|
||||||
} from './hash-to-curve.js';
|
|
||||||
import { CurvePointsType, PointType, CurvePointsRes, weierstrassPoints } from './weierstrass.js';
|
import { CurvePointsType, PointType, CurvePointsRes, weierstrassPoints } from './weierstrass.js';
|
||||||
|
|
||||||
type Fp = bigint; // Can be different field?
|
type Fp = bigint; // Can be different field?
|
||||||
@ -32,9 +27,14 @@ export type SignatureCoder<Fp2> = {
|
|||||||
|
|
||||||
export type CurveType<Fp, Fp2, Fp6, Fp12> = {
|
export type CurveType<Fp, Fp2, Fp6, Fp12> = {
|
||||||
r: bigint;
|
r: bigint;
|
||||||
G1: Omit<CurvePointsType<Fp>, 'n'>;
|
G1: Omit<CurvePointsType<Fp>, 'n'> & {
|
||||||
|
mapToCurve: htf.MapToCurve<Fp>;
|
||||||
|
htfDefaults: htf.Opts;
|
||||||
|
};
|
||||||
G2: Omit<CurvePointsType<Fp2>, 'n'> & {
|
G2: Omit<CurvePointsType<Fp2>, 'n'> & {
|
||||||
Signature: SignatureCoder<Fp2>;
|
Signature: SignatureCoder<Fp2>;
|
||||||
|
mapToCurve: htf.MapToCurve<Fp2>;
|
||||||
|
htfDefaults: htf.Opts;
|
||||||
};
|
};
|
||||||
x: bigint;
|
x: bigint;
|
||||||
Fp: mod.Field<Fp>;
|
Fp: mod.Field<Fp>;
|
||||||
@ -51,7 +51,7 @@ export type CurveType<Fp, Fp2, Fp6, Fp12> = {
|
|||||||
conjugate(num: Fp12): Fp12;
|
conjugate(num: Fp12): Fp12;
|
||||||
finalExponentiate(num: Fp12): Fp12;
|
finalExponentiate(num: Fp12): Fp12;
|
||||||
};
|
};
|
||||||
htfDefaults: htfOpts;
|
htfDefaults: htf.Opts;
|
||||||
hash: ut.CHash; // Because we need outputLen for DRBG
|
hash: ut.CHash; // Because we need outputLen for DRBG
|
||||||
randomBytes: (bytesLength?: number) => Uint8Array;
|
randomBytes: (bytesLength?: number) => Uint8Array;
|
||||||
};
|
};
|
||||||
@ -68,6 +68,11 @@ export type CurveFn<Fp, Fp2, Fp6, Fp12> = {
|
|||||||
Signature: SignatureCoder<Fp2>;
|
Signature: SignatureCoder<Fp2>;
|
||||||
millerLoop: (ell: [Fp2, Fp2, Fp2][], g1: [Fp, Fp]) => Fp12;
|
millerLoop: (ell: [Fp2, Fp2, Fp2][], g1: [Fp, Fp]) => Fp12;
|
||||||
calcPairingPrecomputes: (x: Fp2, y: Fp2) => [Fp2, Fp2, Fp2][];
|
calcPairingPrecomputes: (x: Fp2, y: Fp2) => [Fp2, Fp2, Fp2][];
|
||||||
|
// prettier-ignore
|
||||||
|
hashToCurve: {
|
||||||
|
G1: ReturnType<(typeof htf.hashToCurve<Fp>)>,
|
||||||
|
G2: ReturnType<(typeof htf.hashToCurve<Fp2>)>,
|
||||||
|
},
|
||||||
pairing: (P: PointType<Fp>, Q: PointType<Fp2>, withFinalExponent?: boolean) => Fp12;
|
pairing: (P: PointType<Fp>, Q: PointType<Fp2>, withFinalExponent?: boolean) => Fp12;
|
||||||
getPublicKey: (privateKey: PrivKey) => Uint8Array;
|
getPublicKey: (privateKey: PrivKey) => Uint8Array;
|
||||||
sign: {
|
sign: {
|
||||||
@ -93,11 +98,9 @@ export type CurveFn<Fp, Fp2, Fp6, Fp12> = {
|
|||||||
publicKeys: (Hex | PointType<Fp>)[]
|
publicKeys: (Hex | PointType<Fp>)[]
|
||||||
) => boolean;
|
) => boolean;
|
||||||
utils: {
|
utils: {
|
||||||
stringToBytes: typeof stringToBytes;
|
stringToBytes: typeof htf.stringToBytes;
|
||||||
hashToField: typeof hashToField;
|
hashToField: typeof htf.hash_to_field;
|
||||||
expandMessageXMD: typeof expandMessageXMD;
|
expandMessageXMD: typeof htf.expand_message_xmd;
|
||||||
getDSTLabel: () => string;
|
|
||||||
setDSTLabel(newLabel: string): void;
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -174,26 +177,18 @@ export function bls<Fp2, Fp6, Fp12>(
|
|||||||
const utils = {
|
const utils = {
|
||||||
hexToBytes: ut.hexToBytes,
|
hexToBytes: ut.hexToBytes,
|
||||||
bytesToHex: ut.bytesToHex,
|
bytesToHex: ut.bytesToHex,
|
||||||
stringToBytes: stringToBytes,
|
stringToBytes: htf.stringToBytes,
|
||||||
// TODO: do we need to export it here?
|
// TODO: do we need to export it here?
|
||||||
hashToField: (
|
hashToField: (
|
||||||
msg: Uint8Array,
|
msg: Uint8Array,
|
||||||
count: number,
|
count: number,
|
||||||
options: Partial<typeof CURVE.htfDefaults> = {}
|
options: Partial<typeof CURVE.htfDefaults> = {}
|
||||||
) => hashToField(msg, count, { ...CURVE.htfDefaults, ...options }),
|
) => htf.hash_to_field(msg, count, { ...CURVE.htfDefaults, ...options }),
|
||||||
expandMessageXMD: (msg: Uint8Array, DST: Uint8Array, lenInBytes: number, H = CURVE.hash) =>
|
expandMessageXMD: (msg: Uint8Array, DST: Uint8Array, lenInBytes: number, H = CURVE.hash) =>
|
||||||
expandMessageXMD(msg, DST, lenInBytes, H),
|
htf.expand_message_xmd(msg, DST, lenInBytes, H),
|
||||||
hashToPrivateKey: (hash: Hex): Uint8Array => Fr.toBytes(ut.hashToPrivateScalar(hash, CURVE.r)),
|
hashToPrivateKey: (hash: Hex): Uint8Array => Fr.toBytes(ut.hashToPrivateScalar(hash, CURVE.r)),
|
||||||
randomBytes: (bytesLength: number = groupLen): Uint8Array => CURVE.randomBytes(bytesLength),
|
randomBytes: (bytesLength: number = groupLen): Uint8Array => CURVE.randomBytes(bytesLength),
|
||||||
randomPrivateKey: (): Uint8Array => utils.hashToPrivateKey(utils.randomBytes(groupLen + 8)),
|
randomPrivateKey: (): Uint8Array => utils.hashToPrivateKey(utils.randomBytes(groupLen + 8)),
|
||||||
getDSTLabel: () => CURVE.htfDefaults.DST,
|
|
||||||
setDSTLabel(newLabel: string) {
|
|
||||||
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-3.1
|
|
||||||
if (typeof newLabel !== 'string' || newLabel.length > 2048 || newLabel.length === 0) {
|
|
||||||
throw new TypeError('Invalid DST');
|
|
||||||
}
|
|
||||||
CURVE.htfDefaults.DST = newLabel;
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Point on G1 curve: (x, y)
|
// Point on G1 curve: (x, y)
|
||||||
@ -201,6 +196,10 @@ export function bls<Fp2, Fp6, Fp12>(
|
|||||||
n: Fr.ORDER,
|
n: Fr.ORDER,
|
||||||
...CURVE.G1,
|
...CURVE.G1,
|
||||||
});
|
});
|
||||||
|
const G1HashToCurve = htf.hashToCurve(G1.Point, CURVE.G1.mapToCurve, {
|
||||||
|
...CURVE.htfDefaults,
|
||||||
|
...CURVE.G1.htfDefaults,
|
||||||
|
});
|
||||||
|
|
||||||
// Sparse multiplication against precomputed coefficients
|
// Sparse multiplication against precomputed coefficients
|
||||||
// TODO: replace with weakmap?
|
// TODO: replace with weakmap?
|
||||||
@ -227,6 +226,11 @@ export function bls<Fp2, Fp6, Fp12>(
|
|||||||
n: Fr.ORDER,
|
n: Fr.ORDER,
|
||||||
...CURVE.G2,
|
...CURVE.G2,
|
||||||
});
|
});
|
||||||
|
const G2HashToCurve = htf.hashToCurve(G2.Point, CURVE.G2.mapToCurve, {
|
||||||
|
...CURVE.htfDefaults,
|
||||||
|
...CURVE.G2.htfDefaults,
|
||||||
|
});
|
||||||
|
|
||||||
const { Signature } = CURVE.G2;
|
const { Signature } = CURVE.G2;
|
||||||
|
|
||||||
// Calculates bilinear pairing
|
// Calculates bilinear pairing
|
||||||
@ -250,8 +254,8 @@ export function bls<Fp2, Fp6, Fp12>(
|
|||||||
function normP2(point: G2Hex): G2 {
|
function normP2(point: G2Hex): G2 {
|
||||||
return point instanceof G2.Point ? point : Signature.decode(point);
|
return point instanceof G2.Point ? point : Signature.decode(point);
|
||||||
}
|
}
|
||||||
function normP2Hash(point: G2Hex): G2 {
|
function normP2Hash(point: G2Hex, htfOpts?: htf.htfBasicOpts): G2 {
|
||||||
return point instanceof G2.Point ? point : G2.Point.hashToCurve(point);
|
return point instanceof G2.Point ? point : (G2HashToCurve.hashToCurve(point, htfOpts) as G2);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Multiplies generator by private key.
|
// Multiplies generator by private key.
|
||||||
@ -262,10 +266,10 @@ export function bls<Fp2, Fp6, Fp12>(
|
|||||||
|
|
||||||
// Executes `hashToCurve` on the message and then multiplies the result by private key.
|
// Executes `hashToCurve` on the message and then multiplies the result by private key.
|
||||||
// S = pk x H(m)
|
// S = pk x H(m)
|
||||||
function sign(message: Hex, privateKey: PrivKey): Uint8Array;
|
function sign(message: Hex, privateKey: PrivKey, htfOpts?: htf.htfBasicOpts): Uint8Array;
|
||||||
function sign(message: G2, privateKey: PrivKey): G2;
|
function sign(message: G2, privateKey: PrivKey, htfOpts?: htf.htfBasicOpts): G2;
|
||||||
function sign(message: G2Hex, privateKey: PrivKey): Uint8Array | G2 {
|
function sign(message: G2Hex, privateKey: PrivKey, htfOpts?: htf.htfBasicOpts): Uint8Array | G2 {
|
||||||
const msgPoint = normP2Hash(message);
|
const msgPoint = normP2Hash(message, htfOpts);
|
||||||
msgPoint.assertValidity();
|
msgPoint.assertValidity();
|
||||||
const sigPoint = msgPoint.multiply(G1.normalizePrivateKey(privateKey));
|
const sigPoint = msgPoint.multiply(G1.normalizePrivateKey(privateKey));
|
||||||
if (message instanceof G2.Point) return sigPoint;
|
if (message instanceof G2.Point) return sigPoint;
|
||||||
@ -274,9 +278,14 @@ export function bls<Fp2, Fp6, Fp12>(
|
|||||||
|
|
||||||
// Checks if pairing of public key & hash is equal to pairing of generator & signature.
|
// Checks if pairing of public key & hash is equal to pairing of generator & signature.
|
||||||
// e(P, H(m)) == e(G, S)
|
// e(P, H(m)) == e(G, S)
|
||||||
function verify(signature: G2Hex, message: G2Hex, publicKey: G1Hex): boolean {
|
function verify(
|
||||||
|
signature: G2Hex,
|
||||||
|
message: G2Hex,
|
||||||
|
publicKey: G1Hex,
|
||||||
|
htfOpts?: htf.htfBasicOpts
|
||||||
|
): boolean {
|
||||||
const P = normP1(publicKey);
|
const P = normP1(publicKey);
|
||||||
const Hm = normP2Hash(message);
|
const Hm = normP2Hash(message, htfOpts);
|
||||||
const G = G1.Point.BASE;
|
const G = G1.Point.BASE;
|
||||||
const S = normP2(signature);
|
const S = normP2(signature);
|
||||||
// Instead of doing 2 exponentiations, we use property of billinear maps
|
// Instead of doing 2 exponentiations, we use property of billinear maps
|
||||||
@ -323,12 +332,17 @@ export function bls<Fp2, Fp6, Fp12>(
|
|||||||
|
|
||||||
// https://ethresear.ch/t/fast-verification-of-multiple-bls-signatures/5407
|
// https://ethresear.ch/t/fast-verification-of-multiple-bls-signatures/5407
|
||||||
// e(G, S) = e(G, SUM(n)(Si)) = MUL(n)(e(G, Si))
|
// e(G, S) = e(G, SUM(n)(Si)) = MUL(n)(e(G, Si))
|
||||||
function verifyBatch(signature: G2Hex, messages: G2Hex[], publicKeys: G1Hex[]): boolean {
|
function verifyBatch(
|
||||||
|
signature: G2Hex,
|
||||||
|
messages: G2Hex[],
|
||||||
|
publicKeys: G1Hex[],
|
||||||
|
htfOpts?: htf.htfBasicOpts
|
||||||
|
): boolean {
|
||||||
if (!messages.length) throw new Error('Expected non-empty messages array');
|
if (!messages.length) throw new Error('Expected non-empty messages array');
|
||||||
if (publicKeys.length !== messages.length)
|
if (publicKeys.length !== messages.length)
|
||||||
throw new Error('Pubkey count should equal msg count');
|
throw new Error('Pubkey count should equal msg count');
|
||||||
const sig = normP2(signature);
|
const sig = normP2(signature);
|
||||||
const nMessages = messages.map(normP2Hash);
|
const nMessages = messages.map((i) => normP2Hash(i, htfOpts));
|
||||||
const nPublicKeys = publicKeys.map(normP1);
|
const nPublicKeys = publicKeys.map(normP1);
|
||||||
try {
|
try {
|
||||||
const paired = [];
|
const paired = [];
|
||||||
@ -365,6 +379,7 @@ export function bls<Fp2, Fp6, Fp12>(
|
|||||||
Signature,
|
Signature,
|
||||||
millerLoop,
|
millerLoop,
|
||||||
calcPairingPrecomputes,
|
calcPairingPrecomputes,
|
||||||
|
hashToCurve: { G1: G1HashToCurve, G2: G2HashToCurve },
|
||||||
pairing,
|
pairing,
|
||||||
getPublicKey,
|
getPublicKey,
|
||||||
sign,
|
sign,
|
||||||
|
@ -13,7 +13,6 @@ import * as mod from './modular.js';
|
|||||||
import * as ut from './utils.js';
|
import * as ut from './utils.js';
|
||||||
import { ensureBytes, Hex, PrivKey } from './utils.js';
|
import { ensureBytes, Hex, PrivKey } from './utils.js';
|
||||||
import { Group, GroupConstructor, wNAF } from './group.js';
|
import { Group, GroupConstructor, wNAF } from './group.js';
|
||||||
import { hash_to_field as hashToField, htfOpts, validateHTFOpts } from './hash-to-curve.js';
|
|
||||||
|
|
||||||
// Be friendly to bad ECMAScript parsers by not using bigint literals like 123n
|
// Be friendly to bad ECMAScript parsers by not using bigint literals like 123n
|
||||||
const _0n = BigInt(0);
|
const _0n = BigInt(0);
|
||||||
@ -39,8 +38,6 @@ export type CurveType = ut.BasicCurve<bigint> & {
|
|||||||
uvRatio?: (u: bigint, v: bigint) => { isValid: boolean; value: bigint };
|
uvRatio?: (u: bigint, v: bigint) => { isValid: boolean; value: bigint };
|
||||||
// RFC 8032 pre-hashing of messages to sign() / verify()
|
// RFC 8032 pre-hashing of messages to sign() / verify()
|
||||||
preHash?: ut.CHash;
|
preHash?: ut.CHash;
|
||||||
// Hash to field options
|
|
||||||
htfDefaults?: htfOpts;
|
|
||||||
mapToCurve?: (scalar: bigint[]) => { x: bigint; y: bigint };
|
mapToCurve?: (scalar: bigint[]) => { x: bigint; y: bigint };
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -59,7 +56,6 @@ function validateOpts(curve: CurveType) {
|
|||||||
if (opts[fn] === undefined) continue; // Optional
|
if (opts[fn] === undefined) continue; // Optional
|
||||||
if (typeof opts[fn] !== 'function') throw new Error(`Invalid ${fn} function`);
|
if (typeof opts[fn] !== 'function') throw new Error(`Invalid ${fn} function`);
|
||||||
}
|
}
|
||||||
if (opts.htfDefaults !== undefined) validateHTFOpts(opts.htfDefaults);
|
|
||||||
// Set defaults
|
// Set defaults
|
||||||
return Object.freeze({ ...opts } as const);
|
return Object.freeze({ ...opts } as const);
|
||||||
}
|
}
|
||||||
@ -114,8 +110,6 @@ export interface PointConstructor extends GroupConstructor<PointType> {
|
|||||||
new (x: bigint, y: bigint): PointType;
|
new (x: bigint, y: bigint): PointType;
|
||||||
fromHex(hex: Hex): PointType;
|
fromHex(hex: Hex): PointType;
|
||||||
fromPrivateKey(privateKey: PrivKey): PointType;
|
fromPrivateKey(privateKey: PrivKey): PointType;
|
||||||
hashToCurve(msg: Hex, options?: Partial<htfOpts>): PointType;
|
|
||||||
encodeToCurve(msg: Hex, options?: Partial<htfOpts>): PointType;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type PubKey = Hex | PointType;
|
export type PubKey = Hex | PointType;
|
||||||
@ -484,25 +478,6 @@ export function twistedEdwards(curveDef: CurveType): CurveFn {
|
|||||||
clearCofactor() {
|
clearCofactor() {
|
||||||
return ExtendedPoint.fromAffine(this).clearCofactor().toAffine();
|
return ExtendedPoint.fromAffine(this).clearCofactor().toAffine();
|
||||||
}
|
}
|
||||||
// Encodes byte string to elliptic curve
|
|
||||||
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-3
|
|
||||||
static hashToCurve(msg: Hex, options?: Partial<htfOpts>) {
|
|
||||||
const { mapToCurve, htfDefaults } = CURVE;
|
|
||||||
if (!mapToCurve) throw new Error('No mapToCurve defined for curve');
|
|
||||||
const u = hashToField(ensureBytes(msg), 2, { ...htfDefaults, ...options } as htfOpts);
|
|
||||||
const { x: x0, y: y0 } = mapToCurve(u[0]);
|
|
||||||
const { x: x1, y: y1 } = mapToCurve(u[1]);
|
|
||||||
const p = new Point(x0, y0).add(new Point(x1, y1)).clearCofactor();
|
|
||||||
return p;
|
|
||||||
}
|
|
||||||
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-16#section-3
|
|
||||||
static encodeToCurve(msg: Hex, options?: Partial<htfOpts>) {
|
|
||||||
const { mapToCurve, htfDefaults } = CURVE;
|
|
||||||
if (!mapToCurve) throw new Error('No mapToCurve defined for curve');
|
|
||||||
const u = hashToField(ensureBytes(msg), 1, { ...htfDefaults, ...options } as htfOpts);
|
|
||||||
const { x, y } = mapToCurve(u[0]);
|
|
||||||
return new Point(x, y).clearCofactor();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
|
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
|
||||||
import { CHash, concatBytes } from './utils.js';
|
import * as ut from './utils.js';
|
||||||
import * as mod from './modular.js';
|
import * as mod from './modular.js';
|
||||||
|
import type { Group, GroupConstructor } from './group.js';
|
||||||
|
|
||||||
export type htfOpts = {
|
export type Opts = {
|
||||||
// DST: a domain separation tag
|
// DST: a domain separation tag
|
||||||
// defined in section 2.2.5
|
// defined in section 2.2.5
|
||||||
DST: string;
|
DST: string;
|
||||||
|
encodeDST: string;
|
||||||
// p: the characteristic of F
|
// p: the characteristic of F
|
||||||
// where F is a finite field of characteristic p and order q = p^m
|
// where F is a finite field of characteristic p and order q = p^m
|
||||||
p: bigint;
|
p: bigint;
|
||||||
@ -22,10 +24,10 @@ export type htfOpts = {
|
|||||||
// wide range of hash functions, including SHA-2, SHA-3, BLAKE2, and others.
|
// wide range of hash functions, including SHA-2, SHA-3, BLAKE2, and others.
|
||||||
// BBS+ uses blake2: https://github.com/hyperledger/aries-framework-go/issues/2247
|
// BBS+ uses blake2: https://github.com/hyperledger/aries-framework-go/issues/2247
|
||||||
// TODO: verify that hash is shake if expand==='xof' via types
|
// TODO: verify that hash is shake if expand==='xof' via types
|
||||||
hash: CHash;
|
hash: ut.CHash;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function validateHTFOpts(opts: htfOpts) {
|
export function validateOpts(opts: Opts) {
|
||||||
if (typeof opts.DST !== 'string') throw new Error('Invalid htf/DST');
|
if (typeof opts.DST !== 'string') throw new Error('Invalid htf/DST');
|
||||||
if (typeof opts.p !== 'bigint') throw new Error('Invalid htf/p');
|
if (typeof opts.p !== 'bigint') throw new Error('Invalid htf/p');
|
||||||
if (typeof opts.m !== 'number') throw new Error('Invalid htf/m');
|
if (typeof opts.m !== 'number') throw new Error('Invalid htf/m');
|
||||||
@ -81,25 +83,25 @@ export function expand_message_xmd(
|
|||||||
msg: Uint8Array,
|
msg: Uint8Array,
|
||||||
DST: Uint8Array,
|
DST: Uint8Array,
|
||||||
lenInBytes: number,
|
lenInBytes: number,
|
||||||
H: CHash
|
H: ut.CHash
|
||||||
): Uint8Array {
|
): Uint8Array {
|
||||||
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-16#section-5.3.3
|
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-16#section-5.3.3
|
||||||
if (DST.length > 255) DST = H(concatBytes(stringToBytes('H2C-OVERSIZE-DST-'), DST));
|
if (DST.length > 255) DST = H(ut.concatBytes(stringToBytes('H2C-OVERSIZE-DST-'), DST));
|
||||||
const b_in_bytes = H.outputLen;
|
const b_in_bytes = H.outputLen;
|
||||||
const r_in_bytes = H.blockLen;
|
const r_in_bytes = H.blockLen;
|
||||||
const ell = Math.ceil(lenInBytes / b_in_bytes);
|
const ell = Math.ceil(lenInBytes / b_in_bytes);
|
||||||
if (ell > 255) throw new Error('Invalid xmd length');
|
if (ell > 255) throw new Error('Invalid xmd length');
|
||||||
const DST_prime = concatBytes(DST, i2osp(DST.length, 1));
|
const DST_prime = ut.concatBytes(DST, i2osp(DST.length, 1));
|
||||||
const Z_pad = i2osp(0, r_in_bytes);
|
const Z_pad = i2osp(0, r_in_bytes);
|
||||||
const l_i_b_str = i2osp(lenInBytes, 2);
|
const l_i_b_str = i2osp(lenInBytes, 2);
|
||||||
const b = new Array<Uint8Array>(ell);
|
const b = new Array<Uint8Array>(ell);
|
||||||
const b_0 = H(concatBytes(Z_pad, msg, l_i_b_str, i2osp(0, 1), DST_prime));
|
const b_0 = H(ut.concatBytes(Z_pad, msg, l_i_b_str, i2osp(0, 1), DST_prime));
|
||||||
b[0] = H(concatBytes(b_0, i2osp(1, 1), DST_prime));
|
b[0] = H(ut.concatBytes(b_0, i2osp(1, 1), DST_prime));
|
||||||
for (let i = 1; i <= ell; i++) {
|
for (let i = 1; i <= ell; i++) {
|
||||||
const args = [strxor(b_0, b[i - 1]), i2osp(i + 1, 1), DST_prime];
|
const args = [strxor(b_0, b[i - 1]), i2osp(i + 1, 1), DST_prime];
|
||||||
b[i] = H(concatBytes(...args));
|
b[i] = H(ut.concatBytes(...args));
|
||||||
}
|
}
|
||||||
const pseudo_random_bytes = concatBytes(...b);
|
const pseudo_random_bytes = ut.concatBytes(...b);
|
||||||
return pseudo_random_bytes.slice(0, lenInBytes);
|
return pseudo_random_bytes.slice(0, lenInBytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -108,7 +110,7 @@ export function expand_message_xof(
|
|||||||
DST: Uint8Array,
|
DST: Uint8Array,
|
||||||
lenInBytes: number,
|
lenInBytes: number,
|
||||||
k: number,
|
k: number,
|
||||||
H: CHash
|
H: ut.CHash
|
||||||
): Uint8Array {
|
): Uint8Array {
|
||||||
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-16#section-5.3.3
|
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-16#section-5.3.3
|
||||||
// DST = H('H2C-OVERSIZE-DST-' || a_very_long_DST, Math.ceil((lenInBytes * k) / 8));
|
// DST = H('H2C-OVERSIZE-DST-' || a_very_long_DST, Math.ceil((lenInBytes * k) / 8));
|
||||||
@ -137,7 +139,7 @@ export function expand_message_xof(
|
|||||||
* @param options `{DST: string, p: bigint, m: number, k: number, expand: 'xmd' | 'xof', hash: H}`
|
* @param options `{DST: string, p: bigint, m: number, k: number, expand: 'xmd' | 'xof', hash: H}`
|
||||||
* @returns [u_0, ..., u_(count - 1)], a list of field elements.
|
* @returns [u_0, ..., u_(count - 1)], a list of field elements.
|
||||||
*/
|
*/
|
||||||
export function hash_to_field(msg: Uint8Array, count: number, options: htfOpts): bigint[][] {
|
export function hash_to_field(msg: Uint8Array, count: number, options: Opts): bigint[][] {
|
||||||
// if options is provided but incomplete, fill any missing fields with the
|
// if options is provided but incomplete, fill any missing fields with the
|
||||||
// value in hftDefaults (ie hash to G2).
|
// value in hftDefaults (ie hash to G2).
|
||||||
const log2p = options.p.toString(2).length;
|
const log2p = options.p.toString(2).length;
|
||||||
@ -175,3 +177,48 @@ export function isogenyMap<T, F extends mod.Field<T>>(field: F, map: [T[], T[],
|
|||||||
return { x, y };
|
return { x, y };
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface Point<T> extends Group<Point<T>> {
|
||||||
|
readonly x: T;
|
||||||
|
readonly y: T;
|
||||||
|
clearCofactor(): Point<T>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PointConstructor<T> extends GroupConstructor<Point<T>> {
|
||||||
|
new (x: T, y: T): Point<T>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type MapToCurve<T> = (scalar: bigint[]) => { x: T; y: T };
|
||||||
|
|
||||||
|
// Separated from initialization opts, so users won't accidentally change per-curve parameters (changing DST is ok!)
|
||||||
|
export type htfBasicOpts = {
|
||||||
|
DST: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function hashToCurve<T>(Point: PointConstructor<T>, mapToCurve: MapToCurve<T>, def: Opts) {
|
||||||
|
validateOpts(def);
|
||||||
|
if (typeof mapToCurve !== 'function')
|
||||||
|
throw new Error('hashToCurve: mapToCurve() has not been defined');
|
||||||
|
|
||||||
|
return {
|
||||||
|
// Encodes byte string to elliptic curve
|
||||||
|
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-3
|
||||||
|
hashToCurve(msg: ut.Hex, options?: htfBasicOpts) {
|
||||||
|
if (!mapToCurve) throw new Error('CURVE.mapToCurve() has not been defined');
|
||||||
|
msg = ut.ensureBytes(msg);
|
||||||
|
const u = hash_to_field(msg, 2, { ...def, DST: def.DST, ...options } as Opts);
|
||||||
|
const { x: x0, y: y0 } = mapToCurve(u[0]);
|
||||||
|
const { x: x1, y: y1 } = mapToCurve(u[1]);
|
||||||
|
return new Point(x0, y0).add(new Point(x1, y1)).clearCofactor();
|
||||||
|
},
|
||||||
|
|
||||||
|
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-16#section-3
|
||||||
|
encodeToCurve(msg: ut.Hex, options?: htfBasicOpts) {
|
||||||
|
if (!mapToCurve) throw new Error('CURVE.mapToCurve() has not been defined');
|
||||||
|
msg = ut.ensureBytes(msg);
|
||||||
|
const u = hash_to_field(msg, 1, { ...def, DST: def.encodeDST, ...options } as Opts);
|
||||||
|
const { x, y } = mapToCurve(u[0]);
|
||||||
|
return new Point(x, y).clearCofactor();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
@ -11,7 +11,6 @@
|
|||||||
import * as mod from './modular.js';
|
import * as mod from './modular.js';
|
||||||
import * as ut from './utils.js';
|
import * as ut from './utils.js';
|
||||||
import { bytesToHex, Hex, PrivKey } from './utils.js';
|
import { bytesToHex, Hex, PrivKey } from './utils.js';
|
||||||
import { hash_to_field, htfOpts, validateHTFOpts } from './hash-to-curve.js';
|
|
||||||
import { Group, GroupConstructor, wNAF } from './group.js';
|
import { Group, GroupConstructor, wNAF } from './group.js';
|
||||||
|
|
||||||
type HmacFnSync = (key: Uint8Array, ...messages: Uint8Array[]) => Uint8Array;
|
type HmacFnSync = (key: Uint8Array, ...messages: Uint8Array[]) => Uint8Array;
|
||||||
@ -39,9 +38,6 @@ export type BasicCurve<T> = ut.BasicCurve<T> & {
|
|||||||
c: ProjectiveConstructor<T>,
|
c: ProjectiveConstructor<T>,
|
||||||
point: ProjectivePointType<T>
|
point: ProjectivePointType<T>
|
||||||
) => ProjectivePointType<T>;
|
) => ProjectivePointType<T>;
|
||||||
// Hash to field options
|
|
||||||
htfDefaults?: htfOpts;
|
|
||||||
mapToCurve?: (scalar: bigint[]) => { x: T; y: T };
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// ASN.1 DER encoding utilities
|
// ASN.1 DER encoding utilities
|
||||||
@ -121,6 +117,7 @@ export interface ProjectivePointType<T> extends Group<ProjectivePointType<T>> {
|
|||||||
multiply(scalar: number | bigint, affinePoint?: PointType<T>): ProjectivePointType<T>;
|
multiply(scalar: number | bigint, affinePoint?: PointType<T>): ProjectivePointType<T>;
|
||||||
multiplyUnsafe(scalar: bigint): ProjectivePointType<T>;
|
multiplyUnsafe(scalar: bigint): ProjectivePointType<T>;
|
||||||
toAffine(invZ?: T): PointType<T>;
|
toAffine(invZ?: T): PointType<T>;
|
||||||
|
clearCofactor(): ProjectivePointType<T>;
|
||||||
}
|
}
|
||||||
// Static methods for 3d XYZ points
|
// Static methods for 3d XYZ points
|
||||||
export interface ProjectiveConstructor<T> extends GroupConstructor<ProjectivePointType<T>> {
|
export interface ProjectiveConstructor<T> extends GroupConstructor<ProjectivePointType<T>> {
|
||||||
@ -139,14 +136,13 @@ export interface PointType<T> extends Group<PointType<T>> {
|
|||||||
toHex(isCompressed?: boolean): string;
|
toHex(isCompressed?: boolean): string;
|
||||||
assertValidity(): void;
|
assertValidity(): void;
|
||||||
multiplyAndAddUnsafe(Q: PointType<T>, a: bigint, b: bigint): PointType<T> | undefined;
|
multiplyAndAddUnsafe(Q: PointType<T>, a: bigint, b: bigint): PointType<T> | undefined;
|
||||||
|
clearCofactor(): PointType<T>;
|
||||||
}
|
}
|
||||||
// Static methods for 2d XY points
|
// Static methods for 2d XY points
|
||||||
export interface PointConstructor<T> extends GroupConstructor<PointType<T>> {
|
export interface PointConstructor<T> extends GroupConstructor<PointType<T>> {
|
||||||
new (x: T, y: T): PointType<T>;
|
new (x: T, y: T): PointType<T>;
|
||||||
fromHex(hex: Hex): PointType<T>;
|
fromHex(hex: Hex): PointType<T>;
|
||||||
fromPrivateKey(privateKey: PrivKey): PointType<T>;
|
fromPrivateKey(privateKey: PrivKey): PointType<T>;
|
||||||
hashToCurve(msg: Hex, options?: Partial<htfOpts>): PointType<T>;
|
|
||||||
encodeToCurve(msg: Hex, options?: Partial<htfOpts>): PointType<T>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type CurvePointsType<T> = BasicCurve<T> & {
|
export type CurvePointsType<T> = BasicCurve<T> & {
|
||||||
@ -162,7 +158,7 @@ function validatePointOpts<T>(curve: CurvePointsType<T>) {
|
|||||||
if (!Fp.isValid(curve[i]))
|
if (!Fp.isValid(curve[i]))
|
||||||
throw new Error(`Invalid curve param ${i}=${opts[i]} (${typeof opts[i]})`);
|
throw new Error(`Invalid curve param ${i}=${opts[i]} (${typeof opts[i]})`);
|
||||||
}
|
}
|
||||||
for (const i of ['isTorsionFree', 'clearCofactor', 'mapToCurve'] as const) {
|
for (const i of ['isTorsionFree', 'clearCofactor'] as const) {
|
||||||
if (curve[i] === undefined) continue; // Optional
|
if (curve[i] === undefined) continue; // Optional
|
||||||
if (typeof curve[i] !== 'function') throw new Error(`Invalid ${i} function`);
|
if (typeof curve[i] !== 'function') throw new Error(`Invalid ${i} function`);
|
||||||
}
|
}
|
||||||
@ -181,8 +177,6 @@ function validatePointOpts<T>(curve: CurvePointsType<T>) {
|
|||||||
}
|
}
|
||||||
if (typeof opts.fromBytes !== 'function') throw new Error('Invalid fromBytes function');
|
if (typeof opts.fromBytes !== 'function') throw new Error('Invalid fromBytes function');
|
||||||
if (typeof opts.toBytes !== 'function') throw new Error('Invalid fromBytes function');
|
if (typeof opts.toBytes !== 'function') throw new Error('Invalid fromBytes function');
|
||||||
// Requires including hashToCurve file
|
|
||||||
if (opts.htfDefaults !== undefined) validateHTFOpts(opts.htfDefaults);
|
|
||||||
// Set defaults
|
// Set defaults
|
||||||
return Object.freeze({ ...opts } as const);
|
return Object.freeze({ ...opts } as const);
|
||||||
}
|
}
|
||||||
@ -671,28 +665,6 @@ export function weierstrassPoints<T>(opts: CurvePointsType<T>) {
|
|||||||
const sum = aP.add(bQ);
|
const sum = aP.add(bQ);
|
||||||
return sum.equals(ProjectivePoint.ZERO) ? undefined : sum.toAffine();
|
return sum.equals(ProjectivePoint.ZERO) ? undefined : sum.toAffine();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Encodes byte string to elliptic curve
|
|
||||||
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-3
|
|
||||||
static hashToCurve(msg: Hex, options?: Partial<htfOpts>) {
|
|
||||||
const { mapToCurve } = CURVE;
|
|
||||||
if (!mapToCurve) throw new Error('CURVE.mapToCurve() has not been defined');
|
|
||||||
msg = ut.ensureBytes(msg);
|
|
||||||
const u = hash_to_field(msg, 2, { ...CURVE.htfDefaults, ...options } as htfOpts);
|
|
||||||
const { x: x0, y: y0 } = mapToCurve(u[0]);
|
|
||||||
const { x: x1, y: y1 } = mapToCurve(u[1]);
|
|
||||||
return new Point(x0, y0).add(new Point(x1, y1)).clearCofactor();
|
|
||||||
}
|
|
||||||
|
|
||||||
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-16#section-3
|
|
||||||
static encodeToCurve(msg: Hex, options?: Partial<htfOpts>) {
|
|
||||||
const { mapToCurve } = CURVE;
|
|
||||||
if (!mapToCurve) throw new Error('CURVE.mapToCurve() has not been defined');
|
|
||||||
msg = ut.ensureBytes(msg);
|
|
||||||
const u = hash_to_field(msg, 1, { ...CURVE.htfDefaults, ...options } as htfOpts);
|
|
||||||
const { x, y } = mapToCurve(u[0]);
|
|
||||||
return new Point(x, y).clearCofactor();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -920,6 +920,7 @@ const htfDefaults = {
|
|||||||
// defined in section 2.2.5
|
// defined in section 2.2.5
|
||||||
// Use utils.getDSTLabel(), utils.setDSTLabel(value)
|
// Use utils.getDSTLabel(), utils.setDSTLabel(value)
|
||||||
DST: 'BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_NUL_',
|
DST: 'BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_NUL_',
|
||||||
|
encodeDST: 'BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_NUL_',
|
||||||
// p: the characteristic of F
|
// p: the characteristic of F
|
||||||
// where F is a finite field of characteristic p and order q = p^m
|
// where F is a finite field of characteristic p and order q = p^m
|
||||||
p: Fp.ORDER,
|
p: Fp.ORDER,
|
||||||
|
@ -12,6 +12,7 @@ import {
|
|||||||
numberToBytesLE,
|
numberToBytesLE,
|
||||||
Hex,
|
Hex,
|
||||||
} from './abstract/utils.js';
|
} from './abstract/utils.js';
|
||||||
|
import * as htf from './abstract/hash-to-curve.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ed25519 Twisted Edwards curve with following addons:
|
* ed25519 Twisted Edwards curve with following addons:
|
||||||
@ -189,15 +190,6 @@ const ED25519_DEF = {
|
|||||||
// Ratio of u to v. Allows us to combine inversion and square root. Uses algo from RFC8032 5.1.3.
|
// Ratio of u to v. Allows us to combine inversion and square root. Uses algo from RFC8032 5.1.3.
|
||||||
// Constant-time, u/√v
|
// Constant-time, u/√v
|
||||||
uvRatio,
|
uvRatio,
|
||||||
htfDefaults: {
|
|
||||||
DST: 'edwards25519_XMD:SHA-512_ELL2_RO_',
|
|
||||||
p: Fp.ORDER,
|
|
||||||
m: 1,
|
|
||||||
k: 128,
|
|
||||||
expand: 'xmd',
|
|
||||||
hash: sha512,
|
|
||||||
},
|
|
||||||
mapToCurve: (scalars: bigint[]) => map_to_curve_elligator2_edwards25519(scalars[0]),
|
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export const ed25519 = twistedEdwards(ED25519_DEF);
|
export const ed25519 = twistedEdwards(ED25519_DEF);
|
||||||
@ -217,6 +209,21 @@ export const ed25519ph = twistedEdwards({
|
|||||||
preHash: sha512,
|
preHash: sha512,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const { hashToCurve, encodeToCurve } = htf.hashToCurve(
|
||||||
|
ed25519.Point,
|
||||||
|
(scalars: bigint[]) => map_to_curve_elligator2_edwards25519(scalars[0]),
|
||||||
|
{
|
||||||
|
DST: 'edwards25519_XMD:SHA-512_ELL2_RO_',
|
||||||
|
encodeDST: 'edwards25519_XMD:SHA-512_ELL2_NU_',
|
||||||
|
p: Fp.ORDER,
|
||||||
|
m: 1,
|
||||||
|
k: 128,
|
||||||
|
expand: 'xmd',
|
||||||
|
hash: sha512,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
export { hashToCurve, encodeToCurve };
|
||||||
|
|
||||||
export const x25519 = montgomery({
|
export const x25519 = montgomery({
|
||||||
P: ED25519_P,
|
P: ED25519_P,
|
||||||
a24: BigInt('121665'),
|
a24: BigInt('121665'),
|
||||||
|
25
src/ed448.ts
25
src/ed448.ts
@ -4,6 +4,7 @@ import { concatBytes, randomBytes, utf8ToBytes, wrapConstructor } from '@noble/h
|
|||||||
import { twistedEdwards } from './abstract/edwards.js';
|
import { twistedEdwards } from './abstract/edwards.js';
|
||||||
import { mod, pow2, Fp as Field } from './abstract/modular.js';
|
import { mod, pow2, Fp as Field } from './abstract/modular.js';
|
||||||
import { montgomery } from './abstract/montgomery.js';
|
import { montgomery } from './abstract/montgomery.js';
|
||||||
|
import * as htf from './abstract/hash-to-curve.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Edwards448 (not Ed448-Goldilocks) curve with following addons:
|
* Edwards448 (not Ed448-Goldilocks) curve with following addons:
|
||||||
@ -189,21 +190,27 @@ const ED448_DEF = {
|
|||||||
// square root exists, and the decoding fails.
|
// square root exists, and the decoding fails.
|
||||||
return { isValid: mod(x2 * v, P) === u, value: x };
|
return { isValid: mod(x2 * v, P) === u, value: x };
|
||||||
},
|
},
|
||||||
htfDefaults: {
|
|
||||||
DST: 'edwards448_XOF:SHAKE256_ELL2_RO_',
|
|
||||||
p: Fp.ORDER,
|
|
||||||
m: 1,
|
|
||||||
k: 224,
|
|
||||||
expand: 'xof',
|
|
||||||
hash: shake256,
|
|
||||||
},
|
|
||||||
mapToCurve: (scalars: bigint[]) => map_to_curve_elligator2_edwards448(scalars[0]),
|
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export const ed448 = twistedEdwards(ED448_DEF);
|
export const ed448 = twistedEdwards(ED448_DEF);
|
||||||
// NOTE: there is no ed448ctx, since ed448 supports ctx by default
|
// NOTE: there is no ed448ctx, since ed448 supports ctx by default
|
||||||
export const ed448ph = twistedEdwards({ ...ED448_DEF, preHash: shake256_64 });
|
export const ed448ph = twistedEdwards({ ...ED448_DEF, preHash: shake256_64 });
|
||||||
|
|
||||||
|
const { hashToCurve, encodeToCurve } = htf.hashToCurve(
|
||||||
|
ed448.Point,
|
||||||
|
(scalars: bigint[]) => map_to_curve_elligator2_edwards448(scalars[0]),
|
||||||
|
{
|
||||||
|
DST: 'edwards448_XOF:SHAKE256_ELL2_RO_',
|
||||||
|
encodeDST: 'edwards448_XOF:SHAKE256_ELL2_NU_',
|
||||||
|
p: Fp.ORDER,
|
||||||
|
m: 1,
|
||||||
|
k: 224,
|
||||||
|
expand: 'xof',
|
||||||
|
hash: shake256,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
export { hashToCurve, encodeToCurve };
|
||||||
|
|
||||||
export const x448 = montgomery({
|
export const x448 = montgomery({
|
||||||
a24: BigInt(39081),
|
a24: BigInt(39081),
|
||||||
montgomeryBits: 448,
|
montgomeryBits: 448,
|
||||||
|
25
src/p256.ts
25
src/p256.ts
@ -3,6 +3,7 @@ import { createCurve } from './_shortw_utils.js';
|
|||||||
import { sha256 } from '@noble/hashes/sha256';
|
import { sha256 } from '@noble/hashes/sha256';
|
||||||
import { Fp as Field } from './abstract/modular.js';
|
import { Fp as Field } from './abstract/modular.js';
|
||||||
import { mapToCurveSimpleSWU } from './abstract/weierstrass.js';
|
import { mapToCurveSimpleSWU } from './abstract/weierstrass.js';
|
||||||
|
import * as htf from './abstract/hash-to-curve.js';
|
||||||
|
|
||||||
// NIST secp256r1 aka P256
|
// NIST secp256r1 aka P256
|
||||||
// https://www.secg.org/sec2-v2.pdf, https://neuromancer.sk/std/nist/P-256
|
// https://www.secg.org/sec2-v2.pdf, https://neuromancer.sk/std/nist/P-256
|
||||||
@ -31,16 +32,22 @@ export const P256 = createCurve(
|
|||||||
Gy: BigInt('0x4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5'),
|
Gy: BigInt('0x4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5'),
|
||||||
h: BigInt(1),
|
h: BigInt(1),
|
||||||
lowS: false,
|
lowS: false,
|
||||||
mapToCurve: (scalars: bigint[]) => mapSWU(scalars[0]),
|
|
||||||
htfDefaults: {
|
|
||||||
DST: 'P256_XMD:SHA-256_SSWU_RO_',
|
|
||||||
p: Fp.ORDER,
|
|
||||||
m: 1,
|
|
||||||
k: 128,
|
|
||||||
expand: 'xmd',
|
|
||||||
hash: sha256,
|
|
||||||
},
|
|
||||||
} as const,
|
} as const,
|
||||||
sha256
|
sha256
|
||||||
);
|
);
|
||||||
export const secp256r1 = P256;
|
export const secp256r1 = P256;
|
||||||
|
|
||||||
|
const { hashToCurve, encodeToCurve } = htf.hashToCurve(
|
||||||
|
secp256r1.Point,
|
||||||
|
(scalars: bigint[]) => mapSWU(scalars[0]),
|
||||||
|
{
|
||||||
|
DST: 'P256_XMD:SHA-256_SSWU_RO_',
|
||||||
|
encodeDST: 'P256_XMD:SHA-256_SSWU_NU_',
|
||||||
|
p: Fp.ORDER,
|
||||||
|
m: 1,
|
||||||
|
k: 128,
|
||||||
|
expand: 'xmd',
|
||||||
|
hash: sha256,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
export { hashToCurve, encodeToCurve };
|
||||||
|
25
src/p384.ts
25
src/p384.ts
@ -3,6 +3,7 @@ import { createCurve } from './_shortw_utils.js';
|
|||||||
import { sha384 } from '@noble/hashes/sha512';
|
import { sha384 } from '@noble/hashes/sha512';
|
||||||
import { Fp as Field } from './abstract/modular.js';
|
import { Fp as Field } from './abstract/modular.js';
|
||||||
import { mapToCurveSimpleSWU } from './abstract/weierstrass.js';
|
import { mapToCurveSimpleSWU } from './abstract/weierstrass.js';
|
||||||
|
import * as htf from './abstract/hash-to-curve.js';
|
||||||
|
|
||||||
// NIST secp384r1 aka P384
|
// NIST secp384r1 aka P384
|
||||||
// https://www.secg.org/sec2-v2.pdf, https://neuromancer.sk/std/nist/P-384
|
// https://www.secg.org/sec2-v2.pdf, https://neuromancer.sk/std/nist/P-384
|
||||||
@ -35,16 +36,22 @@ export const P384 = createCurve({
|
|||||||
Gy: BigInt('0x3617de4a96262c6f5d9e98bf9292dc29f8f41dbd289a147ce9da3113b5f0b8c00a60b1ce1d7e819d7a431d7c90ea0e5f'),
|
Gy: BigInt('0x3617de4a96262c6f5d9e98bf9292dc29f8f41dbd289a147ce9da3113b5f0b8c00a60b1ce1d7e819d7a431d7c90ea0e5f'),
|
||||||
h: BigInt(1),
|
h: BigInt(1),
|
||||||
lowS: false,
|
lowS: false,
|
||||||
mapToCurve: (scalars: bigint[]) => mapSWU(scalars[0]),
|
|
||||||
htfDefaults: {
|
|
||||||
DST: 'P384_XMD:SHA-384_SSWU_RO_',
|
|
||||||
p: Fp.ORDER,
|
|
||||||
m: 1,
|
|
||||||
k: 192,
|
|
||||||
expand: 'xmd',
|
|
||||||
hash: sha384,
|
|
||||||
},
|
|
||||||
} as const,
|
} as const,
|
||||||
sha384
|
sha384
|
||||||
);
|
);
|
||||||
export const secp384r1 = P384;
|
export const secp384r1 = P384;
|
||||||
|
|
||||||
|
const { hashToCurve, encodeToCurve } = htf.hashToCurve(
|
||||||
|
secp384r1.Point,
|
||||||
|
(scalars: bigint[]) => mapSWU(scalars[0]),
|
||||||
|
{
|
||||||
|
DST: 'P384_XMD:SHA-384_SSWU_RO_',
|
||||||
|
encodeDST: 'P384_XMD:SHA-384_SSWU_NU_',
|
||||||
|
p: Fp.ORDER,
|
||||||
|
m: 1,
|
||||||
|
k: 192,
|
||||||
|
expand: 'xmd',
|
||||||
|
hash: sha384,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
export { hashToCurve, encodeToCurve };
|
||||||
|
19
src/p521.ts
19
src/p521.ts
@ -4,6 +4,7 @@ import { sha512 } from '@noble/hashes/sha512';
|
|||||||
import { bytesToHex, PrivKey } from './abstract/utils.js';
|
import { bytesToHex, PrivKey } from './abstract/utils.js';
|
||||||
import { Fp as Field } from './abstract/modular.js';
|
import { Fp as Field } from './abstract/modular.js';
|
||||||
import { mapToCurveSimpleSWU } from './abstract/weierstrass.js';
|
import { mapToCurveSimpleSWU } from './abstract/weierstrass.js';
|
||||||
|
import * as htf from './abstract/hash-to-curve.js';
|
||||||
|
|
||||||
// NIST secp521r1 aka P521
|
// NIST secp521r1 aka P521
|
||||||
// Note that it's 521, which differs from 512 of its hash function.
|
// Note that it's 521, which differs from 512 of its hash function.
|
||||||
@ -48,14 +49,20 @@ export const P521 = createCurve({
|
|||||||
}
|
}
|
||||||
return key.padStart(66 * 2, '0');
|
return key.padStart(66 * 2, '0');
|
||||||
},
|
},
|
||||||
mapToCurve: (scalars: bigint[]) => mapSWU(scalars[0]),
|
} as const, sha512);
|
||||||
htfDefaults: {
|
export const secp521r1 = P521;
|
||||||
|
|
||||||
|
const { hashToCurve, encodeToCurve } = htf.hashToCurve(
|
||||||
|
secp521r1.Point,
|
||||||
|
(scalars: bigint[]) => mapSWU(scalars[0]),
|
||||||
|
{
|
||||||
DST: 'P521_XMD:SHA-512_SSWU_RO_',
|
DST: 'P521_XMD:SHA-512_SSWU_RO_',
|
||||||
|
encodeDST: 'P521_XMD:SHA-512_SSWU_NU_',
|
||||||
p: Fp.ORDER,
|
p: Fp.ORDER,
|
||||||
m: 1,
|
m: 1,
|
||||||
k: 256,
|
k: 256,
|
||||||
expand: 'xmd',
|
expand: 'xmd',
|
||||||
hash: sha512,
|
hash: sha512,
|
||||||
},
|
}
|
||||||
} as const, sha512);
|
);
|
||||||
export const secp521r1 = P521;
|
export { hashToCurve, encodeToCurve };
|
||||||
|
@ -12,7 +12,7 @@ import {
|
|||||||
PrivKey,
|
PrivKey,
|
||||||
} from './abstract/utils.js';
|
} from './abstract/utils.js';
|
||||||
import { randomBytes } from '@noble/hashes/utils';
|
import { randomBytes } from '@noble/hashes/utils';
|
||||||
import { isogenyMap } from './abstract/hash-to-curve.js';
|
import * as htf from './abstract/hash-to-curve.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* secp256k1 belongs to Koblitz curves: it has efficiently computable endomorphism.
|
* secp256k1 belongs to Koblitz curves: it has efficiently computable endomorphism.
|
||||||
@ -62,7 +62,7 @@ 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 = isogenyMap(
|
const isoMap = htf.isogenyMap(
|
||||||
Fp,
|
Fp,
|
||||||
[
|
[
|
||||||
// xNum
|
// xNum
|
||||||
@ -143,22 +143,28 @@ export const secp256k1 = createCurve(
|
|||||||
return { k1neg, k1, k2neg, k2 };
|
return { k1neg, k1, k2neg, k2 };
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
mapToCurve: (scalars: bigint[]) => {
|
|
||||||
const { x, y } = mapSWU(Fp.create(scalars[0]));
|
|
||||||
return isoMap(x, y);
|
|
||||||
},
|
|
||||||
htfDefaults: {
|
|
||||||
DST: 'secp256k1_XMD:SHA-256_SSWU_RO_',
|
|
||||||
p: Fp.ORDER,
|
|
||||||
m: 1,
|
|
||||||
k: 128,
|
|
||||||
expand: 'xmd',
|
|
||||||
hash: sha256,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
sha256
|
sha256
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const { hashToCurve, encodeToCurve } = htf.hashToCurve(
|
||||||
|
secp256k1.Point,
|
||||||
|
(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;
|
||||||
|
@ -1459,8 +1459,8 @@ describe('hash-to-curve', () => {
|
|||||||
];
|
];
|
||||||
for (let i = 0; i < VECTORS_G1.length; i++) {
|
for (let i = 0; i < VECTORS_G1.length; i++) {
|
||||||
const t = VECTORS_G1[i];
|
const t = VECTORS_G1[i];
|
||||||
should(`hashToCurve/G1 Killic (${i})`, async () => {
|
should(`hashToCurve/G1 Killic (${i})`, () => {
|
||||||
const p = await bls.G1.Point.hashToCurve(t.msg, {
|
const p = bls.hashToCurve.G1.hashToCurve(t.msg, {
|
||||||
DST: 'BLS12381G1_XMD:SHA-256_SSWU_RO_TESTGEN',
|
DST: 'BLS12381G1_XMD:SHA-256_SSWU_RO_TESTGEN',
|
||||||
});
|
});
|
||||||
deepStrictEqual(p.toHex(), t.expected);
|
deepStrictEqual(p.toHex(), t.expected);
|
||||||
@ -1514,8 +1514,8 @@ describe('hash-to-curve', () => {
|
|||||||
];
|
];
|
||||||
for (let i = 0; i < VECTORS_G1_RO.length; i++) {
|
for (let i = 0; i < VECTORS_G1_RO.length; i++) {
|
||||||
const t = VECTORS_G1_RO[i];
|
const t = VECTORS_G1_RO[i];
|
||||||
should(`hashToCurve/G1 (BLS12381G1_XMD:SHA-256_SSWU_RO_) (${i})`, async () => {
|
should(`hashToCurve/G1 (BLS12381G1_XMD:SHA-256_SSWU_RO_) (${i})`, () => {
|
||||||
const p = await bls.G1.Point.hashToCurve(t.msg, {
|
const p = bls.hashToCurve.G1.hashToCurve(t.msg, {
|
||||||
DST: 'QUUX-V01-CS02-with-BLS12381G1_XMD:SHA-256_SSWU_RO_',
|
DST: 'QUUX-V01-CS02-with-BLS12381G1_XMD:SHA-256_SSWU_RO_',
|
||||||
});
|
});
|
||||||
deepStrictEqual(p.toHex(), t.expected);
|
deepStrictEqual(p.toHex(), t.expected);
|
||||||
@ -1560,8 +1560,8 @@ describe('hash-to-curve', () => {
|
|||||||
];
|
];
|
||||||
for (let i = 0; i < VECTORS_G1_NU.length; i++) {
|
for (let i = 0; i < VECTORS_G1_NU.length; i++) {
|
||||||
const t = VECTORS_G1_NU[i];
|
const t = VECTORS_G1_NU[i];
|
||||||
should(`hashToCurve/G1 (BLS12381G1_XMD:SHA-256_SSWU_NU_) (${i})`, async () => {
|
should(`hashToCurve/G1 (BLS12381G1_XMD:SHA-256_SSWU_NU_) (${i})`, () => {
|
||||||
const p = await bls.G1.Point.encodeToCurve(t.msg, {
|
const p = bls.hashToCurve.G1.encodeToCurve(t.msg, {
|
||||||
DST: 'QUUX-V01-CS02-with-BLS12381G1_XMD:SHA-256_SSWU_NU_',
|
DST: 'QUUX-V01-CS02-with-BLS12381G1_XMD:SHA-256_SSWU_NU_',
|
||||||
});
|
});
|
||||||
deepStrictEqual(p.toHex(), t.expected);
|
deepStrictEqual(p.toHex(), t.expected);
|
||||||
@ -1597,8 +1597,8 @@ describe('hash-to-curve', () => {
|
|||||||
];
|
];
|
||||||
for (let i = 0; i < VECTORS_ENCODE_G1.length; i++) {
|
for (let i = 0; i < VECTORS_ENCODE_G1.length; i++) {
|
||||||
const t = VECTORS_ENCODE_G1[i];
|
const t = VECTORS_ENCODE_G1[i];
|
||||||
should(`hashToCurve/G1 (Killic, encodeToCurve) (${i})`, async () => {
|
should(`hashToCurve/G1 (Killic, encodeToCurve) (${i})`, () => {
|
||||||
const p = await bls.G1.Point.encodeToCurve(t.msg, {
|
const p = bls.hashToCurve.G1.encodeToCurve(t.msg, {
|
||||||
DST: 'BLS12381G1_XMD:SHA-256_SSWU_NU_TESTGEN',
|
DST: 'BLS12381G1_XMD:SHA-256_SSWU_NU_TESTGEN',
|
||||||
});
|
});
|
||||||
deepStrictEqual(p.toHex(), t.expected);
|
deepStrictEqual(p.toHex(), t.expected);
|
||||||
@ -1643,8 +1643,8 @@ describe('hash-to-curve', () => {
|
|||||||
];
|
];
|
||||||
for (let i = 0; i < VECTORS_G2.length; i++) {
|
for (let i = 0; i < VECTORS_G2.length; i++) {
|
||||||
const t = VECTORS_G2[i];
|
const t = VECTORS_G2[i];
|
||||||
should(`hashToCurve/G2 Killic (${i})`, async () => {
|
should(`hashToCurve/G2 Killic (${i})`, () => {
|
||||||
const p = await bls.G2.Point.hashToCurve(t.msg, {
|
const p = bls.hashToCurve.G2.hashToCurve(t.msg, {
|
||||||
DST: 'BLS12381G2_XMD:SHA-256_SSWU_RO_TESTGEN',
|
DST: 'BLS12381G2_XMD:SHA-256_SSWU_RO_TESTGEN',
|
||||||
});
|
});
|
||||||
deepStrictEqual(p.toHex(), t.expected);
|
deepStrictEqual(p.toHex(), t.expected);
|
||||||
@ -1708,8 +1708,8 @@ describe('hash-to-curve', () => {
|
|||||||
];
|
];
|
||||||
for (let i = 0; i < VECTORS_G2_RO.length; i++) {
|
for (let i = 0; i < VECTORS_G2_RO.length; i++) {
|
||||||
const t = VECTORS_G2_RO[i];
|
const t = VECTORS_G2_RO[i];
|
||||||
should(`hashToCurve/G2 (BLS12381G2_XMD:SHA-256_SSWU_RO_) (${i})`, async () => {
|
should(`hashToCurve/G2 (BLS12381G2_XMD:SHA-256_SSWU_RO_) (${i})`, () => {
|
||||||
const p = await bls.G2.Point.hashToCurve(t.msg, {
|
const p = bls.hashToCurve.G2.hashToCurve(t.msg, {
|
||||||
DST: 'QUUX-V01-CS02-with-BLS12381G2_XMD:SHA-256_SSWU_RO_',
|
DST: 'QUUX-V01-CS02-with-BLS12381G2_XMD:SHA-256_SSWU_RO_',
|
||||||
});
|
});
|
||||||
deepStrictEqual(p.toHex(), t.expected);
|
deepStrictEqual(p.toHex(), t.expected);
|
||||||
@ -1773,8 +1773,8 @@ describe('hash-to-curve', () => {
|
|||||||
];
|
];
|
||||||
for (let i = 0; i < VECTORS_G2_NU.length; i++) {
|
for (let i = 0; i < VECTORS_G2_NU.length; i++) {
|
||||||
const t = VECTORS_G2_NU[i];
|
const t = VECTORS_G2_NU[i];
|
||||||
should(`hashToCurve/G2 (BLS12381G2_XMD:SHA-256_SSWU_NU_) (${i})`, async () => {
|
should(`hashToCurve/G2 (BLS12381G2_XMD:SHA-256_SSWU_NU_) (${i})`, () => {
|
||||||
const p = await bls.G2.Point.encodeToCurve(t.msg, {
|
const p = bls.hashToCurve.G2.encodeToCurve(t.msg, {
|
||||||
DST: 'QUUX-V01-CS02-with-BLS12381G2_XMD:SHA-256_SSWU_NU_',
|
DST: 'QUUX-V01-CS02-with-BLS12381G2_XMD:SHA-256_SSWU_NU_',
|
||||||
});
|
});
|
||||||
deepStrictEqual(p.toHex(), t.expected);
|
deepStrictEqual(p.toHex(), t.expected);
|
||||||
@ -1818,8 +1818,8 @@ describe('hash-to-curve', () => {
|
|||||||
];
|
];
|
||||||
for (let i = 0; i < VECTORS_ENCODE_G2.length; i++) {
|
for (let i = 0; i < VECTORS_ENCODE_G2.length; i++) {
|
||||||
const t = VECTORS_ENCODE_G2[i];
|
const t = VECTORS_ENCODE_G2[i];
|
||||||
should(`hashToCurve/G2 (Killic, encodeToCurve) (${i})`, async () => {
|
should(`hashToCurve/G2 (Killic, encodeToCurve) (${i})`, () => {
|
||||||
const p = await bls.G2.Point.encodeToCurve(t.msg, {
|
const p = bls.hashToCurve.G2.encodeToCurve(t.msg, {
|
||||||
DST: 'BLS12381G2_XMD:SHA-256_SSWU_NU_TESTGEN',
|
DST: 'BLS12381G2_XMD:SHA-256_SSWU_NU_TESTGEN',
|
||||||
});
|
});
|
||||||
deepStrictEqual(p.toHex(), t.expected);
|
deepStrictEqual(p.toHex(), t.expected);
|
||||||
|
@ -5,12 +5,12 @@ import { bytesToHex } from '@noble/hashes/utils';
|
|||||||
import { sha256 } from '@noble/hashes/sha256';
|
import { sha256 } from '@noble/hashes/sha256';
|
||||||
import { sha512 } from '@noble/hashes/sha512';
|
import { sha512 } from '@noble/hashes/sha512';
|
||||||
import { shake128, shake256 } from '@noble/hashes/sha3';
|
import { shake128, shake256 } from '@noble/hashes/sha3';
|
||||||
import { secp256r1 } from '../lib/esm/p256.js';
|
import * as secp256r1 from '../lib/esm/p256.js';
|
||||||
import { secp384r1 } from '../lib/esm/p384.js';
|
import * as secp384r1 from '../lib/esm/p384.js';
|
||||||
import { secp521r1 } from '../lib/esm/p521.js';
|
import * as secp521r1 from '../lib/esm/p521.js';
|
||||||
import { ed25519 } from '../lib/esm/ed25519.js';
|
import * as ed25519 from '../lib/esm/ed25519.js';
|
||||||
import { ed448 } from '../lib/esm/ed448.js';
|
import * as ed448 from '../lib/esm/ed448.js';
|
||||||
import { secp256k1 } from '../lib/esm/secp256k1.js';
|
import * as secp256k1 from '../lib/esm/secp256k1.js';
|
||||||
import { bls12_381 } from '../lib/esm/bls12-381.js';
|
import { bls12_381 } from '../lib/esm/bls12-381.js';
|
||||||
import {
|
import {
|
||||||
stringToBytes,
|
stringToBytes,
|
||||||
@ -111,7 +111,7 @@ function testCurve(curve, ro, nu) {
|
|||||||
for (let i = 0; i < ro.vectors.length; i++) {
|
for (let i = 0; i < ro.vectors.length; i++) {
|
||||||
const t = ro.vectors[i];
|
const t = ro.vectors[i];
|
||||||
should(`(${i})`, () => {
|
should(`(${i})`, () => {
|
||||||
const p = curve.Point.hashToCurve(stringToBytes(t.msg), {
|
const p = curve.hashToCurve(stringToBytes(t.msg), {
|
||||||
DST: ro.dst,
|
DST: ro.dst,
|
||||||
});
|
});
|
||||||
deepStrictEqual(p.x, stringToFp(t.P.x), 'Px');
|
deepStrictEqual(p.x, stringToFp(t.P.x), 'Px');
|
||||||
@ -123,7 +123,7 @@ function testCurve(curve, ro, nu) {
|
|||||||
for (let i = 0; i < nu.vectors.length; i++) {
|
for (let i = 0; i < nu.vectors.length; i++) {
|
||||||
const t = nu.vectors[i];
|
const t = nu.vectors[i];
|
||||||
should(`(${i})`, () => {
|
should(`(${i})`, () => {
|
||||||
const p = curve.Point.encodeToCurve(stringToBytes(t.msg), {
|
const p = curve.encodeToCurve(stringToBytes(t.msg), {
|
||||||
DST: nu.dst,
|
DST: nu.dst,
|
||||||
});
|
});
|
||||||
deepStrictEqual(p.x, stringToFp(t.P.x), 'Px');
|
deepStrictEqual(p.x, stringToFp(t.P.x), 'Px');
|
||||||
@ -137,8 +137,8 @@ testCurve(secp256r1, p256_ro, p256_nu);
|
|||||||
testCurve(secp384r1, p384_ro, p384_nu);
|
testCurve(secp384r1, p384_ro, p384_nu);
|
||||||
testCurve(secp521r1, p521_ro, p521_nu);
|
testCurve(secp521r1, p521_ro, p521_nu);
|
||||||
// TODO: remove same tests from bls12
|
// TODO: remove same tests from bls12
|
||||||
testCurve(bls12_381.G1, g1_ro, g1_nu);
|
testCurve(bls12_381.hashToCurve.G1, g1_ro, g1_nu);
|
||||||
testCurve(bls12_381.G2, g2_ro, g2_nu);
|
testCurve(bls12_381.hashToCurve.G2, g2_ro, g2_nu);
|
||||||
testCurve(secp256k1, secp256k1_ro, secp256k1_nu);
|
testCurve(secp256k1, secp256k1_ro, secp256k1_nu);
|
||||||
testCurve(ed25519, ed25519_ro, ed25519_nu);
|
testCurve(ed25519, ed25519_ro, ed25519_nu);
|
||||||
testCurve(ed448, ed448_ro, ed448_nu);
|
testCurve(ed448, ed448_ro, ed448_nu);
|
||||||
|
@ -255,7 +255,7 @@ should('Starknet.js cross-tests', () => {
|
|||||||
);
|
);
|
||||||
const msgHash = '0x6d1706bd3d1ba7c517be2a2a335996f63d4738e2f182144d078a1dd9997062e';
|
const msgHash = '0x6d1706bd3d1ba7c517be2a2a335996f63d4738e2f182144d078a1dd9997062e';
|
||||||
const sig = starknet.sign(msgHash, privateKey);
|
const sig = starknet.sign(msgHash, privateKey);
|
||||||
const { r, s } = (sig);
|
const { r, s } = sig;
|
||||||
|
|
||||||
deepStrictEqual(
|
deepStrictEqual(
|
||||||
r.toString(),
|
r.toString(),
|
||||||
|
Loading…
Reference in New Issue
Block a user