@totemsdk/txpow 1.0.2 → 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 +12 -15
- 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
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* admission/index.ts — Machine Work Admission public surface.
|
|
3
|
+
*
|
|
4
|
+
* Machine Work Admission allows applications to require computational proof
|
|
5
|
+
* before allocating scarce resources. The work is performed against a Minima
|
|
6
|
+
* block candidate so that application anti-spam work simultaneously searches
|
|
7
|
+
* for valid Minima L1 blocks.
|
|
8
|
+
*/
|
|
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, type MineWorkAdmissionOptions, } from './mine.js';
|
|
12
|
+
export { verifyWorkAdmission, type VerifyWorkAdmissionOptions, } 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, type WorkChallenge, type MachineWorkAction, type MinimaWorkTemplate, type MinimaWorkTemplateProvider, type MinimaWorkRelay, type MachineWorkAdmissionProof, type WorkAdmissionVerification, } from './types.js';
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* admission/index.ts — Machine Work Admission public surface.
|
|
4
|
+
*
|
|
5
|
+
* Machine Work Admission allows applications to require computational proof
|
|
6
|
+
* before allocating scarce resources. The work is performed against a Minima
|
|
7
|
+
* block candidate so that application anti-spam work simultaneously searches
|
|
8
|
+
* for valid Minima L1 blocks.
|
|
9
|
+
*/
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
exports.MACHINE_WORK_DOMAIN = exports.MACHINE_WORK_ADMISSION_VERSION = exports.reconstructTxPoWEnvelope = exports.assembleTxPoWEnvelope = exports.buildEmptyBurnWitnessBytes = exports.buildEmptyBurnTxBytes = exports.buildEmptyWitnessBytes = exports.buildEmptyTransactionBytes = exports.buildEmptyBlockBody = exports.computeBlockCandidateId = exports.templateFreshness = exports.computeSuperLevel = exports.isBlockWinner = exports.serializeSuperParents = exports.buildBlockHeaderTail = exports.verifyWorkAdmission = exports.mineWorkAdmission = exports.canonicalAction = exports.computeActionCommitment = exports.MAX_CHALLENGE_TTL_MS = exports.DEFAULT_CHALLENGE_TTL_MS = exports.canonicalChallenge = exports.challengeFingerprint = exports.validateWorkChallenge = exports.createWorkChallenge = void 0;
|
|
12
|
+
var challenge_js_1 = require("./challenge.js");
|
|
13
|
+
Object.defineProperty(exports, "createWorkChallenge", { enumerable: true, get: function () { return challenge_js_1.createWorkChallenge; } });
|
|
14
|
+
Object.defineProperty(exports, "validateWorkChallenge", { enumerable: true, get: function () { return challenge_js_1.validateWorkChallenge; } });
|
|
15
|
+
Object.defineProperty(exports, "challengeFingerprint", { enumerable: true, get: function () { return challenge_js_1.challengeFingerprint; } });
|
|
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; } });
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* admission/mine.ts — Machine Work Admission mining.
|
|
3
|
+
*
|
|
4
|
+
* Mines a real Minima block candidate whose customHash commits to the machine
|
|
5
|
+
* action. The mining loop reuses the existing TxPoW mining loop
|
|
6
|
+
* (`mineHeaderTail` from ../mine.js) — the same SHA3-256 nonce search that
|
|
7
|
+
* Minima uses for transaction/block mining.
|
|
8
|
+
*
|
|
9
|
+
* The admission target is ALWAYS `challenge.target` — the challenge is the
|
|
10
|
+
* single source of truth. There is no separate admission target argument.
|
|
11
|
+
*
|
|
12
|
+
* Two thresholds:
|
|
13
|
+
* - admission target (receiver-chosen, easier): hash < challenge.target
|
|
14
|
+
* - L1 block target (current Minima block difficulty): hash < blockTarget
|
|
15
|
+
*
|
|
16
|
+
* The loop stops as soon as the admission target is satisfied, unless the same
|
|
17
|
+
* search already discovered a block-target winner. Ordinary admission proofs
|
|
18
|
+
* stay off-chain; only genuine L1 winners are broadcast via the injected
|
|
19
|
+
* template provider.
|
|
20
|
+
*/
|
|
21
|
+
import { type MachineWorkAction, type MachineWorkAdmissionProof, type MinimaWorkRelay, type MinimaWorkTemplateProvider, type WorkChallenge } from './types.js';
|
|
22
|
+
export interface MineWorkAdmissionOptions {
|
|
23
|
+
/** AbortSignal — rejects the Promise when aborted. */
|
|
24
|
+
signal?: AbortSignal;
|
|
25
|
+
/** Hard cap on total iterations (default: unlimited). */
|
|
26
|
+
maxIterations?: number;
|
|
27
|
+
/** Hash iterations per async yield (default: 10_000). */
|
|
28
|
+
chunkSize?: number;
|
|
29
|
+
/** Override the template timeMilli for deterministic testing. */
|
|
30
|
+
timeMilli?: bigint;
|
|
31
|
+
/** Force the pure-JS mining path (for testing). */
|
|
32
|
+
forceJs?: boolean;
|
|
33
|
+
/** Skip the Node.js worker_threads Worker (for testing). */
|
|
34
|
+
_skipWorker?: boolean;
|
|
35
|
+
/**
|
|
36
|
+
* Deterministic 32-byte PRNG for the TxBody (testing only). When omitted a
|
|
37
|
+
* cryptographically random PRNG is generated.
|
|
38
|
+
*/
|
|
39
|
+
prng?: Uint8Array;
|
|
40
|
+
/**
|
|
41
|
+
* Preferred Minima block relay boundary. When supplied, a genuine current
|
|
42
|
+
* Minima block is submitted through the relay exactly once. Falls back to
|
|
43
|
+
* the deprecated provider.broadcastBlockCandidate only when no relay is set.
|
|
44
|
+
*/
|
|
45
|
+
relay?: MinimaWorkRelay;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Mine a Machine Work Admission proof.
|
|
49
|
+
*
|
|
50
|
+
* The admission target is derived from `challenge.target` — the challenge is
|
|
51
|
+
* the single authoritative source. The miner searches the nonce space of a
|
|
52
|
+
* real Minima block candidate (from the injected template provider) whose
|
|
53
|
+
* customHash commits to the action.
|
|
54
|
+
*
|
|
55
|
+
* @param action The application action.
|
|
56
|
+
* @param challenge The receiver-issued challenge (target is authoritative).
|
|
57
|
+
* @param templateProvider Injected provider for the current Minima template.
|
|
58
|
+
* @param options Mining options.
|
|
59
|
+
*/
|
|
60
|
+
export declare function mineWorkAdmission(action: MachineWorkAction, challenge: WorkChallenge, templateProvider: MinimaWorkTemplateProvider, options?: MineWorkAdmissionOptions): Promise<MachineWorkAdmissionProof>;
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* admission/mine.ts — Machine Work Admission mining.
|
|
4
|
+
*
|
|
5
|
+
* Mines a real Minima block candidate whose customHash commits to the machine
|
|
6
|
+
* action. The mining loop reuses the existing TxPoW mining loop
|
|
7
|
+
* (`mineHeaderTail` from ../mine.js) — the same SHA3-256 nonce search that
|
|
8
|
+
* Minima uses for transaction/block mining.
|
|
9
|
+
*
|
|
10
|
+
* The admission target is ALWAYS `challenge.target` — the challenge is the
|
|
11
|
+
* single source of truth. There is no separate admission target argument.
|
|
12
|
+
*
|
|
13
|
+
* Two thresholds:
|
|
14
|
+
* - admission target (receiver-chosen, easier): hash < challenge.target
|
|
15
|
+
* - L1 block target (current Minima block difficulty): hash < blockTarget
|
|
16
|
+
*
|
|
17
|
+
* The loop stops as soon as the admission target is satisfied, unless the same
|
|
18
|
+
* search already discovered a block-target winner. Ordinary admission proofs
|
|
19
|
+
* stay off-chain; only genuine L1 winners are broadcast via the injected
|
|
20
|
+
* template provider.
|
|
21
|
+
*/
|
|
22
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
23
|
+
exports.mineWorkAdmission = mineWorkAdmission;
|
|
24
|
+
const core_1 = require("@totemsdk/core");
|
|
25
|
+
const mine_js_1 = require("../mine.js");
|
|
26
|
+
const commitment_js_1 = require("./commitment.js");
|
|
27
|
+
const template_js_1 = require("./template.js");
|
|
28
|
+
const types_js_1 = require("./types.js");
|
|
29
|
+
function toHex(bytes) {
|
|
30
|
+
return Array.from(bytes).map(b => b.toString(16).padStart(2, '0')).join('');
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Mine a Machine Work Admission proof.
|
|
34
|
+
*
|
|
35
|
+
* The admission target is derived from `challenge.target` — the challenge is
|
|
36
|
+
* the single authoritative source. The miner searches the nonce space of a
|
|
37
|
+
* real Minima block candidate (from the injected template provider) whose
|
|
38
|
+
* customHash commits to the action.
|
|
39
|
+
*
|
|
40
|
+
* @param action The application action.
|
|
41
|
+
* @param challenge The receiver-issued challenge (target is authoritative).
|
|
42
|
+
* @param templateProvider Injected provider for the current Minima template.
|
|
43
|
+
* @param options Mining options.
|
|
44
|
+
*/
|
|
45
|
+
async function mineWorkAdmission(action, challenge, templateProvider, options) {
|
|
46
|
+
const template = await templateProvider.getCurrentTemplate();
|
|
47
|
+
if (templateProvider.validateTemplate) {
|
|
48
|
+
const ok = await templateProvider.validateTemplate(template);
|
|
49
|
+
if (!ok)
|
|
50
|
+
throw new Error('Minima work template failed validation');
|
|
51
|
+
}
|
|
52
|
+
const actionCommitment = (0, commitment_js_1.computeActionCommitment)(action, challenge);
|
|
53
|
+
const admissionTarget = challenge.target;
|
|
54
|
+
const admissionTargetBytes = hexToBytes(admissionTarget);
|
|
55
|
+
const blockTargetBytes = hexToBytes(template.blockDifficulty);
|
|
56
|
+
// The admission target must be easier than or equal to the block target,
|
|
57
|
+
// otherwise the search would be chasing a block that can never admit.
|
|
58
|
+
if (!isLessThanOrEqual(blockTargetBytes, admissionTargetBytes)) {
|
|
59
|
+
throw new Error('challenge.target must be easier than or equal to the current block target');
|
|
60
|
+
}
|
|
61
|
+
// Build the complete block candidate: empty TxBody (same shape Minima's
|
|
62
|
+
// MINEPULSE automine uses) + header tail with the action commitment.
|
|
63
|
+
const prng = options?.prng ?? randomBytes(32);
|
|
64
|
+
const bodyBytes = (0, template_js_1.buildEmptyBlockBody)(prng, template.blockDifficulty);
|
|
65
|
+
const bodyHash = toHex((0, core_1.sha3_256)(bodyBytes));
|
|
66
|
+
const timeMilli = options?.timeMilli ?? template.timeMilli;
|
|
67
|
+
const headerTail = (0, template_js_1.buildBlockHeaderTail)(template, actionCommitment, bodyHash);
|
|
68
|
+
// Mine against the admission target. The same nonce search also checks the
|
|
69
|
+
// block target: if the winning hash beats the block target, it is a block.
|
|
70
|
+
const result = await (0, mine_js_1.mineHeaderTail)(headerTail, admissionTargetBytes, {
|
|
71
|
+
signal: options?.signal,
|
|
72
|
+
maxIterations: options?.maxIterations,
|
|
73
|
+
chunkSize: options?.chunkSize,
|
|
74
|
+
timeMilli,
|
|
75
|
+
forceJs: options?.forceJs,
|
|
76
|
+
_skipWorker: options?._skipWorker,
|
|
77
|
+
});
|
|
78
|
+
// Assemble the complete Minima TxPoW wire format (header | 0x01 | body).
|
|
79
|
+
const envelope = (0, template_js_1.assembleTxPoWEnvelope)(result.minedHeaderBytes, bodyBytes);
|
|
80
|
+
// Exact Minima-derived metadata.
|
|
81
|
+
const superLevel = (0, template_js_1.computeSuperLevel)(result.txpowId, template.blockDifficulty);
|
|
82
|
+
const isBlock = superLevel >= 0;
|
|
83
|
+
const proof = {
|
|
84
|
+
version: types_js_1.MACHINE_WORK_ADMISSION_VERSION,
|
|
85
|
+
actionCommitment,
|
|
86
|
+
challengeId: challenge.challengeId,
|
|
87
|
+
admissionTarget,
|
|
88
|
+
txpow: toHex(result.minedHeaderBytes),
|
|
89
|
+
txpowEnvelope: toHex(envelope),
|
|
90
|
+
txpowId: toHex(result.txpowId),
|
|
91
|
+
nonce: result.nonce.toString(),
|
|
92
|
+
minedAt: Date.now(),
|
|
93
|
+
qualifiesForAdmission: true,
|
|
94
|
+
qualifiesAsMinimaBlock: isBlock,
|
|
95
|
+
superLevel,
|
|
96
|
+
isBlock,
|
|
97
|
+
template,
|
|
98
|
+
};
|
|
99
|
+
// Broadcast genuine Minima blocks exactly once, and only if the template is
|
|
100
|
+
// still current. All Super levels (0..31) are eligible — Super level is
|
|
101
|
+
// block strength, NOT a relay policy. Ordinary admission proofs never broadcast.
|
|
102
|
+
if (isBlock) {
|
|
103
|
+
let latest;
|
|
104
|
+
if (templateProvider.getLatestTemplate) {
|
|
105
|
+
latest = await templateProvider.getLatestTemplate();
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
latest = await templateProvider.getCurrentTemplate();
|
|
109
|
+
}
|
|
110
|
+
if (latest.templateId === template.templateId) {
|
|
111
|
+
if (options?.relay) {
|
|
112
|
+
await options.relay.submitBlock(envelope);
|
|
113
|
+
}
|
|
114
|
+
else if (templateProvider.broadcastBlockCandidate) {
|
|
115
|
+
await templateProvider.broadcastBlockCandidate(proof);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return proof;
|
|
120
|
+
}
|
|
121
|
+
function hexToBytes(hex) {
|
|
122
|
+
const h = hex.startsWith('0x') ? hex.slice(2) : hex;
|
|
123
|
+
const out = new Uint8Array(h.length / 2);
|
|
124
|
+
for (let i = 0; i < h.length; i += 2) {
|
|
125
|
+
out[i / 2] = parseInt(h.slice(i, i + 2), 16);
|
|
126
|
+
}
|
|
127
|
+
return out;
|
|
128
|
+
}
|
|
129
|
+
function randomBytes(length) {
|
|
130
|
+
const out = new Uint8Array(length);
|
|
131
|
+
if (typeof crypto === 'undefined' || !crypto.getRandomValues) {
|
|
132
|
+
throw new Error('SECURITY: crypto.getRandomValues unavailable — cannot generate secure random PRNG');
|
|
133
|
+
}
|
|
134
|
+
crypto.getRandomValues(out);
|
|
135
|
+
return out;
|
|
136
|
+
}
|
|
137
|
+
/** Big-endian 256-bit comparison: true if a <= b. */
|
|
138
|
+
function isLessThanOrEqual(a, b) {
|
|
139
|
+
for (let i = 0; i < 32; i++) {
|
|
140
|
+
if (a[i] < b[i])
|
|
141
|
+
return true;
|
|
142
|
+
if (a[i] > b[i])
|
|
143
|
+
return false;
|
|
144
|
+
}
|
|
145
|
+
return true;
|
|
146
|
+
}
|