@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.
@@ -0,0 +1,406 @@
1
+ /**
2
+ * Type definitions mirroring the BTX service-challenges RPC schema.
3
+ *
4
+ * Source: btx.dev/docs/rpc/service-challenges (verified 2026-05-20).
5
+ * Schema captured against live btxd v0.29.7 at btx-iowa (block 106270).
6
+ *
7
+ * IMPORTANT: We do NOT camelCase rename — the RPC schema is the wire contract.
8
+ * Field names mirror btxd output verbatim.
9
+ */
10
+ /** Difficulty calibration policy for issued challenges. */
11
+ type DifficultyPolicy = 'fixed' | 'adaptive_window';
12
+ /**
13
+ * Standard purpose labels recognized by the chain. Free-form strings allowed too.
14
+ *
15
+ * The `(string & {})` trick keeps autocomplete on the known labels while still
16
+ * accepting arbitrary purpose strings. Don't "simplify" to plain `string` — it
17
+ * loses the autocompletion benefit. See: https://github.com/microsoft/TypeScript/issues/29729
18
+ */
19
+ type ChallengePurpose = 'rate_limit' | 'api_gate' | 'ai_inference_gate' | (string & {});
20
+ /** Parameters for issuing a challenge via getmatmulservicechallenge. */
21
+ interface IssueParams {
22
+ purpose: ChallengePurpose;
23
+ resource: string;
24
+ subject: string;
25
+ /** Calibrates client compute work to roughly this many seconds. btxd default: 1.0. */
26
+ target_solve_time_s?: number;
27
+ /** Challenge lifetime in seconds. btxd default: 300. Range 1–86400. */
28
+ expires_in_s?: number;
29
+ validation_overhead_s?: number;
30
+ propagation_overhead_s?: number;
31
+ /** btxd default: "fixed". */
32
+ difficulty_policy?: DifficultyPolicy;
33
+ difficulty_window_blocks?: number;
34
+ min_solve_time_s?: number;
35
+ max_solve_time_s?: number;
36
+ solver_parallelism?: number;
37
+ solver_duty_cycle_pct?: number;
38
+ }
39
+ /**
40
+ * Binding identifies the (purpose, resource, subject) trio the challenge is
41
+ * scoped to. btxd also embeds hashes + anchor info for replay protection.
42
+ */
43
+ interface ChallengeBinding {
44
+ chain: string;
45
+ purpose: string;
46
+ resource: string;
47
+ subject: string;
48
+ resource_hash: string;
49
+ subject_hash: string;
50
+ salt: string;
51
+ anchor_height: number;
52
+ anchor_hash: string;
53
+ /** Hashing rule used to compute challenge_id. Treat as opaque docstring from btxd. */
54
+ challenge_id_rule?: string;
55
+ seed_derivation_rule?: string;
56
+ /** Open extension — btxd may add fields without breaking us. */
57
+ [k: string]: unknown;
58
+ }
59
+ /**
60
+ * Proof policy describes what btxd will check when redeeming. Treat fields
61
+ * as authoritative — do NOT re-derive verification rules client-side.
62
+ */
63
+ interface ChallengeProofPolicy {
64
+ verification_rule: string;
65
+ sigma_gate_applied: boolean;
66
+ expiration_enforced: boolean;
67
+ challenge_id_required: boolean;
68
+ replay_protection: string;
69
+ redeem_rpc: string;
70
+ solve_rpc: string;
71
+ locally_issued_required: boolean;
72
+ issued_challenge_store?: string;
73
+ issued_challenge_scope?: string;
74
+ [k: string]: unknown;
75
+ }
76
+ /** MatMul parameters needed to solve. Day 2 solver consumes these. */
77
+ interface ChallengeMatmul {
78
+ /** Matrix dimension. btxd ships n=512 in production. */
79
+ n: number;
80
+ /** Block dimension for compression. */
81
+ b: number;
82
+ /** Noise rank. */
83
+ r: number;
84
+ /** Field modulus (Mersenne prime 2^31-1). */
85
+ q: number;
86
+ min_dimension: number;
87
+ max_dimension: number;
88
+ /** Hex-encoded seed for matrix A. */
89
+ seed_a: string;
90
+ /** Hex-encoded seed for matrix B. */
91
+ seed_b: string;
92
+ }
93
+ /** Block header context the challenge is anchored to. */
94
+ interface ChallengeHeaderContext {
95
+ version: number;
96
+ previousblockhash: string;
97
+ merkleroot: string;
98
+ time: number;
99
+ bits: string;
100
+ nonce64_start: number;
101
+ matmul_dim: number;
102
+ seed_a: string;
103
+ seed_b: string;
104
+ }
105
+ /**
106
+ * Inner challenge payload — what the solver actually needs.
107
+ * The Day 2 MatMul solver reads `matmul`, `target`, `noncerange`, and `header_context`.
108
+ */
109
+ interface ChallengePayload {
110
+ chain: string;
111
+ algorithm: string;
112
+ height: number;
113
+ previousblockhash: string;
114
+ mintime: number;
115
+ bits: string;
116
+ difficulty: number;
117
+ /** Hex-encoded target. Digest must compare ≤ target for valid proof. */
118
+ target: string;
119
+ noncerange: string;
120
+ header_context: ChallengeHeaderContext;
121
+ matmul: ChallengeMatmul;
122
+ /** btxd ships additional fields (work_profile, runtime_observability, etc.) we treat as opaque. */
123
+ [k: string]: unknown;
124
+ }
125
+ /**
126
+ * The challenge envelope returned by getmatmulservicechallenge.
127
+ * Opaque to the SDK at the wire boundary — pass through to solver / redeem.
128
+ */
129
+ interface Challenge {
130
+ /** btxd schema kind discriminator (e.g. "matmul_service_challenge_v1"). */
131
+ kind?: string;
132
+ challenge_id: string;
133
+ issued_at: number;
134
+ expires_at: number;
135
+ expires_in_s: number;
136
+ binding: ChallengeBinding;
137
+ proof_policy: ChallengeProofPolicy;
138
+ challenge: ChallengePayload;
139
+ /** Open extension. */
140
+ [k: string]: unknown;
141
+ }
142
+ /** Reasons returned by verify/redeem. Open string — btxd may add more. */
143
+ type VerifyReason = 'ok' | 'invalid_proof' | 'challenge_mismatch' | 'expired' | 'unknown_challenge' | 'already_redeemed' | 'missing_proof' | (string & {});
144
+ /** Outcome of verifymatmulserviceproof / redeemmatmulserviceproof. */
145
+ interface VerifyResult {
146
+ valid: boolean;
147
+ expired?: boolean;
148
+ reason: VerifyReason;
149
+ issued_by_local_node?: boolean;
150
+ redeemed?: boolean;
151
+ redeemable?: boolean;
152
+ mismatch_field?: string;
153
+ }
154
+ /** Single entry in a batch redeem/verify call. */
155
+ interface BatchEntry {
156
+ challenge: Challenge;
157
+ nonce64_hex: string;
158
+ digest_hex: string;
159
+ }
160
+ /** Batch response — sequential per-proof results. */
161
+ interface BatchResult {
162
+ count: number;
163
+ valid: number;
164
+ invalid: number;
165
+ by_reason: Record<string, number>;
166
+ results: VerifyResult[];
167
+ }
168
+ /** Solver output (also returned by solvematmulservicechallenge RPC). */
169
+ interface SolverOutput {
170
+ nonce64_hex: string;
171
+ digest_hex: string;
172
+ proof: Record<string, unknown>;
173
+ }
174
+ /** RPC client configuration. */
175
+ interface BtxClientOpts {
176
+ /**
177
+ * Full JSON-RPC endpoint, e.g. `http://127.0.0.1:19332`.
178
+ *
179
+ * **Security**: use HTTPS (or a localhost-only deployment) when the RPC
180
+ * endpoint is not on `127.0.0.1`. Basic auth over plaintext exposes credentials.
181
+ * Terminate TLS at stunnel/nginx/Caddy in front of btxd; do NOT expose
182
+ * btxd's RPC port directly to the public internet.
183
+ */
184
+ rpcUrl: string;
185
+ /** Basic-auth credentials matching btxd's rpcauth / rpcuser+rpcpassword. */
186
+ rpcAuth: {
187
+ user: string;
188
+ pass: string;
189
+ };
190
+ /** Request timeout in ms (default 30000). */
191
+ timeoutMs?: number;
192
+ }
193
+ /** Base class — all SDK errors extend this for `instanceof BtxError` checks. */
194
+ declare class BtxError extends Error {
195
+ readonly method?: string | undefined;
196
+ constructor(message: string, method?: string | undefined);
197
+ }
198
+ /** btxd returned a JSON-RPC error envelope with code + message. */
199
+ declare class BtxRpcError extends BtxError {
200
+ readonly code: number;
201
+ constructor(code: number, message: string, method?: string);
202
+ }
203
+ /** btxd returned a non-2xx HTTP status. */
204
+ declare class BtxHttpError extends BtxError {
205
+ readonly status: number;
206
+ /** Response body, with `Authorization: Basic ...` patterns redacted. */
207
+ readonly body: string;
208
+ constructor(status: number,
209
+ /** Response body, with `Authorization: Basic ...` patterns redacted. */
210
+ body: string, method?: string);
211
+ }
212
+ /** The HTTP response was 2xx but the body wasn't valid JSON. */
213
+ declare class BtxParseError extends BtxError {
214
+ /** Underlying SyntaxError or similar. Overrides the ES2022 `Error.cause` slot. */
215
+ readonly cause: unknown;
216
+ /** Raw response body (redacted). */
217
+ readonly body: string;
218
+ constructor(
219
+ /** Underlying SyntaxError or similar. Overrides the ES2022 `Error.cause` slot. */
220
+ cause: unknown,
221
+ /** Raw response body (redacted). */
222
+ body: string, method?: string);
223
+ }
224
+ /** Request exceeded `timeoutMs`. */
225
+ declare class BtxTimeoutError extends BtxError {
226
+ readonly timeoutMs: number;
227
+ constructor(timeoutMs: number, method?: string);
228
+ }
229
+ /** Transport-level failure (DNS, TCP reset, TLS, etc.). Wraps the underlying cause. */
230
+ declare class BtxNetworkError extends BtxError {
231
+ /** Underlying error from fetch / dns / tls. Overrides the ES2022 `Error.cause` slot. */
232
+ readonly cause: unknown;
233
+ constructor(
234
+ /** Underlying error from fetch / dns / tls. Overrides the ES2022 `Error.cause` slot. */
235
+ cause: unknown, method?: string);
236
+ }
237
+
238
+ /**
239
+ * JSON-RPC client for BTX service-challenges.
240
+ *
241
+ * Wraps the 5 core RPCs:
242
+ * - getmatmulservicechallenge (issue)
243
+ * - verifymatmulserviceproof (verify, stateless)
244
+ * - redeemmatmulserviceproof (verify + consume, anti-replay)
245
+ * - verifymatmulserviceproofs (batch verify)
246
+ * - redeemmatmulserviceproofs (batch redeem)
247
+ *
248
+ * Plus helper RPCs:
249
+ * - solvematmulservicechallenge (server-side solver — for fixtures + tests)
250
+ *
251
+ * RPC reference: https://btx.dev/docs/rpc/service-challenges
252
+ *
253
+ * Error model:
254
+ * - {@link BtxRpcError} — btxd returned a JSON-RPC error envelope
255
+ * - {@link BtxHttpError} — non-2xx HTTP status
256
+ * - {@link BtxParseError} — 2xx but body wasn't valid JSON
257
+ * - {@link BtxTimeoutError} — request exceeded `timeoutMs`
258
+ * - {@link BtxNetworkError} — DNS / TCP / TLS-level failure
259
+ * - All extend {@link BtxError}.
260
+ */
261
+ declare class BtxChallengeClient {
262
+ private readonly opts;
263
+ constructor(opts: BtxClientOpts);
264
+ /** Low-level: raw JSON-RPC call. Exposed for forward compatibility. */
265
+ call<T = unknown>(method: string, params?: unknown[]): Promise<T>;
266
+ /** Issue a fresh challenge bound to (purpose, resource, subject). */
267
+ issue(params: IssueParams): Promise<Challenge>;
268
+ /**
269
+ * Verify a proof statelessly. Does NOT consume the challenge.
270
+ * Use this for diagnostic / monitoring paths.
271
+ * For admission control, use {@link redeem} instead — verification alone
272
+ * does not prevent replay.
273
+ */
274
+ verify(challenge: Challenge, nonce64_hex: string, digest_hex: string, lookup_local_status?: boolean): Promise<VerifyResult>;
275
+ /**
276
+ * Verify-and-consume atomically. THIS is the admission control entry point.
277
+ * On success, the challenge is marked redeemed and cannot be replayed.
278
+ */
279
+ redeem(challenge: Challenge, nonce64_hex: string, digest_hex: string): Promise<VerifyResult>;
280
+ /** Batch verify. Spec range 1–256 (audit M2). No consumption. */
281
+ verifyBatch(entries: BatchEntry[]): Promise<BatchResult>;
282
+ /** Batch verify + consume. Sequential per-entry. Spec range 1–256 (audit M2). */
283
+ redeemBatch(entries: BatchEntry[]): Promise<BatchResult>;
284
+ /**
285
+ * Server-side local solver. Useful when generating fixtures or pre-computing
286
+ * for tests. For production browser-side solving, ship the WASM solver —
287
+ * RPC-based solving puts compute load on YOUR node, defeating the point.
288
+ */
289
+ solve(challenge: Challenge): Promise<SolverOutput>;
290
+ private assertBatchSize;
291
+ }
292
+
293
+ /**
294
+ * Top-level pure-JS solver for the BTX MatMul service-challenge PoW.
295
+ *
296
+ * Ported from `btxd v0.29.7 src/matmul/matmul_pow.cpp` lines 293-358 (`Solve`).
297
+ *
298
+ * A = FromSeed(seed_a, n)
299
+ * B = FromSeed(seed_b, n)
300
+ * for nonce in [start, start + maxTries):
301
+ * sigma = DeriveSigma(state(nonce))
302
+ * noise = noise::Generate(sigma, n, r)
303
+ * E = noise.E_L · noise.E_R
304
+ * F = noise.F_L · noise.F_R
305
+ * A' = A + E
306
+ * B' = B + F
307
+ * result = transcript::CanonicalMatMul(A', B', b, sigma)
308
+ * if uintLE(result.transcript_hash) <= uintBE(target): return success
309
+ *
310
+ * Comparison semantics: `transcript_hash` is the raw SHA-256d output, treated
311
+ * as a 256-bit integer in **little-endian byte order** (btxd's `uint256` ↔
312
+ * `arith_uint256` convention). `target` arrives as a BE hex string. Both are
313
+ * converted to `bigint` for the `<=` test.
314
+ */
315
+
316
+ /** Options for {@link solveJs}. */
317
+ interface SolveJsOptions {
318
+ /** Max nonces to try before giving up. Default 1_000_000. */
319
+ maxTries?: number;
320
+ /** Override the starting nonce (default: challenge.header_context.nonce64_start). */
321
+ nonceStart?: bigint;
322
+ /** Optional callback fired every N attempts for progress reporting. */
323
+ onAttempt?: (attemptIndex: number, nonce: bigint) => void;
324
+ /** Frequency of `onAttempt` calls (default every 1 attempt). */
325
+ attemptInterval?: number;
326
+ }
327
+
328
+ /**
329
+ * How a challenge should be solved.
330
+ *
331
+ * - `'rpc'` — delegate to btxd's `solvematmulservicechallenge`. Requires
332
+ * `opts.rpcClient`. Server-side / Node-only.
333
+ *
334
+ * - `'pure-js'` — solve locally with a pure-TypeScript MatMul implementation.
335
+ * Browser-compatible. Ports the canonical CPU path of btxd's matmul solver.
336
+ * At default difficulty + n=512, pure-JS solving is slow (perf is currently
337
+ * bounded by `BigInt`-based M31 multiplication; WASM/SIMD acceleration is
338
+ * planned for a future iteration).
339
+ *
340
+ * - `'auto'` — pick automatically: `'rpc'` if `opts.rpcClient` is provided,
341
+ * else `'pure-js'`.
342
+ */
343
+ type SolverMode = 'rpc' | 'pure-js' | 'auto';
344
+ /** Options for {@link Solver.solve}. */
345
+ interface SolverOptions {
346
+ /** Solve strategy. Default: `'auto'` (rpc if client provided, else pure-js). */
347
+ mode?: SolverMode;
348
+ /** Required for `mode === 'rpc'`. Ignored for `'pure-js'`. */
349
+ rpcClient?: BtxChallengeClient;
350
+ /** Forwarded to the pure-JS solver. Ignored for `'rpc'`. */
351
+ pureJs?: SolveJsOptions;
352
+ }
353
+ /**
354
+ * Solve a BTX service challenge to produce a (nonce, digest, proof) tuple
355
+ * that btxd will accept on redemption.
356
+ *
357
+ * **Modes**:
358
+ * - `'rpc'`: delegate to btxd's `solvematmulservicechallenge` RPC. Pass an
359
+ * authenticated `BtxChallengeClient` in `opts.rpcClient`. **Production
360
+ * note**: the solve RPC shares the matmul backend with block-template
361
+ * mining; consumers MUST point at a dedicated non-mining btxd, otherwise
362
+ * individual solves can queue behind mining work for 10+ minutes.
363
+ * - `'pure-js'`: solve locally with the ported TypeScript MatMul. Browser-
364
+ * compatible. Slower than `'rpc'` but no node required.
365
+ * - `'auto'`: prefers `'rpc'` if a client is provided, else `'pure-js'`.
366
+ *
367
+ * @example Server-side (Node, RPC mode)
368
+ * ```typescript
369
+ * import { BtxChallengeClient, Solver } from '@btx/challenges-sdk';
370
+ *
371
+ * const client = new BtxChallengeClient({
372
+ * rpcUrl: 'http://127.0.0.1:19332',
373
+ * rpcAuth: { user: 'rpcuser', pass: 'rpcpass' },
374
+ * });
375
+ *
376
+ * const challenge = await client.issue({
377
+ * purpose: 'ai_inference_gate',
378
+ * resource: 'model:gpt-x|route:/v1/generate',
379
+ * subject: 'tenant:abc123',
380
+ * });
381
+ *
382
+ * const proof = await Solver.solve(challenge, { mode: 'rpc', rpcClient: client });
383
+ * const result = await client.redeem(challenge, proof.nonce64_hex, proof.digest_hex);
384
+ *
385
+ * if (result.valid && result.reason === 'ok') {
386
+ * // Admit the caller.
387
+ * }
388
+ * ```
389
+ *
390
+ * @example Browser-side (pure-JS mode)
391
+ * ```typescript
392
+ * import { Solver } from '@btx/challenges-sdk';
393
+ *
394
+ * // Solve a challenge with no server-side help. Slow at default difficulty;
395
+ * // for production browser use cases, calibrate via `target_solve_time_s`.
396
+ * const proof = await Solver.solve(challenge, {
397
+ * mode: 'pure-js',
398
+ * pureJs: { maxTries: 100_000 },
399
+ * });
400
+ * ```
401
+ */
402
+ declare class Solver {
403
+ static solve(challenge: Challenge, opts?: SolverOptions): Promise<SolverOutput>;
404
+ }
405
+
406
+ export { type BatchEntry, type BatchResult, BtxChallengeClient, type BtxClientOpts, BtxError, BtxHttpError, BtxNetworkError, BtxParseError, BtxRpcError, BtxTimeoutError, type Challenge, type ChallengeBinding, type ChallengeHeaderContext, type ChallengeMatmul, type ChallengePayload, type ChallengeProofPolicy, type ChallengePurpose, type DifficultyPolicy, type IssueParams, Solver, type SolverOutput, type VerifyReason, type VerifyResult };