@noble/curves 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +99 -15
- package/lib/edwards.d.ts +108 -0
- package/lib/edwards.js +554 -0
- package/lib/esm/edwards.js +550 -0
- package/lib/esm/group.js +107 -0
- package/lib/esm/modular.js +10 -1
- package/lib/esm/montgomery.js +189 -0
- package/lib/esm/utils.js +62 -5
- package/lib/esm/{shortw.js → weierstrass.js} +79 -158
- package/lib/group.d.ts +33 -0
- package/lib/group.js +111 -0
- package/lib/modular.d.ts +2 -1
- package/lib/modular.js +13 -3
- package/lib/montgomery.d.ts +20 -0
- package/lib/montgomery.js +191 -0
- package/lib/utils.d.ts +26 -2
- package/lib/utils.js +71 -7
- package/lib/{shortw.d.ts → weierstrass.d.ts} +22 -35
- package/lib/{shortw.js → weierstrass.js} +78 -157
- package/package.json +23 -11
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import * as mod from './modular.js';
|
|
2
|
+
import { ensureBytes, numberToBytesLE, bytesToNumberLE,
|
|
3
|
+
// nLength,
|
|
4
|
+
} from './utils.js';
|
|
5
|
+
const _0n = BigInt(0);
|
|
6
|
+
const _1n = BigInt(1);
|
|
7
|
+
function validateOpts(curve) {
|
|
8
|
+
for (const i of ['a24']) {
|
|
9
|
+
if (typeof curve[i] !== 'bigint')
|
|
10
|
+
throw new Error(`Invalid curve param ${i}=${curve[i]} (${typeof curve[i]})`);
|
|
11
|
+
}
|
|
12
|
+
for (const i of ['montgomeryBits', 'nByteLength']) {
|
|
13
|
+
if (curve[i] === undefined)
|
|
14
|
+
continue; // Optional
|
|
15
|
+
if (!Number.isSafeInteger(curve[i]))
|
|
16
|
+
throw new Error(`Invalid curve param ${i}=${curve[i]} (${typeof curve[i]})`);
|
|
17
|
+
}
|
|
18
|
+
for (const fn of ['adjustScalarBytes', 'domain', 'powPminus2']) {
|
|
19
|
+
if (curve[fn] === undefined)
|
|
20
|
+
continue; // Optional
|
|
21
|
+
if (typeof curve[fn] !== 'function')
|
|
22
|
+
throw new Error(`Invalid ${fn} function`);
|
|
23
|
+
}
|
|
24
|
+
for (const i of ['Gu']) {
|
|
25
|
+
if (curve[i] === undefined)
|
|
26
|
+
continue; // Optional
|
|
27
|
+
if (typeof curve[i] !== 'string')
|
|
28
|
+
throw new Error(`Invalid curve param ${i}=${curve[i]} (${typeof curve[i]})`);
|
|
29
|
+
}
|
|
30
|
+
// Set defaults
|
|
31
|
+
// ...nLength(curve.n, curve.nBitLength),
|
|
32
|
+
return Object.freeze({ ...curve });
|
|
33
|
+
}
|
|
34
|
+
// NOTE: not really montgomery curve, just bunch of very specific methods for X25519/X448 (RFC 7748, https://www.rfc-editor.org/rfc/rfc7748)
|
|
35
|
+
// Uses only one coordinate instead of two
|
|
36
|
+
export function montgomery(curveDef) {
|
|
37
|
+
const CURVE = validateOpts(curveDef);
|
|
38
|
+
const { P } = CURVE;
|
|
39
|
+
const modP = (a) => mod.mod(a, P);
|
|
40
|
+
const montgomeryBits = CURVE.montgomeryBits;
|
|
41
|
+
const montgomeryBytes = Math.ceil(montgomeryBits / 8);
|
|
42
|
+
const fieldLen = CURVE.nByteLength;
|
|
43
|
+
const adjustScalarBytes = CURVE.adjustScalarBytes || ((bytes) => bytes);
|
|
44
|
+
const powPminus2 = CURVE.powPminus2 || ((x) => mod.pow(x, P - BigInt(2), P));
|
|
45
|
+
/**
|
|
46
|
+
* Checks for num to be in range:
|
|
47
|
+
* For strict == true: `0 < num < max`.
|
|
48
|
+
* For strict == false: `0 <= num < max`.
|
|
49
|
+
* Converts non-float safe numbers to bigints.
|
|
50
|
+
*/
|
|
51
|
+
function normalizeScalar(num, max, strict = true) {
|
|
52
|
+
if (!max)
|
|
53
|
+
throw new TypeError('Specify max value');
|
|
54
|
+
if (typeof num === 'number' && Number.isSafeInteger(num))
|
|
55
|
+
num = BigInt(num);
|
|
56
|
+
if (typeof num === 'bigint' && num < max) {
|
|
57
|
+
if (strict) {
|
|
58
|
+
if (_0n < num)
|
|
59
|
+
return num;
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
if (_0n <= num)
|
|
63
|
+
return num;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
throw new TypeError('Expected valid scalar: 0 < scalar < max');
|
|
67
|
+
}
|
|
68
|
+
// cswap from RFC7748
|
|
69
|
+
// NOTE: cswap is not from RFC7748!
|
|
70
|
+
/*
|
|
71
|
+
cswap(swap, x_2, x_3):
|
|
72
|
+
dummy = mask(swap) AND (x_2 XOR x_3)
|
|
73
|
+
x_2 = x_2 XOR dummy
|
|
74
|
+
x_3 = x_3 XOR dummy
|
|
75
|
+
Return (x_2, x_3)
|
|
76
|
+
Where mask(swap) is the all-1 or all-0 word of the same length as x_2
|
|
77
|
+
and x_3, computed, e.g., as mask(swap) = 0 - swap.
|
|
78
|
+
*/
|
|
79
|
+
function cswap(swap, x_2, x_3) {
|
|
80
|
+
const dummy = modP(swap * (x_2 - x_3));
|
|
81
|
+
x_2 = modP(x_2 - dummy);
|
|
82
|
+
x_3 = modP(x_3 + dummy);
|
|
83
|
+
return [x_2, x_3];
|
|
84
|
+
}
|
|
85
|
+
// x25519 from 4
|
|
86
|
+
/**
|
|
87
|
+
*
|
|
88
|
+
* @param pointU u coordinate (x) on Montgomery Curve 25519
|
|
89
|
+
* @param scalar by which the point would be multiplied
|
|
90
|
+
* @returns new Point on Montgomery curve
|
|
91
|
+
*/
|
|
92
|
+
function montgomeryLadder(pointU, scalar) {
|
|
93
|
+
const { P } = CURVE;
|
|
94
|
+
const u = normalizeScalar(pointU, P);
|
|
95
|
+
// Section 5: Implementations MUST accept non-canonical values and process them as
|
|
96
|
+
// if they had been reduced modulo the field prime.
|
|
97
|
+
const k = normalizeScalar(scalar, P);
|
|
98
|
+
// The constant a24 is (486662 - 2) / 4 = 121665 for curve25519/X25519
|
|
99
|
+
const a24 = CURVE.a24;
|
|
100
|
+
const x_1 = u;
|
|
101
|
+
let x_2 = _1n;
|
|
102
|
+
let z_2 = _0n;
|
|
103
|
+
let x_3 = u;
|
|
104
|
+
let z_3 = _1n;
|
|
105
|
+
let swap = _0n;
|
|
106
|
+
let sw;
|
|
107
|
+
for (let t = BigInt(montgomeryBits - 1); t >= _0n; t--) {
|
|
108
|
+
const k_t = (k >> t) & _1n;
|
|
109
|
+
swap ^= k_t;
|
|
110
|
+
sw = cswap(swap, x_2, x_3);
|
|
111
|
+
x_2 = sw[0];
|
|
112
|
+
x_3 = sw[1];
|
|
113
|
+
sw = cswap(swap, z_2, z_3);
|
|
114
|
+
z_2 = sw[0];
|
|
115
|
+
z_3 = sw[1];
|
|
116
|
+
swap = k_t;
|
|
117
|
+
const A = x_2 + z_2;
|
|
118
|
+
const AA = modP(A * A);
|
|
119
|
+
const B = x_2 - z_2;
|
|
120
|
+
const BB = modP(B * B);
|
|
121
|
+
const E = AA - BB;
|
|
122
|
+
const C = x_3 + z_3;
|
|
123
|
+
const D = x_3 - z_3;
|
|
124
|
+
const DA = modP(D * A);
|
|
125
|
+
const CB = modP(C * B);
|
|
126
|
+
const dacb = DA + CB;
|
|
127
|
+
const da_cb = DA - CB;
|
|
128
|
+
x_3 = modP(dacb * dacb);
|
|
129
|
+
z_3 = modP(x_1 * modP(da_cb * da_cb));
|
|
130
|
+
x_2 = modP(AA * BB);
|
|
131
|
+
z_2 = modP(E * (AA + modP(a24 * E)));
|
|
132
|
+
}
|
|
133
|
+
// (x_2, x_3) = cswap(swap, x_2, x_3)
|
|
134
|
+
sw = cswap(swap, x_2, x_3);
|
|
135
|
+
x_2 = sw[0];
|
|
136
|
+
x_3 = sw[1];
|
|
137
|
+
// (z_2, z_3) = cswap(swap, z_2, z_3)
|
|
138
|
+
sw = cswap(swap, z_2, z_3);
|
|
139
|
+
z_2 = sw[0];
|
|
140
|
+
z_3 = sw[1];
|
|
141
|
+
// z_2^(p - 2)
|
|
142
|
+
const z2 = powPminus2(z_2);
|
|
143
|
+
// Return x_2 * (z_2^(p - 2))
|
|
144
|
+
return modP(x_2 * z2);
|
|
145
|
+
}
|
|
146
|
+
function encodeUCoordinate(u) {
|
|
147
|
+
return numberToBytesLE(modP(u), montgomeryBytes);
|
|
148
|
+
}
|
|
149
|
+
function decodeUCoordinate(uEnc) {
|
|
150
|
+
const u = ensureBytes(uEnc, montgomeryBytes);
|
|
151
|
+
// Section 5: When receiving such an array, implementations of X25519
|
|
152
|
+
// MUST mask the most significant bit in the final byte.
|
|
153
|
+
// This is very ugly way, but it works because fieldLen-1 is outside of bounds for X448, so this becomes NOOP
|
|
154
|
+
// fieldLen - scalaryBytes = 1 for X448 and = 0 for X25519
|
|
155
|
+
u[fieldLen - 1] &= 127; // 0b0111_1111
|
|
156
|
+
return bytesToNumberLE(u);
|
|
157
|
+
}
|
|
158
|
+
function decodeScalar(n) {
|
|
159
|
+
const bytes = ensureBytes(n);
|
|
160
|
+
if (bytes.length !== montgomeryBytes && bytes.length !== fieldLen)
|
|
161
|
+
throw new Error(`Expected ${montgomeryBytes} or ${fieldLen} bytes, got ${bytes.length}`);
|
|
162
|
+
return bytesToNumberLE(adjustScalarBytes(bytes));
|
|
163
|
+
}
|
|
164
|
+
// Multiply point u by scalar
|
|
165
|
+
function scalarMult(u, scalar) {
|
|
166
|
+
const pointU = decodeUCoordinate(u);
|
|
167
|
+
const _scalar = decodeScalar(scalar);
|
|
168
|
+
const pu = montgomeryLadder(pointU, _scalar);
|
|
169
|
+
// The result was not contributory
|
|
170
|
+
// https://cr.yp.to/ecdh.html#validate
|
|
171
|
+
if (pu === _0n)
|
|
172
|
+
throw new Error('Invalid private or public key received');
|
|
173
|
+
return encodeUCoordinate(pu);
|
|
174
|
+
}
|
|
175
|
+
// Multiply base point by scalar
|
|
176
|
+
function scalarMultBase(scalar) {
|
|
177
|
+
return scalarMult(CURVE.Gu, scalar);
|
|
178
|
+
}
|
|
179
|
+
return {
|
|
180
|
+
// NOTE: we can get 'y' coordinate from 'u', but Point.fromHex also wants 'x' coordinate oddity flag, and we cannot get 'x' without knowing 'v'
|
|
181
|
+
// Need to add generic conversion between twisted edwards and complimentary curve for JubJub
|
|
182
|
+
scalarMult,
|
|
183
|
+
scalarMultBase,
|
|
184
|
+
// NOTE: these function work on complimentary montgomery curve
|
|
185
|
+
// getSharedSecret: (privateKey: Hex, publicKey: Hex) => scalarMult(publicKey, privateKey),
|
|
186
|
+
getPublicKey: (privateKey) => scalarMultBase(privateKey),
|
|
187
|
+
Gu: CURVE.Gu,
|
|
188
|
+
};
|
|
189
|
+
}
|
package/lib/esm/utils.js
CHANGED
|
@@ -1,6 +1,19 @@
|
|
|
1
1
|
/*! @noble/curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
export function validateOpts(curve) {
|
|
3
|
+
for (const i of ['P', 'n', 'h', 'Gx', 'Gy']) {
|
|
4
|
+
if (typeof curve[i] !== 'bigint')
|
|
5
|
+
throw new Error(`Invalid curve param ${i}=${curve[i]} (${typeof curve[i]})`);
|
|
6
|
+
}
|
|
7
|
+
for (const i of ['nBitLength', 'nByteLength']) {
|
|
8
|
+
if (curve[i] === undefined)
|
|
9
|
+
continue; // Optional
|
|
10
|
+
if (!Number.isSafeInteger(curve[i]))
|
|
11
|
+
throw new Error(`Invalid curve param ${i}=${curve[i]} (${typeof curve[i]})`);
|
|
12
|
+
}
|
|
13
|
+
// Set defaults
|
|
14
|
+
return Object.freeze({ ...nLength(curve.n, curve.nBitLength), ...curve });
|
|
15
|
+
}
|
|
16
|
+
import * as mod from './modular.js';
|
|
4
17
|
const hexes = Array.from({ length: 256 }, (v, i) => i.toString(16).padStart(2, '0'));
|
|
5
18
|
export function bytesToHex(uint8a) {
|
|
6
19
|
if (!(uint8a instanceof Uint8Array))
|
|
@@ -42,13 +55,23 @@ export function hexToBytes(hex) {
|
|
|
42
55
|
return array;
|
|
43
56
|
}
|
|
44
57
|
// Big Endian
|
|
45
|
-
export function
|
|
58
|
+
export function bytesToNumberBE(bytes) {
|
|
46
59
|
return hexToNumber(bytesToHex(bytes));
|
|
47
60
|
}
|
|
48
|
-
export function
|
|
61
|
+
export function bytesToNumberLE(uint8a) {
|
|
62
|
+
if (!(uint8a instanceof Uint8Array))
|
|
63
|
+
throw new Error('Expected Uint8Array');
|
|
64
|
+
return BigInt('0x' + bytesToHex(Uint8Array.from(uint8a).reverse()));
|
|
65
|
+
}
|
|
66
|
+
export const numberToBytesBE = (n, len) => hexToBytes(n.toString(16).padStart(len * 2, '0'));
|
|
67
|
+
export const numberToBytesLE = (n, len) => numberToBytesBE(n, len).reverse();
|
|
68
|
+
export function ensureBytes(hex, expectedLength) {
|
|
49
69
|
// Uint8Array.from() instead of hash.slice() because node.js Buffer
|
|
50
70
|
// is instance of Uint8Array, and its slice() creates **mutable** copy
|
|
51
|
-
|
|
71
|
+
const bytes = hex instanceof Uint8Array ? Uint8Array.from(hex) : hexToBytes(hex);
|
|
72
|
+
if (typeof expectedLength === 'number' && bytes.length !== expectedLength)
|
|
73
|
+
throw new Error(`Expected ${expectedLength} bytes`);
|
|
74
|
+
return bytes;
|
|
52
75
|
}
|
|
53
76
|
// Copies several Uint8Arrays into one.
|
|
54
77
|
export function concatBytes(...arrays) {
|
|
@@ -65,3 +88,37 @@ export function concatBytes(...arrays) {
|
|
|
65
88
|
}
|
|
66
89
|
return result;
|
|
67
90
|
}
|
|
91
|
+
// CURVE.n lengths
|
|
92
|
+
export function nLength(n, nBitLength) {
|
|
93
|
+
// Bit size, byte size of CURVE.n
|
|
94
|
+
const _nBitLength = nBitLength !== undefined ? nBitLength : n.toString(2).length;
|
|
95
|
+
const nByteLength = Math.ceil(_nBitLength / 8);
|
|
96
|
+
return { nBitLength: _nBitLength, nByteLength };
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Can take (n+8) or more bytes of uniform input e.g. from CSPRNG or KDF
|
|
100
|
+
* and convert them into private scalar, with the modulo bias being neglible.
|
|
101
|
+
* As per FIPS 186 B.4.1.
|
|
102
|
+
* https://research.kudelskisecurity.com/2020/07/28/the-definitive-guide-to-modulo-bias-and-how-to-avoid-it/
|
|
103
|
+
* @param hash hash output from sha512, or a similar function
|
|
104
|
+
* @returns valid private scalar
|
|
105
|
+
*/
|
|
106
|
+
const _1n = BigInt(1);
|
|
107
|
+
export function hashToPrivateScalar(hash, CURVE_ORDER, isLE = false) {
|
|
108
|
+
hash = ensureBytes(hash);
|
|
109
|
+
const orderLen = nLength(CURVE_ORDER).nByteLength;
|
|
110
|
+
const minLen = orderLen + 8;
|
|
111
|
+
if (orderLen < 16 || hash.length < minLen || hash.length > 1024)
|
|
112
|
+
throw new Error('Expected valid bytes of private key as per FIPS 186');
|
|
113
|
+
const num = isLE ? bytesToNumberLE(hash) : bytesToNumberBE(hash);
|
|
114
|
+
return mod.mod(num, CURVE_ORDER - _1n) + _1n;
|
|
115
|
+
}
|
|
116
|
+
export function equalBytes(b1, b2) {
|
|
117
|
+
// We don't care about timing attacks here
|
|
118
|
+
if (b1.length !== b2.length)
|
|
119
|
+
return false;
|
|
120
|
+
for (let i = 0; i < b1.length; i++)
|
|
121
|
+
if (b1[i] !== b2[i])
|
|
122
|
+
return false;
|
|
123
|
+
return true;
|
|
124
|
+
}
|