@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.
- package/dist/cjs/package.json +1 -1
- package/dist/cjs/src/primitives/Hash.js +17 -24
- package/dist/cjs/src/primitives/Hash.js.map +1 -1
- package/dist/cjs/src/primitives/utils.js +22 -15
- package/dist/cjs/src/primitives/utils.js.map +1 -1
- package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
- package/dist/esm/src/primitives/Hash.js +17 -24
- package/dist/esm/src/primitives/Hash.js.map +1 -1
- package/dist/esm/src/primitives/utils.js +22 -15
- package/dist/esm/src/primitives/utils.js.map +1 -1
- package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
- package/dist/types/src/primitives/Hash.d.ts.map +1 -1
- package/dist/types/src/primitives/utils.d.ts.map +1 -1
- package/dist/types/tsconfig.types.tsbuildinfo +1 -1
- package/dist/umd/bundle.js +1 -1
- package/dist/umd/bundle.js.map +1 -1
- package/docs/reference/primitives.md +19 -12
- package/package.json +1 -1
- package/src/primitives/Hash.ts +21 -27
- package/src/primitives/__tests/Hash.test.ts +48 -0
- package/src/primitives/__tests/utils.test.ts +43 -0
- package/src/primitives/utils.ts +31 -16
|
@@ -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
|
-
|
|
6104
|
-
const
|
|
6105
|
-
|
|
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
|
-
|
|
6110
|
-
const
|
|
6111
|
-
const
|
|
6112
|
-
|
|
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
|
-
|
|
6117
|
-
const
|
|
6118
|
-
const
|
|
6119
|
-
const
|
|
6120
|
-
|
|
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
package/src/primitives/Hash.ts
CHANGED
|
@@ -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
|
-
|
|
184
|
-
|
|
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
|
-
|
|
187
|
-
|
|
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
|
-
|
|
191
|
-
|
|
192
|
-
|
|
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
|
-
|
|
200
|
-
|
|
201
|
-
|
|
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([])
|
package/src/primitives/utils.ts
CHANGED
|
@@ -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
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
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
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
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
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
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
|
|