@bsv/sdk 1.6.18 → 1.6.20

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 (64) hide show
  1. package/README.md +11 -11
  2. package/dist/cjs/package.json +5 -9
  3. package/dist/cjs/src/auth/transports/SimplifiedFetchTransport.js +39 -39
  4. package/dist/cjs/src/auth/transports/SimplifiedFetchTransport.js.map +1 -1
  5. package/dist/cjs/src/primitives/ECDSA.js +69 -167
  6. package/dist/cjs/src/primitives/ECDSA.js.map +1 -1
  7. package/dist/cjs/src/primitives/Hash.js +660 -436
  8. package/dist/cjs/src/primitives/Hash.js.map +1 -1
  9. package/dist/cjs/src/primitives/Point.js +285 -293
  10. package/dist/cjs/src/primitives/Point.js.map +1 -1
  11. package/dist/cjs/src/script/ScriptEvaluationError.js +27 -0
  12. package/dist/cjs/src/script/ScriptEvaluationError.js.map +1 -0
  13. package/dist/cjs/src/script/Spend.js +13 -7
  14. package/dist/cjs/src/script/Spend.js.map +1 -1
  15. package/dist/cjs/src/script/index.js +3 -1
  16. package/dist/cjs/src/script/index.js.map +1 -1
  17. package/dist/cjs/src/transaction/Beef.js +4 -4
  18. package/dist/cjs/src/transaction/Transaction.js +3 -3
  19. package/dist/cjs/src/transaction/Transaction.js.map +1 -1
  20. package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
  21. package/dist/esm/src/auth/transports/SimplifiedFetchTransport.js +39 -39
  22. package/dist/esm/src/auth/transports/SimplifiedFetchTransport.js.map +1 -1
  23. package/dist/esm/src/primitives/ECDSA.js +69 -167
  24. package/dist/esm/src/primitives/ECDSA.js.map +1 -1
  25. package/dist/esm/src/primitives/Hash.js +672 -444
  26. package/dist/esm/src/primitives/Hash.js.map +1 -1
  27. package/dist/esm/src/primitives/Point.js +268 -293
  28. package/dist/esm/src/primitives/Point.js.map +1 -1
  29. package/dist/esm/src/script/ScriptEvaluationError.js +33 -0
  30. package/dist/esm/src/script/ScriptEvaluationError.js.map +1 -0
  31. package/dist/esm/src/script/Spend.js +14 -8
  32. package/dist/esm/src/script/Spend.js.map +1 -1
  33. package/dist/esm/src/script/index.js +1 -0
  34. package/dist/esm/src/script/index.js.map +1 -1
  35. package/dist/esm/src/transaction/Beef.js +4 -4
  36. package/dist/esm/src/transaction/Transaction.js +3 -3
  37. package/dist/esm/src/transaction/Transaction.js.map +1 -1
  38. package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
  39. package/dist/types/src/auth/transports/SimplifiedFetchTransport.d.ts +1 -1
  40. package/dist/types/src/auth/transports/SimplifiedFetchTransport.d.ts.map +1 -1
  41. package/dist/types/src/primitives/ECDSA.d.ts.map +1 -1
  42. package/dist/types/src/primitives/Hash.d.ts +12 -19
  43. package/dist/types/src/primitives/Hash.d.ts.map +1 -1
  44. package/dist/types/src/primitives/Point.d.ts +37 -5
  45. package/dist/types/src/primitives/Point.d.ts.map +1 -1
  46. package/dist/types/src/script/ScriptEvaluationError.d.ts +24 -0
  47. package/dist/types/src/script/ScriptEvaluationError.d.ts.map +1 -0
  48. package/dist/types/src/script/Spend.d.ts.map +1 -1
  49. package/dist/types/src/script/index.d.ts +1 -0
  50. package/dist/types/src/script/index.d.ts.map +1 -1
  51. package/dist/types/src/transaction/Transaction.d.ts.map +1 -1
  52. package/dist/types/tsconfig.types.tsbuildinfo +1 -1
  53. package/dist/umd/bundle.js +20 -1
  54. package/dist/umd/bundle.js.map +1 -0
  55. package/package.json +5 -9
  56. package/src/auth/transports/SimplifiedFetchTransport.ts +64 -67
  57. package/src/primitives/ECDSA.ts +80 -222
  58. package/src/primitives/Hash.ts +752 -589
  59. package/src/primitives/Point.ts +277 -336
  60. package/src/script/ScriptEvaluationError.ts +44 -0
  61. package/src/script/Spend.ts +14 -12
  62. package/src/script/index.ts +1 -0
  63. package/src/transaction/Beef.ts +4 -4
  64. package/src/transaction/Transaction.ts +11 -3
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bsv/sdk",
3
- "version": "1.6.18",
3
+ "version": "1.6.20",
4
4
  "type": "module",
