@totemsdk/txpow 1.0.2 → 1.1.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.
Files changed (61) hide show
  1. package/LICENSE +202 -21
  2. package/NOTICE +60 -0
  3. package/dist/admission/challenge.js +14 -21
  4. package/dist/admission/commitment.js +9 -13
  5. package/dist/admission/index.js +6 -34
  6. package/dist/admission/mine.js +14 -17
  7. package/dist/admission/template.js +32 -47
  8. package/dist/admission/types.js +2 -5
  9. package/dist/admission/verify.js +12 -15
  10. package/dist/calibrate.js +4 -8
  11. package/dist/cjs/admission/challenge.d.ts +66 -0
  12. package/dist/cjs/admission/challenge.js +155 -0
  13. package/dist/cjs/admission/commitment.d.ts +34 -0
  14. package/dist/cjs/admission/commitment.js +73 -0
  15. package/dist/cjs/admission/index.d.ts +14 -0
  16. package/dist/cjs/admission/index.js +42 -0
  17. package/dist/cjs/admission/mine.d.ts +60 -0
  18. package/dist/cjs/admission/mine.js +146 -0
  19. package/dist/cjs/admission/template.d.ts +138 -0
  20. package/dist/cjs/admission/template.js +247 -0
  21. package/dist/cjs/admission/types.d.ts +224 -0
  22. package/dist/cjs/admission/types.js +24 -0
  23. package/dist/cjs/admission/verify.d.ts +50 -0
  24. package/dist/cjs/admission/verify.js +156 -0
  25. package/dist/cjs/calibrate.d.ts +35 -0
  26. package/dist/cjs/calibrate.js +68 -0
  27. package/dist/cjs/constants.d.ts +27 -0
  28. package/dist/cjs/constants.js +47 -0
  29. package/dist/cjs/difficulty.d.ts +44 -0
  30. package/dist/cjs/difficulty.js +111 -0
  31. package/dist/cjs/index.d.ts +28 -0
  32. package/dist/cjs/index.js +88 -0
  33. package/dist/cjs/magic.d.ts +19 -0
  34. package/dist/cjs/magic.js +35 -0
  35. package/dist/cjs/mine-wasm.d.ts +69 -0
  36. package/dist/cjs/mine-wasm.js +214 -0
  37. package/dist/cjs/mine.d.ts +131 -0
  38. package/dist/cjs/mine.js +407 -0
  39. package/dist/cjs/package.json +1 -0
  40. package/dist/cjs/serialization.d.ts +106 -0
  41. package/dist/cjs/serialization.js +160 -0
  42. package/dist/cjs/verify.d.ts +76 -0
  43. package/dist/cjs/verify.js +232 -0
  44. package/dist/cjs/wasm/miner.wasm +0 -0
  45. package/dist/cjs/workers/browser.worker.d.ts +26 -0
  46. package/dist/cjs/workers/browser.worker.js +63 -0
  47. package/dist/cjs/workers/node.worker.d.ts +33 -0
  48. package/dist/cjs/workers/node.worker.js +47 -0
  49. package/dist/constants.js +5 -8
  50. package/dist/difficulty.d.ts +24 -0
  51. package/dist/difficulty.js +54 -6
  52. package/dist/index.d.ts +1 -1
  53. package/dist/index.js +9 -62
  54. package/dist/magic.js +12 -15
  55. package/dist/mine-wasm.js +8 -15
  56. package/dist/mine.js +23 -31
  57. package/dist/serialization.js +28 -34
  58. package/dist/verify.js +15 -20
  59. package/dist/workers/browser.worker.js +4 -6
  60. package/dist/workers/node.worker.js +2 -3
  61. package/package.json +34 -15
@@ -1,4 +1,3 @@
1
- "use strict";
2
1
  /**
3
2
  * admission/template.ts — Minima block-candidate template handling.
4
3
  *
@@ -12,23 +11,9 @@
12
11
  * template provider. A real Minima node / Axia integration supplies live chain
13
12
  * state; tests supply fixtures.
14
13
  */
