Skip to content

Commit

Permalink
secp256k1 & other implementations: reduce bundle size by 20% by using…
Browse files Browse the repository at this point in the history
… PURE.

PURE annotation helps bundlers during tree-shaking and eliminates dead code.

* secp256k1: 75.4kb => 62.3kb

* ed25519: 67.5kb => 51.1kb

* ed448: 55.1kb => 44.0kb

* p256: 67.8kb => 59.8kb

* p384: 75.4kb => 67.4kb

* p521: 75.8kb => 67.8kb
  • Loading branch information
paulmillr committed May 20, 2023
1 parent 648fd2c commit ff5b231
Show file tree
Hide file tree
Showing 6 changed files with 190 additions and 180 deletions.
115 changes: 60 additions & 55 deletions src/ed25519.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
Hex,
numberToBytesLE,
} from './abstract/utils.js';
import * as htf from './abstract/hash-to-curve.js';
import { createHasher, htfBasicOpts, expand_message_xmd } from './abstract/hash-to-curve.js';
import { AffinePoint } from './abstract/curve.js';

/**
Expand Down Expand Up @@ -142,21 +142,22 @@ export const ed25519ph = twistedEdwards({
prehash: sha512,
});

export const x25519 = montgomery({
P: ED25519_P,
a: BigInt(486662),
montgomeryBits: 255, // n is 253 bits
nByteLength: 32,
Gu: BigInt(9),
powPminus2: (x: bigint): bigint => {
const P = ED25519_P;
// x^(p-2) aka x^(2^255-21)
const { pow_p_5_8, b2 } = ed25519_pow_2_252_3(x);
return mod(pow2(pow_p_5_8, BigInt(3), P) * b2, P);
},
adjustScalarBytes,
randomBytes,
});
export const x25519 = /* @__PURE__ */ (() =>
montgomery({
P: ED25519_P,
a: BigInt(486662),
montgomeryBits: 255, // n is 253 bits
nByteLength: 32,
Gu: BigInt(9),
powPminus2: (x: bigint): bigint => {
const P = ED25519_P;
// x^(p-2) aka x^(2^255-21)
const { pow_p_5_8, b2 } = ed25519_pow_2_252_3(x);
return mod(pow2(pow_p_5_8, BigInt(3), P) * b2, P);
},
adjustScalarBytes,
randomBytes,
}))();