5
5
  "description": "BSV Blockchain Software Development Kit",
6
6
  "main": "dist/cjs/mod.js",
@@ -216,7 +216,7 @@
216
216
  "lint": "ts-standard --fix src/**/*.ts",
217
217
  "build": "npm run build:ts && npm run build:umd",
218
218
  "build:ts": "tsc -b && tsconfig-to-dual-package tsconfig.cjs.json",
219
- "build:umd": "webpack --config webpack.config.js",
219
+ "build:umd": "rspack --config rspack.config.js",
220
220
  "dev": "tsc -b -w",
221
221
  "prepublish": "npm run build",
222
222
  "doc": "ts2md",
@@ -243,6 +243,7 @@
243
243
  "devDependencies": {
244
244
  "@eslint/js": "^9.23.0",
245
245
  "@jest/globals": "^29.7.0",
246
+ "@rspack/cli": "^1.4.9",
246
247
  "@types/jest": "^29.5.14",
247
248
  "@types/node": "^22.13.14",
248
249
  "eslint": "^9.23.0",
@@ -255,17 +256,12 @@
255
256
  "ts2md": "^0.2.8",
256
257
  "tsconfig-to-dual-package": "^1.2.0",
257
258
  "typescript": "5.1",
258
- "typescript-eslint": "^8.29.0",
259
- "webpack": "^5.98.0",
260
- "webpack-cli": "^6.0.1"
259
+ "typescript-eslint": "^8.29.0"
261
260
  },
262
261
  "ts-standard": {
263
262
  "project": "tsconfig.eslint.json",
264
263
  "ignore": [
265
- "dist",
266
- "src/script/ScriptTemplate.ts",
267
- "src/transaction/Transaction.ts",
268
- "src/auth/transports/SimplifiedFetchTransport.ts"
264
+ "dist"
269
265
  ]
270
266
  }
271
267
  }
@@ -1,12 +1,10 @@
1
1
  // @ts-nocheck
2
- // @ts-ignore
3
- import { AuthMessage, RequestedCertificateSet, Transport } from "../types.js"
2
+ // @ts-expect-error
3
+ import { AuthMessage, RequestedCertificateSet, Transport } from '../types.js'
4
4
  import * as Utils from '../../primitives/utils.js'
5
5
 
6
- const SUCCESS_STATUS_CODES = [200, 402]
7
-
8
6
  // Only bind window.fetch in the browser
9
- const defaultFetch = typeof window !== 'undefined' ? fetch.bind(window) : fetch;
7
+ const defaultFetch = typeof window !== 'undefined' ? fetch.bind(window) : fetch
10
8
 
11
9
  /**
12
10
  * Implements an HTTP-specific transport for handling Peer mutual authentication messages.
@@ -17,13 +15,12 @@ export class SimplifiedFetchTransport implements Transport {
17
15
  fetchClient: typeof fetch
18
16
  baseUrl: string
19
17
 
20
-
21
18
  /**
22
19
  * Constructs a new instance of SimplifiedFetchTransport.
23
20
  * @param baseUrl - The base URL for all HTTP requests made by this transport.
24
21
  * @param fetchClient - A fetch implementation to use for HTTP requests (default: global fetch).
25
22
  */
