@hpke/common
Version:
A Hybrid Public Key Encryption (HPKE) internal-use common module for @hpke family modules.
160 lines (159 loc) • 6.45 kB
JavaScript
/**
* This file is based on noble-curves (https://github.com/paulmillr/noble-curves).
*
* noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com)
*
* The original file is located at:
* https://github.com/paulmillr/noble-curves/blob/b9d49d2b41d550571a0c5be443ecb62109fa3373/src/abstract/montgomery.ts
*/
/**
* Montgomery curve methods. It's not really whole montgomery curve,
* just bunch of very specific methods for X25519 / X448 from
* [RFC 7748](https://www.rfc-editor.org/rfc/rfc7748)
* @module
*/
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
import { abytes, aInRange, bytesToNumberLE, copyBytes, numberToBytesLE, randomBytesAsync, validateObject, } from "../utils/noble.js";
import { createKeygen } from "./curve.js";
import { mod } from "./modular.js";
import { N_0, N_1, N_2 } from "../consts.js";
function validateOpts(curve) {
validateObject(curve, {
adjustScalarBytes: "function",
powPminus2: "function",
});
return Object.freeze({ ...curve });
}
export function montgomery(curveDef) {
const CURVE = validateOpts(curveDef);
const { P, type, adjustScalarBytes, powPminus2, randomBytes: rand } = CURVE;
const is25519 = type === "x25519";
if (!is25519 && type !== "x448")
throw new Error("invalid type");
const randomBytes_ = rand || randomBytesAsync;
const montgomeryBits = is25519 ? 255n : 448n;
const fieldLen = is25519 ? 32 : 56;
const Gu = is25519 ? 9n : 5n;
// RFC 7748 #5:
// The constant a24 is (486662 - 2) / 4 = 121665 for curve25519/X25519 and
// (156326 - 2) / 4 = 39081 for curve448/X448
// const a = is25519 ? 156326n : 486662n;
const a24 = is25519 ? 121665n : 39081n;
// RFC: x25519 "the resulting integer is of the form 2^254 plus
// eight times a value between 0 and 2^251 - 1 (inclusive)"
// x448: "2^447 plus four times a value between 0 and 2^445 - 1 (inclusive)"
const minScalar = is25519 ? N_2 ** 254n : N_2 ** 447n;
const maxAdded = is25519 ? 8n * N_2 ** 251n - N_1 : 4n * N_2 ** 445n - N_1;
const maxScalar = minScalar + maxAdded + N_1; // (inclusive)
const modP = (n) => mod(n, P);
const GuBytes = encodeU(Gu);
function encodeU(u) {
return numberToBytesLE(modP(u), fieldLen);
}
function decodeU(u) {
const _u = copyBytes(abytes(u, fieldLen, "uCoordinate"));
// RFC: When receiving such an array, implementations of X25519
// (but not X448) MUST mask the most significant bit in the final byte.
if (is25519)
_u[31] &= 127; // 0b0111_1111
// RFC: Implementations MUST accept non-canonical values and process them as
// if they had been reduced modulo the field prime. The non-canonical
// values are 2^255 - 19 through 2^255 - 1 for X25519 and 2^448 - 2^224
// - 1 through 2^448 - 1 for X448.
return modP(bytesToNumberLE(_u));
}
function decodeScalar(scalar) {
return bytesToNumberLE(adjustScalarBytes(copyBytes(abytes(scalar, fieldLen, "scalar"))));
}
function scalarMult(scalar, u) {
const pu = montgomeryLadder(decodeU(u), decodeScalar(scalar));
// Some public keys are useless, of low-order. Curve author doesn't think
// it needs to be validated, but we do it nonetheless.
// https://cr.yp.to/ecdh.html#validate
if (pu === N_0)
throw new Error("invalid private or public key received");
return encodeU(pu);
}
// Computes public key from private. By doing scalar multiplication of base point.
function scalarMultBase(scalar) {
return scalarMult(scalar, GuBytes);
}
const getPublicKey = scalarMultBase;
const getSharedSecret = scalarMult;
// cswap from RFC7748 "example code"
function cswap(swap, x_2, x_3) {
// dummy = mask(swap) AND (x_2 XOR x_3)
// Where mask(swap) is the all-1 or all-0 word of the same length as x_2
// and x_3, computed, e.g., as mask(swap) = 0 - swap.
const dummy = modP(swap * (x_2 - x_3));
x_2 = modP(x_2 - dummy); // x_2 = x_2 XOR dummy
x_3 = modP(x_3 + dummy); // x_3 = x_3 XOR dummy
return { x_2, x_3 };
}
/**
* Montgomery x-only multiplication ladder.
* @param pointU u coordinate (x) on Montgomery Curve 25519
* @param scalar by which the point would be multiplied
* @returns new Point on Montgomery curve
*/
function montgomeryLadder(u, scalar) {
aInRange("u", u, N_0, P);
aInRange("scalar", scalar, minScalar, maxScalar);
const k = scalar;
const x_1 = u;
let x_2 = N_1;
let z_2 = N_0;
let x_3 = u;
let z_3 = N_1;
let swap = N_0;
for (let t = montgomeryBits - 1n; t >= N_0; t--) {
const k_t = (k >> t) & N_1;
swap ^= k_t;
({ x_2, x_3 } = cswap(swap, x_2, x_3));
({ x_2: z_2, x_3: z_3 } = cswap(swap, z_2, z_3));
swap = k_t;
const A = x_2 + z_2;
const AA = modP(A * A);
const B = x_2 - z_2;
const BB = modP(B * B);
const E = AA - BB;
const C = x_3 + z_3;
const D = x_3 - z_3;
const DA = modP(D * A);
const CB = modP(C * B);
const dacb = DA + CB;
const da_cb = DA - CB;
x_3 = modP(dacb * dacb);
z_3 = modP(x_1 * modP(da_cb * da_cb));
x_2 = modP(AA * BB);
z_2 = modP(E * (AA + modP(a24 * E)));
}
({ x_2, x_3 } = cswap(swap, x_2, x_3));
({ x_2: z_2, x_3: z_3 } = cswap(swap, z_2, z_3));
const z2 = powPminus2(z_2); // `Fp.pow(x, P - N_2)` is much slower equivalent
return modP(x_2 * z2); // Return x_2 * (z_2^(p - 2))
}
const lengths = {
secretKey: fieldLen,
publicKey: fieldLen,
seed: fieldLen,
};
const randomSecretKey = async (seed) => {
if (seed === undefined) {
seed = await randomBytes_(fieldLen);
}
abytes(seed, lengths.seed, "seed");
return seed;
};
const utils = { randomSecretKey };
return Object.freeze({
keygen: createKeygen(randomSecretKey, getPublicKey),
getSharedSecret,
getPublicKey,
scalarMult,
scalarMultBase,
utils,
GuBytes: GuBytes.slice(),
lengths,
});
}