@bsv/sdk 1.9.23 → 1.9.24

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bsv/sdk",
3
- "version": "1.9.23",
3
+ "version": "1.9.24",
4
4
  "type": "module",
5
5
  "description": "BSV Blockchain Software Development Kit",
6
6
  "main": "dist/cjs/mod.js",
@@ -282,13 +282,15 @@ export function toArray (
282
282
  return res
283
283
  }
284
284
 
285
- function htonl (w: number): number {
286
- const res =
287
- (w >>> 24) |
288
- ((w >>> 8) & 0xff00) |
289
- ((w << 8) & 0xff0000) |
290
- ((w & 0xff) << 24)
291
- return res >>> 0
285
+ /**
286
+ * @deprecated
287
+ * This function behaves differently from the standard C `htonl()`.
288
+ * It always performs an unconditional 32-bit byte swap.
289
+ * Use `swapBytes32()` for explicit byte swapping, or `realHtonl()` for
290
+ * standards-compliant host-to-network conversion.
291
+ */
292
+ export function htonl (w: number): number {
293
+ return swapBytes32(w)
292
294
  }
293
295
 
294
296
  function toHex32 (msg: number[], endian?: 'little' | 'big'): string {
@@ -1857,3 +1859,66 @@ export function pbkdf2 (
1857
1859
  const out = pbkdf2Fast(p, s, iterations, keylen)
1858
1860
  return Array.from(out)
1859
1861
  }
1862
+
1863
+ /**
1864
+ * Unconditionally swaps the byte order of a 32-bit unsigned integer.
1865
+ *
1866
+ * This function performs a strict 32-bit byte swap regardless of host
1867
+ * endianness. It is equivalent to the behavior commonly referred to as
1868
+ * `bswap32` in low-level libraries.
1869
+ *
1870
+ * This function is introduced as part of TOB-20 to provide a clearly-named
1871
+ * alternative to `htonl()`, which was previously implemented as an
1872
+ * unconditional byte swap and did not match the semantics of the traditional
1873
+ * C `htonl()` function.
1874
+ *
1875
+ * @param w - A 32-bit unsigned integer.
1876
+ * @returns The value with its byte order reversed.
1877
+ *
1878
+ * @example
1879
+ * swapBytes32(0x11223344) // → 0x44332211
1880
+ */
1881
+ export function swapBytes32 (w: number): number {
1882
+ const res =
1883
+ (w >>> 24) |
1884
+ ((w >>> 8) & 0xff00) |
1885
+ ((w << 8) & 0xff0000) |
1886
+ ((w & 0xff) << 24)
1887
+ return res >>> 0
1888
+ }
1889
+
1890
+ // Detect the host machine's endianness at runtime.
1891
+ //
1892
+ // This is used by `realHtonl()` to determine whether the value must be
1893
+ // byte-swapped or returned unchanged. JavaScript engines on common platforms
1894
+ // are almost always little-endian, but this check is included for correctness.
1895
+ const isLittleEndian = (() => {
1896
+ const b = new ArrayBuffer(4)
1897
+ const a = new Uint32Array(b)
1898
+ const c = new Uint8Array(b)
1899
+ a[0] = 0x01020304
1900
+ return c[0] === 0x04
1901
+ })()
1902
+
1903
+ /**
1904
+ * Converts a 32-bit unsigned integer from host byte order to network byte order.
1905
+ *
1906
+ * Unlike the legacy `htonl()` implementation (which always swapped bytes),
1907
+ * this function behaves like the traditional C `htonl()`:
1908
+ *
1909
+ * - On **little-endian** machines → performs a byte swap.
1910
+ * - On **big-endian** machines → returns the value unchanged.
1911
+ *
1912
+ * This function is provided to resolve TOB-20, which identified that the
1913
+ * previous `htonl()` implementation had a misleading name and did not match
1914
+ * platform-dependent semantics.
1915
+ *
1916
+ * @param w - A 32-bit unsigned integer.
1917
+ * @returns The value converted to network byte order.
1918
+ *
1919
+ * @example
1920
+ * realHtonl(0x11223344) // → 0x44332211 on little-endian systems
1921
+ */
1922
+ export function realHtonl (w: number): number {
1923
+ return isLittleEndian ? swapBytes32(w) : (w >>> 0)
1924
+ }
@@ -105,9 +105,9 @@ describe('Hash', function () {
105
105
  describe('BaseHash padding and endianness', () => {
106
106
  it('encodes length in big-endian for SHA1', () => {
107
107
  const sha1 = new (hash as any).SHA1()
108
- ;(sha1 as any).pendingTotal = 12345
109
- const pad = (sha1 as any)._pad() as number[]
110
- const padLength = (sha1 as any).padLength as number
108
+ ;(sha1).pendingTotal = 12345
109
+ const pad = (sha1)._pad() as number[]
110
+ const padLength = (sha1).padLength as number
111
111
  const lengthBytes = pad.slice(-padLength)
112
112
 
113
113
  const totalBits = BigInt(12345) * 8n
@@ -123,9 +123,9 @@ describe('Hash', function () {
123
123
 
124
124
  it('encodes length in little-endian for RIPEMD160', () => {
125
125
  const ripemd = new (hash as any).RIPEMD160()
126
- ;(ripemd as any).pendingTotal = 12345
127
- const pad = (ripemd as any)._pad() as number[]
128
- const padLength = (ripemd as any).padLength as number
126
+ ;(ripemd).pendingTotal = 12345
127
+ const pad = (ripemd)._pad() as number[]
128
+ const padLength = (ripemd).padLength as number
129
129
  const lengthBytes = pad.slice(-padLength)
130
130
 
131
131
  const totalBits = BigInt(12345) * 8n
@@ -141,11 +141,11 @@ describe('Hash', function () {
141
141
 
142
142
  it('throws when message length exceeds maximum encodable bits', () => {
143
143
  const sha1 = new (hash as any).SHA1()
144
- ;(sha1 as any).padLength = 1
145
- ;(sha1 as any).pendingTotal = 40
144
+ ;(sha1).padLength = 1
145
+ ;(sha1).pendingTotal = 40
146
146
 
147
147
  expect(() => {
148
- ;(sha1 as any)._pad()
148
+ ;(sha1)._pad()
149
149
  }).toThrow(new Error('Message too long for this hash function'))
150
150
  })
151
151
  })
@@ -180,7 +180,6 @@ describe('Hash', function () {
180
180
  })
181
181
 
182
182
  describe('Hash strict length validation (TOB-21)', () => {
183
-
184
183
  it('throws when pendingTotal is not a safe integer', () => {
185
184
  const h = new SHA1()
186
185
 
@@ -201,4 +200,57 @@ describe('Hash', function () {
201
200
  }).toThrow('Message too long for this hash function')
202
201
  })
203
202
  })
203
+
204
+ describe('TOB-20 byte-order helper functions', () => {
205
+ const { htonl, swapBytes32, realHtonl } = hash
206
+
207
+ it('swapBytes32 performs a strict 32-bit byte swap', () => {
208
+ expect(swapBytes32(0x11223344)).toBe(0x44332211)
209
+ expect(swapBytes32(0xaabbccdd)).toBe(0xddccbbaa)
210
+ expect(swapBytes32(0x00000000)).toBe(0x00000000)
211
+ expect(swapBytes32(0xffffffff)).toBe(0xffffffff)
212
+ })
213
+
214
+ it('swapBytes32 always returns an unsigned 32-bit integer', () => {
215
+ expect(swapBytes32(-1)).toBe(0xffffffff) // wraps to unsigned
216
+ expect(swapBytes32(0x80000000)).toBe(0x00000080) // MSB becomes LSB
217
+ })
218
+
219
+ it('htonl is now an alias for swapBytes32 (deprecated)', () => {
220
+ expect(htonl(0x11223344)).toBe(swapBytes32(0x11223344))
221
+ expect(htonl(0xaabbccdd)).toBe(swapBytes32(0xaabbccdd))
222
+ })
223
+
224
+ it('realHtonl matches swapBytes32 on little-endian systems', () => {
225
+ // All JS engines used for Node/Jest are little-endian
226
+ expect(realHtonl(0x11223344)).toBe(0x44332211)
227
+ expect(realHtonl(0xaabbccdd)).toBe(0xddccbbaa)
228
+ })
229
+
230
+ it('realHtonl preserves value when system is big-endian (forced simulation)', () => {
231
+ // We simulate the big-endian branch of realHtonl by calling
232
+ // the fallback path directly.
233
+ const forceBigEndianRealHtonl = (w: number) => (w >>> 0)
234
+
235
+ expect(forceBigEndianRealHtonl(0x11223344)).toBe(0x11223344)
236
+ expect(forceBigEndianRealHtonl(0xaabbccdd)).toBe(0xaabbccdd)
237
+ expect(forceBigEndianRealHtonl(0xffffffff)).toBe(0xffffffff >>> 0)
238
+ })
239
+
240
+ it('htonl, swapBytes32, realHtonl never throw for any 32-bit input', () => {
241
+ const inputs = [
242
+ 0, 1, -1,
243
+ 0x7fffffff,
244
+ 0x80000000,
245
+ 0xffffffff,
246
+ 0x12345678
247
+ ]
248
+
249
+ for (const n of inputs) {
250
+ expect(() => htonl(n)).not.toThrow()
251
+ expect(() => swapBytes32(n)).not.toThrow()
252
+ expect(() => realHtonl(n)).not.toThrow()
253
+ }
254
+ })
255
+ })
204
256
  })