@bsv/sdk 1.9.20 → 1.9.23

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.
Files changed (37) hide show
  1. package/dist/cjs/package.json +1 -1
  2. package/dist/cjs/src/primitives/DRBG.js +12 -1
  3. package/dist/cjs/src/primitives/DRBG.js.map +1 -1
  4. package/dist/cjs/src/primitives/JacobianPoint.js +15 -1
  5. package/dist/cjs/src/primitives/JacobianPoint.js.map +1 -1
  6. package/dist/cjs/src/primitives/Point.js +5 -0
  7. package/dist/cjs/src/primitives/Point.js.map +1 -1
  8. package/dist/cjs/src/primitives/index.js +1 -3
  9. package/dist/cjs/src/primitives/index.js.map +1 -1
  10. package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
  11. package/dist/esm/src/primitives/DRBG.js +12 -1
  12. package/dist/esm/src/primitives/DRBG.js.map +1 -1
  13. package/dist/esm/src/primitives/JacobianPoint.js +15 -1
  14. package/dist/esm/src/primitives/JacobianPoint.js.map +1 -1
  15. package/dist/esm/src/primitives/Point.js +5 -0
  16. package/dist/esm/src/primitives/Point.js.map +1 -1
  17. package/dist/esm/src/primitives/index.js +0 -1
  18. package/dist/esm/src/primitives/index.js.map +1 -1
  19. package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
  20. package/dist/types/src/primitives/DRBG.d.ts +12 -1
  21. package/dist/types/src/primitives/DRBG.d.ts.map +1 -1
  22. package/dist/types/src/primitives/JacobianPoint.d.ts.map +1 -1
  23. package/dist/types/src/primitives/Point.d.ts.map +1 -1
  24. package/dist/types/src/primitives/index.d.ts +0 -1
  25. package/dist/types/src/primitives/index.d.ts.map +1 -1
  26. package/dist/types/tsconfig.types.tsbuildinfo +1 -1
  27. package/dist/umd/bundle.js +1 -1
  28. package/dist/umd/bundle.js.map +1 -1
  29. package/docs/reference/auth.md +2 -2
  30. package/docs/reference/primitives.md +90 -31
  31. package/package.json +1 -1
  32. package/src/primitives/DRBG.ts +12 -1
  33. package/src/primitives/JacobianPoint.ts +18 -1
  34. package/src/primitives/Point.ts +4 -0
  35. package/src/primitives/__tests/Curve.unit.test.ts +63 -0
  36. package/src/primitives/__tests/utils.test.ts +17 -0
  37. package/src/primitives/index.ts +0 -1
