@btx-tools/challenges-sdk 0.0.1

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/index.js ADDED
@@ -0,0 +1,702 @@
1
+ import { sha256 } from '@noble/hashes/sha2.js';
2
+
3
+ // src/types.ts
4
+ var BtxError = class extends Error {
5
+ constructor(message, method) {
6
+ super(message);
7
+ this.method = method;
8
+ this.name = "BtxError";
9
+ }
10
+ method;
11
+ };
12
+ var BtxRpcError = class extends BtxError {
13
+ constructor(code, message, method) {
14
+ super(message, method);
15
+ this.code = code;
16
+ this.name = "BtxRpcError";
17
+ }
18
+ code;
19
+ };
20
+ var BtxHttpError = class extends BtxError {
21
+ constructor(status, body, method) {
22
+ super(`HTTP ${status}: ${body.slice(0, 200)}`, method);
23
+ this.status = status;
24
+ this.body = body;
25
+ this.name = "BtxHttpError";
26
+ }
27
+ status;
28
+ body;
29
+ };
30
+ var BtxParseError = class extends BtxError {
31
+ constructor(cause, body, method) {
32
+ const causeMsg = cause instanceof Error ? cause.message : String(cause);
33
+ super(`Failed to parse RPC response: ${causeMsg}`, method);
34
+ this.cause = cause;
35
+ this.body = body;
36
+ this.name = "BtxParseError";
37
+ }
38
+ cause;
39
+ body;
40
+ };
41
+ var BtxTimeoutError = class extends BtxError {
42
+ constructor(timeoutMs, method) {
43
+ super(`RPC request timed out after ${timeoutMs}ms`, method);
44
+ this.timeoutMs = timeoutMs;
45
+ this.name = "BtxTimeoutError";
46
+ }
47
+ timeoutMs;
48
+ };
49
+ var BtxNetworkError = class extends BtxError {
50
+ constructor(cause, method) {
51
+ const causeMsg = cause instanceof Error ? cause.message : String(cause);
52
+ super(`RPC network error: ${causeMsg}`, method);
53
+ this.cause = cause;
54
+ this.name = "BtxNetworkError";
55
+ }
56
+ cause;
57
+ };
58
+
59
+ // src/client.ts
60
+ function base64Utf8(input) {
61
+ if (typeof Buffer !== "undefined") {
62
+ return Buffer.from(input, "utf8").toString("base64");
63
+ }
64
+ const bytes = new TextEncoder().encode(input);
65
+ let binary = "";
66
+ for (let i = 0; i < bytes.length; i++) {
67
+ binary += String.fromCharCode(bytes[i]);
68
+ }
69
+ return btoa(binary);
70
+ }
71
+ function redactSensitive(body) {
72
+ return body.replace(/authorization\s*:\s*basic\s+[A-Za-z0-9+/=]+/gi, "authorization: basic [REDACTED]").replace(/"password"\s*:\s*"[^"]*"/gi, '"password":"[REDACTED]"').replace(/"rpcpassword"\s*:\s*"[^"]*"/gi, '"rpcpassword":"[REDACTED]"').replace(/\b(rpc(?:user|password|auth))\s*=\s*\S+/gi, "$1=[REDACTED]");
73
+ }
74
+ function nextRequestId() {
75
+ const c = globalThis.crypto;
76
+ if (c && typeof c.randomUUID === "function") return c.randomUUID();
77
+ return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
78
+ }
79
+ var BtxChallengeClient = class {
80
+ constructor(opts) {
81
+ this.opts = opts;
82
+ }
83
+ opts;
84
+ /** Low-level: raw JSON-RPC call. Exposed for forward compatibility. */
85
+ async call(method, params = []) {
86
+ const id = nextRequestId();
87
+ const auth = "Basic " + base64Utf8(`${this.opts.rpcAuth.user}:${this.opts.rpcAuth.pass}`);
88
+ const body = JSON.stringify({ jsonrpc: "1.0", id, method, params });
89
+ const ctrl = new AbortController();
90
+ const timeoutMs = this.opts.timeoutMs ?? 3e4;
91
+ const timer = setTimeout(() => ctrl.abort(), timeoutMs);
92
+ let res;
93
+ try {
94
+ res = await fetch(this.opts.rpcUrl, {
95
+ method: "POST",
96
+ headers: {
97
+ "content-type": "application/json",
98
+ authorization: auth
99
+ },
100
+ body,
101
+ signal: ctrl.signal
102
+ });
103
+ } catch (err) {
104
+ clearTimeout(timer);
105
+ if (err instanceof Error && err.name === "AbortError") {
106
+ throw new BtxTimeoutError(timeoutMs, method);
107
+ }
108
+ throw new BtxNetworkError(err, method);
109
+ } finally {
110
+ clearTimeout(timer);
111
+ }
112
+ if (!res.ok) {
113
+ const rawBody2 = await res.text().catch(() => "");
114
+ throw new BtxHttpError(res.status, redactSensitive(rawBody2), method);
115
+ }
116
+ const rawBody = await res.text();
117
+ let data;
118
+ try {
119
+ data = JSON.parse(rawBody);
120
+ } catch (err) {
121
+ throw new BtxParseError(err, redactSensitive(rawBody), method);
122
+ }
123
+ if (data.error) {
124
+ throw new BtxRpcError(data.error.code, data.error.message, method);
125
+ }
126
+ return data.result;
127
+ }
128
+ /** Issue a fresh challenge bound to (purpose, resource, subject). */
129
+ async issue(params) {
130
+ const ordered = [
131
+ ["purpose", params.purpose],
132
+ ["resource", params.resource],
133
+ ["subject", params.subject],
134
+ ["target_solve_time_s", params.target_solve_time_s],
135
+ ["expires_in_s", params.expires_in_s],
136
+ ["validation_overhead_s", params.validation_overhead_s],
137
+ ["propagation_overhead_s", params.propagation_overhead_s],
138
+ ["difficulty_policy", params.difficulty_policy],
139
+ ["difficulty_window_blocks", params.difficulty_window_blocks],
140
+ ["min_solve_time_s", params.min_solve_time_s],
141
+ ["max_solve_time_s", params.max_solve_time_s],
142
+ ["solver_parallelism", params.solver_parallelism],
143
+ ["solver_duty_cycle_pct", params.solver_duty_cycle_pct]
144
+ ];
145
+ let lastSet = 2;
146
+ for (let i = ordered.length - 1; i > 2; i--) {
147
+ if (ordered[i][1] !== void 0) {
148
+ lastSet = i;
149
+ break;
150
+ }
151
+ }
152
+ const args = ordered.slice(0, lastSet + 1).map(([, v]) => v);
153
+ return this.call("getmatmulservicechallenge", args);
154
+ }
155
+ /**
156
+ * Verify a proof statelessly. Does NOT consume the challenge.
157
+ * Use this for diagnostic / monitoring paths.
158
+ * For admission control, use {@link redeem} instead — verification alone
159
+ * does not prevent replay.
160
+ */
161
+ async verify(challenge, nonce64_hex, digest_hex, lookup_local_status = true) {
162
+ return this.call("verifymatmulserviceproof", [
163
+ challenge,
164
+ nonce64_hex,
165
+ digest_hex,
166
+ lookup_local_status
167
+ ]);
168
+ }
169
+ /**
170
+ * Verify-and-consume atomically. THIS is the admission control entry point.
171
+ * On success, the challenge is marked redeemed and cannot be replayed.
172
+ */
173
+ async redeem(challenge, nonce64_hex, digest_hex) {
174
+ return this.call("redeemmatmulserviceproof", [
175
+ challenge,
176
+ nonce64_hex,
177
+ digest_hex
178
+ ]);
179
+ }
180
+ /** Batch verify. Spec range 1–256 (audit M2). No consumption. */
181
+ async verifyBatch(entries) {
182
+ this.assertBatchSize(entries);
183
+ return this.call("verifymatmulserviceproofs", [entries]);
184
+ }
185
+ /** Batch verify + consume. Sequential per-entry. Spec range 1–256 (audit M2). */
186
+ async redeemBatch(entries) {
187
+ this.assertBatchSize(entries);
188
+ return this.call("redeemmatmulserviceproofs", [entries]);
189
+ }
190
+ /**
191
+ * Server-side local solver. Useful when generating fixtures or pre-computing
192
+ * for tests. For production browser-side solving, ship the WASM solver —
193
+ * RPC-based solving puts compute load on YOUR node, defeating the point.
194
+ */
195
+ async solve(challenge) {
196
+ return this.call("solvematmulservicechallenge", [challenge]);
197
+ }
198
+ assertBatchSize(entries) {
199
+ if (entries.length < 1 || entries.length > 256) {
200
+ throw new RangeError(
201
+ `Batch size must be between 1 and 256 (per BTX RPC spec), got ${entries.length}`
202
+ );
203
+ }
204
+ }
205
+ };
206
+ function headerInputForNonce(ctx, nonce64) {
207
+ return {
208
+ version: ctx.version,
209
+ previousblockhash: ctx.previousblockhash,
210
+ merkleroot: ctx.merkleroot,
211
+ time: ctx.time,
212
+ bits: ctx.bits,
213
+ nonce64,
214
+ matmul_dim: ctx.matmul_dim,
215
+ seed_a: ctx.seed_a,
216
+ seed_b: ctx.seed_b
217
+ };
218
+ }
219
+ function serializeMatMulHeader(input) {
220
+ const buf = new Uint8Array(4 + 32 + 32 + 4 + 4 + 8 + 2 + 32 + 32);
221
+ let off = 0;
222
+ writeUint32LE(buf, off, input.version >>> 0);
223
+ off += 4;
224
+ buf.set(parseUint256HexToLE(input.previousblockhash, "previousblockhash"), off);
225
+ off += 32;
226
+ buf.set(parseUint256HexToLE(input.merkleroot, "merkleroot"), off);
227
+ off += 32;
228
+ writeUint32LE(buf, off, input.time >>> 0);
229
+ off += 4;
230
+ writeUint32LE(buf, off, parseBitsHex(input.bits));
231
+ off += 4;
232
+ writeUint64LE(buf, off, input.nonce64);
233
+ off += 8;
234
+ writeUint16LE(buf, off, input.matmul_dim & 65535);
235
+ off += 2;
236
+ buf.set(parseUint256HexToLE(input.seed_a, "seed_a"), off);
237
+ off += 32;
238
+ buf.set(parseUint256HexToLE(input.seed_b, "seed_b"), off);
239
+ off += 32;
240
+ return buf;
241
+ }
242
+ function computeMatMulHeaderHash(input) {
243
+ return sha256(serializeMatMulHeader(input));
244
+ }
245
+ function deriveSigma(input) {
246
+ const headerHash = computeMatMulHeaderHash(input);
247
+ const sigmaRaw = sha256(headerHash);
248
+ const sigmaBE = new Uint8Array(32);
249
+ for (let i = 0; i < 32; i++) sigmaBE[i] = sigmaRaw[31 - i];
250
+ return sigmaBE;
251
+ }
252
+ function parseUint256HexToLE(hex, field) {
253
+ const beBytes = parseHexFixed(hex, 32, field);
254
+ const leBytes = new Uint8Array(32);
255
+ for (let i = 0; i < 32; i++) leBytes[i] = beBytes[31 - i];
256
+ return leBytes;
257
+ }
258
+ function parseHexFixed(hex, byteLen, field) {
259
+ const h = hex.startsWith("0x") || hex.startsWith("0X") ? hex.slice(2) : hex;
260
+ if (h.length !== byteLen * 2) {
261
+ throw new Error(
262
+ `header.${field}: expected ${byteLen * 2} hex chars (${byteLen} bytes), got ${h.length}`
263
+ );
264
+ }
265
+ const out = new Uint8Array(byteLen);
266
+ for (let i = 0; i < byteLen; i++) {
267
+ const byte = parseInt(h.slice(i * 2, i * 2 + 2), 16);
268
+ if (Number.isNaN(byte)) {
269
+ throw new Error(`header.${field}: invalid hex at byte ${i}`);
270
+ }
271
+ out[i] = byte;
272
+ }
273
+ return out;
274
+ }
275
+ function parseBitsHex(bits) {
276
+ const h = bits.startsWith("0x") || bits.startsWith("0X") ? bits.slice(2) : bits;
277
+ if (h.length !== 8) {
278
+ throw new Error(`header.bits: expected 8 hex chars, got ${h.length}`);
279
+ }
280
+ const n = parseInt(h, 16);
281
+ if (Number.isNaN(n)) {
282
+ throw new Error(`header.bits: invalid hex`);
283
+ }
284
+ return n >>> 0;
285
+ }
286
+ function writeUint16LE(buf, offset, value) {
287
+ buf[offset] = value & 255;
288
+ buf[offset + 1] = value >>> 8 & 255;
289
+ }
290
+ function writeUint32LE(buf, offset, value) {
291
+ buf[offset] = value & 255;
292
+ buf[offset + 1] = value >>> 8 & 255;
293
+ buf[offset + 2] = value >>> 16 & 255;
294
+ buf[offset + 3] = value >>> 24 & 255;
295
+ }
296
+ function writeUint64LE(buf, offset, value) {
297
+ const lo = Number(value & 0xffffffffn);
298
+ const hi = Number(value >> 32n & 0xffffffffn);
299
+ writeUint32LE(buf, offset, lo);
300
+ writeUint32LE(buf, offset + 4, hi);
301
+ }
302
+
303
+ // src/matmul/constants.ts
304
+ var M31_MODULUS = 2147483647;
305
+ var NOISE_TAG_EL = "matmul_noise_EL_v1";
306
+ var NOISE_TAG_ER = "matmul_noise_ER_v1";
307
+ var NOISE_TAG_FL = "matmul_noise_FL_v1";
308
+ var NOISE_TAG_FR = "matmul_noise_FR_v1";
309
+ var TRANSCRIPT_COMPRESS_TAG = "matmul-compress-v1";
310
+
311
+ // src/matmul/field.ts
312
+ var MODULUS_BIG = BigInt(M31_MODULUS);
313
+ function reduce64(x) {
314
+ let v = (x & MODULUS_BIG) + (x >> 31n);
315
+ v = (v & MODULUS_BIG) + (v >> 31n);
316
+ let result = Number(v);
317
+ if (result >= M31_MODULUS) result -= M31_MODULUS;
318
+ return result;
319
+ }
320
+ function add(a, b) {
321
+ const s = a + b;
322
+ return s >= M31_MODULUS ? s - M31_MODULUS : s;
323
+ }
324
+ function fromOracle(seed, index) {
325
+ if (seed.length !== 32) {
326
+ throw new Error(`field.fromOracle: seed must be 32 bytes, got ${seed.length}`);
327
+ }
328
+ const indexLe = new Uint8Array(4);
329
+ writeUint32LE2(indexLe, 0, index >>> 0);
330
+ for (let retry = 0; retry < 256; retry++) {
331
+ const hasher = sha256.create();
332
+ hasher.update(seed);
333
+ hasher.update(indexLe);
334
+ if (retry > 0) {
335
+ const retryLe = new Uint8Array(4);
336
+ writeUint32LE2(retryLe, 0, retry);
337
+ hasher.update(retryLe);
338
+ }
339
+ const hash2 = hasher.digest();
340
+ const candidate = readUint32LE(hash2, 0) & M31_MODULUS;
341
+ if (candidate < M31_MODULUS) return candidate;
342
+ }
343
+ const fallback = sha256.create();
344
+ fallback.update(seed);
345
+ fallback.update(indexLe);
346
+ fallback.update(new TextEncoder().encode("oracle-fallback"));
347
+ const hash = fallback.digest();
348
+ return readUint32LE(hash, 0) % M31_MODULUS;
349
+ }
350
+ function dot(a, b, len) {
351
+ let acc = 0n;
352
+ let pending = 0;
353
+ for (let i = 0; i < len; i++) {
354
+ acc += BigInt(a[i]) * BigInt(b[i]);
355
+ if (++pending === 4) {
356
+ acc = (acc & MODULUS_BIG) + (acc >> 31n);
357
+ acc = (acc & MODULUS_BIG) + (acc >> 31n);
358
+ pending = 0;
359
+ }
360
+ }
361
+ return reduce64(acc);
362
+ }
363
+ function writeUint32LE2(buf, offset, value) {
364
+ buf[offset] = value & 255;
365
+ buf[offset + 1] = value >>> 8 & 255;
366
+ buf[offset + 2] = value >>> 16 & 255;
367
+ buf[offset + 3] = value >>> 24 & 255;
368
+ }
369
+ function readUint32LE(buf, offset) {
370
+ return (buf[offset] | buf[offset + 1] << 8 | buf[offset + 2] << 16 | buf[offset + 3] << 24) >>> 0;
371
+ }
372
+
373
+ // src/matmul/matrix.ts
374
+ function zeros(rows, cols) {
375
+ return { rows, cols, data: new Uint32Array(rows * cols) };
376
+ }
377
+ function fromSeedRect(seed, rows, cols) {
378
+ const m = zeros(rows, cols);
379
+ const total = rows * cols;
380
+ for (let i = 0; i < total; i++) {
381
+ m.data[i] = fromOracle(seed, i);
382
+ }
383
+ return m;
384
+ }
385
+ function matAdd(a, b) {
386
+ if (a.rows !== b.rows || a.cols !== b.cols) {
387
+ throw new Error(
388
+ `matAdd: dim mismatch a=${a.rows}x${a.cols} b=${b.rows}x${b.cols}`
389
+ );
390
+ }
391
+ const out = zeros(a.rows, a.cols);
392
+ for (let i = 0; i < a.data.length; i++) {
393
+ out.data[i] = add(a.data[i], b.data[i]);
394
+ }
395
+ return out;
396
+ }
397
+ function matMul(a, b) {
398
+ if (a.cols !== b.rows) {
399
+ throw new Error(
400
+ `matMul: inner-dim mismatch a.cols=${a.cols} b.rows=${b.rows}`
401
+ );
402
+ }
403
+ const out = zeros(a.rows, b.cols);
404
+ const rowBuf = new Uint32Array(a.cols);
405
+ const colBuf = new Uint32Array(a.cols);
406
+ for (let i = 0; i < a.rows; i++) {
407
+ for (let k = 0; k < a.cols; k++) rowBuf[k] = a.data[i * a.cols + k];
408
+ for (let j = 0; j < b.cols; j++) {
409
+ for (let k = 0; k < a.cols; k++) colBuf[k] = b.data[k * b.cols + j];
410
+ out.data[i * b.cols + j] = dot(rowBuf, colBuf, a.cols);
411
+ }
412
+ }
413
+ return out;
414
+ }
415
+ function deriveNoiseSeed(domainTag, sigmaBE) {
416
+ if (sigmaBE.length !== 32) {
417
+ throw new Error(`deriveNoiseSeed: sigma must be 32 bytes, got ${sigmaBE.length}`);
418
+ }
419
+ if (domainTag.length !== 18) {
420
+ throw new Error(`deriveNoiseSeed: domain tag must be 18 chars, got ${domainTag.length}`);
421
+ }
422
+ const hasher = sha256.create();
423
+ hasher.update(new TextEncoder().encode(domainTag));
424
+ hasher.update(sigmaBE);
425
+ return hasher.digest();
426
+ }
427
+ function generate(sigmaBE, n, r) {
428
+ const seedEL = deriveNoiseSeed(NOISE_TAG_EL, sigmaBE);
429
+ const seedER = deriveNoiseSeed(NOISE_TAG_ER, sigmaBE);
430
+ const seedFL = deriveNoiseSeed(NOISE_TAG_FL, sigmaBE);
431
+ const seedFR = deriveNoiseSeed(NOISE_TAG_FR, sigmaBE);
432
+ return {
433
+ E_L: fromSeedRect(seedEL, n, r),
434
+ E_R: fromSeedRect(seedER, r, n),
435
+ F_L: fromSeedRect(seedFL, n, r),
436
+ F_R: fromSeedRect(seedFR, r, n)
437
+ };
438
+ }
439
+ function deriveCompressionSeed(sigmaBE) {
440
+ const hasher = sha256.create();
441
+ hasher.update(new TextEncoder().encode(TRANSCRIPT_COMPRESS_TAG));
442
+ hasher.update(sigmaBE);
443
+ return hasher.digest();
444
+ }
445
+ function deriveCompressionVector(sigmaBE, b) {
446
+ if (b <= 0) throw new Error("block size b must be positive");
447
+ const seed = deriveCompressionSeed(sigmaBE);
448
+ const len = b * b;
449
+ const vec = new Uint32Array(len);
450
+ for (let k = 0; k < len; k++) vec[k] = fromOracle(seed, k);
451
+ return vec;
452
+ }
453
+ function compressBlock(block, v) {
454
+ if (block.length !== v.length) {
455
+ throw new Error(
456
+ `compressBlock: dim mismatch block.length=${block.length} v.length=${v.length}`
457
+ );
458
+ }
459
+ return dot(block, v, block.length);
460
+ }
461
+ var TranscriptHasher = class {
462
+ hasher = sha256.create();
463
+ compressVec;
464
+ b;
465
+ le4 = new Uint8Array(4);
466
+ constructor(sigmaBE, b) {
467
+ this.b = b;
468
+ this.compressVec = deriveCompressionVector(sigmaBE, b);
469
+ }
470
+ /**
471
+ * Append the LE32-encoded compressed `c_block` to the transcript.
472
+ * Ignores `i`/`j`/`ell` for hashing (btxd's `(void)i;(void)j;(void)ell;`
473
+ * proves they don't participate in the digest), but accepts them so the
474
+ * call-site reads identically to the C++.
475
+ */
476
+ addIntermediate(_i, _j, _ell, cBlock) {
477
+ if (cBlock.length !== this.b * this.b) {
478
+ throw new Error(
479
+ `addIntermediate: c_block must be ${this.b * this.b} elements, got ${cBlock.length}`
480
+ );
481
+ }
482
+ const compressed = compressBlock(cBlock, this.compressVec);
483
+ this.le4[0] = compressed & 255;
484
+ this.le4[1] = compressed >>> 8 & 255;
485
+ this.le4[2] = compressed >>> 16 & 255;
486
+ this.le4[3] = compressed >>> 24 & 255;
487
+ this.hasher.update(this.le4);
488
+ }
489
+ /** SHA-256d: inner = SHA256(transcript); return SHA256(inner). */
490
+ finalize() {
491
+ const inner = this.hasher.digest();
492
+ return sha256(inner);
493
+ }
494
+ };
495
+ function canonicalMatMul(aPrime, bPrime, b, sigmaBE) {
496
+ if (aPrime.rows !== aPrime.cols || bPrime.rows !== bPrime.cols || aPrime.rows !== bPrime.rows) {
497
+ throw new Error("canonicalMatMul requires square matrices of equal size");
498
+ }
499
+ if (b === 0 || aPrime.rows % b !== 0) {
500
+ throw new Error("canonicalMatMul: invalid transcript block size");
501
+ }
502
+ const n = aPrime.rows;
503
+ const N = n / b;
504
+ const cPrime = zeros(n, n);
505
+ const hasher = new TranscriptHasher(sigmaBE, b);
506
+ const aBlock = new Uint32Array(b * b);
507
+ const bBlock = new Uint32Array(b * b);
508
+ const cBlock = new Uint32Array(b * b);
509
+ for (let i = 0; i < N; i++) {
510
+ for (let j = 0; j < N; j++) {
511
+ cBlock.fill(0);
512
+ for (let ell = 0; ell < N; ell++) {
513
+ readBlock(aPrime, i, ell, b, aBlock);
514
+ readBlock(bPrime, ell, j, b, bBlock);
515
+ multiplyAndAccumulateBlock(aBlock, bBlock, cBlock, b);
516
+ hasher.addIntermediate(i, j, ell, cBlock);
517
+ }
518
+ writeBlock(cPrime, i, j, b, cBlock);
519
+ }
520
+ }
521
+ return { cPrime, transcriptHash: hasher.finalize() };
522
+ }
523
+ function readBlock(m, bi, bj, b, out) {
524
+ const rowStart = bi * b;
525
+ const colStart = bj * b;
526
+ const stride = m.cols;
527
+ for (let r = 0; r < b; r++) {
528
+ const src = (rowStart + r) * stride + colStart;
529
+ const dst = r * b;
530
+ for (let c = 0; c < b; c++) {
531
+ out[dst + c] = m.data[src + c];
532
+ }
533
+ }
534
+ }
535
+ function writeBlock(m, bi, bj, b, block) {
536
+ const rowStart = bi * b;
537
+ const colStart = bj * b;
538
+ const stride = m.cols;
539
+ for (let r = 0; r < b; r++) {
540
+ const dst = (rowStart + r) * stride + colStart;
541
+ const src = r * b;
542
+ for (let c = 0; c < b; c++) {
543
+ m.data[dst + c] = block[src + c];
544
+ }
545
+ }
546
+ }
547
+ function multiplyAndAccumulateBlock(a, b_buf, c, b) {
548
+ const colBuf = new Uint32Array(b);
549
+ for (let j = 0; j < b; j++) {
550
+ for (let k = 0; k < b; k++) colBuf[k] = b_buf[k * b + j];
551
+ for (let i = 0; i < b; i++) {
552
+ const rowStart = i * b;
553
+ let acc = 0n;
554
+ let pending = 0;
555
+ for (let k = 0; k < b; k++) {
556
+ acc += BigInt(a[rowStart + k]) * BigInt(colBuf[k]);
557
+ if (++pending === 4) {
558
+ acc = (acc & MOD_BIG) + (acc >> 31n);
559
+ acc = (acc & MOD_BIG) + (acc >> 31n);
560
+ pending = 0;
561
+ }
562
+ }
563
+ acc = (acc & MOD_BIG) + (acc >> 31n);
564
+ acc = (acc & MOD_BIG) + (acc >> 31n);
565
+ let folded = Number(acc);
566
+ if (folded >= MOD_NUM) folded -= MOD_NUM;
567
+ c[rowStart + j] = add(c[rowStart + j], folded);
568
+ }
569
+ }
570
+ }
571
+ var MOD_NUM = 2147483647;
572
+ var MOD_BIG = 0x7fffffffn;
573
+
574
+ // src/matmul/pow.ts
575
+ var MAX_U64 = (1n << 64n) - 1n;
576
+ function solveJs(challenge, options = {}) {
577
+ const payload = challenge.challenge;
578
+ const ctx = payload.header_context;
579
+ const { n, b, r } = payload.matmul;
580
+ const maxTries = options.maxTries ?? 1e6;
581
+ const nonceStart = options.nonceStart ?? BigInt(ctx.nonce64_start ?? 0);
582
+ const attemptInterval = options.attemptInterval ?? 1;
583
+ if (n <= 0 || b <= 0 || r <= 0) {
584
+ throw new Error(`solveJs: invalid matmul params (n=${n}, b=${b}, r=${r})`);
585
+ }
586
+ if (n % b !== 0) {
587
+ throw new Error(`solveJs: n=${n} not divisible by b=${b}`);
588
+ }
589
+ const seedA = parseHex32(payload.matmul.seed_a, "seed_a");
590
+ const seedB = parseHex32(payload.matmul.seed_b, "seed_b");
591
+ const targetBE = parseHex32(payload.target, "target");
592
+ const targetValue = bytesBEToBigInt(targetBE);
593
+ const A = fromSeedRect(seedA, n, n);
594
+ const B = fromSeedRect(seedB, n, n);
595
+ let nonce = nonceStart & MAX_U64;
596
+ for (let attempt = 0; attempt < maxTries; attempt++) {
597
+ if (options.onAttempt && attempt % attemptInterval === 0) {
598
+ options.onAttempt(attempt, nonce);
599
+ }
600
+ const headerInput = headerInputForNonce(ctx, nonce);
601
+ const sigmaBE = deriveSigma(headerInput);
602
+ const noise = generate(sigmaBE, n, r);
603
+ const E = matMul(noise.E_L, noise.E_R);
604
+ const F = matMul(noise.F_L, noise.F_R);
605
+ const aPrime = matAdd(A, E);
606
+ const bPrime = matAdd(B, F);
607
+ const result = canonicalMatMul(aPrime, bPrime, b, sigmaBE);
608
+ const digestValue = bytesLEToBigInt(result.transcriptHash);
609
+ if (digestValue <= targetValue) {
610
+ const nonce64_hex = nonce.toString(16).padStart(16, "0");
611
+ const digest_hex = bytesLEToHexBE(result.transcriptHash);
612
+ return {
613
+ nonce64_hex,
614
+ digest_hex,
615
+ // Same shape btxd's solve RPC returns: {challenge, nonce64_hex, digest_hex}.
616
+ // See `solvematmulservicechallenge` in btxd src/rpc/mining.cpp.
617
+ proof: { challenge, nonce64_hex, digest_hex }
618
+ };
619
+ }
620
+ if (nonce === MAX_U64) return null;
621
+ nonce = nonce + 1n & MAX_U64;
622
+ }
623
+ return null;
624
+ }
625
+ function parseHex32(hex, field) {
626
+ const h = hex.startsWith("0x") || hex.startsWith("0X") ? hex.slice(2) : hex;
627
+ if (h.length !== 64) {
628
+ throw new Error(`solveJs.${field}: expected 64 hex chars, got ${h.length}`);
629
+ }
630
+ const out = new Uint8Array(32);
631
+ for (let i = 0; i < 32; i++) {
632
+ const byte = parseInt(h.slice(i * 2, i * 2 + 2), 16);
633
+ if (Number.isNaN(byte)) {
634
+ throw new Error(`solveJs.${field}: invalid hex at byte ${i}`);
635
+ }
636
+ out[i] = byte;
637
+ }
638
+ return out;
639
+ }
640
+ function bytesBEToBigInt(bytes) {
641
+ let v = 0n;
642
+ for (let i = 0; i < bytes.length; i++) {
643
+ v = v << 8n | BigInt(bytes[i]);
644
+ }
645
+ return v;
646
+ }
647
+ function bytesLEToBigInt(bytes) {
648
+ let v = 0n;
649
+ for (let i = bytes.length - 1; i >= 0; i--) {
650
+ v = v << 8n | BigInt(bytes[i]);
651
+ }
652
+ return v;
653
+ }
654
+ function bytesLEToHexBE(bytes) {
655
+ const chars = [];
656
+ for (let i = bytes.length - 1; i >= 0; i--) {
657
+ chars.push(bytes[i].toString(16).padStart(2, "0"));
658
+ }
659
+ return chars.join("");
660
+ }
661
+
662
+ // src/solver.ts
663
+ var Solver = class {
664
+ static async solve(challenge, opts = {}) {
665
+ const mode = opts.mode ?? (opts.rpcClient ? "rpc" : "pure-js");
666
+ switch (mode) {
667
+ case "rpc":
668
+ return solveViaRpc(challenge, opts);
669
+ case "pure-js":
670
+ return solveViaPureJs(challenge, opts);
671
+ case "auto":
672
+ if (opts.rpcClient) return solveViaRpc(challenge, opts);
673
+ return solveViaPureJs(challenge, opts);
674
+ default: {
675
+ const _exhaustive = mode;
676
+ throw new Error(`Solver.solve: unknown mode "${_exhaustive}"`);
677
+ }
678
+ }
679
+ }
680
+ };
681
+ async function solveViaRpc(challenge, opts) {
682
+ if (!opts.rpcClient) {
683
+ throw new Error(
684
+ 'Solver.solve: mode="rpc" requires opts.rpcClient. Construct a BtxChallengeClient and pass it via opts.rpcClient.'
685
+ );
686
+ }
687
+ return opts.rpcClient.solve(challenge);
688
+ }
689
+ async function solveViaPureJs(challenge, opts) {
690
+ const result = solveJs(challenge, opts.pureJs);
691
+ if (result === null) {
692
+ const tries = opts.pureJs?.maxTries ?? 1e6;
693
+ throw new Error(
694
+ `Solver.solve: pure-JS solver exhausted maxTries=${tries} without finding a proof. Increase maxTries or lower challenge difficulty (target_solve_time_s).`
695
+ );
696
+ }
697
+ return result;
698
+ }
699
+
700
+ export { BtxChallengeClient, BtxError, BtxHttpError, BtxNetworkError, BtxParseError, BtxRpcError, BtxTimeoutError, Solver };
701
+ //# sourceMappingURL=index.js.map
702
+ //# sourceMappingURL=index.js.map