Skip to content

Commit 15d4703

Browse files
authored
common: add utilities for curves. (#602)
1 parent d81e885 commit 15d4703

File tree

5 files changed

+311
-0
lines changed

5 files changed

+311
-0
lines changed

packages/common/mod.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,3 +51,7 @@ export {
5151

5252
export { hmac } from "./src/hash/hmac.ts";
5353
export { sha256, sha384, sha512 } from "./src/hash/sha2.ts";
54+
55+
export { type DST_SCALAR } from "./src/curve/hash-to-curve.ts";
56+
export { mod, pow2 } from "./src/curve/modular.ts";
57+
export { montgomery, type MontgomeryECDH } from "./src/curve/montgomery.ts";

packages/common/src/curve/curve.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
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/curve.ts
8+
*/
9+
10+
/**
11+
* Methods for elliptic curve multiplication by scalars.
12+
* Contains wNAF, pippenger.
13+
* @module
14+
*/
15+
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
16+
import type { Signer } from "../utils/noble.ts";
17+
18+
// import { Field, FpInvertBatch, type IField, validateField } from "./modular.ts";
19+
20+
export interface CurveLengths {
21+
secretKey?: number;
22+
publicKey?: number;
23+
publicKeyUncompressed?: number;
24+
publicKeyHasPrefix?: boolean;
25+
signature?: number;
26+
seed?: number;
27+
}
28+
29+
type KeygenFn = (
30+
seed?: Uint8Array,
31+
isCompressed?: boolean,
32+
) => { secretKey: Uint8Array; publicKey: Uint8Array };
33+
export function createKeygen(
34+
// deno-lint-ignore ban-types
35+
randomSecretKey: Function,
36+
getPublicKey: Signer["getPublicKey"],
37+
): KeygenFn {
38+
return function keygen(seed?: Uint8Array) {
39+
const secretKey = randomSecretKey(seed);
40+
return { secretKey, publicKey: getPublicKey(secretKey) };
41+
};
42+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
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/hash-to-curve.ts
8+
*/
9+
10+
/**
11+
* hash-to-curve from RFC 9380.
12+
* Hashes arbitrary-length byte strings to a list of one or more elements of a finite field F.
13+
* https://www.rfc-editor.org/rfc/rfc9380
14+
* @module
15+
*/
16+
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
17+
import { asciiToBytes } from "../utils/noble.ts";
18+
19+
export const DST_SCALAR: Uint8Array = asciiToBytes("HashToScalar-");
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
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/modular.ts
8+
*/
9+
10+
/**
11+
* Utils for modular division and fields.
12+
* Field over 11 is a finite (Galois) field is integer number operations `mod 11`.
13+
* There is no division: it is replaced by modular multiplicative inverse.
14+
* @module
15+
*/
16+
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
17+
18+
// Numbers aren't used in x25519 / x448 builds
19+
// prettier-ignore
20+
const _0n = /* @__PURE__ */ BigInt(0);
21+
22+
// Calculates a modulo b
23+
export function mod(a: bigint, b: bigint): bigint {
24+
const result = a % b;
25+
return result >= _0n ? result : b + result;
26+
}
27+
28+
/** Does `x^(2^power)` mod p. `pow2(30, 4)` == `30^(2^4)` */
29+
export function pow2(x: bigint, power: bigint, modulo: bigint): bigint {
30+
let res = x;
31+
while (power-- > _0n) {
32+
res *= res;
33+
res %= modulo;
34+
}
35+
return res;
36+
}
Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
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

Comments
 (0)