/**
* Converts ed25519 public key to x25519 public key. Uses formula:
Expand Down Expand Up @@ -260,29 +261,29 @@ function map_to_curve_elligator2_edwards25519(u: bigint) {
return { x: Fp.mul(xn, inv[0]), y: Fp.mul(yn, inv[1]) }; // 13. return (xn, xd, yn, yd)
}

const { hashToCurve, encodeToCurve } = htf.createHasher(
ed25519.ExtendedPoint,
(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 };
const htf = /* @__PURE__ */ (() =>
createHasher(
ed25519.ExtendedPoint,
(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 const hashToCurve = /* @__PURE__ */ (() => htf.hashToCurve)();
export const encodeToCurve = /* @__PURE__ */ (() => htf.encodeToCurve)();

function assertRstPoint(other: unknown) {
if (!(other instanceof RistrettoPoint)) throw new Error('RistrettoPoint expected');
if (!(other instanceof RistPoint)) throw new Error('RistrettoPoint expected');
}

// √(-1) aka √(a) aka 2^((p-1)/4)
const SQRT_M1 = BigInt(
'19681161376707505956807079304988542015446066515923890162744021073123829784752'
);
const SQRT_M1 = ED25519_SQRT_M1;
// √(ad - 1)
const SQRT_AD_MINUS_ONE = BigInt(
'25063068953384623474111414158702152701244531502492656460079210482610430750235'
Expand Down Expand Up @@ -339,16 +340,15 @@ function calcElligatorRistrettoMap(r0: bigint): ExtendedPoint {
* but it should work in its own namespace: do not combine those two.
* https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-ristretto255-decaf448
*/
export class RistrettoPoint {
static BASE = new RistrettoPoint(ed25519.ExtendedPoint.BASE);
static ZERO = new RistrettoPoint(ed25519.ExtendedPoint.ZERO);

class RistPoint {
static BASE: RistPoint;
static ZERO: RistPoint;
// Private property to discourage combining ExtendedPoint + RistrettoPoint
// Always use Ristretto encoding/decoding instead.
constructor(private readonly ep: ExtendedPoint) {}

static fromAffine(ap: AffinePoint<bigint>) {
return new RistrettoPoint(ed25519.ExtendedPoint.fromAffine(ap));
return new RistPoint(ed25519.ExtendedPoint.fromAffine(ap));
}

/**
Expand All @@ -358,21 +358,21 @@ export class RistrettoPoint {
* https://ristretto.group/formulas/elligator.html
* @param hex 64-byte output of a hash function
*/
static hashToCurve(hex: Hex): RistrettoPoint {
static hashToCurve(hex: Hex): RistPoint {
hex = ensureBytes('ristrettoHash', hex, 64);
const r1 = bytes255ToNumberLE(hex.slice(0, 32));
const R1 = calcElligatorRistrettoMap(r1);
const r2 = bytes255ToNumberLE(hex.slice(32, 64));
const R2 = calcElligatorRistrettoMap(r2);
return new RistrettoPoint(R1.add(R2));
return new RistPoint(R1.add(R2));
}

/**
* Converts ristretto-encoded string to ristretto point.
* https://ristretto.group/formulas/decoding.html
* @param hex Ristretto-encoded 32 bytes. Not every 32-byte string is valid ristretto encoding
*/
static fromHex(hex: Hex): RistrettoPoint {
static fromHex(hex: Hex): RistPoint {
hex = ensureBytes('ristrettoHex', hex, 32);
const { a, d } = ed25519.CURVE;
const P = ed25519.CURVE.Fp.ORDER;
Expand All @@ -396,7 +396,7 @@ export class RistrettoPoint {
const y = mod(u1 * Dy); // 11
const t = mod(x * y); // 12
if (!isValid || isNegativeLE(t, P) || y === _0n) throw new Error(emsg);
return new RistrettoPoint(new ed25519.ExtendedPoint(x, y, _1n, t));
return new RistPoint(new ed25519.ExtendedPoint(x, y, _1n, t));
}

/**
Expand Down Expand Up @@ -440,7 +440,7 @@ export class RistrettoPoint {
}

// Compare one point to another.
equals(other: RistrettoPoint): boolean {
equals(other: RistPoint): boolean {
assertRstPoint(other);
const { ex: X1, ey: Y1 } = this.ep;
const { ex: X2, ey: Y2 } = other.ep;
Expand All @@ -451,31 +451,36 @@ export class RistrettoPoint {
return one || two;
}

add(other: RistrettoPoint): RistrettoPoint {
add(other: RistPoint): RistPoint {
assertRstPoint(other);
return new RistrettoPoint(this.ep.add(other.ep));
return new RistPoint(this.ep.add(other.ep));
}

subtract(other: RistrettoPoint): RistrettoPoint {
subtract(other: RistPoint): RistPoint {
assertRstPoint(other);
return new RistrettoPoint(this.ep.subtract(other.ep));
return new RistPoint(this.ep.subtract(other.ep));
}

multiply(scalar: bigint): RistrettoPoint {
return new RistrettoPoint(this.ep.multiply(scalar));
multiply(scalar: bigint): RistPoint {
return new RistPoint(this.ep.multiply(scalar));
}

multiplyUnsafe(scalar: bigint): RistrettoPoint {
return new RistrettoPoint(this.ep.multiplyUnsafe(scalar));
multiplyUnsafe(scalar: bigint): RistPoint {
return new RistPoint(this.ep.multiplyUnsafe(scalar));
}
}
export const RistrettoPoint = /* @__PURE__ */ (() => {
if (!RistPoint.BASE) RistPoint.BASE = new RistPoint(ed25519.ExtendedPoint.BASE);
if (!RistPoint.ZERO) RistPoint.ZERO = new RistPoint(ed25519.ExtendedPoint.ZERO);
return RistPoint;
})();

// https://datatracker.ietf.org/doc/draft-irtf-cfrg-hash-to-curve/14/
// Appendix B. Hashing to ristretto255
export const hash_to_ristretto255 = (msg: Uint8Array, options: htf.htfBasicOpts) => {
export const hash_to_ristretto255 = (msg: Uint8Array, options: htfBasicOpts) => {
const d = options.DST;
const DST = typeof d === 'string' ? utf8ToBytes(d) : d;
const uniform_bytes = htf.expand_message_xmd(msg, DST, 64, sha512);
const P = RistrettoPoint.hashToCurve(uniform_bytes);
const uniform_bytes = expand_message_xmd(msg, DST, 64, sha512);
const P = RistPoint.hashToCurve(uniform_bytes);
return P;
};
63 changes: 33 additions & 30 deletions src/ed448.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { concatBytes, randomBytes, utf8ToBytes, wrapConstructor } from '@noble/h
import { twistedEdwards } from './abstract/edwards.js';
import { mod, pow2, Field } from './abstract/modular.js';
import { montgomery } from './abstract/montgomery.js';
import * as htf from './abstract/hash-to-curve.js';
import { createHasher } from './abstract/hash-to-curve.js';

/**
* Edwards448 (not Ed448-Goldilocks) curve with following addons:
Expand Down Expand Up @@ -122,21 +122,22 @@ export const ed448 = twistedEdwards(ED448_DEF);
// NOTE: there is no ed448ctx, since ed448 supports ctx by default
export const ed448ph = twistedEdwards({ ...ED448_DEF, prehash: shake256_64 });

export const x448 = montgomery({
a: BigInt(156326),
montgomeryBits: 448,
nByteLength: 57,
P: ed448P,
Gu: BigInt(5),
powPminus2: (x: bigint): bigint => {
const P = ed448P;
const Pminus3div4 = ed448_pow_Pminus3div4(x);
const Pminus3 = pow2(Pminus3div4, BigInt(2), P);
return mod(Pminus3 * x, P); // Pminus3 * x = Pminus2
},
adjustScalarBytes,
randomBytes,
});
export const x448 = /* @__PURE__ */ (() =>
montgomery({
a: BigInt(156326),
montgomeryBits: 448,
nByteLength: 57,
P: ed448P,
Gu: BigInt(5),
powPminus2: (x: bigint): bigint => {
const P = ed448P;
const Pminus3div4 = ed448_pow_Pminus3div4(x);
const Pminus3 = pow2(Pminus3div4, BigInt(2), P);
return mod(Pminus3 * x, P); // Pminus3 * x = Pminus2
},
adjustScalarBytes,
randomBytes,
}))();

/**
* Converts edwards448 public key to x448 public key. Uses formula:
Expand Down Expand Up @@ -228,17 +229,19 @@ function map_to_curve_elligator2_edwards448(u: bigint) {
return { x: Fp.mul(xEn, inv[0]), y: Fp.mul(yEn, inv[1]) }; // 38. return (xEn, xEd, yEn, yEd)
}

const { hashToCurve, encodeToCurve } = htf.createHasher(
ed448.ExtendedPoint,
(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 };
const htf = /* @__PURE__ */ (() =>
createHasher(
ed448.ExtendedPoint,
(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 const hashToCurve = /* @__PURE__ */ (() => htf.hashToCurve)();
export const encodeToCurve = /* @__PURE__ */ (() => htf.encodeToCurve)();
27 changes: 13 additions & 14 deletions src/p256.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { createCurve } from './_shortw_utils.js';
import { sha256 } from '@noble/hashes/sha256';
import { Field } from './abstract/modular.js';
import { mapToCurveSimpleSWU } from './abstract/weierstrass.js';
import * as htf from './abstract/hash-to-curve.js';
import { createHasher } from './abstract/hash-to-curve.js';

// NIST secp256r1 aka p256
// https://www.secg.org/sec2-v2.pdf, https://neuromancer.sk/std/nist/P-256
Expand All @@ -12,12 +12,6 @@ const Fp = Field(BigInt('0xffffffff00000001000000000000000000000000fffffffffffff
const CURVE_A = Fp.create(BigInt('-3'));
const CURVE_B = BigInt('0x5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b');

const mapSWU = mapToCurveSimpleSWU(Fp, {
A: CURVE_A,
B: CURVE_B,
Z: Fp.create(BigInt('-10')),
});

// prettier-ignore
export const p256 = createCurve({
a: CURVE_A, // Equation params: a, b
Expand All @@ -33,17 +27,22 @@ export const p256 = createCurve({
} as const, sha256);
export const secp256r1 = p256;

const { hashToCurve, encodeToCurve } = htf.createHasher(
secp256r1.ProjectivePoint,
(scalars: bigint[]) => mapSWU(scalars[0]),
{
const mapSWU = /* @__PURE__ */ (() =>
mapToCurveSimpleSWU(Fp, {
A: CURVE_A,
B: CURVE_B,
Z: Fp.create(BigInt('-10')),
}))();

const htf = /* @__PURE__ */ (() =>
createHasher(secp256r1.ProjectivePoint, (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 };
}))();
export const hashToCurve = /* @__PURE__ */ (() => htf.hashToCurve)();
export const encodeToCurve = /* @__PURE__ */ (() => htf.encodeToCurve)();
25 changes: 12 additions & 13 deletions src/p384.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { createCurve } from './_shortw_utils.js';
import { sha384 } from '@noble/hashes/sha512';
import { Field } from './abstract/modular.js';
import { mapToCurveSimpleSWU } from './abstract/weierstrass.js';
import * as htf from './abstract/hash-to-curve.js';
import { createHasher } from './abstract/hash-to-curve.js';

// NIST secp384r1 aka p384
// https://www.secg.org/sec2-v2.pdf, https://neuromancer.sk/std/nist/P-384
Expand Down Expand Up @@ -31,23 +31,22 @@ export const p384 = createCurve({
} as const, sha384);
export const secp384r1 = p384;

const mapSWU = mapToCurveSimpleSWU(Fp, {
A: CURVE_A,
B: CURVE_B,
Z: Fp.create(BigInt('-12')),
});
const mapSWU = /* @__PURE__ */ (() =>
mapToCurveSimpleSWU(Fp, {
A: CURVE_A,
B: CURVE_B,
Z: Fp.create(BigInt('-12')),
}))();

const { hashToCurve, encodeToCurve } = htf.createHasher(
secp384r1.ProjectivePoint,
(scalars: bigint[]) => mapSWU(scalars[0]),
{
const htf = /* @__PURE__ */ (() =>
createHasher(secp384r1.ProjectivePoint, (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 };
}))();
export const hashToCurve = /* @__PURE__ */ (() => htf.hashToCurve)();
export const encodeToCurve = /* @__PURE__ */ (() => htf.encodeToCurve)();
Loading

0 comments on commit ff5b231

Please sign in to comment.