|
| 1 | +/** |
| 2 | + * This file is based on noble-curves (https://github.com/paulmillr/noble-curves). |
| 3 | + * |
| 4 | + * noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) |
| 5 | + * |
| 6 | + * The original file is located at: |
| 7 | + * https://github.com/paulmillr/noble-curves/blob/b9d49d2b41d550571a0c5be443ecb62109fa3373/src/abstract/montgomery.ts |
| 8 | + */ |
| 9 | + |
| 10 | +/** |
| 11 | + * Montgomery curve methods. It's not really whole montgomery curve, |
| 12 | + * just bunch of very specific methods for X25519 / X448 from |
| 13 | + * [RFC 7748](https://www.rfc-editor.org/rfc/rfc7748) |
| 14 | + * @module |
| 15 | + */ |
| 16 | +/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */ |
| 17 | +import { |
| 18 | + abytes, |
| 19 | + aInRange, |
| 20 | + bytesToNumberLE, |
| 21 | + copyBytes, |
| 22 | + type CryptoKeys, |
| 23 | + numberToBytesLE, |
| 24 | + randomBytesAsync, |
| 25 | + validateObject, |
| 26 | +} from "../utils/noble.ts"; |
| 27 | +import { createKeygen, type CurveLengths } from "./curve.ts"; |
| 28 | +import { mod } from "./modular.ts"; |
| 29 | + |
| 30 | +const _0n = BigInt(0); |
| 31 | +const _1n = BigInt(1); |
| 32 | +const _2n = BigInt(2); |
| 33 | + |
| 34 | +export type CurveType = { |
| 35 | + P: bigint; // finite field prime |
| 36 | + type: "x25519" | "x448"; |
| 37 | + adjustScalarBytes: (bytes: Uint8Array) => Uint8Array; |
| 38 | + powPminus2: (x: bigint) => bigint; |
| 39 | + randomBytes?: (bytesLength?: number) => Promise<Uint8Array>; |
| 40 | +}; |
| 41 | + |
| 42 | +export type MontgomeryECDH = { |
| 43 | + scalarMult: (scalar: Uint8Array, u: Uint8Array) => Uint8Array; |
| 44 | + scalarMultBase: (scalar: Uint8Array) => Uint8Array; |
| 45 | + getSharedSecret: ( |
| 46 | + secretKeyA: Uint8Array, |
| 47 | + publicKeyB: Uint8Array, |
| 48 | + ) => Uint8Array; |
| 49 | + getPublicKey: (secretKey: Uint8Array) => Uint8Array; |
| 50 | + utils: { |
| 51 | + randomSecretKey: (seed?: Uint8Array) => Promise<Uint8Array>; |
| 52 | + }; |
| 53 | + GuBytes: Uint8Array; |
| 54 | + lengths: CurveLengths; |
| 55 | + keygen: ( |
| 56 | + seed?: Uint8Array, |
| 57 | + ) => { secretKey: Uint8Array; publicKey: Uint8Array }; |
| 58 | +}; |
| 59 | + |
| 60 | +function validateOpts(curve: CurveType) { |
| 61 | + validateObject(curve, { |
| 62 | + adjustScalarBytes: "function", |
| 63 | + powPminus2: "function", |
| 64 | + }); |
| 65 | + return Object.freeze({ ...curve } as const); |
| 66 | +} |
| 67 | + |
| 68 | +export function montgomery(curveDef: CurveType): MontgomeryECDH { |
| 69 | + const CURVE = validateOpts(curveDef); |
| 70 | + const { P, type, adjustScalarBytes, powPminus2, randomBytes: rand } = CURVE; |
| 71 | + const is25519 = type === "x25519"; |
| 72 | + if (!is25519 && type !== "x448") throw new Error("invalid type"); |
| 73 | + const randomBytes_ = rand || randomBytesAsync; |
| 74 | + |
| 75 | + const montgomeryBits = is25519 ? 255 : 448; |
| 76 | + const fieldLen = is25519 ? 32 : 56; |
| 77 | + const Gu = is25519 ? BigInt(9) : BigInt(5); |
| 78 | + // RFC 7748 #5: |
| 79 | + // The constant a24 is (486662 - 2) / 4 = 121665 for curve25519/X25519 and |
| 80 | + // (156326 - 2) / 4 = 39081 for curve448/X448 |
| 81 | + // const a = is25519 ? 156326n : 486662n; |
| 82 | + const a24 = is25519 ? BigInt(121665) : BigInt(39081); |
| 83 | + // RFC: x25519 "the resulting integer is of the form 2^254 plus |
| 84 | + // eight times a value between 0 and 2^251 - 1 (inclusive)" |
| 85 | + // x448: "2^447 plus four times a value between 0 and 2^445 - 1 (inclusive)" |
| 86 | + const minScalar = is25519 ? _2n ** BigInt(254) : _2n ** BigInt(447); |
| 87 | + const maxAdded = is25519 |
| 88 | + ? BigInt(8) * _2n ** BigInt(251) - _1n |
| 89 | + : BigInt(4) * _2n ** BigInt(445) - _1n; |
| 90 | + const maxScalar = minScalar + maxAdded + _1n; // (inclusive) |
| 91 | + const modP = (n: bigint) => mod(n, P); |
| 92 | + const GuBytes = encodeU(Gu); |
| 93 | + function encodeU(u: bigint): Uint8Array { |
| 94 | + return numberToBytesLE(modP(u), fieldLen); |
| 95 | + } |
| 96 | + function decodeU(u: Uint8Array): bigint { |
| 97 | + const _u = copyBytes(abytes(u, fieldLen, "uCoordinate")); |
| 98 | + // RFC: When receiving such an array, implementations of X25519 |
| 99 | + // (but not X448) MUST mask the most significant bit in the final byte. |
| 100 | + if (is25519) _u[31] &= 127; // 0b0111_1111 |
| 101 | + // RFC: Implementations MUST accept non-canonical values and process them as |
| 102 | + // if they had been reduced modulo the field prime. The non-canonical |
| 103 | + // values are 2^255 - 19 through 2^255 - 1 for X25519 and 2^448 - 2^224 |
| 104 | + // - 1 through 2^448 - 1 for X448. |
| 105 | + return modP(bytesToNumberLE(_u)); |
| 106 | + } |
| 107 | + function decodeScalar(scalar: Uint8Array): bigint { |
| 108 | + return bytesToNumberLE( |
| 109 | + adjustScalarBytes(copyBytes(abytes(scalar, fieldLen, "scalar"))), |
| 110 | + ); |
| 111 | + } |
| 112 | + function scalarMult(scalar: Uint8Array, u: Uint8Array): Uint8Array { |
| 113 | + const pu = montgomeryLadder(decodeU(u), decodeScalar(scalar)); |
| 114 | + // Some public keys are useless, of low-order. Curve author doesn't think |
| 115 | + // it needs to be validated, but we do it nonetheless. |
| 116 | + // https://cr.yp.to/ecdh.html#validate |
| 117 | + if (pu === _0n) throw new Error("invalid private or public key received"); |
| 118 | + return encodeU(pu); |
| 119 | + } |
| 120 | + // Computes public key from private. By doing scalar multiplication of base point. |
| 121 | + function scalarMultBase(scalar: Uint8Array): Uint8Array { |
| 122 | + return scalarMult(scalar, GuBytes); |
| 123 | + } |
| 124 | + const getPublicKey = scalarMultBase; |
| 125 | + const getSharedSecret = scalarMult; |
| 126 | + |
| 127 | + // cswap from RFC7748 "example code" |
| 128 | + function cswap( |
| 129 | + swap: bigint, |
| 130 | + x_2: bigint, |
| 131 | + x_3: bigint, |
| 132 | + ): { x_2: bigint; x_3: bigint } { |
| 133 | + // dummy = mask(swap) AND (x_2 XOR x_3) |
| 134 | + // Where mask(swap) is the all-1 or all-0 word of the same length as x_2 |
| 135 | + // and x_3, computed, e.g., as mask(swap) = 0 - swap. |
| 136 | + const dummy = modP(swap * (x_2 - x_3)); |
| 137 | + x_2 = modP(x_2 - dummy); // x_2 = x_2 XOR dummy |
| 138 | + x_3 = modP(x_3 + dummy); // x_3 = x_3 XOR dummy |
| 139 | + return { x_2, x_3 }; |
| 140 | + } |
| 141 | + |
| 142 | + /** |
| 143 | + * Montgomery x-only multiplication ladder. |
| 144 | + * @param pointU u coordinate (x) on Montgomery Curve 25519 |
| 145 | + * @param scalar by which the point would be multiplied |
| 146 | + * @returns new Point on Montgomery curve |
| 147 | + */ |
| 148 | + function montgomeryLadder(u: bigint, scalar: bigint): bigint { |
| 149 | + aInRange("u", u, _0n, P); |
| 150 | + aInRange("scalar", scalar, minScalar, maxScalar); |
| 151 | + const k = scalar; |
| 152 | + const x_1 = u; |
| 153 | + let x_2 = _1n; |
| 154 | + let z_2 = _0n; |
| 155 | + let x_3 = u; |
| 156 | + let z_3 = _1n; |
| 157 | + let swap = _0n; |
| 158 | + for (let t = BigInt(montgomeryBits - 1); t >= _0n; t--) { |
| 159 | + const k_t = (k >> t) & _1n; |
| 160 | + swap ^= k_t; |
| 161 | + ({ x_2, x_3 } = cswap(swap, x_2, x_3)); |
| 162 | + ({ x_2: z_2, x_3: z_3 } = cswap(swap, z_2, z_3)); |
| 163 | + swap = k_t; |
| 164 | + |
| 165 | + const A = x_2 + z_2; |
| 166 | + const AA = modP(A * A); |
| 167 | + const B = x_2 - z_2; |
| 168 | + const BB = modP(B * B); |
| 169 | + const E = AA - BB; |
| 170 | + const C = x_3 + z_3; |
| 171 | + const D = x_3 - z_3; |
| 172 | + const DA = modP(D * A); |
| 173 | + const CB = modP(C * B); |
| 174 | + const dacb = DA + CB; |
| 175 | + const da_cb = DA - CB; |
| 176 | + x_3 = modP(dacb * dacb); |
| 177 | + z_3 = modP(x_1 * modP(da_cb * da_cb)); |
| 178 | + x_2 = modP(AA * BB); |
| 179 | + z_2 = modP(E * (AA + modP(a24 * E))); |
| 180 | + } |
| 181 | + ({ x_2, x_3 } = cswap(swap, x_2, x_3)); |
| 182 | + ({ x_2: z_2, x_3: z_3 } = cswap(swap, z_2, z_3)); |
| 183 | + const z2 = powPminus2(z_2); // `Fp.pow(x, P - _2n)` is much slower equivalent |
| 184 | + return modP(x_2 * z2); // Return x_2 * (z_2^(p - 2)) |
| 185 | + } |
| 186 | + const lengths = { |
| 187 | + secretKey: fieldLen, |
| 188 | + publicKey: fieldLen, |
| 189 | + seed: fieldLen, |
| 190 | + }; |
| 191 | + const randomSecretKey = async (seed?: Uint8Array) => { |
| 192 | + if (seed === undefined) { |
| 193 | + seed = await randomBytes_(fieldLen); |
| 194 | + } |
| 195 | + abytes(seed, lengths.seed, "seed"); |
| 196 | + return seed; |
| 197 | + }; |
| 198 | + const utils = { randomSecretKey }; |
| 199 | + |
| 200 | + return Object.freeze({ |
| 201 | + keygen: createKeygen(randomSecretKey, getPublicKey), |
| 202 | + getSharedSecret, |
| 203 | + getPublicKey, |
| 204 | + scalarMult, |
| 205 | + scalarMultBase, |
| 206 | + utils, |
| 207 | + GuBytes: GuBytes.slice(), |
| 208 | + lengths, |
| 209 | + }) satisfies CryptoKeys; |
| 210 | +} |
0 commit comments