@totemsdk/txpow 0.1.3 → 0.1.4

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.
@@ -1,347 +0,0 @@
1
- /**
2
- * Parity tests for @totemsdk/txpow serialization.
3
- *
4
- * These tests verify byte-exact output against known reference vectors derived
5
- * from the extension's private serializeTxHeader / serializeTxBody / serializeTxPoW
6
- * and from TxHeaderTests_1768991294482.java.
7
- *
8
- * GATE: serializeTxPoW output must be byte-identical to the extension's functions
9
- * for the same inputs before the extension's private copies are deleted (Task #130).
10
- */
11
-
12
- import { sha3_256 } from '@noble/hashes/sha3';
13
- import {
14
- serializeTxHeader,
15
- serializeTxBody,
16
- serializeTxPoW,
17
- computeTxPoWId,
18
- TX_POW_MIN_DIFFICULTY,
19
- MAX_HASH,
20
- ZERO_HASH,
21
- CASCADE_LEVELS,
22
- serializeMagic,
23
- } from '../index.js';
24
- import { writeMiniNumber, writeMiniData, writeHashToStream, concat } from '@totemsdk/core';
25
-
26
- // ─────────────────────────────────────────────────────────────────────────────
27
- // Helpers
28
- // ─────────────────────────────────────────────────────────────────────────────
29
-
30
- function bytesToHex(b: Uint8Array): string {
31
- return Array.from(b).map(x => x.toString(16).padStart(2, '0')).join('');
32
- }
33
-
34
- function hexToBytes(hex: string): Uint8Array {
35
- const h = hex.startsWith('0x') ? hex.slice(2) : hex;
36
- const out = new Uint8Array(h.length / 2);
37
- for (let i = 0; i < h.length; i += 2) out[i / 2] = parseInt(h.slice(i, i + 2), 16);
38
- return out;
39
- }
40
-
41
- // Known 32-byte body hash (all 0xAB for determinism)
42
- const MOCK_BODY_HASH = new Uint8Array(32).fill(0xab);
43
-
44
- // Deterministic PRNG for test reproducibility
45
- const MOCK_PRNG = new Uint8Array(32).fill(0xcd);
46
-
47
- // Empty pre-serialized transaction and witness (smallest valid inputs)
48
- const EMPTY_TX_BYTES = concat(
49
- writeMiniNumber(0n),
50
- writeMiniNumber(0n),
51
- writeMiniNumber(0n),
52
- writeHashToStream(new Uint8Array([0x00]))
53
- );
54
- const EMPTY_WITNESS_BYTES = concat(
55
- writeMiniNumber(0n),
56
- writeMiniNumber(0n),
57
- writeMiniNumber(0n)
58
- );
59
-
60
- // ─────────────────────────────────────────────────────────────────────────────
61
- // TX_POW_MIN_DIFFICULTY constant
62
- // ─────────────────────────────────────────────────────────────────────────────
63
-
64
- describe('TX_POW_MIN_DIFFICULTY', () => {
65
- it('is 32 bytes', () => {
66
- expect(TX_POW_MIN_DIFFICULTY.length).toBe(32);
67
- });
68
-
69
- it('is less than MAX_HASH (big-endian 256-bit compare)', () => {
70
- for (let i = 0; i < 32; i++) {
71
- if (TX_POW_MIN_DIFFICULTY[i] < MAX_HASH[i]) return;
72
- if (TX_POW_MIN_DIFFICULTY[i] > MAX_HASH[i]) throw new Error('TX_POW_MIN_DIFFICULTY > MAX_HASH');
73
- }
74
- throw new Error('TX_POW_MIN_DIFFICULTY === MAX_HASH');
75
- });
76
-
77
- it('equals floor((2^256 - 1) / 1_000_000)', () => {
78
- const expected = ((2n ** 256n) - 1n) / 1_000_000n;
79
- let hex = expected.toString(16);
80
- if (hex.length % 2 !== 0) hex = '0' + hex;
81
- while (hex.length < 64) hex = '00' + hex;
82
- expect(bytesToHex(TX_POW_MIN_DIFFICULTY)).toBe(hex);
83
- });
84
- });
85
-
86
- // ─────────────────────────────────────────────────────────────────────────────
87
- // computeTxPoWId
88
- // ─────────────────────────────────────────────────────────────────────────────
89
-
90
- describe('computeTxPoWId', () => {
91
- it('returns SHA3-256 of the provided bytes', () => {
92
- const input = new Uint8Array([1, 2, 3, 4]);
93
- const result = computeTxPoWId(input);
94
- const expected = sha3_256(input);
95
- expect(result).toEqual(expected);
96
- });
97
-
98
- it('returns 32 bytes', () => {
99
- const result = computeTxPoWId(MOCK_BODY_HASH);
100
- expect(result.length).toBe(32);
101
- });
102
- });
103
-
104
- // ─────────────────────────────────────────────────────────────────────────────
105
- // serializeMagic
106
- // ─────────────────────────────────────────────────────────────────────────────
107
-
108
- describe('serializeMagic', () => {
109
- it('produces a non-empty byte array', () => {
110
- const magic = serializeMagic();
111
- expect(magic.length).toBeGreaterThan(0);
112
- });
113
-
114
- it('contains expected MiniNumber values for maxTxPoWSize=65536', () => {
115
- const magic = serializeMagic();
116
- const hex = bytesToHex(magic);
117
- // writeMiniNumber(65536n): scale=0 [00], len=3 [03], bytes=[01,00,00]
118
- // so "000301 0000" — confirm first 6 bytes contain expected value
119
- expect(hex.startsWith('000301')).toBe(true);
120
- });
121
-
122
- it('is deterministic', () => {
123
- expect(serializeMagic()).toEqual(serializeMagic());
124
- });
125
- });
126
-
127
- // ─────────────────────────────────────────────────────────────────────────────
128
- // serializeTxHeader
129
- // ─────────────────────────────────────────────────────────────────────────────
130
-
131
- describe('serializeTxHeader', () => {
132
- const FIXED_TIME = 1700000000000n;
133
- const FIXED_NONCE = 0n;
134
-
135
- it('is deterministic for fixed nonce + timeMilli', () => {
136
- const h1 = serializeTxHeader(MOCK_BODY_HASH, { nonce: FIXED_NONCE, timeMilli: FIXED_TIME });
137
- const h2 = serializeTxHeader(MOCK_BODY_HASH, { nonce: FIXED_NONCE, timeMilli: FIXED_TIME });
138
- expect(h1).toEqual(h2);
139
- });
140
-
141
- it('starts with nonce=0 MiniNumber [00 01 00]', () => {
142
- const h = serializeTxHeader(MOCK_BODY_HASH, { nonce: 0n, timeMilli: FIXED_TIME });
143
- expect(Array.from(h.slice(0, 3))).toEqual([0x00, 0x01, 0x00]);
144
- });
145
-
146
- it('starts with nonce=1 MiniNumber [00 01 01] for nonce=1', () => {
147
- const h = serializeTxHeader(MOCK_BODY_HASH, { nonce: 1n, timeMilli: FIXED_TIME });
148
- expect(Array.from(h.slice(0, 3))).toEqual([0x00, 0x01, 0x01]);
149
- });
150
-
151
- it('encodes nonce=128 with 4 bytes (MiniNumber boundary)', () => {
152
- const h = serializeTxHeader(MOCK_BODY_HASH, { nonce: 128n, timeMilli: FIXED_TIME });
153
- // writeMiniNumber(128n): scale=0 [00], len=2 [02], bytes=[00,80]
154
- expect(Array.from(h.slice(0, 4))).toEqual([0x00, 0x02, 0x00, 0x80]);
155
- });
156
-
157
- it('encodes nonce=32768 with 5 bytes (MiniNumber boundary)', () => {
158
- const h = serializeTxHeader(MOCK_BODY_HASH, { nonce: 32768n, timeMilli: FIXED_TIME });
159
- // writeMiniNumber(32768n): scale=0 [00], len=3 [03], bytes=[00,80,00]
160
- expect(Array.from(h.slice(0, 5))).toEqual([0x00, 0x03, 0x00, 0x80, 0x00]);
161
- });
162
-
163
- it('includes CASCADE_LEVELS=32 as single byte in super-parents section', () => {
164
- const h = serializeTxHeader(MOCK_BODY_HASH, { nonce: 0n, timeMilli: FIXED_TIME });
165
- const hex = bytesToHex(h);
166
- // After nonce[3] + chainID[5] + timeMilli[?] + blockNum[3] + blockDiff[36] = starts with 00 01 00
167
- // We check that the byte 0x20 (=32=CASCADE_LEVELS) appears exactly once in isolation
168
- expect(h).toContain(CASCADE_LEVELS);
169
- const idx = h.indexOf(CASCADE_LEVELS);
170
- expect(idx).toBeGreaterThan(0);
171
- expect(hex).toContain('20'); // 0x20 = 32
172
- });
173
-
174
- it('ends with the txBodyHash via writeHashToStream (last 36 bytes = 4-byte len + 32 bytes)', () => {
175
- const h = serializeTxHeader(MOCK_BODY_HASH, { nonce: 0n, timeMilli: FIXED_TIME });
176
- const tail = h.slice(h.length - 36);
177
- // writeHashToStream produces: 00 00 00 20 + 32 bytes
178
- expect(Array.from(tail.slice(0, 4))).toEqual([0x00, 0x00, 0x00, 0x20]);
179
- expect(tail.slice(4)).toEqual(MOCK_BODY_HASH);
180
- });
181
-
182
- it('changes when nonce changes (mining loop property)', () => {
183
- const h0 = serializeTxHeader(MOCK_BODY_HASH, { nonce: 0n, timeMilli: FIXED_TIME });
184
- const h1 = serializeTxHeader(MOCK_BODY_HASH, { nonce: 1n, timeMilli: FIXED_TIME });
185
- expect(h0).not.toEqual(h1);
186
- });
187
- });
188
-
189
- // ─────────────────────────────────────────────────────────────────────────────
190
- // serializeTxBody
191
- // ─────────────────────────────────────────────────────────────────────────────
192
-
193
- describe('serializeTxBody', () => {
194
- it('is deterministic when prng is provided', () => {
195
- const b1 = serializeTxBody(EMPTY_TX_BYTES, EMPTY_WITNESS_BYTES, { prng: MOCK_PRNG });
196
- const b2 = serializeTxBody(EMPTY_TX_BYTES, EMPTY_WITNESS_BYTES, { prng: MOCK_PRNG });
197
- expect(b1).toEqual(b2);
198
- });
199
-
200
- it('starts with writeHashToStream(prng): [00 00 00 20] + 32 prng bytes', () => {
201
- const body = serializeTxBody(EMPTY_TX_BYTES, EMPTY_WITNESS_BYTES, { prng: MOCK_PRNG });
202
- expect(Array.from(body.slice(0, 4))).toEqual([0x00, 0x00, 0x00, 0x20]);
203
- expect(body.slice(4, 36)).toEqual(MOCK_PRNG);
204
- });
205
-
206
- it('uses MAX_HASH as default txnDifficulty (36 bytes after PRNG)', () => {
207
- const body = serializeTxBody(EMPTY_TX_BYTES, EMPTY_WITNESS_BYTES, { prng: MOCK_PRNG });
208
- // After PRNG (36 bytes): writeMiniData(MAX_HASH) = [00 00 00 20] + 32×FF
209
- const diffStart = 36;
210
- expect(Array.from(body.slice(diffStart, diffStart + 4))).toEqual([0x00, 0x00, 0x00, 0x20]);
211
- expect(Array.from(body.slice(diffStart + 4, diffStart + 36))).toEqual(Array(32).fill(0xff));
212
- });
213
-
214
- it('uses provided txnDifficulty correctly', () => {
215
- const customDiff = new Uint8Array(32).fill(0x11);
216
- const body = serializeTxBody(EMPTY_TX_BYTES, EMPTY_WITNESS_BYTES, {
217
- prng: MOCK_PRNG,
218
- txnDifficulty: customDiff,
219
- });
220
- const diffStart = 36;
221
- expect(Array.from(body.slice(diffStart + 4, diffStart + 36))).toEqual(Array(32).fill(0x11));
222
- });
223
-
224
- it('includes the pre-serialized txBytes verbatim', () => {
225
- const mockTx = new Uint8Array([0xde, 0xad, 0xbe, 0xef]);
226
- const body = serializeTxBody(mockTx, EMPTY_WITNESS_BYTES, { prng: MOCK_PRNG });
227
- const bodyHex = bytesToHex(body);
228
- expect(bodyHex).toContain('deadbeef');
229
- });
230
-
231
- it('includes the pre-serialized witnessBytes verbatim', () => {
232
- const mockWitness = new Uint8Array([0xca, 0xfe, 0xba, 0xbe]);
233
- const body = serializeTxBody(EMPTY_TX_BYTES, mockWitness, { prng: MOCK_PRNG });
234
- const bodyHex = bytesToHex(body);
235
- expect(bodyHex).toContain('cafebabe');
236
- });
237
-
238
- it('ends with MiniNumber(0) for empty txpowid list', () => {
239
- const body = serializeTxBody(EMPTY_TX_BYTES, EMPTY_WITNESS_BYTES, { prng: MOCK_PRNG });
240
- // Last 3 bytes: writeMiniNumber(0n) = [00, 01, 00]
241
- expect(Array.from(body.slice(body.length - 3))).toEqual([0x00, 0x01, 0x00]);
242
- });
243
- });
244
-
245
- // ─────────────────────────────────────────────────────────────────────────────
246
- // serializeTxPoW — structure tests
247
- // ─────────────────────────────────────────────────────────────────────────────
248
-
249
- describe('serializeTxPoW', () => {
250
- it('byte at header.length is 0x01 (hasBody flag)', () => {
251
- const FIXED_TIME = 1700000000000n;
252
- const body = serializeTxBody(EMPTY_TX_BYTES, EMPTY_WITNESS_BYTES, { prng: MOCK_PRNG });
253
- const bodyHash = sha3_256(body);
254
- const header = serializeTxHeader(bodyHash, { nonce: 0n, timeMilli: FIXED_TIME });
255
-
256
- const txpow = serializeTxPoW(EMPTY_TX_BYTES, EMPTY_WITNESS_BYTES, {
257
- nonce: 0n,
258
- timeMilli: FIXED_TIME,
259
- prng: MOCK_PRNG,
260
- });
261
-
262
- expect(txpow[header.length]).toBe(0x01);
263
- });
264
-
265
- it('header prefix matches standalone serializeTxHeader output', () => {
266
- const FIXED_TIME = 1700000000000n;
267
- const body = serializeTxBody(EMPTY_TX_BYTES, EMPTY_WITNESS_BYTES, { prng: MOCK_PRNG });
268
- const bodyHash = sha3_256(body);
269
- const expectedHeader = serializeTxHeader(bodyHash, { nonce: 0n, timeMilli: FIXED_TIME });
270
-
271
- const txpow = serializeTxPoW(EMPTY_TX_BYTES, EMPTY_WITNESS_BYTES, {
272
- nonce: 0n,
273
- timeMilli: FIXED_TIME,
274
- prng: MOCK_PRNG,
275
- });
276
-
277
- expect(txpow.slice(0, expectedHeader.length)).toEqual(expectedHeader);
278
- });
279
-
280
- it('txpowId derived from first headerLen bytes matches computeTxPoWId', () => {
281
- const FIXED_TIME = 1700000000000n;
282
- const body = serializeTxBody(EMPTY_TX_BYTES, EMPTY_WITNESS_BYTES, { prng: MOCK_PRNG });
283
- const bodyHash = sha3_256(body);
284
- const header = serializeTxHeader(bodyHash, { nonce: 0n, timeMilli: FIXED_TIME });
285
-
286
- const txpow = serializeTxPoW(EMPTY_TX_BYTES, EMPTY_WITNESS_BYTES, {
287
- nonce: 0n,
288
- timeMilli: FIXED_TIME,
289
- prng: MOCK_PRNG,
290
- });
291
-
292
- const idFromTxPoW = computeTxPoWId(txpow.slice(0, header.length));
293
- const idFromHeader = computeTxPoWId(header);
294
- expect(idFromTxPoW).toEqual(idFromHeader);
295
- });
296
- });
297
-
298
- // ─────────────────────────────────────────────────────────────────────────────
299
- // Nonce MiniNumber encoding boundaries
300
- // ─────────────────────────────────────────────────────────────────────────────
301
-
302
- describe('nonce MiniNumber encoding boundaries', () => {
303
- const BODY_HASH = new Uint8Array(32).fill(0x77);
304
- const TIME = 1700000000000n;
305
-
306
- it('nonce=0 encodes as 3 bytes [00 01 00]', () => {
307
- const n = writeMiniNumber(0n);
308
- expect(n.length).toBe(3);
309
- expect(Array.from(n)).toEqual([0x00, 0x01, 0x00]);
310
- });
311
-
312
- it('nonce=127 encodes as 3 bytes (still 1-byte unscaled)', () => {
313
- const n = writeMiniNumber(127n);
314
- expect(n.length).toBe(3);
315
- expect(Array.from(n)).toEqual([0x00, 0x01, 0x7f]);
316
- });
317
-
318
- it('nonce=128 encodes as 4 bytes (2-byte unscaled: leading zero + 0x80)', () => {
319
- const n = writeMiniNumber(128n);
320
- expect(n.length).toBe(4);
321
- expect(Array.from(n)).toEqual([0x00, 0x02, 0x00, 0x80]);
322
- });
323
-
324
- it('nonce=32767 encodes as 4 bytes', () => {
325
- const n = writeMiniNumber(32767n);
326
- expect(n.length).toBe(4);
327
- expect(Array.from(n)).toEqual([0x00, 0x02, 0x7f, 0xff]);
328
- });
329
-
330
- it('nonce=32768 encodes as 5 bytes (3-byte unscaled: leading zero + 0x80 0x00)', () => {
331
- const n = writeMiniNumber(32768n);
332
- expect(n.length).toBe(5);
333
- expect(Array.from(n)).toEqual([0x00, 0x03, 0x00, 0x80, 0x00]);
334
- });
335
-
336
- it('header byte length changes at nonce 128 boundary', () => {
337
- const h127 = serializeTxHeader(BODY_HASH, { nonce: 127n, timeMilli: TIME });
338
- const h128 = serializeTxHeader(BODY_HASH, { nonce: 128n, timeMilli: TIME });
339
- expect(h128.length).toBe(h127.length + 1);
340
- });
341
-
342
- it('header byte length changes at nonce 32768 boundary', () => {
343
- const h32767 = serializeTxHeader(BODY_HASH, { nonce: 32767n, timeMilli: TIME });
344
- const h32768 = serializeTxHeader(BODY_HASH, { nonce: 32768n, timeMilli: TIME });
345
- expect(h32768.length).toBe(h32767.length + 1);
346
- });
347
- });
package/src/calibrate.ts DELETED
@@ -1,86 +0,0 @@
1
- /**
2
- * calibrate.ts — Hash rate measurement and mining cost estimation.
3
- *
4
- * calibrateHashRate() measures how many SHA3-256 hashes/sec the current
5
- * environment can produce. Used by the extension UX to show a warning
6
- * before starting a mine on slow devices.
7
- *
8
- * estimateMiningCost() converts a difficulty target + hash rate into an
9
- * expected time and confidence label.
10
- */
11
-
12
- import { sha3_256 } from '@noble/hashes/sha3.js';
13
-
14
- export interface MiningEstimate {
15
- expectedHashes: bigint;
16
- expectedSeconds: number;
17
- confidence: 'fast' | 'normal' | 'slow';
18
- }
19
-
20
- /**
21
- * Run 100K trial SHA3-256 hashes and return the measured hash rate (hashes/sec).
22
- *
23
- * Call once per session and cache the result — the cost is ~50ms on a
24
- * typical desktop and ~300ms on a mid-range mobile.
25
- */
26
- export async function calibrateHashRate(): Promise<number> {
27
- const TRIAL_COUNT = 100_000;
28
- const data = new Uint8Array(150).fill(0xab);
29
-
30
- const start = typeof performance !== 'undefined'
31
- ? performance.now()
32
- : Date.now();
33
-
34
- for (let i = 0; i < TRIAL_COUNT; i++) {
35
- sha3_256(data);
36
- }
37
-
38
- const elapsed = (typeof performance !== 'undefined' ? performance.now() : Date.now()) - start;
39
- return Math.round(TRIAL_COUNT / (elapsed / 1000));
40
- }
41
-
42
- /**
43
- * Given a difficulty target and a measured hash rate, return the expected
44
- * number of hashes, expected wall-clock time, and a confidence label.
45
- *
46
- * expectedHashes ≈ MAX_HASH / txnDifficulty
47
- * (geometric distribution: each hash has P(valid) = txnDifficulty / MAX_HASH)
48
- *
49
- * Confidence labels:
50
- * fast < 2 s
51
- * normal 2 – 15 s
52
- * slow > 15 s
53
- */
54
- export function estimateMiningCost(
55
- txnDifficulty: Uint8Array,
56
- hashRatePerSec: number
57
- ): MiningEstimate {
58
- if (hashRatePerSec <= 0) {
59
- throw new Error('hashRatePerSec must be positive');
60
- }
61
-
62
- const diffBigInt = txnDifficulty.reduce(
63
- (acc, b) => (acc << 8n) | BigInt(b),
64
- 0n
65
- );
66
-
67
- if (diffBigInt === 0n) {
68
- return { expectedHashes: 0n, expectedSeconds: 0, confidence: 'fast' };
69
- }
70
-
71
- const MAX_HASH_BIGINT = (2n ** 256n) - 1n;
72
- const expectedHashes = MAX_HASH_BIGINT / diffBigInt;
73
-
74
- const expectedSeconds = Number(expectedHashes) / hashRatePerSec;
75
-
76
- let confidence: 'fast' | 'normal' | 'slow';
77
- if (expectedSeconds < 2) {
78
- confidence = 'fast';
79
- } else if (expectedSeconds < 15) {
80
- confidence = 'normal';
81
- } else {
82
- confidence = 'slow';
83
- }
84
-
85
- return { expectedHashes, expectedSeconds, confidence };
86
- }
package/src/constants.ts DELETED
@@ -1,49 +0,0 @@
1
- /**
2
- * constants.ts — TxPoW-level constants matching Minima Java protocol values.
3
- *
4
- * ZERO_HASH: 32-byte all-zeros — used for MMRRoot, CustomHash, super-parent slot.
5
- * MAX_HASH: 32-byte all-0xFF — default mBlockDifficulty / mTxnDifficulty for
6
- * MEG-side-mined paths. Rejected by checkTxPoWSimple() at block level.
7
- * TX_POW_MIN_DIFFICULTY: Safe transaction difficulty target.
8
- * ≈ MAX_HASH / 1,000,000 = Magic.getMinTxPowWork() floor.
9
- * Local-mining paths MUST use a target ≤ this value.
10
- * MAIN_NET_CHAIN_ID: 1-byte [0x00] — Java MiniData("0x00") = MAIN_NET chain ID.
11
- * CASCADE_LEVELS: 32 — number of super-parent slots in a fresh TxPoW header.
12
- */
13
-
14
- export const CASCADE_LEVELS = 32;
15
-
16
- export const ZERO_HASH = new Uint8Array(32);
17
-
18
- export const MAX_HASH: Uint8Array = (() => {
19
- const b = new Uint8Array(32);
20
- b.fill(0xff);
21
- return b;
22
- })();
23
-
24
- /**
25
- * TX_POW_MIN_DIFFICULTY = floor((2^256 - 1) / 1_000_000)
26
- *
27
- * This is the hardcoded floor constant matching Magic.getMinTxPowWork().
28
- * Any locally mined TxPoW must have mTxnDifficulty ≤ this value to pass
29
- * TxPoWChecker.checkTxPoWSimple() at block inclusion.
30
- *
31
- * Computed: (2n**256n - 1n) / 1_000_000n
32
- * Hex: 0x000010C6F7A0B5ED8538AACDD46595F0C7AC73E0E9DBF12F70000000000000000
33
- */
34
- export const TX_POW_MIN_DIFFICULTY: Uint8Array = (() => {
35
- const maxHashBigInt = (2n ** 256n) - 1n;
36
- const minDiffBigInt = maxHashBigInt / 1_000_000n;
37
-
38
- let hex = minDiffBigInt.toString(16);
39
- if (hex.length % 2 !== 0) hex = '0' + hex;
40
- while (hex.length < 64) hex = '00' + hex;
41
-
42
- const bytes = new Uint8Array(32);
43
- for (let i = 0; i < 32; i++) {
44
- bytes[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
45
- }
46
- return bytes;
47
- })();
48
-
49
- export const MAIN_NET_CHAIN_ID = new Uint8Array([0x00]);
package/src/difficulty.ts DELETED
@@ -1,69 +0,0 @@
1
- /**
2
- * difficulty.ts — Fetch and manage the TxPoW difficulty target.
3
- *
4
- * The live value comes from `GET /v1/wallet/txpow-params` on the Axia API.
5
- * On any error (timeout, network failure, invalid response) we fall back to
6
- * the hardcoded TX_POW_MIN_DIFFICULTY constant, which is the protocol floor.
7
- */
8
-
9
- import { TX_POW_MIN_DIFFICULTY } from './constants.js';
10
-
11
- export interface TxPowParams {
12
- minTxPowWork: string;
13
- }
14
-
15
- function hexToBytes(hex: string): Uint8Array {
16
- const h = hex.startsWith('0x') ? hex.slice(2) : hex;
17
- const out = new Uint8Array(h.length / 2);
18
- for (let i = 0; i < h.length; i += 2) {
19
- out[i / 2] = parseInt(h.slice(i, i + 2), 16);
20
- }
21
- return out;
22
- }
23
-
24
- /**
25
- * Fetch the current minimum TxPoW work from the Axia API.
26
- *
27
- * Falls back to TX_POW_MIN_DIFFICULTY (the hardcoded protocol floor) on any
28
- * error: network failure, timeout, or malformed response.
29
- *
30
- * @param axiaBaseUrl Base URL of the Axia API, e.g. "https://api.axia.to"
31
- * @param timeoutMs Request timeout in ms (default: 3000)
32
- */
33
- export async function fetchTxPowTarget(
34
- axiaBaseUrl: string,
35
- timeoutMs = 3000
36
- ): Promise<Uint8Array> {
37
- try {
38
- const base = axiaBaseUrl.replace(/\/$/, '');
39
- const url = `${base}/v1/wallet/txpow-params`;
40
-
41
- const controller = new AbortController();
42
- const timer = setTimeout(() => controller.abort(), timeoutMs);
43
-
44
- let response: Response;
45
- try {
46
- response = await fetch(url, { signal: controller.signal });
47
- } finally {
48
- clearTimeout(timer);
49
- }
50
-
51
- if (!response.ok) {
52
- throw new Error(`HTTP ${response.status}`);
53
- }
54
-
55
- const data = (await response.json()) as TxPowParams;
56
- if (typeof data.minTxPowWork !== 'string') {
57
- throw new Error('Missing minTxPowWork field');
58
- }
59
-
60
- const clean = data.minTxPowWork.replace('0x', '').replace(/\s/g, '');
61
- if (clean.length !== 64) {
62
- throw new Error(`Invalid minTxPowWork length: ${clean.length} (expected 64 hex chars)`);
63
- }
64
-
65
- return hexToBytes(clean);
66
- } catch {
67
- return new Uint8Array(TX_POW_MIN_DIFFICULTY);
68
- }
69
- }
package/src/index.ts DELETED
@@ -1,56 +0,0 @@
1
- /**
2
- * @totemsdk/txpow
3
- *
4
- * TxPoW envelope serialization and proof-of-work mining for the Minima protocol.
5
- *
6
- * USAGE — MEG-side mining (byte-identical to extension current behaviour):
7
- * const txpow = serializeTxPoW(txBytes, witnessBytes);
8
- * // Submit to Axia: node re-mines with correct difficulty
9
- *
10
- * USAGE — Local mining:
11
- * const target = await fetchTxPowTarget(axiaBaseUrl);
12
- * const txBody = serializeTxBody(txBytes, witnessBytes, { txnDifficulty: target });
13
- * const result = await mineTxPoW(txBody, target);
14
- * const txpow = concat(result.minedHeaderBytes, new Uint8Array([0x01]), txBody);
15
- *
16
- * USAGE — Verify (relay nodes):
17
- * const check = verifyProofOfWork(txpowId, mTxnDifficulty);
18
- * if (!check.valid) drop(check.reason);
19
- */
20
-
21
- export {
22
- serializeTxHeader,
23
- serializeTxBody,
24
- serializeTxPoW,
25
- computeTxPoWId,
26
- type TxHeaderOptions,
27
- type TxBodyOptions,
28
- type TxPoWOptions,
29
- } from './serialization.js';
30
-
31
- export {
32
- MAX_HASH,
33
- ZERO_HASH,
34
- TX_POW_MIN_DIFFICULTY,
35
- CASCADE_LEVELS,
36
- MAIN_NET_CHAIN_ID,
37
- } from './constants.js';
38
-
39
- export { serializeMagic } from './magic.js';
40
-
41
- export {
42
- mineTxPoW,
43
- isLessThan,
44
- type MineResult,
45
- type MineOptions,
46
- } from './mine.js';
47
-
48
- export { fetchTxPowTarget, type TxPowParams } from './difficulty.js';
49
-
50
- export { verifyProofOfWork, verifyTxPoWWork, type VerifyResult } from './verify.js';
51
-
52
- export {
53
- calibrateHashRate,
54
- estimateMiningCost,
55
- type MiningEstimate,
56
- } from './calibrate.js';
package/src/magic.ts DELETED
@@ -1,37 +0,0 @@
1
- /**
2
- * magic.ts — Serialize the Minima Magic struct.
3
- *
4
- * Ported from the extension's MinimaTransactionBuilder.ts::serializeMagic().
5
- * Matches Magic.writeDataStream() in Java exactly.
6
- *
7
- * Magic contains two copies of 4 fields (current + desired parameters):
8
- * maxTxPoWSize (MiniNumber)
9
- * maxKISSVMOps (MiniNumber)
10
- * maxTxnPerBlock(MiniNumber)
11
- * minTxPoWWork (MiniData — 32 bytes)
12
- *
13
- * Default values from Magic.java:
14
- * maxTxPoWSize = 65536
15
- * maxKISSVMOps = 1024
16
- * maxTxnPerBlock = 256
17
- * minTxPoWWork = MAX_HASH (all 0xFF, 32 bytes) — placeholder in fresh TxPoW
18
- */
19
-
20
- import { writeMiniNumber, writeMiniData, concat } from '@totemsdk/core';
21
- import { MAX_HASH } from './constants.js';
22
-
23
- export function serializeMagic(): Uint8Array {
24
- const parts: Uint8Array[] = [];
25
-
26
- parts.push(writeMiniNumber(65536n));
27
- parts.push(writeMiniNumber(1024n));
28
- parts.push(writeMiniNumber(256n));
29
- parts.push(writeMiniData(MAX_HASH));
30
-
31
- parts.push(writeMiniNumber(65536n));
32
- parts.push(writeMiniNumber(1024n));
33
- parts.push(writeMiniNumber(256n));
34
- parts.push(writeMiniData(MAX_HASH));
35
-
36
- return concat(...parts);
37
- }