@totemsdk/txpow 1.0.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +88 -0
- package/dist/admission/challenge.d.ts +66 -0
- package/dist/admission/challenge.js +155 -0
- package/dist/admission/commitment.d.ts +34 -0
- package/dist/admission/commitment.js +73 -0
- package/dist/admission/index.d.ts +14 -0
- package/dist/admission/index.js +42 -0
- package/dist/admission/mine.d.ts +60 -0
- package/dist/admission/mine.js +146 -0
- package/dist/admission/template.d.ts +138 -0
- package/dist/admission/template.js +247 -0
- package/dist/admission/types.d.ts +224 -0
- package/dist/admission/types.js +24 -0
- package/dist/admission/verify.d.ts +50 -0
- package/dist/admission/verify.js +144 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +33 -1
- package/dist/mine.d.ts +24 -0
- package/dist/mine.js +30 -8
- package/package.json +9 -4
package/README.md
CHANGED
|
@@ -69,6 +69,94 @@ const estimatedMs = estimateMiningCost(targetDifficulty, hashesPerSecond);
|
|
|
69
69
|
console.log(`Expected mining time: ${estimatedMs}ms`);
|
|
70
70
|
```
|
|
71
71
|
|
|
72
|
+
## Machine Work Admission
|
|
73
|
+
|
|
74
|
+
Machine Work Admission allows applications to require computational proof before allocating scarce resources. The work is performed against a Minima block candidate so that application anti-spam work simultaneously searches for valid Minima blocks.
|
|
75
|
+
|
|
76
|
+
A receiver issues a `WorkChallenge` (unique, expiring, bound to the receiver and an application domain). A sender commits its application action into the TxPoW header's `customHash` field and mines the nonce space of a real current Minima block candidate. If the hash beats the receiver's admission target, the machine action is admissible. If the same hash also beats the current Minima block target, the candidate is a genuine Minima block (Super-0 … Super-31) and is relayable.
|
|
77
|
+
|
|
78
|
+
Ordinary admission proofs stay off-chain. Only actual Minima blocks are eligible for relay.
|
|
79
|
+
|
|
80
|
+
```typescript
|
|
81
|
+
import {
|
|
82
|
+
createWorkChallenge,
|
|
83
|
+
mineWorkAdmission,
|
|
84
|
+
verifyWorkAdmission,
|
|
85
|
+
type MinimaWorkTemplateProvider,
|
|
86
|
+
type MinimaWorkRelay,
|
|
87
|
+
} from '@totemsdk/txpow';
|
|
88
|
+
|
|
89
|
+
// Receiver: issue a challenge (target chosen by receiver policy)
|
|
90
|
+
const challenge = createWorkChallenge(
|
|
91
|
+
'receiver-address',
|
|
92
|
+
'totem.compute.reserve',
|
|
93
|
+
admissionTargetHex,
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
// Sender: mine the admission proof against a real Minima block candidate
|
|
97
|
+
// The relay boundary keeps Minima networking out of the core primitive.
|
|
98
|
+
const relay: MinimaWorkRelay = {
|
|
99
|
+
submitBlock: async (envelope) => node.submitBlock(envelope),
|
|
100
|
+
};
|
|
101
|
+
const provider: MinimaWorkTemplateProvider = {
|
|
102
|
+
getCurrentTemplate: async () => node.fetchCurrentTemplate(),
|
|
103
|
+
getLatestTemplate: async () => node.fetchCurrentTemplate(),
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
// The challenge target is the single authoritative admission target.
|
|
107
|
+
const proof = await mineWorkAdmission(action, challenge, provider, { relay });
|
|
108
|
+
|
|
109
|
+
// Receiver: verify (never trusts sender-reported hardware speed)
|
|
110
|
+
const result = await verifyWorkAdmission(action, challenge, proof, provider);
|
|
111
|
+
if (result.valid) {
|
|
112
|
+
// allocate the scarce resource
|
|
113
|
+
}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### Minima Super level
|
|
117
|
+
|
|
118
|
+
A TxPoW is a Minima block when `txpowId < blockDifficulty`. Its strength is the exact Minima Super level:
|
|
119
|
+
|
|
120
|
+
```text
|
|
121
|
+
superLevel = floor(log2(blockDifficulty / txpowId))
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
with `MINIMA_CASCADE_LEVELS = 32`, clamped so the maximum represented value is 31.
|
|
125
|
+
|
|
126
|
+
| `superLevel` | Meaning |
|
|
127
|
+
|--------------|---------|
|
|
128
|
+
| `-1` | not a Minima block |
|
|
129
|
+
| `0` | ordinary/base Minima block (Super-0) |
|
|
130
|
+
| `1` | stronger block (Super-1) |
|
|
131
|
+
| … | |
|
|
132
|
+
| `31` | maximum represented Super level |
|
|
133
|
+
|
|
134
|
+
**Super-0 is an ordinary valid Minima block. Non-block TxPoWs have computed Super level -1.** Super level describes block strength — it is not a relay policy. Every valid current block (Super-0 … Super-31) is eligible for relay.
|
|
135
|
+
|
|
136
|
+
### Three distinct claims
|
|
137
|
+
|
|
138
|
+
Verification distinguishes three claims that must never be confused:
|
|
139
|
+
|
|
140
|
+
| Claim | Meaning | Field |
|
|
141
|
+
|-------|---------|-------|
|
|
142
|
+
| **A. admission-valid** | the hash satisfies `challenge.target` | `valid` |
|
|
143
|
+
| **B. Minima block** | the hash beats the block difficulty encoded by the candidate template | `superLevel` / `isBlock` |
|
|
144
|
+
| **C. broadcastable** | a Minima block AND the template is still current AND a live template provider was supplied | `broadcastable` |
|
|
145
|
+
|
|
146
|
+
A stale candidate may remain `valid = true` (and `superLevel >= 0`) while `broadcastable = false`. Offline verification (no `MinimaWorkTemplateProvider`) leaves `broadcastable` undefined — it does **not** claim Minima block contribution.
|
|
147
|
+
|
|
148
|
+
`proof.superLevel`, `proof.isBlock`, and `proof.qualifiesAsMinimaBlock` are derived metadata recorded at mining time. Verification never trusts them — they are recomputed from the re-derived TxPoW ID and the template's block difficulty.
|
|
149
|
+
|
|
150
|
+
### Distributed mining boundary
|
|
151
|
+
|
|
152
|
+
Machine Work Admission is primarily local/off-chain: the requesting machine performs the work, which keeps it attributable to the machine requesting admission. Native Minima distributed mining (`MSG_TXBLOCKMINE`, where receiving peers reset the body PRNG to create independent search spaces) is a separate, optional, rate-limited, integration-controlled mechanism. It is NOT used per machine action — broadcasting every machine action through `MSG_TXBLOCKMINE` would turn machine anti-spam into Minima network spam. The body PRNG is independent candidate entropy; it never changes the `customHash` application commitment.
|
|
153
|
+
|
|
154
|
+
### Authentication boundary
|
|
155
|
+
|
|
156
|
+
`validateWorkChallenge()` validates structural/freshness/domain/recipient properties but does **not** prove that the claimed recipient actually issued the challenge. A `WorkChallenge` is a plain data object; anyone can construct one claiming any recipient. Proving the issuer requires authenticating the enclosing message (e.g. a signed machine-to-machine protocol message in the Edge negotiation layer). `@totemsdk/txpow` deliberately does not implement a parallel identity/signature system.
|
|
157
|
+
|
|
158
|
+
The primitive is generic — it knows only "there is an application action represented by canonical bytes". Domain-specific packages supply the action commitment for negotiation, compute, storage, mailbox, sensor, or rendezvous domains. It does NOT prove identity, authority, payment, or resource availability; higher-level layers must still perform those checks.
|
|
159
|
+
|
|
72
160
|
## Upstream Java source
|
|
73
161
|
|
|
74
162
|
This package is a TypeScript port of Minima's TxPoW envelope structures. Canonical upstream references:
|
|
@@ -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>;
|