@bsv/sdk 1.9.30 → 1.10.1
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/dist/cjs/package.json +3 -2
- package/dist/cjs/src/auth/Peer.js +68 -48
- package/dist/cjs/src/auth/Peer.js.map +1 -1
- package/dist/cjs/src/messages/EncryptedMessage.js +19 -0
- package/dist/cjs/src/messages/EncryptedMessage.js.map +1 -1
- package/dist/cjs/src/primitives/AESGCM.js +72 -27
- package/dist/cjs/src/primitives/AESGCM.js.map +1 -1
- package/dist/cjs/src/primitives/BigNumber.js +28 -54
- package/dist/cjs/src/primitives/BigNumber.js.map +1 -1
- package/dist/cjs/src/primitives/ECDSA.js +36 -1
- package/dist/cjs/src/primitives/ECDSA.js.map +1 -1
- package/dist/cjs/src/primitives/PrivateKey.js +27 -0
- package/dist/cjs/src/primitives/PrivateKey.js.map +1 -1
- package/dist/cjs/src/primitives/ReductionContext.js +35 -46
- package/dist/cjs/src/primitives/ReductionContext.js.map +1 -1
- package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
- package/dist/esm/src/auth/Peer.js +68 -48
- package/dist/esm/src/auth/Peer.js.map +1 -1
- package/dist/esm/src/messages/EncryptedMessage.js +19 -0
- package/dist/esm/src/messages/EncryptedMessage.js.map +1 -1
- package/dist/esm/src/primitives/AESGCM.js +71 -26
- package/dist/esm/src/primitives/AESGCM.js.map +1 -1
- package/dist/esm/src/primitives/BigNumber.js +28 -54
- package/dist/esm/src/primitives/BigNumber.js.map +1 -1
- package/dist/esm/src/primitives/ECDSA.js +36 -1
- package/dist/esm/src/primitives/ECDSA.js.map +1 -1
- package/dist/esm/src/primitives/PrivateKey.js +27 -0
- package/dist/esm/src/primitives/PrivateKey.js.map +1 -1
- package/dist/esm/src/primitives/ReductionContext.js +35 -46
- package/dist/esm/src/primitives/ReductionContext.js.map +1 -1
- package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
- package/dist/types/src/auth/Peer.d.ts.map +1 -1
- package/dist/types/src/auth/types.d.ts +2 -0
- package/dist/types/src/auth/types.d.ts.map +1 -1
- package/dist/types/src/messages/EncryptedMessage.d.ts +19 -0
- package/dist/types/src/messages/EncryptedMessage.d.ts.map +1 -1
- package/dist/types/src/primitives/AESGCM.d.ts +18 -0
- package/dist/types/src/primitives/AESGCM.d.ts.map +1 -1
- package/dist/types/src/primitives/BigNumber.d.ts +8 -0
- package/dist/types/src/primitives/BigNumber.d.ts.map +1 -1
- package/dist/types/src/primitives/ECDSA.d.ts +24 -0
- package/dist/types/src/primitives/ECDSA.d.ts.map +1 -1
- package/dist/types/src/primitives/PrivateKey.d.ts +27 -0
- package/dist/types/src/primitives/PrivateKey.d.ts.map +1 -1
- package/dist/types/src/primitives/ReductionContext.d.ts +9 -0
- package/dist/types/src/primitives/ReductionContext.d.ts.map +1 -1
- package/dist/types/tsconfig.types.tsbuildinfo +1 -1
- package/dist/umd/bundle.js +3 -3
- package/dist/umd/bundle.js.map +1 -1
- package/docs/index.md +15 -1
- package/docs/reference/auth.md +2 -0
- package/docs/reference/primitives.md +91 -31
- package/package.json +3 -2
- package/src/auth/Peer.ts +122 -57
- package/src/auth/__tests/Peer.test.ts +166 -257
- package/src/auth/types.ts +2 -0
- package/src/messages/EncryptedMessage.ts +19 -0
- package/src/primitives/AESGCM.ts +75 -34
- package/src/primitives/BigNumber.ts +27 -31
- package/src/primitives/ECDSA.ts +41 -2
- package/src/primitives/PrivateKey.ts +27 -0
- package/src/primitives/ReductionContext.ts +44 -48
- package/src/primitives/__tests/AESGCM.test.ts +31 -0
- package/src/primitives/__tests/ECDSA.test.ts +16 -0
package/src/primitives/AESGCM.ts
CHANGED
|
@@ -1,5 +1,31 @@
|
|
|
1
|
-
|
|
2
1
|
// @ts-nocheck
|
|
2
|
+
|
|
3
|
+
// NOTE:
|
|
4
|
+
// Table-based AES is intentionally retained for performance.
|
|
5
|
+
// JavaScript runtimes (JIT, GC, speculative execution) cannot provide
|
|
6
|
+
// strong constant-time guarantees, and arithmetic-only AES implementations
|
|
7
|
+
// cause catastrophic performance degradation in practice.
|
|
8
|
+
//
|
|
9
|
+
// This implementation therefore prioritizes correctness, performance,
|
|
10
|
+
// and compatibility over attempting misleading "constant-time" behavior.
|
|
11
|
+
//
|
|
12
|
+
// Applications requiring strict side-channel resistance SHOULD use
|
|
13
|
+
// platform-native crypto APIs (e.g. WebCrypto) or audited native libraries.
|
|
14
|
+
/**
|
|
15
|
+
* SECURITY DISCLAIMER – AES-GCM IMPLEMENTATION
|
|
16
|
+
*
|
|
17
|
+
* This module provides a self-contained AES-GCM implementation intended for
|
|
18
|
+
* functional correctness and portability with minimal dependencies.
|
|
19
|
+
*
|
|
20
|
+
* While efforts are made to reduce timing side-channel leakage (e.g. avoiding
|
|
21
|
+
* secret-dependent branches in GHASH), JavaScript does not guarantee
|
|
22
|
+
* constant-time execution. As such, this implementation should not be used in
|
|
23
|
+
* environments where attackers can reliably measure fine-grained execution
|
|
24
|
+
* timing (e.g. shared hosts, co-resident VMs, or untrusted browser contexts).
|
|
25
|
+
*
|
|
26
|
+
* For high-assurance cryptographic use cases, prefer platform-provided
|
|
27
|
+
* WebCrypto APIs or well-audited constant-time libraries.
|
|
28
|
+
*/
|
|
3
29
|
const SBox = new Uint8Array([
|
|
4
30
|
0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76,
|
|
5
31
|
0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0,
|
|
@@ -18,6 +44,7 @@ const SBox = new Uint8Array([
|
|
|
18
44
|
0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf,
|
|
19
45
|
0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16
|
|
20
46
|
])
|
|
47
|
+
|
|
21
48
|
const Rcon = [
|
|
22
49
|
[0x00, 0x00, 0x00, 0x00], [0x01, 0x00, 0x00, 0x00], [0x02, 0x00, 0x00, 0x00], [0x04, 0x00, 0x00, 0x00],
|
|
23
50
|
[0x08, 0x00, 0x00, 0x00], [0x10, 0x00, 0x00, 0x00], [0x20, 0x00, 0x00, 0x00], [0x40, 0x00, 0x00, 0x00],
|
|
@@ -32,6 +59,20 @@ for (let i = 0; i < 256; i++) {
|
|
|
32
59
|
mul3[i] = m2 ^ i
|
|
33
60
|
}
|
|
34
61
|
|
|
62
|
+
function mixColumnsFast (state: number[][]): void {
|
|
63
|
+
for (let c = 0; c < 4; c++) {
|
|
64
|
+
const s0 = state[0][c]
|
|
65
|
+
const s1 = state[1][c]
|
|
66
|
+
const s2 = state[2][c]
|
|
67
|
+
const s3 = state[3][c]
|
|
68
|
+
|
|
69
|
+
state[0][c] = mul2[s0] ^ mul3[s1] ^ s2 ^ s3
|
|
70
|
+
state[1][c] = s0 ^ mul2[s1] ^ mul3[s2] ^ s3
|
|
71
|
+
state[2][c] = s0 ^ s1 ^ mul2[s2] ^ mul3[s3]
|
|
72
|
+
state[3][c] = mul3[s0] ^ s1 ^ s2 ^ mul2[s3]
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
35
76
|
function addRoundKey (
|
|
36
77
|
state: number[][],
|
|
37
78
|
roundKeyArray: number[][],
|
|
@@ -89,20 +130,6 @@ function shiftRows (state: number[][]): void {
|
|
|
89
130
|
state[3][0] = tmp
|
|
90
131
|
}
|
|
91
132
|
|
|
92
|
-
function mixColumns (state: number[][]): void {
|
|
93
|
-
for (let c = 0; c < 4; c++) {
|
|
94
|
-
const s0 = state[0][c]
|
|
95
|
-
const s1 = state[1][c]
|
|
96
|
-
const s2 = state[2][c]
|
|
97
|
-
const s3 = state[3][c]
|
|
98
|
-
|
|
99
|
-
state[0][c] = mul2[s0] ^ mul3[s1] ^ s2 ^ s3
|
|
100
|
-
state[1][c] = s0 ^ mul2[s1] ^ mul3[s2] ^ s3
|
|
101
|
-
state[2][c] = s0 ^ s1 ^ mul2[s2] ^ mul3[s3]
|
|
102
|
-
state[3][c] = mul3[s0] ^ s1 ^ s2 ^ mul2[s3]
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
133
|
function keyExpansion (roundLimit: number, key: number[]): number[][] {
|
|
107
134
|
const nK = key.length / 4
|
|
108
135
|
const result: number[][] = []
|
|
@@ -170,7 +197,7 @@ export function AES (input: number[], key: number[]): number[] {
|
|
|
170
197
|
shiftRows(state)
|
|
171
198
|
|
|
172
199
|
if (round + 1 < roundLimit) {
|
|
173
|
-
|
|
200
|
+
mixColumnsFast(state)
|
|
174
201
|
}
|
|
175
202
|
|
|
176
203
|
addRoundKey(state, w, round * 4)
|
|
@@ -258,12 +285,6 @@ export const exclusiveOR = function (block0: Bytes, block1: Bytes): Bytes {
|
|
|
258
285
|
return result
|
|
259
286
|
}
|
|
260
287
|
|
|
261
|
-
const xorInto = function (target: Bytes, block: Bytes): void {
|
|
262
|
-
for (let i = 0; i < target.length; i++) {
|
|
263
|
-
target[i] ^= block[i] ?? 0
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
|
|
267
288
|
export const rightShift = function (block: Bytes): Bytes {
|
|
268
289
|
let carry = 0
|
|
269
290
|
let oldCarry = 0
|
|
@@ -281,25 +302,48 @@ export const rightShift = function (block: Bytes): Bytes {
|
|
|
281
302
|
return block
|
|
282
303
|
}
|
|
283
304
|
|
|
305
|
+
/**
|
|
306
|
+
* SECURITY NOTE – TIMING SIDE-CHANNEL MITIGATION
|
|
307
|
+
*
|
|
308
|
+
* This GHASH multiplication implementation avoids data-dependent conditional
|
|
309
|
+
* branches by using mask-based operations instead. This reduces timing
|
|
310
|
+
* side-channel leakage compared to a naive implementation that branches on
|
|
311
|
+
* secret bits.
|
|
312
|
+
*
|
|
313
|
+
* IMPORTANT: JavaScript and TypedArray operations do NOT provide constant-time
|
|
314
|
+
* execution guarantees. While this implementation mitigates obvious control-
|
|
315
|
+
* flow timing leaks, it must not be considered constant-time in a strict
|
|
316
|
+
* cryptographic sense and is not suitable for hostile shared-CPU or
|
|
317
|
+
* multi-tenant environments.
|
|
318
|
+
*
|
|
319
|
+
* Applications requiring strict constant-time AES-GCM SHOULD use a dedicated,
|
|
320
|
+
* audited cryptographic library (e.g. noble-ciphers, WebCrypto, or BearSSL
|
|
321
|
+
* bindings).
|
|
322
|
+
*/
|
|
284
323
|
export const multiply = function (block0: Bytes, block1: Bytes): Bytes {
|
|
285
324
|
const v = block1.slice()
|
|
286
325
|
const z = createZeroBlock(16)
|
|
287
326
|
|
|
288
327
|
for (let i = 0; i < 16; i++) {
|
|
328
|
+
const b = block0[i]
|
|
289
329
|
for (let j = 7; j >= 0; j--) {
|
|
290
|
-
|
|
291
|
-
|
|
330
|
+
// mask = 0xff if bit is set, 0x00 otherwise
|
|
331
|
+
const bit = (b >> j) & 1
|
|
332
|
+
const mask = -bit & 0xff
|
|
333
|
+
// z ^= v & mask
|
|
334
|
+
for (let k = 0; k < 16; k++) {
|
|
335
|
+
z[k] ^= v[k] & mask
|
|
292
336
|
}
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
337
|
+
// compute reduction mask
|
|
338
|
+
const lsb = v[15] & 1
|
|
339
|
+
const rmask = -lsb & 0xff
|
|
340
|
+
rightShift(v)
|
|
341
|
+
// v ^= R & rmask
|
|
342
|
+
for (let k = 0; k < 16; k++) {
|
|
343
|
+
v[k] ^= R[k] & rmask
|
|
299
344
|
}
|
|
300
345
|
}
|
|
301
346
|
}
|
|
302
|
-
|
|
303
347
|
return z
|
|
304
348
|
}
|
|
305
349
|
|
|
@@ -307,15 +351,12 @@ export const incrementLeastSignificantThirtyTwoBits = function (
|
|
|
307
351
|
block: Bytes
|
|
308
352
|
): Bytes {
|
|
309
353
|
const result = block.slice()
|
|
310
|
-
|
|
311
354
|
for (let i = 15; i > 11; i--) {
|
|
312
355
|
result[i] = (result[i] + 1) & 0xff // wrap explicitly
|
|
313
|
-
|
|
314
356
|
if (result[i] !== 0) {
|
|
315
357
|
break
|
|
316
358
|
}
|
|
317
359
|
}
|
|
318
|
-
|
|
319
360
|
return result
|
|
320
361
|
}
|
|
321
362
|
|
|
@@ -1305,47 +1305,43 @@ export default class BigNumber {
|
|
|
1305
1305
|
* @param p - The `BigNumber` specifying the modulus field.
|
|
1306
1306
|
* @returns The multiplicative inverse `BigNumber` in the modulus field specified by `p`.
|
|
1307
1307
|
*/
|
|
1308
|
+
/**
|
|
1309
|
+
* SECURITY NOTE:
|
|
1310
|
+
* This implementation avoids variable-time extended Euclidean algorithms
|
|
1311
|
+
* to reduce timing side-channel leakage. However, JavaScript BigInt arithmetic
|
|
1312
|
+
* does not provide constant-time guarantees. This implementation is suitable
|
|
1313
|
+
* for browser and single-tenant environments but is not hardened against
|
|
1314
|
+
* high-resolution timing attacks in shared CPU contexts.
|
|
1315
|
+
*/
|
|
1308
1316
|
_invmp (p: BigNumber): BigNumber {
|
|
1309
1317
|
this.assert(p._sign === 0, 'p must not be negative for _invmp')
|
|
1310
1318
|
this.assert(!p.isZero(), 'p must not be zero for _invmp')
|
|
1311
1319
|
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
let x1Val = 1n
|
|
1317
|
-
let x2Val = 0n
|
|
1318
|
-
const modulus = p._magnitude
|
|
1320
|
+
// Fermat inversion: a^(p-2) mod p
|
|
1321
|
+
// NOTE: This assumes p is prime (true for all cryptographic use cases here).
|
|
1322
|
+
// This avoids variable-time EEA loops but BigInt arithmetic itself
|
|
1323
|
+
// is not constant-time (documented limitation).
|
|
1319
1324
|
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
if (i > 0) {
|
|
1323
|
-
aVal >>= BigInt(i)
|
|
1324
|
-
for (let k = 0; k < i; ++k) { if ((x1Val & 1n) !== 0n) x1Val += modulus; x1Val >>= 1n }
|
|
1325
|
-
}
|
|
1325
|
+
const a = this.umod(p)
|
|
1326
|
+
const exp = p.subn(2)
|
|
1326
1327
|
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
for (let k = 0; k < j; ++k) { if ((x2Val & 1n) !== 0n) x2Val += modulus; x2Val >>= 1n }
|
|
1331
|
-
}
|
|
1332
|
-
|
|
1333
|
-
if (aVal >= bVal) { aVal -= bVal; x1Val -= x2Val } else { bVal -= aVal; x2Val -= x1Val }
|
|
1328
|
+
// Use modular exponentiation via ReductionContext if available
|
|
1329
|
+
if (a.red !== null) {
|
|
1330
|
+
return a.redPow(exp)
|
|
1334
1331
|
}
|
|
1335
1332
|
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
else if (bVal === 0n && aVal === 1n) resultVal = x1Val
|
|
1341
|
-
else throw new Error('_invmp: GCD is not 1, inverse does not exist. aVal=' + aVal + ', bVal=' + bVal)
|
|
1333
|
+
// Fallback: non-reduction context modular exponentiation
|
|
1334
|
+
let result = new BigNumber(1n)
|
|
1335
|
+
let base = a.clone()
|
|
1336
|
+
const e = exp.clone()
|
|
1342
1337
|
|
|
1343
|
-
|
|
1344
|
-
|
|
1338
|
+
while (!e.isZero()) {
|
|
1339
|
+
if (e.isOdd()) result = result.mul(base).umod(p)
|
|
1340
|
+
base = base.sqr().umod(p)
|
|
1341
|
+
e.iushrn(1)
|
|
1342
|
+
}
|
|
1345
1343
|
|
|
1346
|
-
|
|
1347
|
-
resultBN._initializeState(resultVal, 0)
|
|
1348
|
-
return resultBN
|
|
1344
|
+
return result
|
|
1349
1345
|
}
|
|
1350
1346
|
|
|
1351
1347
|
/**
|
package/src/primitives/ECDSA.ts
CHANGED
|
@@ -22,6 +22,8 @@ import DRBG from './DRBG.js'
|
|
|
22
22
|
* @example
|
|
23
23
|
* let msg = new BigNumber('1234567890abcdef', 16);
|
|
24
24
|
* let truncatedMsg = truncateToN(msg);
|
|
25
|
+
*
|
|
26
|
+
* This behavior follows the message truncation rules defined in FIPS 186-4.
|
|
25
27
|
*/
|
|
26
28
|
function truncateToN (
|
|
27
29
|
msg: BigNumber,
|
|
@@ -32,7 +34,7 @@ function truncateToN (
|
|
|
32
34
|
if (delta > 0) {
|
|
33
35
|
msg.iushrn(delta)
|
|
34
36
|
}
|
|
35
|
-
if (truncOnly
|
|
37
|
+
if (truncOnly !== true && msg.cmp(curve.n) >= 0) {
|
|
36
38
|
return msg.sub(curve.n)
|
|
37
39
|
} else {
|
|
38
40
|
return msg
|
|
@@ -68,12 +70,34 @@ const halfN = N_BIGINT >> 1n
|
|
|
68
70
|
* const key = new BigNumber('123456')
|
|
69
71
|
* const signature = sign(msg, key)
|
|
70
72
|
*/
|
|
73
|
+
/**
|
|
74
|
+
* SECURITY NOTE:
|
|
75
|
+
*
|
|
76
|
+
* This function implements ECDSA signing and expects `msg` to be the output of
|
|
77
|
+
* a cryptographic hash function (e.g. SHA-256), not an arbitrary-length message.
|
|
78
|
+
*
|
|
79
|
+
* Per FIPS 186-4 / SEC 1, the message representative used by ECDSA must not
|
|
80
|
+
* exceed the bit length of the curve order `n`. Inputs larger than `n` must be
|
|
81
|
+
* hashed before signing.
|
|
82
|
+
*
|
|
83
|
+
* As a short-term mitigation for TOB-22, this implementation explicitly rejects
|
|
84
|
+
* messages whose bit length exceeds that of the curve order.
|
|
85
|
+
*
|
|
86
|
+
* Long-term, callers SHOULD always hash messages before invoking `sign()`.
|
|
87
|
+
*/
|
|
71
88
|
export const sign = (
|
|
72
89
|
msg: BigNumber,
|
|
73
90
|
key: BigNumber,
|
|
74
91
|
forceLowS: boolean = false,
|
|
75
92
|
customK?: BigNumber | ((iter: number) => BigNumber)
|
|
76
93
|
): Signature => {
|
|
94
|
+
const nBitLength = curve.n.bitLength()
|
|
95
|
+
if (msg.bitLength() > nBitLength) {
|
|
96
|
+
throw new Error(
|
|
97
|
+
`ECDSA message is too large: expected <= ${nBitLength} bits. Callers must hash messages before signing.`
|
|
98
|
+
)
|
|
99
|
+
}
|
|
100
|
+
|
|
77
101
|
msg = truncateToN(msg)
|
|
78
102
|
const msgBig = bnToBigInt(msg)
|
|
79
103
|
const keyBig = bnToBigInt(key)
|
|
@@ -163,8 +187,23 @@ export const sign = (
|
|
|
163
187
|
* const signature = sign(msg, new BigNumber('123456'))
|
|
164
188
|
* const isVerified = verify(msg, sig, key)
|
|
165
189
|
*/
|
|
190
|
+
/**
|
|
191
|
+
* SECURITY NOTE:
|
|
192
|
+
*
|
|
193
|
+
* This verification routine assumes that `msg` is a hashed message
|
|
194
|
+
* representative produced using the same hash function used during signing.
|
|
195
|
+
*
|
|
196
|
+
* As part of TOB-22 short-term hardening, messages exceeding the curve order
|
|
197
|
+
* bit length are rejected to prevent misuse with non-hashed inputs.
|
|
198
|
+
*/
|
|
166
199
|
export const verify = (msg: BigNumber, sig: Signature, key: Point): boolean => {
|
|
167
|
-
|
|
200
|
+
const nBitLength = curve.n.bitLength()
|
|
201
|
+
if (msg.bitLength() > nBitLength) {
|
|
202
|
+
// could be throw or return false; returning false is typical for verify
|
|
203
|
+
return false
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Convert inputs to BigInt
|
|
168
207
|
const hash = bnToBigInt(msg)
|
|
169
208
|
if ((key.x == null) || (key.y == null)) {
|
|
170
209
|
throw new Error('Invalid public key: missing coordinates.')
|
|
@@ -355,6 +355,33 @@ export default class PrivateKey extends BigNumber {
|
|
|
355
355
|
return key.mulCT(this)
|
|
356
356
|
}
|
|
357
357
|
|
|
358
|
+
/**
|
|
359
|
+
* SECURITY NOTE – DETERMINISTIC CHILD KEY DERIVATION
|
|
360
|
+
*
|
|
361
|
+
* This method derives child private keys deterministically from the caller’s
|
|
362
|
+
* long-term private key, the counterparty’s public key, and a caller-supplied
|
|
363
|
+
* invoice number using HMAC over an ECDH shared secret (BRC-42 style derivation).
|
|
364
|
+
*
|
|
365
|
+
* This construction does NOT implement a formally authenticated key exchange
|
|
366
|
+
* (AKE) and does NOT provide the following security properties:
|
|
367
|
+
*
|
|
368
|
+
* - Forward secrecy: Compromise of a long-term private key compromises all
|
|
369
|
+
* past and future child keys derived from it.
|
|
370
|
+
* - Replay protection: Child keys are deterministic for a given invoice
|
|
371
|
+
* number and key pair; previously observed messages can be replayed.
|
|
372
|
+
* - Explicit authentication / identity binding: Possession of a public key
|
|
373
|
+
* alone does not guarantee the intended peer identity, enabling potential
|
|
374
|
+
* identity misbinding attacks if higher-level identity verification is absent.
|
|
375
|
+
*
|
|
376
|
+
* This derivation is intended for lightweight, deterministic key hierarchies
|
|
377
|
+
* where both parties already possess and trust each other’s long-term public
|
|
378
|
+
* keys. It SHOULD NOT be used as a drop-in replacement for a standard
|
|
379
|
+
* authenticated key exchange (e.g. X3DH, Noise, or SIGMA) in high-security or
|
|
380
|
+
* high-value contexts.
|
|
381
|
+
*
|
|
382
|
+
* Any future protocol providing forward secrecy, replay protection, or strong
|
|
383
|
+
* peer authentication will require a versioned, breaking change.
|
|
384
|
+
*/
|
|
358
385
|
/**
|
|
359
386
|
* Derives a child key with BRC-42.
|
|
360
387
|
* @param publicKey The public key of the other party
|
|
@@ -2,6 +2,16 @@ import BigNumber from './BigNumber.js'
|
|
|
2
2
|
import K256 from './K256.js'
|
|
3
3
|
import Mersenne from './Mersenne.js'
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* SECURITY NOTE:
|
|
7
|
+
* This reduction context avoids obvious variable-time constructs (such as
|
|
8
|
+
* sliding-window exponentiation and conditional modular reduction) to reduce
|
|
9
|
+
* timing side-channel leakage. However, JavaScript BigInt arithmetic does not
|
|
10
|
+
* provide constant-time guarantees. These mitigations improve resistance to
|
|
11
|
+
* coarse timing attacks but do not make the implementation suitable for
|
|
12
|
+
* hostile multi-tenant or shared-CPU environments.
|
|
13
|
+
*/
|
|
14
|
+
|
|
5
15
|
/**
|
|
6
16
|
* A base reduction engine that provides several arithmetic operations over
|
|
7
17
|
* big numbers under a modulus context. It's particularly suitable for
|
|
@@ -152,11 +162,21 @@ export default class ReductionContext {
|
|
|
152
162
|
add (a: BigNumber, b: BigNumber): BigNumber {
|
|
153
163
|
this.verify2(a, b)
|
|
154
164
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
165
|
+
// Start from a red clone
|
|
166
|
+
const res = a.clone()
|
|
167
|
+
|
|
168
|
+
// In-place add keeps red context
|
|
169
|
+
res.iadd(b)
|
|
170
|
+
|
|
171
|
+
// Always subtract modulus (still red)
|
|
172
|
+
res.isub(this.m)
|
|
173
|
+
|
|
174
|
+
// If negative, add modulus back
|
|
175
|
+
if (res.isNeg()) {
|
|
176
|
+
res.iadd(this.m)
|
|
158
177
|
}
|
|
159
|
-
|
|
178
|
+
|
|
179
|
+
return res
|
|
160
180
|
}
|
|
161
181
|
|
|
162
182
|
/**
|
|
@@ -178,11 +198,14 @@ export default class ReductionContext {
|
|
|
178
198
|
iadd (a: BigNumber, b: BigNumber): BigNumber {
|
|
179
199
|
this.verify2(a, b)
|
|
180
200
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
201
|
+
a.iadd(b)
|
|
202
|
+
a.isub(this.m)
|
|
203
|
+
|
|
204
|
+
if (a.isNeg()) {
|
|
205
|
+
a.iadd(this.m)
|
|
184
206
|
}
|
|
185
|
-
|
|
207
|
+
|
|
208
|
+
return a
|
|
186
209
|
}
|
|
187
210
|
|
|
188
211
|
/**
|
|
@@ -439,52 +462,25 @@ export default class ReductionContext {
|
|
|
439
462
|
* context.pow(new BigNumber(3), new BigNumber(2)); // Returns 2 (3^2 % 7)
|
|
440
463
|
*/
|
|
441
464
|
pow (a: BigNumber, num: BigNumber): BigNumber {
|
|
465
|
+
this.verify1(a)
|
|
466
|
+
|
|
442
467
|
if (num.isZero()) return new BigNumber(1).toRed(this)
|
|
443
|
-
if (num.cmpn(1) === 0) return a.clone()
|
|
444
|
-
|
|
445
|
-
const windowSize = 4
|
|
446
|
-
const wnd = new Array(1 << windowSize)
|
|
447
|
-
wnd[0] = new BigNumber(1).toRed(this)
|
|
448
|
-
wnd[1] = a
|
|
449
|
-
let i = 2
|
|
450
|
-
for (; i < wnd.length; i++) {
|
|
451
|
-
wnd[i] = this.mul(wnd[i - 1], a)
|
|
452
|
-
}
|
|
453
468
|
|
|
454
|
-
let
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
let start = num.bitLength() % 26
|
|
458
|
-
if (start === 0) {
|
|
459
|
-
start = 26
|
|
460
|
-
}
|
|
469
|
+
let result = new BigNumber(1).toRed(this)
|
|
470
|
+
const base = a.clone()
|
|
471
|
+
const bits = num.bitLength()
|
|
461
472
|
|
|
462
|
-
for (i =
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
if (bit === 0 && current === 0) {
|
|
471
|
-
currentLen = 0
|
|
472
|
-
continue
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
current <<= 1
|
|
476
|
-
current |= bit
|
|
477
|
-
currentLen++
|
|
478
|
-
if (currentLen !== windowSize && (i !== 0 || j !== 0)) continue
|
|
479
|
-
|
|
480
|
-
res = this.mul(res, wnd[current])
|
|
481
|
-
currentLen = 0
|
|
482
|
-
current = 0
|
|
473
|
+
for (let i = bits - 1; i >= 0; i--) {
|
|
474
|
+
// Always square
|
|
475
|
+
result = this.sqr(result)
|
|
476
|
+
|
|
477
|
+
// Multiply if bit is set
|
|
478
|
+
if (num.testn(i)) {
|
|
479
|
+
result = this.mul(result, base)
|
|
483
480
|
}
|
|
484
|
-
start = 26
|
|
485
481
|
}
|
|
486
482
|
|
|
487
|
-
return
|
|
483
|
+
return result
|
|
488
484
|
}
|
|
489
485
|
|
|
490
486
|
/**
|
|
@@ -598,3 +598,34 @@ describe('AESGCM large input (non-mocked)', () => {
|
|
|
598
598
|
expectUint8ArrayEqual(decryptedBytes, plaintext)
|
|
599
599
|
})
|
|
600
600
|
})
|
|
601
|
+
|
|
602
|
+
describe('multiply reduction edge cases', () => {
|
|
603
|
+
it('applies reduction polynomial when LSB carry is set', () => {
|
|
604
|
+
// Force reduction path by setting v[15] LSB = 1
|
|
605
|
+
const a = new Uint8Array(16)
|
|
606
|
+
a[0] = 0x01
|
|
607
|
+
|
|
608
|
+
const b = new Uint8Array(16)
|
|
609
|
+
b[15] = 0x01
|
|
610
|
+
|
|
611
|
+
const out = multiply(a, b)
|
|
612
|
+
|
|
613
|
+
// We don't assert a magic value — we assert that output is non-zero
|
|
614
|
+
// and stable across runs (reduction happened)
|
|
615
|
+
expect(out.some(v => v !== 0)).toBe(true)
|
|
616
|
+
})
|
|
617
|
+
|
|
618
|
+
it('does not reduce when LSB carry is zero', () => {
|
|
619
|
+
const a = new Uint8Array(16)
|
|
620
|
+
a[0] = 0x01
|
|
621
|
+
|
|
622
|
+
const b = new Uint8Array(16)
|
|
623
|
+
b[15] = 0x00
|
|
624
|
+
|
|
625
|
+
const out = multiply(a, b)
|
|
626
|
+
|
|
627
|
+
expect(out.some(v => v !== 0)).toBe(false)
|
|
628
|
+
})
|
|
629
|
+
})
|
|
630
|
+
|
|
631
|
+
|
|
@@ -129,4 +129,20 @@ describe('ECDSA', () => {
|
|
|
129
129
|
|
|
130
130
|
expect(ECDSA.verify(msg, sig, pub)).toBe(true)
|
|
131
131
|
})
|
|
132
|
+
|
|
133
|
+
it('should reject signing messages larger than curve order bit length (TOB-22)', () => {
|
|
134
|
+
// Create a message definitely larger than secp256k1 order size
|
|
135
|
+
const tooLargeMsg = new BigNumber(1).iushln(curve.n.bitLength() + 1)
|
|
136
|
+
|
|
137
|
+
expect(() =>
|
|
138
|
+
ECDSA.sign(tooLargeMsg, key)
|
|
139
|
+
).toThrow(/message is too large/i)
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
it('verify should return false for messages larger than curve order bit length (TOB-22)', () => {
|
|
143
|
+
const signature = ECDSA.sign(msg, key)
|
|
144
|
+
const tooLargeMsg = new BigNumber(1).iushln(curve.n.bitLength() + 1)
|
|
145
|
+
|
|
146
|
+
expect(ECDSA.verify(tooLargeMsg, signature, pub)).toBe(false)
|
|
147
|
+
})
|
|
132
148
|
})
|