26
- constructor(baseUrl: string, fetchClient = defaultFetch) {
23
+ constructor (baseUrl: string, fetchClient = defaultFetch) {
27
24
  this.fetchClient = fetchClient
28
25
  this.baseUrl = baseUrl
29
26
  }
@@ -33,47 +30,48 @@ export class SimplifiedFetchTransport implements Transport {
33
30
  * Handles both general and authenticated message types. For general messages,
34
31
  * the payload is deserialized and sent as an HTTP request. For other message types,
35
32
  * the message is sent as a POST request to the `/auth` endpoint.
36
- *
33
+ *
37
34
  * @param message - The AuthMessage to send.
38
35
  * @returns A promise that resolves when the message is successfully sent.
39
- *
36
+ *
40
37
  * @throws Will throw an error if no listener has been registered via `onData`.
41
38
  */
42
- async send(message: AuthMessage): Promise<void> {
43
- if (!this.onDataCallback) {
39
+ async send (message: AuthMessage): Promise<void> {
40
+ if (this.onDataCallback == null) {
44
41
  throw new Error('Listen before you start speaking. God gave you two ears and one mouth for a reason.')
45
42
  }
46
43
  if (message.messageType !== 'general') {
47
- return new Promise(async (resolve, reject) => {
48
- try {
49
- const responsePromise = this.fetchClient(`${this.baseUrl}/.well-known/auth`, {
50
- method: 'POST',
51
- headers: {
52
- 'Content-Type': 'application/json'
53
- },
54
- body: JSON.stringify(message)
55
- })
44
+ return await new Promise((resolve, reject) => {
45
+ void (async () => {
46
+ try {
47
+ const responsePromise = this.fetchClient(`${this.baseUrl}/.well-known/auth`, {
48
+ method: 'POST',
49
+ headers: {
50
+ 'Content-Type': 'application/json'
51
+ },
52
+ body: JSON.stringify(message)
53
+ })
56
54
 
57
- // For initialRequest message, mark connection as established and start pool.
58
- if (message.messageType !== "initialRequest") {
59
- resolve()
60
- }
61
- const response = await responsePromise
62
- // Handle the response if data is received and callback is set
63
- if (response.ok && this.onDataCallback) {
64
- const responseMessage = await response.json()
65
- this.onDataCallback(responseMessage as AuthMessage)
66
- } else {
55
+ // For initialRequest message, mark connection as established and start pool.
56
+ if (message.messageType !== 'initialRequest') {
57
+ resolve()
58
+ }
59
+ const response = await responsePromise
60
+ // Handle the response if data is received and callback is set
61
+ if (response.ok && (this.onDataCallback != null)) {
62
+ const responseMessage = await response.json()
63
+ this.onDataCallback(responseMessage as AuthMessage)
64
+ } else {
67
65
  // Server may be a non authenticated server
68
- throw new Error('HTTP server failed to authenticate')
66
+ throw new Error('HTTP server failed to authenticate')
67
+ }
68
+ if (message.messageType === 'initialRequest') {
69
+ resolve()
70
+ }
71
+ } catch (e) {
72
+ reject(e)
69
73
  }
70
- if (message.messageType === "initialRequest") {
71
- resolve()
72
- }
73
- } catch (e) {
74
- reject(e)
75
- return
76
- }
74
+ })()
77
75
  })
78
76
  } else {
79
77
  // Parse message payload
@@ -81,7 +79,7 @@ export class SimplifiedFetchTransport implements Transport {
81
79
 
82
80
  // Send the byte array as the HTTP payload
83
81
  const url = `${this.baseUrl}${httpRequest.urlPostfix}`
84
- let httpRequestWithAuthHeaders: any = httpRequest
82
+ const httpRequestWithAuthHeaders: any = httpRequest
85
83
  if (typeof httpRequest.headers !== 'object') {
86
84
  httpRequestWithAuthHeaders.headers = {}
87
85
  }
@@ -95,27 +93,27 @@ export class SimplifiedFetchTransport implements Transport {
95
93
  httpRequestWithAuthHeaders.headers['x-bsv-auth-request-id'] = httpRequest.requestId
96
94
 
97
95
  // Ensure Content-Type is set for requests with a body
98
- if (httpRequestWithAuthHeaders.body) {
99
- const headers = httpRequestWithAuthHeaders.headers;
100
- if (!headers['content-type']) {
101
- throw new Error('Content-Type header is required for requests with a body.');
96
+ if (httpRequestWithAuthHeaders.body != null) {
97
+ const headers = httpRequestWithAuthHeaders.headers
98
+ if (headers['content-type'] == null) {
99
+ throw new Error('Content-Type header is required for requests with a body.')
102
100
  }
103
101
 
104
- const contentType = headers['content-type'];
102
+ const contentType = String(headers['content-type'] ?? '')
105
103
 
106
104
  // Transform body based on Content-Type
107
105
  if (contentType.includes('application/json')) {
108
106
  // Convert byte array to JSON string
109
- httpRequestWithAuthHeaders.body = Utils.toUTF8(httpRequestWithAuthHeaders.body);
107
+ httpRequestWithAuthHeaders.body = Utils.toUTF8(httpRequestWithAuthHeaders.body)
110
108
  } else if (contentType.includes('application/x-www-form-urlencoded')) {
111
109
  // Convert byte array to URL-encoded string
112
- httpRequestWithAuthHeaders.body = Utils.toUTF8(httpRequestWithAuthHeaders.body);
110
+ httpRequestWithAuthHeaders.body = Utils.toUTF8(httpRequestWithAuthHeaders.body)
113
111
  } else if (contentType.includes('text/plain')) {
114
112
  // Convert byte array to plain UTF-8 string
115
- httpRequestWithAuthHeaders.body = Utils.toUTF8(httpRequestWithAuthHeaders.body);
113
+ httpRequestWithAuthHeaders.body = Utils.toUTF8(httpRequestWithAuthHeaders.body)
116
114
  } else {
117
115
  // For all other content types, treat as binary data
118
- httpRequestWithAuthHeaders.body = new Uint8Array(httpRequestWithAuthHeaders.body);
116
+ httpRequestWithAuthHeaders.body = new Uint8Array(httpRequestWithAuthHeaders.body)
119
117
  }
120
118
  }
121
119
 
@@ -132,14 +130,13 @@ export class SimplifiedFetchTransport implements Transport {
132
130
  // Try parsing JSON error
133
131
  const errorInfo = await response.json()
134
132
  // Otherwise just throw whatever we got
135
- throw new Error(`HTTP ${response.status} - ${JSON.stringify(errorInfo)}`);
133
+ throw new Error(`HTTP ${response.status} - ${JSON.stringify(errorInfo)}`)
136
134
  }
137
135
 
138
136
  const parsedBody = await response.arrayBuffer()
139
137
  const payloadWriter = new Utils.Writer()
140
- if(response.headers.get('x-bsv-auth-request-id') != null)
141
- {
142
- payloadWriter.write(Utils.toArray(response.headers.get('x-bsv-auth-request-id'), 'base64'));
138
+ if (response.headers.get('x-bsv-auth-request-id') != null) {
139
+ payloadWriter.write(Utils.toArray(response.headers.get('x-bsv-auth-request-id'), 'base64'))
143
140
  }
144
141
  payloadWriter.writeVarIntNum(response.status)
145
142
 
@@ -147,7 +144,7 @@ export class SimplifiedFetchTransport implements Transport {
147
144
  // Parse response headers from the server and include only the signed headers:
148
145
  // - Include custom headers prefixed with x-bsv (excluding those starting with x-bsv-auth)
149
146
  // - Include the authorization header
150
- const includedHeaders: [string, string][] = []
147
+ const includedHeaders: Array<[string, string]> = []
151
148
  response.headers.forEach((value, key) => {
152
149
  const lowerKey = key.toLowerCase()
153
150
  if ((lowerKey.startsWith('x-bsv-') || lowerKey === 'authorization') && !lowerKey.startsWith('x-bsv-auth')) {
@@ -174,7 +171,7 @@ export class SimplifiedFetchTransport implements Transport {
174
171
  }
175
172
 
176
173
  // Handle body
177
- if (parsedBody) {
174
+ if (parsedBody != null) {
178
175
  const bodyAsArray = Array.from(new Uint8Array(parsedBody))
179
176
  payloadWriter.writeVarIntNum(bodyAsArray.length)
180
177
  payloadWriter.write(bodyAsArray)
@@ -191,11 +188,11 @@ export class SimplifiedFetchTransport implements Transport {
191
188
  yourNonce: response.headers.get('x-bsv-auth-your-nonce'),
192
189
  requestedCertificates: JSON.parse(response.headers.get('x-bsv-auth-requested-certificates')) as RequestedCertificateSet,
193
190
  payload: payloadWriter.toArray(),
194
- signature: Utils.toArray(response.headers.get('x-bsv-auth-signature'), 'hex'),
191
+ signature: Utils.toArray(response.headers.get('x-bsv-auth-signature'), 'hex')
195
192
  }
196
193
 
197
194
  // If the server didn't provide the correct authentication headers, throw an error
198
- if (!responseMessage.version) {
195
+ if (responseMessage.version == null) {
199
196
  throw new Error('HTTP server failed to authenticate')
200
197
  }
201
198
 
@@ -205,30 +202,30 @@ export class SimplifiedFetchTransport implements Transport {
205
202
  }
206
203
 
207
204
  /**
208
- * Registers a callback to handle incoming messages.
205
+ * Registers a callback to handle incoming messages.
209
206
  * This must be called before sending any messages to ensure responses can be processed.
210
- *
207
+ *
211
208
  * @param callback - A function to invoke when an incoming AuthMessage is received.
212
209
  * @returns A promise that resolves once the callback is set.
213
210
  */
214
- async onData(callback: (message: AuthMessage) => Promise<void>): Promise<void> {
211
+ async onData (callback: (message: AuthMessage) => Promise<void>): Promise<void> {
215
212
  this.onDataCallback = (m) => {
216
- callback(m)
213
+ void callback(m)
217
214
  }
218
215
  }
219
216
 
220
217
  /**
221
218
  * Deserializes a request payload from a byte array into an HTTP request-like structure.
222
- *
219
+ *
223
220
  * @param payload - The serialized payload to deserialize.
224
221
  * @returns An object representing the deserialized request, including the method,
225
222
  * URL postfix (path and query string), headers, body, and request ID.
226
223
  */
227
- deserializeRequestPayload(payload: number[]): {
228
- method: string,
229
- urlPostfix: string,
230
- headers: Record<string, string>,
231
- body: number[],
224
+ deserializeRequestPayload (payload: number[]): {
225
+ method: string
226
+ urlPostfix: string
227
+ headers: Record<string, string>
228
+ body: number[]
232
229
  requestId: string
233
230
  } {
234
231
  // Create a reader
@@ -288,4 +285,4 @@ export class SimplifiedFetchTransport implements Transport {
288
285
  requestId
289
286
  }
290
287
  }
291
- }
288
+ }
@@ -1,7 +1,7 @@
1
1
  import BigNumber from './BigNumber.js'
2
2
  import Signature from './Signature.js'
3
3
  import Curve from './Curve.js'
4
- import Point from './Point.js'
4
+ import Point, { scalarMultiplyWNAF, biModInv, BI_ZERO, biModMul, GX_BIGINT, GY_BIGINT, jpAdd, N_BIGINT, modInvN, modMulN, modN } from './Point.js'
5
5
  import DRBG from './DRBG.js'
6
6
 
7
7
  /**
@@ -39,6 +39,11 @@ function truncateToN (
39
39
  }
40
40
  }
41
41
 
42
+ const curve = new Curve()
43
+ const bytes = curve.n.byteLength()
44
+ const ns1 = curve.n.subn(1)
45
+ const halfN = N_BIGINT >> 1n
46
+
42
47
  /**
43
48
  * Generates a digital signature for a given message.
44
49
  *
@@ -60,84 +65,80 @@ export const sign = (
60
65
  forceLowS: boolean = false,
61
66
  customK?: BigNumber | ((iter: number) => BigNumber)
62
67
  ): Signature => {
63
- const curve = new Curve()
68
+ // —— prepare inputs ────────────────────────────────────────────────────────
64
69
  msg = truncateToN(msg)
70
+ const msgBig = BigInt('0x' + msg.toString(16))
71
+ const keyBig = BigInt('0x' + key.toString(16))
65
72
 
66
- // Zero-extend key to provide enough entropy
67
- const bytes = curve.n.byteLength()
73
+ // DRBG seeding identical to previous implementation
68
74
  const bkey = key.toArray('be', bytes)
69
-
70
- // Zero-extend nonce to have the same byte size as N
71
75
  const nonce = msg.toArray('be', bytes)
72
-
73
- // Instantiate Hmac_DRBG
74
76
  const drbg = new DRBG(bkey, nonce)
75
77
 
76
- // Number of bytes to generate
77
- const ns1 = curve.n.subn(1)
78
-
79
78
  for (let iter = 0; ; iter++) {
80
- // Compute the k-value
81
- let k =
79
+ // —— k generation & basic validity checks ───────────────────────────────
80
+ let kBN =
82
81
  typeof customK === 'function'
83
82
  ? customK(iter)
84
83
  : BigNumber.isBN(customK)
85
84
  ? customK
86
85
  : new BigNumber(drbg.generate(bytes), 16)
87
- if (k != null) {
88
- k = truncateToN(k, true)
89
- } else {
90
- throw new Error('k is undefined')
91
- }
92
- if (k.cmpn(1) <= 0 || k.cmp(ns1) >= 0) {
86
+
87
+ if (kBN == null) throw new Error('k is undefined')
88
+ kBN = truncateToN(kBN, true)
89
+
90
+ if (kBN.cmpn(1) <= 0 || kBN.cmp(ns1) >= 0) {
93
91
  if (BigNumber.isBN(customK)) {
94
- throw new Error(
95
- 'Invalid fixed custom K value (must be more than 1 and less than N-1)'
96
- )
97
- } else {
98
- continue
92
+ throw new Error('Invalid fixed custom K value (must be >1 and <N‑1)')
99
93
  }
94
+ continue
100
95
  }
101
96
 
102
- const kp = curve.g.mul(k)
103
- if (kp.isInfinity()) {
97
+ const kBig = BigInt('0x' + kBN.toString(16))
98
+
99
+ // —— R = k·G (Jacobian, window‑NAF) ──────────────────────────────────────
100
+ const R = scalarMultiplyWNAF(kBig, { x: GX_BIGINT, y: GY_BIGINT })
101
+ if (R.Z === 0n) { // point at infinity – should never happen for valid k
104
102
  if (BigNumber.isBN(customK)) {
105
- throw new Error(
106
- 'Invalid fixed custom K value (must not create a point at infinity when multiplied by the generator point)'
107
- )
108
- } else {
109
- continue
103
+ throw new Error('Invalid fixed custom K value (k·G at infinity)')
110
104
  }
105
+ continue
111
106
  }
112
107
 
113
- const kpX = kp.getX()
114
- const r = kpX.umod(curve.n)
115
- if (r.cmpn(0) === 0) {
108
+ // affine X coordinate of R
109
+ const zInv = biModInv(R.Z)
110
+ const zInv2 = biModMul(zInv, zInv)
111
+ const xAff = biModMul(R.X, zInv2)
112
+ const rBig = modN(xAff)
113
+
114
+ if (rBig === 0n) {
116
115
  if (BigNumber.isBN(customK)) {
117
- throw new Error(
118
- 'Invalid fixed custom K value (when multiplied by G, the resulting x coordinate mod N must not be zero)'
119
- )
120
- } else {
121
- continue
116
+ throw new Error('Invalid fixed custom K value (r == 0)')
122
117
  }
118
+ continue
123
119
  }
124
120
 
125
- let s = k.invm(curve.n).mul(r.mul(key).iadd(msg))
126
- s = s.umod(curve.n)
127
- if (s.cmpn(0) === 0) {
121
+ // —— s = k⁻¹ · (msg + r·key) mod n ─────────────────────────────────────
122
+ const kInv = modInvN(kBig)
123
+ const rTimesKey = modMulN(rBig, keyBig)
124
+ const sum = modN(msgBig + rTimesKey)
125
+ let sBig = modMulN(kInv, sum)
126
+
127
+ if (sBig === 0n) {
128
128
  if (BigNumber.isBN(customK)) {
129
- throw new Error(
130
- 'Invalid fixed custom K value (when used with the key, it cannot create a zero value for S)'
131
- )
132
- } else {
133
- continue
129
+ throw new Error('Invalid fixed custom K value (s == 0)')
134
130
  }
131
+ continue
135
132
  }
136
133
 
137
- // Use complement of `s`, if it is > `n / 2`
138
- if (forceLowS && s.cmp(curve.n.ushrn(1)) > 0) {
139
- s = curve.n.sub(s)
134
+ // low‑S mitigation (BIP‑62/BIP‑340 style)
135
+ if (forceLowS && sBig > halfN) {
136
+ sBig = N_BIGINT - sBig
140
137
  }
138
+
139
+ // —— convert back to BigNumber & return ─────────────────────────────────
140
+ const r = new BigNumber(rBig.toString(16), 16)
141
+ const s = new BigNumber(sBig.toString(16), 16)
141
142
  return new Signature(r, s)
142
143
  }
143
144
  }
@@ -161,177 +162,7 @@ export const sign = (
161
162
  * const isVerified = verify(msg, sig, key)
162
163
  */
163
164
  export const verify = (msg: BigNumber, sig: Signature, key: Point): boolean => {
164
- // Curve parameters for secp256k1
165
- const zero = BigInt(0)
166
- const one = BigInt(1)
167
- const two = BigInt(2)
168
- const three = BigInt(3)
169
- const p = BigInt(
170
- '0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F'
171
- ) // Field prime
172
- const n = BigInt(
173
- '0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141'
174
- ) // Order of the curve
175
- const G = {
176
- x: BigInt(
177
- '0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798'
178
- ),
179
- y: BigInt(
180
- '0x483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8'
181
- )
182
- }
183
-
184
- // Modular arithmetic functions
185
- const mod = (a: bigint, m: bigint): bigint => ((a % m) + m) % m
186
- const modInv = (a: bigint, m: bigint): bigint => {
187
- // Extended Euclidean Algorithm for modular inverse
188
- let [oldr, r] = [a, m]
189
- let [olds, s] = [BigInt(1), BigInt(0)]
190
- while (r !== zero) {
191
- const q = oldr / r;
192
- [oldr, r] = [r, oldr - q * r];
193
- [olds, s] = [s, olds - q * s]
194
- }
195
- if (oldr > one) return zero // No inverse
196
- return mod(olds, m)
197
- }
198
- const modMul = (a: bigint, b: bigint, m: bigint): bigint => mod(a * b, m)
199
- const modSub = (a: bigint, b: bigint, m: bigint): bigint => mod(a - b, m)
200
-
201
- // Define constants
202
- const four = BigInt(4)
203
- const eight = BigInt(8)
204
-
205
- // Elliptic curve point operations in Jacobian coordinates
206
- interface JacobianPoint {
207
- X: bigint
208
- Y: bigint
209
- Z: bigint
210
- }
211
-
212
- // Point Doubling
213
- const pointDouble = (P: JacobianPoint): JacobianPoint => {
214
- const { X: X1, Y: Y1, Z: Z1 } = P
215
-
216
- if (Y1 === zero) {
217
- return { X: zero, Y: one, Z: zero } // Point at infinity
218
- }
219
-
220
- const Y1sq = modMul(Y1, Y1, p) // Y1^2
221
- const S = modMul(four, modMul(X1, Y1sq, p), p) // S = 4 * X1 * Y1^2
222
- const M = modMul(three, modMul(X1, X1, p), p) // M = 3 * X1^2
223
- const X3 = modSub(modMul(M, M, p), modMul(two, S, p), p) // X3 = M^2 - 2 * S
224
- const Y3 = modSub(
225
- modMul(M, modSub(S, X3, p), p),
226
- modMul(eight, modMul(Y1sq, Y1sq, p), p),
227
- p
228
- ) // Y3 = M * (S - X3) - 8 * Y1^4
229
- const Z3 = modMul(two, modMul(Y1, Z1, p), p) // Z3 = 2 * Y1 * Z1
230
-
231
- return { X: X3, Y: Y3, Z: Z3 }
232
- }
233
-
234
- // Point Addition
235
- const pointAdd = (P: JacobianPoint, Q: JacobianPoint): JacobianPoint => {
236
- if (P.Z === zero) return Q
237
- if (Q.Z === zero) return P
238
-
239
- const Z1Z1 = modMul(P.Z, P.Z, p)
240
- const Z2Z2 = modMul(Q.Z, Q.Z, p)
241
- const U1 = modMul(P.X, Z2Z2, p)
242
- const U2 = modMul(Q.X, Z1Z1, p)
243
- const S1 = modMul(P.Y, modMul(Z2Z2, Q.Z, p), p)
244
- const S2 = modMul(Q.Y, modMul(Z1Z1, P.Z, p), p)
245
-
246
- const H = modSub(U2, U1, p)
247
- const r = modSub(S2, S1, p)
248
-
249
- if (H === zero) {
250
- if (r === zero) {
251
- // P == Q
252
- return pointDouble(P)
253
- } else {
254
- // Point at infinity
255
- return { X: zero, Y: one, Z: zero }
256
- }
257
- }
258
-
259
- const HH = modMul(H, H, p)
260
- const HHH = modMul(H, HH, p)
261
- const V = modMul(U1, HH, p)
262
-
263
- const X3 = modSub(modSub(modMul(r, r, p), HHH, p), modMul(two, V, p), p)
264
- const Y3 = modSub(modMul(r, modSub(V, X3, p), p), modMul(S1, HHH, p), p)
265
- const Z3 = modMul(H, modMul(P.Z, Q.Z, p), p)
266
-
267
- return { X: X3, Y: Y3, Z: Z3 }
268
- }
269
-
270
- // Scalar Multiplication
271
- const scalarMultiply = (
272
- k: bigint,
273
- P: { x: bigint, y: bigint }
274
- ): JacobianPoint => {
275
- const N: JacobianPoint = { X: P.x, Y: P.y, Z: one }
276
- let Q: JacobianPoint = { X: zero, Y: one, Z: zero } // Point at infinity
277
-
278
- const kBin = k.toString(2)
279
- for (let i = 0; i < kBin.length; i++) {
280
- Q = pointDouble(Q)
281
- if (kBin[i] === '1') {
282
- Q = pointAdd(Q, N)
283
- }
284
- }
285
- return Q
286
- }
287
-
288
- // Verify Function Using Jacobian Coordinates
289
- const verifyECDSA = (
290
- hash: bigint,
291
- publicKey: { x: bigint, y: bigint },
292
- signature: { r: bigint, s: bigint }
293
- ): boolean => {
294
- const { r, s } = signature
295
- const z = hash
296
-
297
- // Check r and s are in [1, n - 1]
298
- if (r <= zero || r >= n || s <= zero || s >= n) {
299
- return false
300
- }
301
-
302
- const w = modInv(s, n) // w = s^-1 mod n
303
- if (w === zero) {
304
- return false // No inverse exists
305
- }
306
- const u1 = modMul(z, w, n)
307
- const u2 = modMul(r, w, n)
308
-
309
- // Compute point R = u1 * G + u2 * Q
310
- const RG = scalarMultiply(u1, G)
311
- const RQ = scalarMultiply(u2, publicKey)
312
- const R = pointAdd(RG, RQ)
313
-
314
- if (R.Z === zero) {
315
- // Point at infinity
316
- return false
317
- }
318
-
319
- // Compute affine x-coordinate x1 = X / Z^2 mod p
320
- const ZInv = modInv(R.Z, p)
321
- if (ZInv === zero) {
322
- return false // No inverse exists
323
- }
324
- const ZInv2 = modMul(ZInv, ZInv, p)
325
- const x1affine = modMul(R.X, ZInv2, p)
326
-
327
- // Compute v = x1_affine mod n
328
- const v = mod(x1affine, n)
329
-
330
- // Signature is valid if v == r mod n
331
- return v === r
332
- }
333
-
334
- // Convert inputs to BigInt
165
+ // Convert inputs to BigInt
335
166
  const hash = BigInt('0x' + msg.toString(16))
336
167
  if ((key.x == null) || (key.y == null)) {
337
168
  throw new Error('Invalid public key: missing coordinates.')
@@ -346,5 +177,32 @@ export const verify = (msg: BigNumber, sig: Signature, key: Point): boolean => {
346
177
  s: BigInt('0x' + sig.s.toString(16))
347
178
  }
348
179
 
349
- return verifyECDSA(hash, publicKey, signature)
180
+ const { r, s } = signature
181
+ const z = hash
182
+
183
+ // Check r and s are in [1, n - 1]
184
+ if (r <= BI_ZERO || r >= N_BIGINT || s <= BI_ZERO || s >= N_BIGINT) {
185
+ return false
186
+ }
187
+
188
+ // ── compute u₁ = z·s⁻¹ mod n and u₂ = r·s⁻¹ mod n ───────────────────────
189
+ const w = modInvN(s) // s⁻¹ mod n
190
+ if (w === 0n) return false // should never happen
191
+ const u1 = modMulN(z, w)
192
+ const u2 = modMulN(r, w)
193
+
194
+ // ── R = u₁·G + u₂·Q (Jacobian, window‑NAF) ──────────────────────────────
195
+ const RG = scalarMultiplyWNAF(u1, { x: GX_BIGINT, y: GY_BIGINT })
196
+ const RQ = scalarMultiplyWNAF(u2, publicKey)
197
+ const R = jpAdd(RG, RQ)
198
+ if (R.Z === 0n) return false // point at infinity
199
+
200
+ // ── affine x‑coordinate of R (mod p) ─────────────────────────────────────
201
+ const zInv = biModInv(R.Z) // (Z⁻¹ mod p)
202
+ const zInv2 = biModMul(zInv, zInv) // Z⁻²
203
+ const xAff = biModMul(R.X, zInv2) // X / Z² mod p
204
+
205
+ // ── v = xAff mod n and final check ───────────────────────────────────────
206
+ const v = modN(xAff)
207
+ return v === r
350
208
  }