15
- Object.defineProperty(exports, "__esModule", { value: true });
16
- exports.serializeSuperParents = serializeSuperParents;
17
- exports.buildEmptyBurnTxBytes = buildEmptyBurnTxBytes;
18
- exports.buildEmptyBurnWitnessBytes = buildEmptyBurnWitnessBytes;
19
- exports.buildEmptyTransactionBytes = buildEmptyTransactionBytes;
20
- exports.buildEmptyWitnessBytes = buildEmptyWitnessBytes;
21
- exports.buildEmptyBlockBody = buildEmptyBlockBody;
22
- exports.assembleTxPoWEnvelope = assembleTxPoWEnvelope;
23
- exports.buildBlockHeaderTail = buildBlockHeaderTail;
24
- exports.computeBlockCandidateId = computeBlockCandidateId;
25
- exports.isBlockWinner = isBlockWinner;
26
- exports.computeSuperLevel = computeSuperLevel;
27
- exports.templateFreshness = templateFreshness;
28
- exports.reconstructTxPoWEnvelope = reconstructTxPoWEnvelope;
29
- const core_1 = require("@totemsdk/core");
30
- const core_2 = require("@totemsdk/core");
31
- const constants_js_1 = require("../constants.js");
14
+ import { sha3_256 } from '@totemsdk/core';
15
+ import { writeMiniNumber, writeMiniData, writeHashToStream, concat, } from '@totemsdk/core';
16
+ import { CASCADE_LEVELS } from '../constants.js';
32
17
  function hexToBytes(hex) {
33
18
  const h = hex.startsWith('0x') ? hex.slice(2) : hex;
34
19
  const out = new Uint8Array(h.length / 2);
@@ -46,9 +31,9 @@ function toHex(bytes) {
46
31
  * Consecutive equal hashes are coalesced into (count: MiniByte, hash) runs.
47
32
  * A fresh candidate with all-distinct parents serializes as 32 runs.
48
33
  */
49
- function serializeSuperParents(superParents) {
50
- if (superParents.length !== constants_js_1.CASCADE_LEVELS) {
51
- throw new Error(`superParents must have ${constants_js_1.CASCADE_LEVELS} entries, got ${superParents.length}`);
34
+ export function serializeSuperParents(superParents) {
35
+ if (superParents.length !== CASCADE_LEVELS) {
36
+ throw new Error(`superParents must have ${CASCADE_LEVELS} entries, got ${superParents.length}`);
52
37
  }
53
38
  const parts = [];
54
39
  let runHash = superParents[0];
@@ -59,14 +44,14 @@ function serializeSuperParents(superParents) {
59
44
  }
60
45
  else {
61
46
  parts.push(new Uint8Array([runCount]));
62
- parts.push((0, core_2.writeHashToStream)(hexToBytes(runHash)));
47
+ parts.push(writeHashToStream(hexToBytes(runHash)));
63
48
  runHash = superParents[i];
64
49
  runCount = 1;
65
50
  }
66
51
  }
67
52
  parts.push(new Uint8Array([runCount]));
68
- parts.push((0, core_2.writeHashToStream)(hexToBytes(runHash)));
69
- return (0, core_2.concat)(...parts);
53
+ parts.push(writeHashToStream(hexToBytes(runHash)));
54
+ return concat(...parts);
70
55
  }
71
56
  /**
72
57
  * Build the empty burn transaction bytes (TxBody.writeDataStream fields 5-6).
@@ -74,29 +59,29 @@ function serializeSuperParents(superParents) {
74
59
  * Equivalent to serializeTransaction({ linkHash: [0x00], inputs: [], outputs: [],
75
60
  * state: [] }): 0 inputs, 0 outputs, 0 state, linkHash = ZERO_TXPOWID (1 byte).
76
61
  */
77
- function buildEmptyBurnTxBytes() {
78
- return (0, core_2.concat)((0, core_2.writeMiniNumber)(0n, 0), (0, core_2.writeMiniNumber)(0n, 0), (0, core_2.writeMiniNumber)(0n, 0), (0, core_2.writeHashToStream)(new Uint8Array([0x00])));
62
+ export function buildEmptyBurnTxBytes() {
63
+ return concat(writeMiniNumber(0n, 0), writeMiniNumber(0n, 0), writeMiniNumber(0n, 0), writeHashToStream(new Uint8Array([0x00])));
79
64
  }
80
65
  /**
81
66
  * Build the empty burn witness bytes (TxBody.writeDataStream field 6).
82
67
  * 0 signatures, 0 coinproofs, 0 scriptproofs.
83
68
  */
84
- function buildEmptyBurnWitnessBytes() {
85
- return (0, core_2.concat)((0, core_2.writeMiniNumber)(0n, 0), (0, core_2.writeMiniNumber)(0n, 0), (0, core_2.writeMiniNumber)(0n, 0));
69
+ export function buildEmptyBurnWitnessBytes() {
70
+ return concat(writeMiniNumber(0n, 0), writeMiniNumber(0n, 0), writeMiniNumber(0n, 0));
86
71
  }
87
72
  /**
88
73
  * Build the empty transaction bytes (TxBody.writeDataStream field 3).
89
74
  * 0 inputs, 0 outputs, 0 state, linkHash = ZERO_TXPOWID (1 byte).
90
75
  */
91
- function buildEmptyTransactionBytes() {
92
- return (0, core_2.concat)((0, core_2.writeMiniNumber)(0n, 0), (0, core_2.writeMiniNumber)(0n, 0), (0, core_2.writeMiniNumber)(0n, 0), (0, core_2.writeHashToStream)(new Uint8Array([0x00])));
76
+ export function buildEmptyTransactionBytes() {
77
+ return concat(writeMiniNumber(0n, 0), writeMiniNumber(0n, 0), writeMiniNumber(0n, 0), writeHashToStream(new Uint8Array([0x00])));
93
78
  }
94
79
  /**
95
80
  * Build the empty witness bytes (TxBody.writeDataStream field 4).
96
81
  * 0 signatures, 0 coinproofs, 0 scriptproofs.
97
82
  */
98
- function buildEmptyWitnessBytes() {
99
- return (0, core_2.concat)((0, core_2.writeMiniNumber)(0n, 0), (0, core_2.writeMiniNumber)(0n, 0), (0, core_2.writeMiniNumber)(0n, 0));
83
+ export function buildEmptyWitnessBytes() {
84
+ return concat(writeMiniNumber(0n, 0), writeMiniNumber(0n, 0), writeMiniNumber(0n, 0));
100
85
  }
101
86
  /**
102
87
  * Build the serialized TxBody for a fresh block candidate.
@@ -110,8 +95,8 @@ function buildEmptyWitnessBytes() {
110
95
  * @param txnDifficulty Transaction difficulty (32-byte hex). For a block
111
96
  * candidate this is typically the block difficulty.
112
97
  */
113
- function buildEmptyBlockBody(prng, txnDifficulty) {
114
- return (0, core_2.concat)((0, core_2.writeHashToStream)(prng), (0, core_2.writeMiniData)(hexToBytes(txnDifficulty)), buildEmptyTransactionBytes(), buildEmptyWitnessBytes(), buildEmptyBurnTxBytes(), buildEmptyBurnWitnessBytes(), (0, core_2.writeMiniNumber)(0n, 0));
98
+ export function buildEmptyBlockBody(prng, txnDifficulty) {
99
+ return concat(writeHashToStream(prng), writeMiniData(hexToBytes(txnDifficulty)), buildEmptyTransactionBytes(), buildEmptyWitnessBytes(), buildEmptyBurnTxBytes(), buildEmptyBurnWitnessBytes(), writeMiniNumber(0n, 0));
115
100
  }
116
101
  /**
117
102
  * Assemble the complete Minima TxPoW wire format:
@@ -123,8 +108,8 @@ function buildEmptyBlockBody(prng, txnDifficulty) {
123
108
  * @param headerBytes Serialized TxHeader bytes (with the winning nonce).
124
109
  * @param bodyBytes Serialized TxBody bytes.
125
110
  */
126
- function assembleTxPoWEnvelope(headerBytes, bodyBytes) {
127
- return (0, core_2.concat)(headerBytes, new Uint8Array([0x01]), bodyBytes);
111
+ export function assembleTxPoWEnvelope(headerBytes, bodyBytes) {
112
+ return concat(headerBytes, new Uint8Array([0x01]), bodyBytes);
128
113
  }
129
114
  /**
130
115
  * Build the TxHeader tail (everything after the nonce field) for a block
@@ -138,21 +123,21 @@ function assembleTxPoWEnvelope(headerBytes, bodyBytes) {
138
123
  * @param customHash The action commitment (32-byte hex) to place in mCustomHash.
139
124
  * @param txBodyHash SHA3-256 of the serialized TxBody (32-byte hex).
140
125
  */
141
- function buildBlockHeaderTail(template, customHash, txBodyHash) {
142
- return (0, core_2.concat)((0, core_2.writeMiniData)(hexToBytes(template.chainId)), (0, core_2.writeMiniNumber)(template.timeMilli, 0), (0, core_2.writeMiniNumber)(template.blockNumber, 0), (0, core_2.writeMiniData)(hexToBytes(template.blockDifficulty)), serializeSuperParents(template.superParents), (0, core_2.writeHashToStream)(hexToBytes(template.mmrRoot)), (0, core_2.writeMiniNumber)(template.mmrTotal, 0), hexToBytes(template.magic), (0, core_2.writeHashToStream)(hexToBytes(customHash)), (0, core_2.writeHashToStream)(hexToBytes(txBodyHash)));
126
+ export function buildBlockHeaderTail(template, customHash, txBodyHash) {
127
+ return concat(writeMiniData(hexToBytes(template.chainId)), writeMiniNumber(template.timeMilli, 0), writeMiniNumber(template.blockNumber, 0), writeMiniData(hexToBytes(template.blockDifficulty)), serializeSuperParents(template.superParents), writeHashToStream(hexToBytes(template.mmrRoot)), writeMiniNumber(template.mmrTotal, 0), hexToBytes(template.magic), writeHashToStream(hexToBytes(customHash)), writeHashToStream(hexToBytes(txBodyHash)));
143
128
  }
144
129
  /**
145
130
  * Compute the TxPoW ID for a mined block-candidate header.
146
131
  * txpowId = SHA3-256(serialized TxHeader).
147
132
  */
148
- function computeBlockCandidateId(headerBytes) {
149
- return (0, core_1.sha3_256)(headerBytes);
133
+ export function computeBlockCandidateId(headerBytes) {
134
+ return sha3_256(headerBytes);
150
135
  }
151
136
  /**
152
137
  * Check whether a txpowId beats the block target (i.e. is a genuine Minima block).
153
138
  * valid = txpowId < blockDifficulty (big-endian 256-bit comparison).
154
139
  */
155
- function isBlockWinner(txpowId, blockDifficulty) {
140
+ export function isBlockWinner(txpowId, blockDifficulty) {
156
141
  const target = hexToBytes(blockDifficulty);
157
142
  for (let i = 0; i < 32; i++) {
158
143
  if (txpowId[i] < target[i])
@@ -201,7 +186,7 @@ function bytesToBigInt(bytes) {
201
186
  * @param txpowId SHA3-256(header), 32 bytes.
202
187
  * @param blockDifficulty 32-byte hex block difficulty target.
203
188
  */
204
- function computeSuperLevel(txpowId, blockDifficulty) {
189
+ export function computeSuperLevel(txpowId, blockDifficulty) {
205
190
  const diffBigInt = bytesToBigInt(hexToBytes(blockDifficulty));
206
191
  const idBigInt = bytesToBigInt(txpowId);
207
192
  // Not a block: quot = 0 → super = -1
@@ -210,8 +195,8 @@ function computeSuperLevel(txpowId, blockDifficulty) {
210
195
  const quot = diffBigInt / idBigInt;
211
196
  let superLevel = bigIntBitLength(quot) - 1;
212
197
  // Clamp to the maximum represented Super level, exactly as Minima does.
213
- if (superLevel >= constants_js_1.CASCADE_LEVELS) {
214
- superLevel = constants_js_1.CASCADE_LEVELS - 1;
198
+ if (superLevel >= CASCADE_LEVELS) {
199
+ superLevel = CASCADE_LEVELS - 1;
215
200
  }
216
201
  return superLevel;
217
202
  }
@@ -220,7 +205,7 @@ function computeSuperLevel(txpowId, blockDifficulty) {
220
205
  * was captured within the window, and "current" for L1 broadcast if it matches
221
206
  * the latest template id.
222
207
  */
223
- function templateFreshness(template, latest, options) {
208
+ export function templateFreshness(template, latest, options) {
224
209
  const now = options?.now ?? Date.now();
225
210
  const windowMs = options?.admissionWindowMs ?? 5 * 60 * 1000;
226
211
  const age = now - template.capturedAt;
@@ -239,9 +224,9 @@ function templateFreshness(template, latest, options) {
239
224
  * @param headerBytes The mined TxHeader bytes.
240
225
  * @param prng The PRNG used when the proof was mined (32 bytes).
241
226
  */
242
- function reconstructTxPoWEnvelope(proofTemplate, headerBytes, prng) {
227
+ export function reconstructTxPoWEnvelope(proofTemplate, headerBytes, prng) {
243
228
  const body = buildEmptyBlockBody(prng, proofTemplate.blockDifficulty);
244
- const bodyHash = toHex((0, core_1.sha3_256)(body));
229
+ const bodyHash = toHex(sha3_256(body));
245
230
  const envelope = assembleTxPoWEnvelope(headerBytes, body);
246
231
  return { envelope, bodyHash };
247
232
  }
@@ -1,4 +1,3 @@
1
- "use strict";
2
1
  /**
3
2
  * admission/types.ts — Machine Work Admission core types.
4
3
  *
@@ -16,9 +15,7 @@
16
15
  * canonical bytes". It does NOT know about negotiation, compute, storage, or
17
16
  * any particular Edge resource. Domain-specific packages supply the action.
18
17
  */
19
- Object.defineProperty(exports, "__esModule", { value: true });
20
- exports.MACHINE_WORK_DOMAIN = exports.MACHINE_WORK_ADMISSION_VERSION = void 0;
21
18
  /** Current Machine Work Admission protocol version. */
22
- exports.MACHINE_WORK_ADMISSION_VERSION = 1;
19
+ export const MACHINE_WORK_ADMISSION_VERSION = 1;
23
20
  /** Canonical protocol domain prefix for the commitment hash. */
24
- exports.MACHINE_WORK_DOMAIN = 'totem.machine-work-admission';
21
+ export const MACHINE_WORK_DOMAIN = 'totem.machine-work-admission';
@@ -1,4 +1,3 @@
1
- "use strict";
2
1
  /**
3
2
  * admission/verify.ts — Machine Work Admission verification.
4
3
  *
@@ -20,13 +19,11 @@
20
19
  * broadcastable = false. Offline verification (no provider) must NOT claim
21
20
  * Minima block contribution — `broadcastable` is left undefined.
22
21
  */
23
- Object.defineProperty(exports, "__esModule", { value: true });
24
- exports.verifyWorkAdmission = verifyWorkAdmission;
25
- const core_1 = require("@totemsdk/core");
26
- const commitment_js_1 = require("./commitment.js");
27
- const challenge_js_1 = require("./challenge.js");
28
- const template_js_1 = require("./template.js");
29
- const types_js_1 = require("./types.js");
22
+ import { sha3_256 } from '@totemsdk/core';
23
+ import { computeActionCommitment } from './commitment.js';
24
+ import { validateWorkChallenge } from './challenge.js';
25
+ import { computeSuperLevel, templateFreshness } from './template.js';
26
+ import { MACHINE_WORK_ADMISSION_VERSION, } from './types.js';
30
27
  function hexToBytes(hex) {
31
28
  const h = hex.startsWith('0x') ? hex.slice(2) : hex;
32
29
  const out = new Uint8Array(h.length / 2);
@@ -64,12 +61,12 @@ function isLessThan(a, b) {
64
61
  * block contribution (`broadcastable` is undefined).
65
62
  * @param options Verification options.
66
63
  */
67
- async function verifyWorkAdmission(action, challenge, proof, templateProvider, options) {
64
+ export async function verifyWorkAdmission(action, challenge, proof, templateProvider, options) {
68
65
  // 1. Structural checks
69
66
  if (!proof || typeof proof !== 'object') {
70
67
  return { valid: false, reason: 'proof is not an object' };
71
68
  }
72
- if (proof.version !== types_js_1.MACHINE_WORK_ADMISSION_VERSION) {
69
+ if (proof.version !== MACHINE_WORK_ADMISSION_VERSION) {
73
70
  return { valid: false, reason: `unsupported proof version ${proof.version}` };
74
71
  }
75
72
  if (proof.challengeId !== challenge.challengeId) {
@@ -85,7 +82,7 @@ async function verifyWorkAdmission(action, challenge, proof, templateProvider, o
85
82
  return { valid: false, reason: 'proof admissionTarget does not match challenge target' };
86
83
  }
87
84
  // 2. Challenge validity (expiry, recipient, domain)
88
- const challengeCheck = (0, challenge_js_1.validateWorkChallenge)(challenge, {
85
+ const challengeCheck = validateWorkChallenge(challenge, {
89
86
  recipient: action.recipient,
90
87
  domain: action.domain,
91
88
  now: options?.now,
@@ -94,13 +91,13 @@ async function verifyWorkAdmission(action, challenge, proof, templateProvider, o
94
91
  return { valid: false, reason: challengeCheck.reason };
95
92
  }
96
93
  // 3. Recompute the canonical commitment — must match the proof
97
- const expectedCommitment = (0, commitment_js_1.computeActionCommitment)(action, challenge);
94
+ const expectedCommitment = computeActionCommitment(action, challenge);
98
95
  if (expectedCommitment !== proof.actionCommitment) {
99
96
  return { valid: false, reason: 'action commitment mismatch' };
100
97
  }
101
98
  // 4. Re-derive the TxPoW ID from the mined header and check the target
102
99
  const headerBytes = hexToBytes(proof.txpow);
103
- const txpowId = (0, core_1.sha3_256)(headerBytes);
100
+ const txpowId = sha3_256(headerBytes);
104
101
  const txpowIdHex = toHex(txpowId);
105
102
  if (txpowIdHex !== proof.txpowId) {
106
103
  return { valid: false, reason: 'txpowId does not match mined header' };
@@ -125,7 +122,7 @@ async function verifyWorkAdmission(action, challenge, proof, templateProvider, o
125
122
  }
126
123
  }
127
124
  // 5a. Template freshness for admission (uses the proof's capture time).
128
- const freshness = (0, template_js_1.templateFreshness)(proof.template, trustedTemplate, {
125
+ const freshness = templateFreshness(proof.template, trustedTemplate, {
129
126
  admissionWindowMs: options?.admissionWindowMs,
130
127
  now: options?.now,
131
128
  });
@@ -141,7 +138,7 @@ async function verifyWorkAdmission(action, challenge, proof, templateProvider, o
141
138
  // 6. Level B: recompute the exact Minima Super level from the TRUSTED block
142
139
  // difficulty and the re-derived txpowId. Without a trusted template we
143
140
  // cannot claim Minima block contribution (isBlock = false).
144
- const superLevel = trustedTemplate ? (0, template_js_1.computeSuperLevel)(txpowId, trustedTemplate.blockDifficulty) : -1;
141
+ const superLevel = trustedTemplate ? computeSuperLevel(txpowId, trustedTemplate.blockDifficulty) : -1;
145
142
  const isBlock = trustedTemplate ? superLevel >= 0 : false;
146
143
  // 7. Level C: broadcastability requires a live template provider, a current
147
144
  // template, and isBlock. Offline mode leaves broadcastable undefined.
package/dist/calibrate.js CHANGED
@@ -1,4 +1,3 @@
1
- "use strict";
2
1
  /**
3
2
  * calibrate.ts — Hash rate measurement and mining cost estimation.
4
3
  *
@@ -9,24 +8,21 @@
9
8
  * estimateMiningCost() converts a difficulty target + hash rate into an
10
9
  * expected time and confidence label.
11
10
  */
12
- Object.defineProperty(exports, "__esModule", { value: true });
13
- exports.calibrateHashRate = calibrateHashRate;
14
- exports.estimateMiningCost = estimateMiningCost;
15
- const core_1 = require("@totemsdk/core");
11
+ import { sha3_256 } from '@totemsdk/core';
16
12
  /**
17
13
  * Run 100K trial SHA3-256 hashes and return the measured hash rate (hashes/sec).
18
14
  *
19
15
  * Call once per session and cache the result — the cost is ~50ms on a
20
16
  * typical desktop and ~300ms on a mid-range mobile.
21
17
  */
22
- async function calibrateHashRate() {
18
+ export async function calibrateHashRate() {
23
19
  const TRIAL_COUNT = 100000;
24
20
  const data = new Uint8Array(150).fill(0xab);
25
21
  const start = typeof performance !== 'undefined'
26
22
  ? performance.now()
27
23
  : Date.now();
28
24
  for (let i = 0; i < TRIAL_COUNT; i++) {
29
- (0, core_1.sha3_256)(data);
25
+ sha3_256(data);
30
26
  }
31
27
  const elapsed = (typeof performance !== 'undefined' ? performance.now() : Date.now()) - start;
32
28
  return Math.round(TRIAL_COUNT / (elapsed / 1000));
@@ -43,7 +39,7 @@ async function calibrateHashRate() {
43
39
  * normal 2 – 15 s
44
40
  * slow > 15 s
45
41
  */
46
- function estimateMiningCost(txnDifficulty, hashRatePerSec) {
42
+ export function estimateMiningCost(txnDifficulty, hashRatePerSec) {
47
43
  if (hashRatePerSec <= 0) {
48
44
  throw new Error('hashRatePerSec must be positive');
49
45
  }
@@ -0,0 +1,66 @@
1
+ /**
2
+ * admission/challenge.ts — WorkChallenge creation and validation.
3
+ *
4
+ * The receiver controls the admission requirement. A challenge must be unique
5
+ * enough to prevent useful pre-mining, must expire, must bind to the intended
6
+ * receiver, and must bind to an application domain. Verification NEVER trusts
7
+ * sender-reported hardware speed — it only checks `hash < target`.
8
+ *
9
+ * ── AUTHENTICATION BOUNDARY ────────────────────────────────────────────────
10
+ * `validateWorkChallenge()` validates structural/freshness/domain/recipient
11
+ * properties but does NOT prove that the claimed recipient actually issued the
12
+ * challenge. A WorkChallenge is a plain data object; anyone can construct one
13
+ * claiming any recipient. Proving the issuer requires authenticating the
14
+ * enclosing message (e.g. a signed machine-to-machine protocol message in the
15
+ * Edge negotiation layer). @totemsdk/txpow deliberately does NOT implement a
16
+ * parallel identity/signature system — that belongs to the application layer.
17
+ * ───────────────────────────────────────────────────────────────────────────
18
+ */
19
+ import { type WorkChallenge } from './types.js';
20
+ /** Default challenge lifetime: 5 minutes. */
21
+ export declare const DEFAULT_CHALLENGE_TTL_MS: number;
22
+ /** Maximum challenge lifetime: 24 hours. */
23
+ export declare const MAX_CHALLENGE_TTL_MS: number;
24
+ /**
25
+ * Create a WorkChallenge.
26
+ *
27
+ * @param recipient The intended receiver the challenge binds to.
28
+ * @param domain Application domain (open-ended, e.g. "totem.compute.reserve").
29
+ * @param target Absolute 32-byte cryptographic target (hex).
30
+ * @param options Optional overrides (challengeId, nonce, ttl, issuedAt, network).
31
+ */
32
+ export declare function createWorkChallenge(recipient: string, domain: string, target: string, options?: {
33
+ challengeId?: string;
34
+ nonce?: string;
35
+ ttlMs?: number;
36
+ issuedAt?: number;
37
+ network?: string;
38
+ }): WorkChallenge;
39
+ /**
40
+ * Validate a WorkChallenge at verification time.
41
+ *
42
+ * Checks structural integrity, expiry, and that the challenge binds to the
43
+ * expected recipient/domain. Does NOT check the proof hash — that is the
44
+ * caller's job via verifyWorkAdmission.
45
+ */
46
+ export declare function validateWorkChallenge(challenge: WorkChallenge, expected?: {
47
+ recipient?: string;
48
+ domain?: string;
49
+ now?: number;
50
+ }): {
51
+ valid: boolean;
52
+ reason?: string;
53
+ };
54
+ /**
55
+ * Derive a stable challenge fingerprint (SHA3-256 of canonical challenge bytes).
56
+ * Useful for higher-level code that tracks already-consumed challenge IDs.
57
+ */
58
+ export declare function challengeFingerprint(challenge: WorkChallenge): string;
59
+ /**
60
+ * Canonical serialization of a WorkChallenge.
61
+ *
62
+ * Deterministic field ordering with length-prefixed strings so that no
63
+ * ambiguous concatenation is possible. Used for the commitment and for
64
+ * challenge fingerprints.
65
+ */
66
+ export declare function canonicalChallenge(challenge: WorkChallenge): string;
@@ -0,0 +1,155 @@
1
+ "use strict";
2
+ /**
3
+ * admission/challenge.ts — WorkChallenge creation and validation.
4
+ *
5
+ * The receiver controls the admission requirement. A challenge must be unique
6
+ * enough to prevent useful pre-mining, must expire, must bind to the intended
7
+ * receiver, and must bind to an application domain. Verification NEVER trusts
8
+ * sender-reported hardware speed — it only checks `hash < target`.
9
+ *
10
+ * ── AUTHENTICATION BOUNDARY ────────────────────────────────────────────────
11
+ * `validateWorkChallenge()` validates structural/freshness/domain/recipient
12
+ * properties but does NOT prove that the claimed recipient actually issued the
13
+ * challenge. A WorkChallenge is a plain data object; anyone can construct one
14
+ * claiming any recipient. Proving the issuer requires authenticating the
15
+ * enclosing message (e.g. a signed machine-to-machine protocol message in the
16
+ * Edge negotiation layer). @totemsdk/txpow deliberately does NOT implement a
17
+ * parallel identity/signature system — that belongs to the application layer.
18
+ * ───────────────────────────────────────────────────────────────────────────
19
+ */
20
+ Object.defineProperty(exports, "__esModule", { value: true });
21
+ exports.MAX_CHALLENGE_TTL_MS = exports.DEFAULT_CHALLENGE_TTL_MS = void 0;
22
+ exports.createWorkChallenge = createWorkChallenge;
23
+ exports.validateWorkChallenge = validateWorkChallenge;
24
+ exports.challengeFingerprint = challengeFingerprint;
25
+ exports.canonicalChallenge = canonicalChallenge;
26
+ const core_1 = require("@totemsdk/core");
27
+ const types_js_1 = require("./types.js");
28
+ /** Default challenge lifetime: 5 minutes. */
29
+ exports.DEFAULT_CHALLENGE_TTL_MS = 5 * 60 * 1000;
30
+ /** Maximum challenge lifetime: 24 hours. */
31
+ exports.MAX_CHALLENGE_TTL_MS = 24 * 60 * 60 * 1000;
32
+ function toHex(bytes) {
33
+ return Array.from(bytes).map(b => b.toString(16).padStart(2, '0')).join('');
34
+ }
35
+ function randomHex(bytes) {
36
+ const out = new Uint8Array(bytes);
37
+ if (typeof crypto === 'undefined' || !crypto.getRandomValues) {
38
+ throw new Error('SECURITY: crypto.getRandomValues unavailable — cannot generate secure random challenge');
39
+ }
40
+ crypto.getRandomValues(out);
41
+ return toHex(out);
42
+ }
43
+ /**
44
+ * Create a WorkChallenge.
45
+ *
46
+ * @param recipient The intended receiver the challenge binds to.
47
+ * @param domain Application domain (open-ended, e.g. "totem.compute.reserve").
48
+ * @param target Absolute 32-byte cryptographic target (hex).
49
+ * @param options Optional overrides (challengeId, nonce, ttl, issuedAt, network).
50
+ */
51
+ function createWorkChallenge(recipient, domain, target, options) {
52
+ if (!recipient)
53
+ throw new Error('WorkChallenge requires a recipient');
54
+ if (!domain)
55
+ throw new Error('WorkChallenge requires a domain');
56
+ if (!/^[0-9a-fA-F]{64}$/.test(target)) {
57
+ throw new Error('WorkChallenge target must be 32-byte hex (64 hex chars)');
58
+ }
59
+ const ttlMs = options?.ttlMs ?? exports.DEFAULT_CHALLENGE_TTL_MS;
60
+ if (ttlMs <= 0 || ttlMs > exports.MAX_CHALLENGE_TTL_MS) {
61
+ throw new Error(`WorkChallenge ttlMs must be in (0, ${exports.MAX_CHALLENGE_TTL_MS}]`);
62
+ }
63
+ const issuedAt = options?.issuedAt ?? Date.now();
64
+ const challengeId = options?.challengeId ?? randomHex(16);
65
+ const nonce = options?.nonce ?? randomHex(16);
66
+ return {
67
+ version: types_js_1.MACHINE_WORK_ADMISSION_VERSION,
68
+ challengeId,
69
+ recipient,
70
+ domain,
71
+ target,
72
+ nonce,
73
+ issuedAt,
74
+ expiresAt: issuedAt + ttlMs,
75
+ network: options?.network,
76
+ };
77
+ }
78
+ /**
79
+ * Validate a WorkChallenge at verification time.
80
+ *
81
+ * Checks structural integrity, expiry, and that the challenge binds to the
82
+ * expected recipient/domain. Does NOT check the proof hash — that is the
83
+ * caller's job via verifyWorkAdmission.
84
+ */
85
+ function validateWorkChallenge(challenge, expected) {
86
+ if (!challenge || typeof challenge !== 'object') {
87
+ return { valid: false, reason: 'challenge is not an object' };
88
+ }
89
+ if (challenge.version !== types_js_1.MACHINE_WORK_ADMISSION_VERSION) {
90
+ return { valid: false, reason: `unsupported challenge version ${challenge.version}` };
91
+ }
92
+ if (!challenge.challengeId) {
93
+ return { valid: false, reason: 'challenge is missing challengeId' };
94
+ }
95
+ if (!challenge.recipient) {
96
+ return { valid: false, reason: 'challenge is missing recipient' };
97
+ }
98
+ if (!challenge.domain) {
99
+ return { valid: false, reason: 'challenge is missing domain' };
100
+ }
101
+ if (!/^[0-9a-fA-F]{64}$/.test(challenge.target)) {
102
+ return { valid: false, reason: 'challenge target must be 32-byte hex' };
103
+ }
104
+ if (typeof challenge.issuedAt !== 'number' || typeof challenge.expiresAt !== 'number') {
105
+ return { valid: false, reason: 'challenge issuedAt/expiresAt must be numbers' };
106
+ }
107
+ const now = expected?.now ?? Date.now();
108
+ if (now > challenge.expiresAt) {
109
+ return { valid: false, reason: 'challenge has expired' };
110
+ }
111
+ if (now < challenge.issuedAt - 60000) {
112
+ return { valid: false, reason: 'challenge issuedAt is in the future' };
113
+ }
114
+ if (expected?.recipient && challenge.recipient !== expected.recipient) {
115
+ return { valid: false, reason: 'challenge recipient mismatch' };
116
+ }
117
+ if (expected?.domain && challenge.domain !== expected.domain) {
118
+ return { valid: false, reason: 'challenge domain mismatch' };
119
+ }
120
+ return { valid: true };
121
+ }
122
+ /**
123
+ * Derive a stable challenge fingerprint (SHA3-256 of canonical challenge bytes).
124
+ * Useful for higher-level code that tracks already-consumed challenge IDs.
125
+ */
126
+ function challengeFingerprint(challenge) {
127
+ const canonical = canonicalChallenge(challenge);
128
+ return toHex((0, core_1.sha3_256)(new TextEncoder().encode(canonical)));
129
+ }
130
+ /**
131
+ * Canonical serialization of a WorkChallenge.
132
+ *
133
+ * Deterministic field ordering with length-prefixed strings so that no
134
+ * ambiguous concatenation is possible. Used for the commitment and for
135
+ * challenge fingerprints.
136
+ */
137
+ function canonicalChallenge(challenge) {
138
+ const parts = [];
139
+ const push = (s) => {
140
+ parts.push(String(s.length).padStart(8, '0'));
141
+ parts.push(s);
142
+ };
143
+ push('totem.work-challenge');
144
+ push(String(challenge.version));
145
+ push(challenge.challengeId);
146
+ push(challenge.recipient);
147
+ push(challenge.domain);
148
+ push(challenge.target);
149
+ push(challenge.nonce);
150
+ push(String(challenge.issuedAt));
151
+ push(String(challenge.expiresAt));
152
+ if (challenge.network !== undefined)
153
+ push(challenge.network);
154
+ return parts.join('');
155
+ }
@@ -0,0 +1,34 @@
1
+ /**
2
+ * admission/commitment.ts — Canonical domain-separated action commitment.
3
+ *
4
+ * The commitment is a deterministic SHA3-256 over a length-prefixed canonical
5
+ * encoding of every security-relevant field. It changes if any of the
6
+ * following change:
7
+ * - protocol domain / version
8
+ * - action domain, sender, recipient, actionId, payloadHash, context
9
+ * - challenge challengeId, nonce, target, expiresAt
10
+ *
11
+ * The resulting 32-byte commitment is placed in the TxPoW header's customHash
12
+ * field, so the mined nonce commits the work to sender, recipient, action,
13
+ * challenge, target, and freshness.
14
+ */
15
+ import { type MachineWorkAction, type WorkChallenge } from './types.js';
16
+ /**
17
+ * Canonical serialization of a MachineWorkAction.
18
+ *
19
+ * Length-prefixed fields in a fixed order; context keys are sorted so that
20
+ * equivalent input produces identical output.
21
+ */
22
+ export declare function canonicalAction(action: MachineWorkAction): string;
23
+ /**
24
+ * Compute the canonical action commitment.
25
+ *
26
+ * commitment = SHA3-256(
27
+ * protocolDomain || protocolVersion ||
28
+ * canonicalAction(action) || canonicalChallenge(challenge)
29
+ * )
30
+ *
31
+ * @param action The application action.
32
+ * @param challenge The challenge the work is bound to.
33
+ */
34
+ export declare function computeActionCommitment(action: MachineWorkAction, challenge: WorkChallenge): string;
@@ -0,0 +1,73 @@
1
+ "use strict";
2
+ /**
3
+ * admission/commitment.ts — Canonical domain-separated action commitment.
4
+ *
5
+ * The commitment is a deterministic SHA3-256 over a length-prefixed canonical
6
+ * encoding of every security-relevant field. It changes if any of the
7
+ * following change:
8
+ * - protocol domain / version
9
+ * - action domain, sender, recipient, actionId, payloadHash, context
10
+ * - challenge challengeId, nonce, target, expiresAt
11
+ *
12
+ * The resulting 32-byte commitment is placed in the TxPoW header's customHash
13
+ * field, so the mined nonce commits the work to sender, recipient, action,
14
+ * challenge, target, and freshness.
15
+ */
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.canonicalAction = canonicalAction;
18
+ exports.computeActionCommitment = computeActionCommitment;
19
+ const core_1 = require("@totemsdk/core");
20
+ const types_js_1 = require("./types.js");
21
+ const challenge_js_1 = require("./challenge.js");
22
+ function toHex(bytes) {
23
+ return Array.from(bytes).map(b => b.toString(16).padStart(2, '0')).join('');
24
+ }
25
+ /**
26
+ * Canonical serialization of a MachineWorkAction.
27
+ *
28
+ * Length-prefixed fields in a fixed order; context keys are sorted so that
29
+ * equivalent input produces identical output.
30
+ */
31
+ function canonicalAction(action) {
32
+ const parts = [];
33
+ const push = (s) => {
34
+ parts.push(String(s.length).padStart(8, '0'));
35
+ parts.push(s);
36
+ };
37
+ push('totem.machine-work-action');
38
+ push(String(action.version));
39
+ push(action.domain);
40
+ push(action.sender);
41
+ push(action.recipient);
42
+ push(action.actionId);
43
+ push(action.payloadHash);
44
+ if (action.context) {
45
+ const keys = Object.keys(action.context).sort();
46
+ for (const key of keys) {
47
+ push(key);
48
+ push(action.context[key]);
49
+ }
50
+ }
51
+ return parts.join('');
52
+ }
53
+ /**
54
+ * Compute the canonical action commitment.
55
+ *
56
+ * commitment = SHA3-256(
57
+ * protocolDomain || protocolVersion ||
58
+ * canonicalAction(action) || canonicalChallenge(challenge)
59
+ * )
60
+ *
61
+ * @param action The application action.
62
+ * @param challenge The challenge the work is bound to.
63
+ */
64
+ function computeActionCommitment(action, challenge) {
65
+ const input = types_js_1.MACHINE_WORK_DOMAIN +
66
+ '|' +
67
+ String(types_js_1.MACHINE_WORK_ADMISSION_VERSION) +
68
+ '|' +
69
+ canonicalAction(action) +
70
+ '|' +
71
+ (0, challenge_js_1.canonicalChallenge)(challenge);
72
+ return toHex((0, core_1.sha3_256)(new TextEncoder().encode(input)));
73
+ }