@bsv/sdk 1.9.22 → 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/dist/cjs/package.json +1 -1
- package/dist/cjs/src/primitives/Hash.js +70 -5
- package/dist/cjs/src/primitives/Hash.js.map +1 -1
- package/dist/cjs/src/primitives/JacobianPoint.js +15 -1
- package/dist/cjs/src/primitives/JacobianPoint.js.map +1 -1
- package/dist/cjs/src/primitives/Point.js +5 -0
- package/dist/cjs/src/primitives/Point.js.map +1 -1
- package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
- package/dist/esm/src/primitives/Hash.js +68 -6
- package/dist/esm/src/primitives/Hash.js.map +1 -1
- package/dist/esm/src/primitives/JacobianPoint.js +15 -1
- package/dist/esm/src/primitives/JacobianPoint.js.map +1 -1
- package/dist/esm/src/primitives/Point.js +5 -0
- package/dist/esm/src/primitives/Point.js.map +1 -1
- package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
- package/dist/types/src/primitives/Hash.d.ts +47 -0
- package/dist/types/src/primitives/Hash.d.ts.map +1 -1
- package/dist/types/src/primitives/JacobianPoint.d.ts.map +1 -1
- package/dist/types/src/primitives/Point.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/package.json +1 -1
- package/src/primitives/Hash.ts +72 -7
- package/src/primitives/JacobianPoint.ts +18 -1
- package/src/primitives/Point.ts +4 -0
- package/src/primitives/__tests/Curve.unit.test.ts +63 -0
- package/src/primitives/__tests/Hash.test.ts +62 -10
- package/src/primitives/__tests/utils.test.ts +17 -0
package/package.json
CHANGED
package/src/primitives/Hash.ts
CHANGED
|
@@ -282,13 +282,15 @@ export function toArray (
|
|
|
282
282
|
return res
|
|
283
283
|
}
|
|
284
284
|
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
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
|
+
}
|
|
@@ -73,6 +73,14 @@ export default class JacobianPoint extends BasePoint {
|
|
|
73
73
|
}
|
|
74
74
|
|
|
75
75
|
this.zOne = this.z === this.curve.one
|
|
76
|
+
|
|
77
|
+
// --- Canonicalize point at infinity ---
|
|
78
|
+
if (this.isInfinity()) {
|
|
79
|
+
this.x = this.curve.one
|
|
80
|
+
this.y = this.curve.one
|
|
81
|
+
this.z = new BigNumber(0).toRed(this.curve.red)
|
|
82
|
+
this.zOne = false
|
|
83
|
+
}
|
|
76
84
|
}
|
|
77
85
|
|
|
78
86
|
/**
|
|
@@ -361,9 +369,18 @@ export default class JacobianPoint extends BasePoint {
|
|
|
361
369
|
return true
|
|
362
370
|
}
|
|
363
371
|
|
|
372
|
+
p = p as JacobianPoint
|
|
373
|
+
|
|
374
|
+
// --- Infinity handling ---
|
|
375
|
+
if (this.isInfinity() && p.isInfinity()) {
|
|
376
|
+
return true
|
|
377
|
+
}
|
|
378
|
+
if (this.isInfinity() !== p.isInfinity()) {
|
|
379
|
+
return false
|
|
380
|
+
}
|
|
381
|
+
|
|
364
382
|
// x1 * z2^2 == x2 * z1^2
|
|
365
383
|
const z2 = this.z.redSqr()
|
|
366
|
-
p = p as JacobianPoint
|
|
367
384
|
const pz2 = p.z.redSqr()
|
|
368
385
|
if (this.x.redMul(pz2).redISub(p.x.redMul(z2)).cmpn(0) !== 0) {
|
|
369
386
|
return false
|
package/src/primitives/Point.ts
CHANGED
|
@@ -445,6 +445,10 @@ export default class Point extends BasePoint {
|
|
|
445
445
|
* const encodedPointHex = aPoint.encode(true, 'hex');
|
|
446
446
|
*/
|
|
447
447
|
encode (compact: boolean = true, enc?: 'hex'): number[] | string {
|
|
448
|
+
if (this.inf) {
|
|
449
|
+
if (enc === 'hex') return '00'
|
|
450
|
+
return [0x00]
|
|
451
|
+
}
|
|
448
452
|
const len = this.curve.p.byteLength()
|
|
449
453
|
const x = this.getX().toArray('be', len)
|
|
450
454
|
let res: number[]
|
|
@@ -212,3 +212,66 @@ describe('Point codec', () => {
|
|
|
212
212
|
makeShortTest(shortPointOddY)
|
|
213
213
|
)
|
|
214
214
|
})
|
|
215
|
+
|
|
216
|
+
describe('JacobianPoint – Infinity handling and equality (TOB-18)', () => {
|
|
217
|
+
function J(x: any, y: any, z: any) {
|
|
218
|
+
return new JPoint(x, y, z)
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
it('Multiple infinity representations are canonicalized and equal', () => {
|
|
222
|
+
const inf1 = J(null, null, null)
|
|
223
|
+
const inf2 = J('0', '0', '0')
|
|
224
|
+
const inf3 = J(new BigNumber(0), new BigNumber(0), new BigNumber(0))
|
|
225
|
+
|
|
226
|
+
expect(inf1.isInfinity()).toBe(true)
|
|
227
|
+
expect(inf2.isInfinity()).toBe(true)
|
|
228
|
+
expect(inf3.isInfinity()).toBe(true)
|
|
229
|
+
|
|
230
|
+
expect(inf1.eq(inf2)).toBe(true)
|
|
231
|
+
expect(inf2.eq(inf3)).toBe(true)
|
|
232
|
+
expect(inf1.eq(inf3)).toBe(true)
|
|
233
|
+
})
|
|
234
|
+
|
|
235
|
+
it('Infinity must not equal a finite point', () => {
|
|
236
|
+
const inf = J(null, null, null)
|
|
237
|
+
|
|
238
|
+
const good = J(
|
|
239
|
+
new BigNumber('e7789226', 16),
|
|
240
|
+
new BigNumber('4b76b191', 16),
|
|
241
|
+
new BigNumber('cbf8d990', 16)
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
expect(inf.eq(good)).toBe(false)
|
|
245
|
+
expect(good.eq(inf)).toBe(false)
|
|
246
|
+
})
|
|
247
|
+
|
|
248
|
+
it('Infinity equals infinity (canonicalized)', () => {
|
|
249
|
+
const inf1 = J(null, null, null)
|
|
250
|
+
const inf2 = J('0', '0', '0')
|
|
251
|
+
|
|
252
|
+
expect(inf1.eq(inf2)).toBe(true)
|
|
253
|
+
expect(inf2.eq(inf1)).toBe(true)
|
|
254
|
+
})
|
|
255
|
+
|
|
256
|
+
it('Infinity is detected when z is zero in RED form', () => {
|
|
257
|
+
const redZero = new BigNumber(0).toRed(new Curve().red)
|
|
258
|
+
const p = J('1', '2', redZero)
|
|
259
|
+
|
|
260
|
+
expect(p.isInfinity()).toBe(true)
|
|
261
|
+
|
|
262
|
+
const clean = J(null, null, null)
|
|
263
|
+
expect(p.eq(clean)).toBe(true)
|
|
264
|
+
expect(clean.eq(p)).toBe(true)
|
|
265
|
+
})
|
|
266
|
+
|
|
267
|
+
it('eq() must handle mixed canonical and non-canonical infinity cases', () => {
|
|
268
|
+
const canonical = J(null, null, null)
|
|
269
|
+
const messy = J('1', '1', new BigNumber(0))
|
|
270
|
+
|
|
271
|
+
expect(messy.isInfinity()).toBe(true)
|
|
272
|
+
expect(canonical.eq(messy)).toBe(true)
|
|
273
|
+
expect(messy.eq(canonical)).toBe(true)
|
|
274
|
+
})
|
|
275
|
+
})
|
|
276
|
+
|
|
277
|
+
|
|
@@ -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
|
|
109
|
-
const pad = (sha1
|
|
110
|
-
const padLength = (sha1
|
|
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
|
|
127
|
-
const pad = (ripemd
|
|
128
|
-
const padLength = (ripemd
|
|
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
|
|
145
|
-
;(sha1
|
|
144
|
+
;(sha1).padLength = 1
|
|
145
|
+
;(sha1).pendingTotal = 40
|
|
146
146
|
|
|
147
147
|
expect(() => {
|
|
148
|
-
;(sha1
|
|
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
|
})
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
toBase58Check,
|
|
12
12
|
verifyNotNull
|
|
13
13
|
} from '../../primitives/utils'
|
|
14
|
+
import Point from '../../primitives/Point'
|
|
14
15
|
|
|
15
16
|
describe('utils', () => {
|
|
16
17
|
it('should convert to array', () => {
|
|
@@ -359,3 +360,19 @@ describe('toUTF8 strict UTF-8 decoding (TOB-21)', () => {
|
|
|
359
360
|
|
|
360
361
|
})
|
|
361
362
|
|
|
363
|
+
describe('Point.encode infinity handling', () => {
|
|
364
|
+
it('encodes infinity as 00 (array)', () => {
|
|
365
|
+
const p = new Point(null, null)
|
|
366
|
+
expect(p.encode()).toEqual([0x00])
|
|
367
|
+
})
|
|
368
|
+
|
|
369
|
+
it('encodes infinity as 00 (hex)', () => {
|
|
370
|
+
const p = new Point(null, null)
|
|
371
|
+
expect(p.encode(true, 'hex')).toBe('00')
|
|
372
|
+
})
|
|
373
|
+
|
|
374
|
+
it('does not throw for infinity', () => {
|
|
375
|
+
const p = new Point(null, null)
|
|
376
|
+
expect(() => p.encode()).not.toThrow()
|
|
377
|
+
})
|
|
378
|
+
})
|