@bsv/sdk 1.9.16 → 1.9.18

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.
@@ -6099,25 +6099,31 @@ toUTF8 = (arr: number[]): string => {
6099
6099
  }
6100
6100
  if (byte <= 127) {
6101
6101
  result += String.fromCharCode(byte);
6102
+ continue;
6102
6103
  }
6103
- else if (byte >= 192 && byte <= 223) {
6104
- const byte2 = arr[i + 1];
6105
- skip = 1;
6104
+ if (byte >= 192 && byte <= 223) {
6105
+ const avail = arr.length - (i + 1);
6106
+ const byte2 = avail >= 1 ? arr[i + 1] : 0;
6107
+ skip = Math.min(1, avail);
6106
6108
  const codePoint = ((byte & 31) << 6) | (byte2 & 63);
6107
6109
  result += String.fromCharCode(codePoint);
6110
+ continue;
6108
6111
  }
6109
- else if (byte >= 224 && byte <= 239) {
6110
- const byte2 = arr[i + 1];
6111
- const byte3 = arr[i + 2];
6112
- skip = 2;
6112
+ if (byte >= 224 && byte <= 239) {
6113
+ const avail = arr.length - (i + 1);
6114
+ const byte2 = avail >= 1 ? arr[i + 1] : 0;
6115
+ const byte3 = avail >= 2 ? arr[i + 2] : 0;
6116
+ skip = Math.min(2, avail);
6113
6117
  const codePoint = ((byte & 15) << 12) | ((byte2 & 63) << 6) | (byte3 & 63);
6114
6118
  result += String.fromCharCode(codePoint);
6119
+ continue;
6115
6120
  }
6116
- else if (byte >= 240 && byte <= 247) {
6117
- const byte2 = arr[i + 1];
6118
- const byte3 = arr[i + 2];
6119
- const byte4 = arr[i + 3];
6120
- skip = 3;
6121
+ if (byte >= 240 && byte <= 247) {
6122
+ const avail = arr.length - (i + 1);
6123
+ const byte2 = avail >= 1 ? arr[i + 1] : 0;
6124
+ const byte3 = avail >= 2 ? arr[i + 2] : 0;
6125
+ const byte4 = avail >= 3 ? arr[i + 3] : 0;
6126
+ skip = Math.min(3, avail);
6121
6127
  const codePoint = ((byte & 7) << 18) |
6122
6128
  ((byte2 & 63) << 12) |
6123
6129
  ((byte3 & 63) << 6) |
@@ -6125,6 +6131,7 @@ toUTF8 = (arr: number[]): string => {
6125
6131
  const surrogate1 = 55296 + ((codePoint - 65536) >> 10);
6126
6132
  const surrogate2 = 56320 + ((codePoint - 65536) & 1023);
6127
6133
  result += String.fromCharCode(surrogate1, surrogate2);
6134
+ continue;
6128
6135
  }
6129
6136
  }
6130
6137
  return result;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bsv/sdk",
3
- "version": "1.9.16",
3
+ "version": "1.9.18",
4
4
  "type": "module",
5
5
  "description": "BSV Blockchain Software Development Kit",
6
6
  "main": "dist/cjs/mod.js",
@@ -168,48 +168,42 @@ abstract class BaseHash {
168
168
  * @returns Returns an array denoting the padding.
169
169
  */
170
170
  private _pad (): number[] {
171
- //
172
- let len = this.pendingTotal
171
+ const len = this.pendingTotal
173
172
  const bytes = this._delta8
174
173
  const k = bytes - ((len + this.padLength) % bytes)
175
174
  const res = new Array(k + this.padLength)
176
175
  res[0] = 0x80
177
- let i
176
+ let i: number
178
177
  for (i = 1; i < k; i++) {
179
178
  res[i] = 0
180
179
  }
181
180
 
182
181
  // Append length
183
- len <<= 3
184
- let t
182
+ const lengthBytes = this.padLength
183
+ const maxBits = 1n << BigInt(lengthBytes * 8)
184
+ let totalBits = BigInt(len) * 8n
185
+
186
+ if (totalBits >= maxBits) {
187
+ throw new Error('Message too long for this hash function')
188
+ }
189
+
185
190
  if (this.endian === 'big') {
186
- for (t = 8; t < this.padLength; t++) {
187
- res[i++] = 0
191
+ const lenArray = new Array<number>(lengthBytes)
192
+
193
+ for (let b = lengthBytes - 1; b >= 0; b--) {
194
+ lenArray[b] = Number(totalBits & 0xffn)
195
+ totalBits >>= 8n
188
196
  }
189
197
 
190
- res[i++] = 0
191
- res[i++] = 0
192
- res[i++] = 0
193
- res[i++] = 0
194
- res[i++] = (len >>> 24) & 0xff
195
- res[i++] = (len >>> 16) & 0xff
196
- res[i++] = (len >>> 8) & 0xff
197
- res[i++] = len & 0xff
198
+ for (let b = 0; b < lengthBytes; b++) {
199
+ res[i++] = lenArray[b]
200
+ }
198
201
  } else {
199
- res[i++] = len & 0xff
200
- res[i++] = (len >>> 8) & 0xff
201
- res[i++] = (len >>> 16) & 0xff
202
- res[i++] = (len >>> 24) & 0xff
203
- res[i++] = 0
204
- res[i++] = 0
205
- res[i++] = 0
206
- res[i++] = 0
207
-
208
- for (t = 8; t < this.padLength; t++) {
209
- res[i++] = 0
202
+ for (let b = 0; b < lengthBytes; b++) {
203
+ res[i++] = Number(totalBits & 0xffn)
204
+ totalBits >>= 8n
210
205
  }
211
206
  }
212
-
213
207
  return res
214
208
  }
215
209
  }
@@ -101,6 +101,54 @@ describe('Hash', function () {
101
101
  )
102
102
  })
103
103
 
104
+ describe('BaseHash padding and endianness', () => {
105
+ it('encodes length in big-endian for SHA1', () => {
106
+ const sha1 = new (hash as any).SHA1()
107
+ ;(sha1 as any).pendingTotal = 12345
108
+ const pad = (sha1 as any)._pad() as number[]
109
+ const padLength = (sha1 as any).padLength as number
110
+ const lengthBytes = pad.slice(-padLength)
111
+
112
+ const totalBits = BigInt(12345) * 8n
113
+ const expected = new Array<number>(padLength)
114
+ let tmp = totalBits
115
+ for (let i = padLength - 1; i >= 0; i--) {
116
+ expected[i] = Number(tmp & 0xffn)
117
+ tmp >>= 8n
118
+ }
119
+
120
+ expect(lengthBytes).toEqual(expected)
121
+ })
122
+
123
+ it('encodes length in little-endian for RIPEMD160', () => {
124
+ const ripemd = new (hash as any).RIPEMD160()
125
+ ;(ripemd as any).pendingTotal = 12345
126
+ const pad = (ripemd as any)._pad() as number[]
127
+ const padLength = (ripemd as any).padLength as number
128
+ const lengthBytes = pad.slice(-padLength)
129
+
130
+ const totalBits = BigInt(12345) * 8n
131
+ const expected = new Array<number>(padLength)
132
+ let tmp = totalBits
133
+ for (let i = 0; i < padLength; i++) {
134
+ expected[i] = Number(tmp & 0xffn)
135
+ tmp >>= 8n
136
+ }
137
+
138
+ expect(lengthBytes).toEqual(expected)
139
+ })
140
+
141
+ it('throws when message length exceeds maximum encodable bits', () => {
142
+ const sha1 = new (hash as any).SHA1()
143
+ ;(sha1 as any).padLength = 1
144
+ ;(sha1 as any).pendingTotal = 40
145
+
146
+ expect(() => {
147
+ ;(sha1 as any)._pad()
148
+ }).toThrow(new Error('Message too long for this hash function'))
149
+ })
150
+ })
151
+
104
152
  describe('PBKDF2 vectors', () => {
105
153
  for (let i = 0; i < PBKDF2Vectors.length; i++) {
106
154
  const v = PBKDF2Vectors[i]
@@ -4,6 +4,7 @@ import {
4
4
  zero2,
5
5
  toHex,
6
6
  encode,
7
+ toUTF8,
7
8
  fromBase58,
8
9
  toBase58,
9
10
  fromBase58Check,
@@ -209,6 +210,48 @@ describe('utils', () => {
209
210
  })
210
211
  })
211
212
 
213
+ describe('toUTF8 bounds checks', () => {
214
+ const guarded = (arr: number[]): number[] => {
215
+ const target = arr.slice()
216
+ const handler: ProxyHandler<number[]> = {
217
+ get (t, prop, receiver) {
218
+ if (prop === 'length' || typeof prop !== 'string') {
219
+ return Reflect.get(t, prop as any, receiver)
220
+ }
221
+ const idx = Number(prop)
222
+ if (Number.isInteger(idx)) {
223
+ if (idx < 0 || idx >= t.length) {
224
+ throw new Error(`out-of-bounds read at index ${idx} (length ${t.length})`)
225
+ }
226
+ }
227
+ return Reflect.get(t, prop as any, receiver)
228
+ }
229
+ }
230
+ return new Proxy(target, handler) as unknown as number[]
231
+ }
232
+
233
+ it('does not access out-of-bounds on truncated 2-byte sequence', () => {
234
+ const input = guarded([0xC3])
235
+ expect(() => toUTF8(input)).not.toThrow()
236
+ })
237
+
238
+ it('does not access out-of-bounds on truncated 3-byte sequences', () => {
239
+ const input1 = guarded([0xE2])
240
+ const input2 = guarded([0xE2, 0x82])
241
+ expect(() => toUTF8(input1)).not.toThrow()
242
+ expect(() => toUTF8(input2)).not.toThrow()
243
+ })
244
+
245
+ it('does not access out-of-bounds on truncated 4-byte sequences', () => {
246
+ const input1 = guarded([0xF0])
247
+ const input2 = guarded([0xF0, 0x9F])
248
+ const input3 = guarded([0xF0, 0x9F, 0x98])
249
+ expect(() => toUTF8(input1)).not.toThrow()
250
+ expect(() => toUTF8(input2)).not.toThrow()
251
+ expect(() => toUTF8(input3)).not.toThrow()
252
+ })
253
+ })
254
+
212
255
  describe('toArray base64', () => {
213
256
  it('decodes empty string to empty array', () => {
214
257
  expect(toArray('', 'base64')).toEqual([])
@@ -237,7 +237,6 @@ export const toUTF8 = (arr: number[]): string => {
237
237
 
238
238
  for (let i = 0; i < arr.length; i++) {
239
239
  const byte = arr[i]
240
-
241
240
  // this byte is part of a multi-byte sequence, skip it
242
241
  // added to avoid modifying i within the loop which is considered unsafe.
243
242
  if (skip > 0) {
@@ -248,26 +247,41 @@ export const toUTF8 = (arr: number[]): string => {
248
247
  // 1-byte sequence (0xxxxxxx)
249
248
  if (byte <= 0x7f) {
250
249
  result += String.fromCharCode(byte)
251
- } else if (byte >= 0xc0 && byte <= 0xdf) {
252
- // 2-byte sequence (110xxxxx 10xxxxxx)
253
- const byte2 = arr[i + 1]
254
- skip = 1
250
+ continue
251
+ }
252
+
253
+ // 2-byte sequence (110xxxxx 10xxxxxx)
254
+ if (byte >= 0xc0 && byte <= 0xdf) {
255
+ const avail = arr.length - (i + 1)
256
+ const byte2 = avail >= 1 ? arr[i + 1] : 0
257
+ skip = Math.min(1, avail)
258
+
255
259
  const codePoint = ((byte & 0x1f) << 6) | (byte2 & 0x3f)
256
260
  result += String.fromCharCode(codePoint)
257
- } else if (byte >= 0xe0 && byte <= 0xef) {
258
- // 3-byte sequence (1110xxxx 10xxxxxx 10xxxxxx)
259
- const byte2 = arr[i + 1]
260
- const byte3 = arr[i + 2]
261
- skip = 2
261
+ continue
262
+ }
263
+
264
+ // 3-byte sequence (1110xxxx 10xxxxxx 10xxxxxx)
265
+ if (byte >= 0xe0 && byte <= 0xef) {
266
+ const avail = arr.length - (i + 1)
267
+ const byte2 = avail >= 1 ? arr[i + 1] : 0
268
+ const byte3 = avail >= 2 ? arr[i + 2] : 0
269
+ skip = Math.min(2, avail)
270
+
262
271
  const codePoint =
263
272
  ((byte & 0x0f) << 12) | ((byte2 & 0x3f) << 6) | (byte3 & 0x3f)
264
273
  result += String.fromCharCode(codePoint)
265
- } else if (byte >= 0xf0 && byte <= 0xf7) {
266
- // 4-byte sequence (11110xxx 10xxxxxx 10xxxxxx 10xxxxxx)
267
- const byte2 = arr[i + 1]
268
- const byte3 = arr[i + 2]
269
- const byte4 = arr[i + 3]
270
- skip = 3
274
+ continue
275
+ }
276
+
277
+ // 4-byte sequence (11110xxx 10xxxxxx 10xxxxxx 10xxxxxx)
278
+ if (byte >= 0xf0 && byte <= 0xf7) {
279
+ const avail = arr.length - (i + 1)
280
+ const byte2 = avail >= 1 ? arr[i + 1] : 0
281
+ const byte3 = avail >= 2 ? arr[i + 2] : 0
282
+ const byte4 = avail >= 3 ? arr[i + 3] : 0
283
+ skip = Math.min(3, avail)
284
+
271
285
  const codePoint =
272
286
  ((byte & 0x07) << 18) |
273
287
  ((byte2 & 0x3f) << 12) |
@@ -278,6 +292,7 @@ export const toUTF8 = (arr: number[]): string => {
278
292
  const surrogate1 = 0xd800 + ((codePoint - 0x10000) >> 10)
279
293
  const surrogate2 = 0xdc00 + ((codePoint - 0x10000) & 0x3ff)
280
294
  result += String.fromCharCode(surrogate1, surrogate2)
295
+ continue
281
296
  }
282
297
  }
283
298