@@ -502,7 +502,7 @@ export class MasterCertificate extends Certificate {
502
502
  static async createKeyringForVerifier(subjectWallet: ProtoWallet, certifier: WalletCounterparty, verifier: WalletCounterparty, fields: Record<CertificateFieldNameUnder50Bytes, Base64String>, fieldsToReveal: string[], masterKeyring: Record<CertificateFieldNameUnder50Bytes, Base64String>, serialNumber: Base64String, privileged?: boolean, privilegedReason?: string): Promise<Record<CertificateFieldNameUnder50Bytes, string>>
503
503
  static async issueCertificateForSubject(certifierWallet: ProtoWallet, subject: WalletCounterparty, fields: Record<CertificateFieldNameUnder50Bytes, string>, certificateType: string, getRevocationOutpoint = async (_serial: string): Promise<string> => {
504
504
  void _serial;
505
- return "Certificate revocation not tracked.";
505
+ return "00".repeat(32);
506
506
  }, serialNumber?: string): Promise<MasterCertificate>
507
507
  static async decryptFields(subjectOrCertifierWallet: ProtoWallet, masterKeyring: Record<CertificateFieldNameUnder50Bytes, Base64String>, fields: Record<CertificateFieldNameUnder50Bytes, Base64String>, counterparty: WalletCounterparty, privileged?: boolean, privilegedReason?: string): Promise<Record<CertificateFieldNameUnder50Bytes, string>>
508
508
  static async decryptField(subjectOrCertifierWallet: ProtoWallet, masterKeyring: Record<CertificateFieldNameUnder50Bytes, Base64String>, fieldName: Base64String, fieldValue: Base64String, counterparty: WalletCounterparty, privileged?: boolean, privilegedReason?: string): Promise<{
@@ -634,7 +634,7 @@ can also includes a revocation outpoint to manage potential revocation.
634
634
  ```ts
635
635
  static async issueCertificateForSubject(certifierWallet: ProtoWallet, subject: WalletCounterparty, fields: Record<CertificateFieldNameUnder50Bytes, string>, certificateType: string, getRevocationOutpoint = async (_serial: string): Promise<string> => {
636
636
  void _serial;
637
- return "Certificate revocation not tracked.";
637
+ return "00".repeat(32);
638
638
  }, serialNumber?: string): Promise<MasterCertificate>
639
639
  ```
640
640
  See also: [CertificateFieldNameUnder50Bytes](./wallet.md#type-certificatefieldnameunder50bytes), [MasterCertificate](./auth.md#class-mastercertificate), [ProtoWallet](./wallet.md#class-protowallet), [WalletCounterparty](./wallet.md#type-walletcounterparty)
@@ -829,7 +829,17 @@ Links: [API](#api), [Interfaces](#interfaces), [Classes](#classes), [Functions](
829
829
  ---
830
830
  ### Class: DRBG
831
831
 
832
- This class behaves as a HMAC-based deterministic random bit generator (DRBG). It implements a deterministic random number generator using SHA256HMAC HASH function. It takes an initial entropy and nonce when instantiated for seeding purpose.
832
+ HMAC-DRBG used **only** for deterministic ECDSA nonce generation.
833
+
834
+ This implementation follows the RFC 6979-style HMAC-DRBG construction for secp256k1
835
+ and is wired internally into the ECDSA signing code. It is **not forward-secure**
836
+ and MUST NOT be used as a general-purpose DRBG, key generator, or randomness source.
837
+
838
+ Security note:
839
+ - Intended scope: internal ECDSA nonce generation with fixed-size inputs.
840
+ - Out-of-scope: generic randomness, long-lived session keys, or any context
841
+ where forward secrecy is required.
842
+ - API stability: this class is internal.
833
843
 
834
844
  Example
835
845
 
@@ -4953,8 +4963,10 @@ Links: [API](#api), [Interfaces](#interfaces), [Classes](#classes), [Functions](
4953
4963
  | [AES](#function-aes) |
4954
4964
  | [AESGCM](#function-aesgcm) |
4955
4965
  | [AESGCMDecrypt](#function-aesgcmdecrypt) |
4966
+ | [assertValidHex](#function-assertvalidhex) |
4956
4967
  | [base64ToArray](#function-base64toarray) |
4957
4968
  | [ghash](#function-ghash) |
4969
+ | [normalizeHex](#function-normalizehex) |
4958
4970
  | [pbkdf2](#function-pbkdf2) |
4959
4971
  | [red](#function-red) |
4960
4972
  | [toArray](#function-toarray) |
@@ -4994,6 +5006,15 @@ export function AESGCMDecrypt(cipherText: number[], additionalAuthenticatedData:
4994
5006
 
4995
5007
  Links: [API](#api), [Interfaces](#interfaces), [Classes](#classes), [Functions](#functions), [Types](#types), [Enums](#enums), [Variables](#variables)
4996
5008
 
5009
+ ---
5010
+ ### Function: assertValidHex
5011
+
5012
+ ```ts
5013
+ export function assertValidHex(msg: string): void
5014
+ ```
5015
+
5016
+ Links: [API](#api), [Interfaces](#interfaces), [Classes](#classes), [Functions](#functions), [Types](#types), [Enums](#enums), [Variables](#variables)
5017
+
4997
5018
  ---
4998
5019
  ### Function: base64ToArray
4999
5020
 
@@ -5012,6 +5033,15 @@ export function ghash(input: number[], hashSubKey: number[]): number[]
5012
5033
 
5013
5034
  Links: [API](#api), [Interfaces](#interfaces), [Classes](#classes), [Functions](#functions), [Types](#types), [Enums](#enums), [Variables](#variables)
5014
5035
 
5036
+ ---
5037
+ ### Function: normalizeHex
5038
+
5039
+ ```ts
5040
+ export function normalizeHex(msg: string): string
5041
+ ```
5042
+
5043
+ Links: [API](#api), [Interfaces](#interfaces), [Classes](#classes), [Functions](#functions), [Types](#types), [Enums](#enums), [Variables](#variables)
5044
+
5015
5045
  ---
5016
5046
  ### Function: pbkdf2
5017
5047
 
@@ -5939,7 +5969,7 @@ sign = (msg: BigNumber, key: BigNumber, forceLowS: boolean = false, customK?: Bi
5939
5969
  if (kBN == null)
5940
5970
  throw new Error("k is undefined");
5941
5971
  kBN = truncateToN(kBN, true);
5942
- if (kBN.cmpn(1) <= 0 || kBN.cmp(ns1) >= 0) {
5972
+ if (kBN.cmpn(1) < 0 || kBN.cmp(ns1) > 0) {
5943
5973
  if (BigNumber.isBN(customK)) {
5944
5974
  throw new Error("Invalid fixed custom K value (must be >1 and <N\u20111)");
5945
5975
  }
@@ -6090,49 +6120,78 @@ Links: [API](#api), [Interfaces](#interfaces), [Classes](#classes), [Functions](
6090
6120
  ```ts
6091
6121
  toUTF8 = (arr: number[]): string => {
6092
6122
  let result = "";
6093
- let skip = 0;
6123
+ const replacementChar = "\uFFFD";
6094
6124
  for (let i = 0; i < arr.length; i++) {
6095
- const byte = arr[i];
6096
- if (skip > 0) {
6097
- skip--;
6125
+ const byte1 = arr[i];
6126
+ if (byte1 <= 127) {
6127
+ result += String.fromCharCode(byte1);
6098
6128
  continue;
6099
6129
  }
6100
- if (byte <= 127) {
6101
- result += String.fromCharCode(byte);
6102
- continue;
6103
- }
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);
6108
- const codePoint = ((byte & 31) << 6) | (byte2 & 63);
6130
+ const emitReplacement = (): void => {
6131
+ result += replacementChar;
6132
+ };
6133
+ if (byte1 >= 192 && byte1 <= 223) {
6134
+ if (i + 1 >= arr.length) {
6135
+ emitReplacement();
6136
+ continue;
6137
+ }
6138
+ const byte2 = arr[i + 1];
6139
+ if ((byte2 & 192) !== 128) {
6140
+ emitReplacement();
6141
+ i += 1;
6142
+ continue;
6143
+ }
6144
+ const codePoint = ((byte1 & 31) << 6) | (byte2 & 63);
6109
6145
  result += String.fromCharCode(codePoint);
6146
+ i += 1;
6110
6147
  continue;
6111
6148
  }
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);
6117
- const codePoint = ((byte & 15) << 12) | ((byte2 & 63) << 6) | (byte3 & 63);
6149
+ if (byte1 >= 224 && byte1 <= 239) {
6150
+ if (i + 2 >= arr.length) {
6151
+ emitReplacement();
6152
+ continue;
6153
+ }
6154
+ const byte2 = arr[i + 1];
6155
+ const byte3 = arr[i + 2];
6156
+ if ((byte2 & 192) !== 128 || (byte3 & 192) !== 128) {
6157
+ emitReplacement();
6158
+ i += 2;
6159
+ continue;
6160
+ }
6161
+ const codePoint = ((byte1 & 15) << 12) |
6162
+ ((byte2 & 63) << 6) |
6163
+ (byte3 & 63);
6118
6164
  result += String.fromCharCode(codePoint);
6165
+ i += 2;
6119
6166
  continue;
6120
6167
  }
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);
6127
- const codePoint = ((byte & 7) << 18) |
6168
+ if (byte1 >= 240 && byte1 <= 247) {
6169
+ if (i + 3 >= arr.length) {
6170
+ emitReplacement();
6171
+ continue;
6172
+ }
6173
+ const byte2 = arr[i + 1];
6174
+ const byte3 = arr[i + 2];
6175
+ const byte4 = arr[i + 3];
6176
+ if ((byte2 & 192) !== 128 ||
6177
+ (byte3 & 192) !== 128 ||
6178
+ (byte4 & 192) !== 128) {
6179
+ emitReplacement();
6180
+ i += 3;
6181
+ continue;
6182
+ }
6183
+ const codePoint = ((byte1 & 7) << 18) |
6128
6184
  ((byte2 & 63) << 12) |
6129
6185
  ((byte3 & 63) << 6) |
6130
6186
  (byte4 & 63);
6131
- const surrogate1 = 55296 + ((codePoint - 65536) >> 10);
6132
- const surrogate2 = 56320 + ((codePoint - 65536) & 1023);
6133
- result += String.fromCharCode(surrogate1, surrogate2);
6187
+ const offset = codePoint - 65536;
6188
+ const highSurrogate = 55296 + (offset >> 10);
6189
+ const lowSurrogate = 56320 + (offset & 1023);
6190
+ result += String.fromCharCode(highSurrogate, lowSurrogate);
6191
+ i += 3;
6134
6192
  continue;
6135
6193
  }
6194
+ emitReplacement();
6136
6195
  }
6137
6196
  return result;
6138
6197
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bsv/sdk",
3
- "version": "1.9.20",
3
+ "version": "1.9.23",
4
4
  "type": "module",
5
5
  "description": "BSV Blockchain Software Development Kit",
6
6
  "main": "dist/cjs/mod.js",
@@ -2,7 +2,18 @@ import { SHA256HMAC } from './Hash.js'
2
2
  import { toHex, toArray } from './utils.js'
3
3
 
4
4
  /**
5
- * This class behaves as a HMAC-based deterministic random bit generator (DRBG). It implements a deterministic random number generator using SHA256HMAC HASH function. It takes an initial entropy and nonce when instantiated for seeding purpose.
5
+ * HMAC-DRBG used **only** for deterministic ECDSA nonce generation.
6
+ *
7
+ * This implementation follows the RFC 6979-style HMAC-DRBG construction for secp256k1
8
+ * and is wired internally into the ECDSA signing code. It is **not forward-secure**
9
+ * and MUST NOT be used as a general-purpose DRBG, key generator, or randomness source.
10
+ *
11
+ * Security note:
12
+ * - Intended scope: internal ECDSA nonce generation with fixed-size inputs.
13
+ * - Out-of-scope: generic randomness, long-lived session keys, or any context
14
+ * where forward secrecy is required.
15
+ * - API stability: this class is internal.
16
+ *
6
17
  * @class DRBG
7
18
  *
8
19
  * @constructor
@@ -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
@@ -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
+
@@ -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
+ })
@@ -5,7 +5,6 @@ export { default as PublicKey } from './PublicKey.js'
5
5
  export { default as Signature } from './Signature.js'
6
6
  export { default as PrivateKey, KeyShares } from './PrivateKey.js'
7
7
  export { default as SymmetricKey } from './SymmetricKey.js'
8
- export { default as DRBG } from './DRBG.js'
9
8
  export * as ECDSA from './ECDSA.js'
10
9
  export * as Utils from './utils.js'
11
10
  export * as Hash from './Hash.js'