UNPKG

@hpke/common

Version:

A Hybrid Public Key Encryption (HPKE) internal-use common module for @hpke family modules.

160 lines (159 loc) 6.45 kB
/** * 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, }); }