@totemsdk/txpow 1.0.1 → 1.1.0
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/admission/challenge.js +14 -21
- package/dist/admission/commitment.js +9 -13
- package/dist/admission/index.js +6 -34
- package/dist/admission/mine.js +14 -17
- package/dist/admission/template.js +32 -47
- package/dist/admission/types.js +2 -5
- package/dist/admission/verify.js +41 -32
- package/dist/calibrate.js +4 -8
- package/dist/cjs/admission/challenge.d.ts +66 -0
- package/dist/cjs/admission/challenge.js +155 -0
- package/dist/cjs/admission/commitment.d.ts +34 -0
- package/dist/cjs/admission/commitment.js +73 -0
- package/dist/cjs/admission/index.d.ts +14 -0
- package/dist/cjs/admission/index.js +42 -0
- package/dist/cjs/admission/mine.d.ts +60 -0
- package/dist/cjs/admission/mine.js +146 -0
- package/dist/cjs/admission/template.d.ts +138 -0
- package/dist/cjs/admission/template.js +247 -0
- package/dist/cjs/admission/types.d.ts +224 -0
- package/dist/cjs/admission/types.js +24 -0
- package/dist/cjs/admission/verify.d.ts +50 -0
- package/dist/cjs/admission/verify.js +156 -0
- package/dist/cjs/calibrate.d.ts +35 -0
- package/dist/cjs/calibrate.js +68 -0
- package/dist/cjs/constants.d.ts +27 -0
- package/dist/cjs/constants.js +47 -0
- package/dist/cjs/difficulty.d.ts +44 -0
- package/dist/cjs/difficulty.js +111 -0
- package/dist/cjs/index.d.ts +28 -0
- package/dist/cjs/index.js +88 -0
- package/dist/cjs/magic.d.ts +19 -0
- package/dist/cjs/magic.js +35 -0
- package/dist/cjs/mine-wasm.d.ts +69 -0
- package/dist/cjs/mine-wasm.js +214 -0
- package/dist/cjs/mine.d.ts +131 -0
- package/dist/cjs/mine.js +407 -0
- package/dist/cjs/package.json +1 -0
- package/dist/cjs/serialization.d.ts +106 -0
- package/dist/cjs/serialization.js +160 -0
- package/dist/cjs/verify.d.ts +76 -0
- package/dist/cjs/verify.js +232 -0
- package/dist/cjs/wasm/miner.wasm +0 -0
- package/dist/cjs/workers/browser.worker.d.ts +26 -0
- package/dist/cjs/workers/browser.worker.js +63 -0
- package/dist/cjs/workers/node.worker.d.ts +33 -0
- package/dist/cjs/workers/node.worker.js +47 -0
- package/dist/constants.js +5 -8
- package/dist/difficulty.d.ts +24 -0
- package/dist/difficulty.js +54 -6
- package/dist/index.d.ts +1 -1
- package/dist/index.js +9 -62
- package/dist/magic.js +12 -15
- package/dist/mine-wasm.js +8 -15
- package/dist/mine.js +23 -31
- package/dist/serialization.js +28 -34
- package/dist/verify.js +15 -20
- package/dist/workers/browser.worker.js +4 -6
- package/dist/workers/node.worker.js +2 -3
- package/package.json +31 -13
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
"use strict";
|
|
2
1
|
/**
|
|
3
2
|
* admission/challenge.ts — WorkChallenge creation and validation.
|
|
4
3
|
*
|
|
@@ -17,18 +16,12 @@
|
|
|
17
16
|
* parallel identity/signature system — that belongs to the application layer.
|
|
18
17
|
* ───────────────────────────────────────────────────────────────────────────
|
|
19
18
|
*/
|
|
20
|
-
|
|
21
|
-
|
|
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");
|
|
19
|
+
import { sha3_256 } from '@totemsdk/core';
|
|
20
|
+
import { MACHINE_WORK_ADMISSION_VERSION } from './types.js';
|
|
28
21
|
/** Default challenge lifetime: 5 minutes. */
|
|
29
|
-
|
|
22
|
+
export const DEFAULT_CHALLENGE_TTL_MS = 5 * 60 * 1000;
|
|
30
23
|
/** Maximum challenge lifetime: 24 hours. */
|
|
31
|
-
|
|
24
|
+
export const MAX_CHALLENGE_TTL_MS = 24 * 60 * 60 * 1000;
|
|
32
25
|
function toHex(bytes) {
|
|
33
26
|
return Array.from(bytes).map(b => b.toString(16).padStart(2, '0')).join('');
|
|
34
27
|
}
|
|
@@ -48,7 +41,7 @@ function randomHex(bytes) {
|
|
|
48
41
|
* @param target Absolute 32-byte cryptographic target (hex).
|
|
49
42
|
* @param options Optional overrides (challengeId, nonce, ttl, issuedAt, network).
|
|
50
43
|
*/
|
|
51
|
-
function createWorkChallenge(recipient, domain, target, options) {
|
|
44
|
+
export function createWorkChallenge(recipient, domain, target, options) {
|
|
52
45
|
if (!recipient)
|
|
53
46
|
throw new Error('WorkChallenge requires a recipient');
|
|
54
47
|
if (!domain)
|
|
@@ -56,15 +49,15 @@ function createWorkChallenge(recipient, domain, target, options) {
|
|
|
56
49
|
if (!/^[0-9a-fA-F]{64}$/.test(target)) {
|
|
57
50
|
throw new Error('WorkChallenge target must be 32-byte hex (64 hex chars)');
|
|
58
51
|
}
|
|
59
|
-
const ttlMs = options?.ttlMs ??
|
|
60
|
-
if (ttlMs <= 0 || ttlMs >
|
|
61
|
-
throw new Error(`WorkChallenge ttlMs must be in (0, ${
|
|
52
|
+
const ttlMs = options?.ttlMs ?? DEFAULT_CHALLENGE_TTL_MS;
|
|
53
|
+
if (ttlMs <= 0 || ttlMs > MAX_CHALLENGE_TTL_MS) {
|
|
54
|
+
throw new Error(`WorkChallenge ttlMs must be in (0, ${MAX_CHALLENGE_TTL_MS}]`);
|
|
62
55
|
}
|
|
63
56
|
const issuedAt = options?.issuedAt ?? Date.now();
|
|
64
57
|
const challengeId = options?.challengeId ?? randomHex(16);
|
|
65
58
|
const nonce = options?.nonce ?? randomHex(16);
|
|
66
59
|
return {
|
|
67
|
-
version:
|
|
60
|
+
version: MACHINE_WORK_ADMISSION_VERSION,
|
|
68
61
|
challengeId,
|
|
69
62
|
recipient,
|
|
70
63
|
domain,
|
|
@@ -82,11 +75,11 @@ function createWorkChallenge(recipient, domain, target, options) {
|
|
|
82
75
|
* expected recipient/domain. Does NOT check the proof hash — that is the
|
|
83
76
|
* caller's job via verifyWorkAdmission.
|
|
84
77
|
*/
|
|
85
|
-
function validateWorkChallenge(challenge, expected) {
|
|
78
|
+
export function validateWorkChallenge(challenge, expected) {
|
|
86
79
|
if (!challenge || typeof challenge !== 'object') {
|
|
87
80
|
return { valid: false, reason: 'challenge is not an object' };
|
|
88
81
|
}
|
|
89
|
-
if (challenge.version !==
|
|
82
|
+
if (challenge.version !== MACHINE_WORK_ADMISSION_VERSION) {
|
|
90
83
|
return { valid: false, reason: `unsupported challenge version ${challenge.version}` };
|
|
91
84
|
}
|
|
92
85
|
if (!challenge.challengeId) {
|
|
@@ -123,9 +116,9 @@ function validateWorkChallenge(challenge, expected) {
|
|
|
123
116
|
* Derive a stable challenge fingerprint (SHA3-256 of canonical challenge bytes).
|
|
124
117
|
* Useful for higher-level code that tracks already-consumed challenge IDs.
|
|
125
118
|
*/
|
|
126
|
-
function challengeFingerprint(challenge) {
|
|
119
|
+
export function challengeFingerprint(challenge) {
|
|
127
120
|
const canonical = canonicalChallenge(challenge);
|
|
128
|
-
return toHex(
|
|
121
|
+
return toHex(sha3_256(new TextEncoder().encode(canonical)));
|
|
129
122
|
}
|
|
130
123
|
/**
|
|
131
124
|
* Canonical serialization of a WorkChallenge.
|
|
@@ -134,7 +127,7 @@ function challengeFingerprint(challenge) {
|
|
|
134
127
|
* ambiguous concatenation is possible. Used for the commitment and for
|
|
135
128
|
* challenge fingerprints.
|
|
136
129
|
*/
|
|
137
|
-
function canonicalChallenge(challenge) {
|
|
130
|
+
export function canonicalChallenge(challenge) {
|
|
138
131
|
const parts = [];
|
|
139
132
|
const push = (s) => {
|
|
140
133
|
parts.push(String(s.length).padStart(8, '0'));
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
"use strict";
|
|
2
1
|
/**
|
|
3
2
|
* admission/commitment.ts — Canonical domain-separated action commitment.
|
|
4
3
|
*
|
|
@@ -13,12 +12,9 @@
|
|
|
13
12
|
* field, so the mined nonce commits the work to sender, recipient, action,
|
|
14
13
|
* challenge, target, and freshness.
|
|
15
14
|
*/
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
const core_1 = require("@totemsdk/core");
|
|
20
|
-
const types_js_1 = require("./types.js");
|
|
21
|
-
const challenge_js_1 = require("./challenge.js");
|
|
15
|
+
import { sha3_256 } from '@totemsdk/core';
|
|
16
|
+
import { MACHINE_WORK_ADMISSION_VERSION, MACHINE_WORK_DOMAIN, } from './types.js';
|
|
17
|
+
import { canonicalChallenge } from './challenge.js';
|
|
22
18
|
function toHex(bytes) {
|
|
23
19
|
return Array.from(bytes).map(b => b.toString(16).padStart(2, '0')).join('');
|
|
24
20
|
}
|
|
@@ -28,7 +24,7 @@ function toHex(bytes) {
|
|
|
28
24
|
* Length-prefixed fields in a fixed order; context keys are sorted so that
|
|
29
25
|
* equivalent input produces identical output.
|
|
30
26
|
*/
|
|
31
|
-
function canonicalAction(action) {
|
|
27
|
+
export function canonicalAction(action) {
|
|
32
28
|
const parts = [];
|
|
33
29
|
const push = (s) => {
|
|
34
30
|
parts.push(String(s.length).padStart(8, '0'));
|
|
@@ -61,13 +57,13 @@ function canonicalAction(action) {
|
|
|
61
57
|
* @param action The application action.
|
|
62
58
|
* @param challenge The challenge the work is bound to.
|
|
63
59
|
*/
|
|
64
|
-
function computeActionCommitment(action, challenge) {
|
|
65
|
-
const input =
|
|
60
|
+
export function computeActionCommitment(action, challenge) {
|
|
61
|
+
const input = MACHINE_WORK_DOMAIN +
|
|
66
62
|
'|' +
|
|
67
|
-
String(
|
|
63
|
+
String(MACHINE_WORK_ADMISSION_VERSION) +
|
|
68
64
|
'|' +
|
|
69
65
|
canonicalAction(action) +
|
|
70
66
|
'|' +
|
|
71
|
-
|
|
72
|
-
return toHex(
|
|
67
|
+
canonicalChallenge(challenge);
|
|
68
|
+
return toHex(sha3_256(new TextEncoder().encode(input)));
|
|
73
69
|
}
|
package/dist/admission/index.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
"use strict";
|
|
2
1
|
/**
|
|
3
2
|
* admission/index.ts — Machine Work Admission public surface.
|
|
4
3
|
*
|
|
@@ -7,36 +6,9 @@
|
|
|
7
6
|
* block candidate so that application anti-spam work simultaneously searches
|
|
8
7
|
* for valid Minima L1 blocks.
|
|
9
8
|
*/
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
Object.defineProperty(exports, "canonicalChallenge", { enumerable: true, get: function () { return challenge_js_1.canonicalChallenge; } });
|
|
17
|
-
Object.defineProperty(exports, "DEFAULT_CHALLENGE_TTL_MS", { enumerable: true, get: function () { return challenge_js_1.DEFAULT_CHALLENGE_TTL_MS; } });
|
|
18
|
-
Object.defineProperty(exports, "MAX_CHALLENGE_TTL_MS", { enumerable: true, get: function () { return challenge_js_1.MAX_CHALLENGE_TTL_MS; } });
|
|
19
|
-
var commitment_js_1 = require("./commitment.js");
|
|
20
|
-
Object.defineProperty(exports, "computeActionCommitment", { enumerable: true, get: function () { return commitment_js_1.computeActionCommitment; } });
|
|
21
|
-
Object.defineProperty(exports, "canonicalAction", { enumerable: true, get: function () { return commitment_js_1.canonicalAction; } });
|
|
22
|
-
var mine_js_1 = require("./mine.js");
|
|
23
|
-
Object.defineProperty(exports, "mineWorkAdmission", { enumerable: true, get: function () { return mine_js_1.mineWorkAdmission; } });
|
|
24
|
-
var verify_js_1 = require("./verify.js");
|
|
25
|
-
Object.defineProperty(exports, "verifyWorkAdmission", { enumerable: true, get: function () { return verify_js_1.verifyWorkAdmission; } });
|
|
26
|
-
var template_js_1 = require("./template.js");
|
|
27
|
-
Object.defineProperty(exports, "buildBlockHeaderTail", { enumerable: true, get: function () { return template_js_1.buildBlockHeaderTail; } });
|
|
28
|
-
Object.defineProperty(exports, "serializeSuperParents", { enumerable: true, get: function () { return template_js_1.serializeSuperParents; } });
|
|
29
|
-
Object.defineProperty(exports, "isBlockWinner", { enumerable: true, get: function () { return template_js_1.isBlockWinner; } });
|
|
30
|
-
Object.defineProperty(exports, "computeSuperLevel", { enumerable: true, get: function () { return template_js_1.computeSuperLevel; } });
|
|
31
|
-
Object.defineProperty(exports, "templateFreshness", { enumerable: true, get: function () { return template_js_1.templateFreshness; } });
|
|
32
|
-
Object.defineProperty(exports, "computeBlockCandidateId", { enumerable: true, get: function () { return template_js_1.computeBlockCandidateId; } });
|
|
33
|
-
Object.defineProperty(exports, "buildEmptyBlockBody", { enumerable: true, get: function () { return template_js_1.buildEmptyBlockBody; } });
|
|
34
|
-
Object.defineProperty(exports, "buildEmptyTransactionBytes", { enumerable: true, get: function () { return template_js_1.buildEmptyTransactionBytes; } });
|
|
35
|
-
Object.defineProperty(exports, "buildEmptyWitnessBytes", { enumerable: true, get: function () { return template_js_1.buildEmptyWitnessBytes; } });
|
|
36
|
-
Object.defineProperty(exports, "buildEmptyBurnTxBytes", { enumerable: true, get: function () { return template_js_1.buildEmptyBurnTxBytes; } });
|
|
37
|
-
Object.defineProperty(exports, "buildEmptyBurnWitnessBytes", { enumerable: true, get: function () { return template_js_1.buildEmptyBurnWitnessBytes; } });
|
|
38
|
-
Object.defineProperty(exports, "assembleTxPoWEnvelope", { enumerable: true, get: function () { return template_js_1.assembleTxPoWEnvelope; } });
|
|
39
|
-
Object.defineProperty(exports, "reconstructTxPoWEnvelope", { enumerable: true, get: function () { return template_js_1.reconstructTxPoWEnvelope; } });
|
|
40
|
-
var types_js_1 = require("./types.js");
|
|
41
|
-
Object.defineProperty(exports, "MACHINE_WORK_ADMISSION_VERSION", { enumerable: true, get: function () { return types_js_1.MACHINE_WORK_ADMISSION_VERSION; } });
|
|
42
|
-
Object.defineProperty(exports, "MACHINE_WORK_DOMAIN", { enumerable: true, get: function () { return types_js_1.MACHINE_WORK_DOMAIN; } });
|
|
9
|
+
export { createWorkChallenge, validateWorkChallenge, challengeFingerprint, canonicalChallenge, DEFAULT_CHALLENGE_TTL_MS, MAX_CHALLENGE_TTL_MS, } from './challenge.js';
|
|
10
|
+
export { computeActionCommitment, canonicalAction, } from './commitment.js';
|
|
11
|
+
export { mineWorkAdmission, } from './mine.js';
|
|
12
|
+
export { verifyWorkAdmission, } from './verify.js';
|
|
13
|
+
export { buildBlockHeaderTail, serializeSuperParents, isBlockWinner, computeSuperLevel, templateFreshness, computeBlockCandidateId, buildEmptyBlockBody, buildEmptyTransactionBytes, buildEmptyWitnessBytes, buildEmptyBurnTxBytes, buildEmptyBurnWitnessBytes, assembleTxPoWEnvelope, reconstructTxPoWEnvelope, } from './template.js';
|
|
14
|
+
export { MACHINE_WORK_ADMISSION_VERSION, MACHINE_WORK_DOMAIN, } from './types.js';
|
package/dist/admission/mine.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
"use strict";
|
|
2
1
|
/**
|
|
3
2
|
* admission/mine.ts — Machine Work Admission mining.
|
|
4
3
|
*
|
|
@@ -19,13 +18,11 @@
|
|
|
19
18
|
* stay off-chain; only genuine L1 winners are broadcast via the injected
|
|
20
19
|
* template provider.
|
|
21
20
|
*/
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
const template_js_1 = require("./template.js");
|
|
28
|
-
const types_js_1 = require("./types.js");
|
|
21
|
+
import { sha3_256 } from '@totemsdk/core';
|
|
22
|
+
import { mineHeaderTail } from '../mine.js';
|
|
23
|
+
import { computeActionCommitment } from './commitment.js';
|
|
24
|
+
import { buildBlockHeaderTail, buildEmptyBlockBody, assembleTxPoWEnvelope, computeSuperLevel, } from './template.js';
|
|
25
|
+
import { MACHINE_WORK_ADMISSION_VERSION, } from './types.js';
|
|
29
26
|
function toHex(bytes) {
|
|
30
27
|
return Array.from(bytes).map(b => b.toString(16).padStart(2, '0')).join('');
|
|
31
28
|
}
|
|
@@ -42,14 +39,14 @@ function toHex(bytes) {
|
|
|
42
39
|
* @param templateProvider Injected provider for the current Minima template.
|
|
43
40
|
* @param options Mining options.
|
|
44
41
|
*/
|
|
45
|
-
async function mineWorkAdmission(action, challenge, templateProvider, options) {
|
|
42
|
+
export async function mineWorkAdmission(action, challenge, templateProvider, options) {
|
|
46
43
|
const template = await templateProvider.getCurrentTemplate();
|
|
47
44
|
if (templateProvider.validateTemplate) {
|
|
48
45
|
const ok = await templateProvider.validateTemplate(template);
|
|
49
46
|
if (!ok)
|
|
50
47
|
throw new Error('Minima work template failed validation');
|
|
51
48
|
}
|
|
52
|
-
const actionCommitment =
|
|
49
|
+
const actionCommitment = computeActionCommitment(action, challenge);
|
|
53
50
|
const admissionTarget = challenge.target;
|
|
54
51
|
const admissionTargetBytes = hexToBytes(admissionTarget);
|
|
55
52
|
const blockTargetBytes = hexToBytes(template.blockDifficulty);
|
|
@@ -61,13 +58,13 @@ async function mineWorkAdmission(action, challenge, templateProvider, options) {
|
|
|
61
58
|
// Build the complete block candidate: empty TxBody (same shape Minima's
|
|
62
59
|
// MINEPULSE automine uses) + header tail with the action commitment.
|
|
63
60
|
const prng = options?.prng ?? randomBytes(32);
|
|
64
|
-
const bodyBytes =
|
|
65
|
-
const bodyHash = toHex(
|
|
61
|
+
const bodyBytes = buildEmptyBlockBody(prng, template.blockDifficulty);
|
|
62
|
+
const bodyHash = toHex(sha3_256(bodyBytes));
|
|
66
63
|
const timeMilli = options?.timeMilli ?? template.timeMilli;
|
|
67
|
-
const headerTail =
|
|
64
|
+
const headerTail = buildBlockHeaderTail(template, actionCommitment, bodyHash);
|
|
68
65
|
// Mine against the admission target. The same nonce search also checks the
|
|
69
66
|
// block target: if the winning hash beats the block target, it is a block.
|
|
70
|
-
const result = await
|
|
67
|
+
const result = await mineHeaderTail(headerTail, admissionTargetBytes, {
|
|
71
68
|
signal: options?.signal,
|
|
72
69
|
maxIterations: options?.maxIterations,
|
|
73
70
|
chunkSize: options?.chunkSize,
|
|
@@ -76,12 +73,12 @@ async function mineWorkAdmission(action, challenge, templateProvider, options) {
|
|
|
76
73
|
_skipWorker: options?._skipWorker,
|
|
77
74
|
});
|
|
78
75
|
// Assemble the complete Minima TxPoW wire format (header | 0x01 | body).
|
|
79
|
-
const envelope =
|
|
76
|
+
const envelope = assembleTxPoWEnvelope(result.minedHeaderBytes, bodyBytes);
|
|
80
77
|
// Exact Minima-derived metadata.
|
|
81
|
-
const superLevel =
|
|
78
|
+
const superLevel = computeSuperLevel(result.txpowId, template.blockDifficulty);
|
|
82
79
|
const isBlock = superLevel >= 0;
|
|
83
80
|
const proof = {
|
|
84
|
-
version:
|
|
81
|
+
version: MACHINE_WORK_ADMISSION_VERSION,
|
|
85
82
|
actionCommitment,
|
|
86
83
|
challengeId: challenge.challengeId,
|
|
87
84
|
admissionTarget,
|
|
@@ -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
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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 !==
|
|
51
|
-
throw new Error(`superParents must have ${
|
|
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(
|
|
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(
|
|
69
|
-
return
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 >=
|
|
214
|
-
superLevel =
|
|
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(
|
|
229
|
+
const bodyHash = toHex(sha3_256(body));
|
|
245
230
|
const envelope = assembleTxPoWEnvelope(headerBytes, body);
|
|
246
231
|
return { envelope, bodyHash };
|
|
247
232
|
}
|
package/dist/admission/types.js
CHANGED
|
@@ -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
|
-
|
|
19
|
+
export const MACHINE_WORK_ADMISSION_VERSION = 1;
|
|
23
20
|
/** Canonical protocol domain prefix for the commitment hash. */
|
|
24
|
-
|
|
21
|
+
export const MACHINE_WORK_DOMAIN = 'totem.machine-work-admission';
|
package/dist/admission/verify.js
CHANGED
|
@@ -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
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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 !==
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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' };
|
|
@@ -109,33 +106,45 @@ async function verifyWorkAdmission(action, challenge, proof, templateProvider, o
|
|
|
109
106
|
if (!isLessThan(txpowId, admissionTarget)) {
|
|
110
107
|
return { valid: false, reason: 'txpowId does not beat admission target' };
|
|
111
108
|
}
|
|
112
|
-
// 5. Template
|
|
113
|
-
|
|
109
|
+
// 5. Resolve a trusted template. Template fields — notably `blockDifficulty`,
|
|
110
|
+
// which drives the Super-level claim — must come from a freshly-fetched
|
|
111
|
+
// trusted template, never from the prover's copy (RFC-020 H4).
|
|
112
|
+
let trustedTemplate = options?.latestTemplate ?? null;
|
|
113
|
+
if (templateProvider) {
|
|
114
|
+
if (templateProvider.getLatestTemplate) {
|
|
115
|
+
trustedTemplate = await templateProvider.getLatestTemplate();
|
|
116
|
+
}
|
|
117
|
+
else if (options?.latestTemplate) {
|
|
118
|
+
trustedTemplate = options.latestTemplate;
|
|
119
|
+
}
|
|
120
|
+
else {
|
|
121
|
+
trustedTemplate = await templateProvider.getCurrentTemplate();
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
// 5a. Template freshness for admission (uses the proof's capture time).
|
|
125
|
+
const freshness = templateFreshness(proof.template, trustedTemplate, {
|
|
114
126
|
admissionWindowMs: options?.admissionWindowMs,
|
|
115
127
|
now: options?.now,
|
|
116
128
|
});
|
|
117
129
|
if (!freshness.admissionValid) {
|
|
118
130
|
return { valid: false, reason: 'proof template is stale for admission' };
|
|
119
131
|
}
|
|
120
|
-
//
|
|
121
|
-
//
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
132
|
+
// 5b. The prover's declared block difficulty must byte-match the trusted
|
|
133
|
+
// template — otherwise a prover could set a max difficulty to fake a
|
|
134
|
+
// Minima block.
|
|
135
|
+
if (trustedTemplate && trustedTemplate.blockDifficulty !== proof.template.blockDifficulty) {
|
|
136
|
+
return { valid: false, reason: 'proof template blockDifficulty does not match the trusted template' };
|
|
137
|
+
}
|
|
138
|
+
// 6. Level B: recompute the exact Minima Super level from the TRUSTED block
|
|
139
|
+
// difficulty and the re-derived txpowId. Without a trusted template we
|
|
140
|
+
// cannot claim Minima block contribution (isBlock = false).
|
|
141
|
+
const superLevel = trustedTemplate ? computeSuperLevel(txpowId, trustedTemplate.blockDifficulty) : -1;
|
|
142
|
+
const isBlock = trustedTemplate ? superLevel >= 0 : false;
|
|
143
|
+
// 7. Level C: broadcastability requires a live template provider, a current
|
|
144
|
+
// template, and isBlock. Offline mode leaves broadcastable undefined.
|
|
126
145
|
let broadcastable;
|
|
127
146
|
if (templateProvider) {
|
|
128
|
-
|
|
129
|
-
if (templateProvider.getLatestTemplate) {
|
|
130
|
-
latest = await templateProvider.getLatestTemplate();
|
|
131
|
-
}
|
|
132
|
-
else if (options?.latestTemplate) {
|
|
133
|
-
latest = options.latestTemplate;
|
|
134
|
-
}
|
|
135
|
-
else {
|
|
136
|
-
latest = await templateProvider.getCurrentTemplate();
|
|
137
|
-
}
|
|
138
|
-
broadcastable = isBlock && latest.templateId === proof.template.templateId;
|
|
147
|
+
broadcastable = isBlock && trustedTemplate !== null && freshness.broadcastable;
|
|
139
148
|
}
|
|
140
149
|
return { valid: true, superLevel, isBlock, broadcastable };
|
|
141
150
|
}
|
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
|
-
|
|
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
|
-
|
|
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
|
}
|