@totemsdk/txpow 0.1.7 → 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 +11 -6
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* admission/template.ts — Minima block-candidate template handling.
|
|
3
|
+
*
|
|
4
|
+
* Builds a TxHeader tail that is structurally capable of becoming a legitimate
|
|
5
|
+
* Minima block. The fields (block number, block difficulty, super-parents, MMR
|
|
6
|
+
* root/total, magic, time) come from the current chain tip via an injected
|
|
7
|
+
* MinimaWorkTemplateProvider — mirroring TxPoWGenerator.generateTxPoW in
|
|
8
|
+
* Minima. The action commitment is placed in the header's customHash field.
|
|
9
|
+
*
|
|
10
|
+
* The TxPoW package stays transport/node-client agnostic: callers inject the
|
|
11
|
+
* template provider. A real Minima node / Axia integration supplies live chain
|
|
12
|
+
* state; tests supply fixtures.
|
|
13
|
+
*/
|
|
14
|
+
import type { MinimaWorkTemplate } from './types.js';
|
|
15
|
+
/**
|
|
16
|
+
* Serialize the super-parent RLE runs per TxHeader.writeDataStream.
|
|
17
|
+
*
|
|
18
|
+
* Consecutive equal hashes are coalesced into (count: MiniByte, hash) runs.
|
|
19
|
+
* A fresh candidate with all-distinct parents serializes as 32 runs.
|
|
20
|
+
*/
|
|
21
|
+
export declare function serializeSuperParents(superParents: string[]): Uint8Array;
|
|
22
|
+
/**
|
|
23
|
+
* Build the empty burn transaction bytes (TxBody.writeDataStream fields 5-6).
|
|
24
|
+
*
|
|
25
|
+
* Equivalent to serializeTransaction({ linkHash: [0x00], inputs: [], outputs: [],
|
|
26
|
+
* state: [] }): 0 inputs, 0 outputs, 0 state, linkHash = ZERO_TXPOWID (1 byte).
|
|
27
|
+
*/
|
|
28
|
+
export declare function buildEmptyBurnTxBytes(): Uint8Array;
|
|
29
|
+
/**
|
|
30
|
+
* Build the empty burn witness bytes (TxBody.writeDataStream field 6).
|
|
31
|
+
* 0 signatures, 0 coinproofs, 0 scriptproofs.
|
|
32
|
+
*/
|
|
33
|
+
export declare function buildEmptyBurnWitnessBytes(): Uint8Array;
|
|
34
|
+
/**
|
|
35
|
+
* Build the empty transaction bytes (TxBody.writeDataStream field 3).
|
|
36
|
+
* 0 inputs, 0 outputs, 0 state, linkHash = ZERO_TXPOWID (1 byte).
|
|
37
|
+
*/
|
|
38
|
+
export declare function buildEmptyTransactionBytes(): Uint8Array;
|
|
39
|
+
/**
|
|
40
|
+
* Build the empty witness bytes (TxBody.writeDataStream field 4).
|
|
41
|
+
* 0 signatures, 0 coinproofs, 0 scriptproofs.
|
|
42
|
+
*/
|
|
43
|
+
export declare function buildEmptyWitnessBytes(): Uint8Array;
|
|
44
|
+
/**
|
|
45
|
+
* Build the serialized TxBody for a fresh block candidate.
|
|
46
|
+
*
|
|
47
|
+
* Mirrors TxBody.writeDataStream with an empty transaction and witness (the
|
|
48
|
+
* same shape Minima's MINEPULSE automine uses for block candidates):
|
|
49
|
+
* mPRNG | mTxnDifficulty | mTransaction | mWitness |
|
|
50
|
+
* mBurnTransaction | mBurnWitness | mTxPowIDList
|
|
51
|
+
*
|
|
52
|
+
* @param prng 32-byte PRNG (deterministic for tests; random otherwise).
|
|
53
|
+
* @param txnDifficulty Transaction difficulty (32-byte hex). For a block
|
|
54
|
+
* candidate this is typically the block difficulty.
|
|
55
|
+
*/
|
|
56
|
+
export declare function buildEmptyBlockBody(prng: Uint8Array, txnDifficulty: string): Uint8Array;
|
|
57
|
+
/**
|
|
58
|
+
* Assemble the complete Minima TxPoW wire format:
|
|
59
|
+
* TxHeader | 0x01 (hasBody) | TxBody
|
|
60
|
+
*
|
|
61
|
+
* This is the representation required for network submission of a genuine L1
|
|
62
|
+
* block candidate. A mined header alone is NOT sufficient.
|
|
63
|
+
*
|
|
64
|
+
* @param headerBytes Serialized TxHeader bytes (with the winning nonce).
|
|
65
|
+
* @param bodyBytes Serialized TxBody bytes.
|
|
66
|
+
*/
|
|
67
|
+
export declare function assembleTxPoWEnvelope(headerBytes: Uint8Array, bodyBytes: Uint8Array): Uint8Array;
|
|
68
|
+
/**
|
|
69
|
+
* Build the TxHeader tail (everything after the nonce field) for a block
|
|
70
|
+
* candidate with the given customHash commitment.
|
|
71
|
+
*
|
|
72
|
+
* Field order (TxHeader.writeDataStream):
|
|
73
|
+
* mNonce | mChainID | mTimeMilli | mBlockNumber | mBlockDifficulty |
|
|
74
|
+
* super-parents RLE | mMMRRoot | mMMRTotal | mMagic | mCustomHash | mTxBodyHash
|
|
75
|
+
*
|
|
76
|
+
* @param template The current Minima work template.
|
|
77
|
+
* @param customHash The action commitment (32-byte hex) to place in mCustomHash.
|
|
78
|
+
* @param txBodyHash SHA3-256 of the serialized TxBody (32-byte hex).
|
|
79
|
+
*/
|
|
80
|
+
export declare function buildBlockHeaderTail(template: MinimaWorkTemplate, customHash: string, txBodyHash: string): Uint8Array;
|
|
81
|
+
/**
|
|
82
|
+
* Compute the TxPoW ID for a mined block-candidate header.
|
|
83
|
+
* txpowId = SHA3-256(serialized TxHeader).
|
|
84
|
+
*/
|
|
85
|
+
export declare function computeBlockCandidateId(headerBytes: Uint8Array): Uint8Array;
|
|
86
|
+
/**
|
|
87
|
+
* Check whether a txpowId beats the block target (i.e. is a genuine Minima block).
|
|
88
|
+
* valid = txpowId < blockDifficulty (big-endian 256-bit comparison).
|
|
89
|
+
*/
|
|
90
|
+
export declare function isBlockWinner(txpowId: Uint8Array, blockDifficulty: string): boolean;
|
|
91
|
+
/**
|
|
92
|
+
* Compute the Minima Super level for a TxPoW ID against a block difficulty,
|
|
93
|
+
* with Minima's exact integer semantics.
|
|
94
|
+
*
|
|
95
|
+
* From TxPoW.calculateTXPOWID() / getSuperLevel():
|
|
96
|
+
* quot = blockDifficulty / txpowId (unsigned BigInteger division)
|
|
97
|
+
* super = quot.bitLength() - 1 (floor(log2(quot)))
|
|
98
|
+
* if super >= MINIMA_CASCADE_LEVELS (32) → clamp to 31
|
|
99
|
+
*
|
|
100
|
+
* When the TxPoW is NOT a block (txpowId >= blockDifficulty), quot = 0,
|
|
101
|
+
* bitLength(0) = 0, so super = -1.
|
|
102
|
+
*
|
|
103
|
+
* Result:
|
|
104
|
+
* -1 — not a Minima block
|
|
105
|
+
* 0 — ordinary/base Minima block (Super-0)
|
|
106
|
+
* 1..31 — Super-1 … Super-31 (stronger blocks; 31 is the maximum represented)
|
|
107
|
+
*
|
|
108
|
+
* @param txpowId SHA3-256(header), 32 bytes.
|
|
109
|
+
* @param blockDifficulty 32-byte hex block difficulty target.
|
|
110
|
+
*/
|
|
111
|
+
export declare function computeSuperLevel(txpowId: Uint8Array, blockDifficulty: string): number;
|
|
112
|
+
/**
|
|
113
|
+
* Default staleness policy: a template is "current enough" for admission if it
|
|
114
|
+
* was captured within the window, and "current" for L1 broadcast if it matches
|
|
115
|
+
* the latest template id.
|
|
116
|
+
*/
|
|
117
|
+
export declare function templateFreshness(template: MinimaWorkTemplate, latest: MinimaWorkTemplate | null, options?: {
|
|
118
|
+
admissionWindowMs?: number;
|
|
119
|
+
now?: number;
|
|
120
|
+
}): {
|
|
121
|
+
admissionValid: boolean;
|
|
122
|
+
broadcastable: boolean;
|
|
123
|
+
};
|
|
124
|
+
/**
|
|
125
|
+
* Reconstruct the complete Minima TxPoW envelope for a proof.
|
|
126
|
+
*
|
|
127
|
+
* Rebuilds the empty block TxBody deterministically, recomputes the body hash,
|
|
128
|
+
* and reassembles header | 0x01 | body. Used by verification to confirm the
|
|
129
|
+
* proof corresponds to a complete, Minima-serializable candidate.
|
|
130
|
+
*
|
|
131
|
+
* @param proofTemplate The template the proof was mined against.
|
|
132
|
+
* @param headerBytes The mined TxHeader bytes.
|
|
133
|
+
* @param prng The PRNG used when the proof was mined (32 bytes).
|
|
134
|
+
*/
|
|
135
|
+
export declare function reconstructTxPoWEnvelope(proofTemplate: MinimaWorkTemplate, headerBytes: Uint8Array, prng: Uint8Array): {
|
|
136
|
+
envelope: Uint8Array;
|
|
137
|
+
bodyHash: string;
|
|
138
|
+
};
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* admission/template.ts — Minima block-candidate template handling.
|
|
4
|
+
*
|
|
5
|
+
* Builds a TxHeader tail that is structurally capable of becoming a legitimate
|
|
6
|
+
* Minima block. The fields (block number, block difficulty, super-parents, MMR
|
|
7
|
+
* root/total, magic, time) come from the current chain tip via an injected
|
|
8
|
+
* MinimaWorkTemplateProvider — mirroring TxPoWGenerator.generateTxPoW in
|
|
9
|
+
* Minima. The action commitment is placed in the header's customHash field.
|
|
10
|
+
*
|
|
11
|
+
* The TxPoW package stays transport/node-client agnostic: callers inject the
|
|
12
|
+
* template provider. A real Minima node / Axia integration supplies live chain
|
|
13
|
+
* state; tests supply fixtures.
|
|
14
|
+
*/
|
|
15
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
16
|
+
exports.serializeSuperParents = serializeSuperParents;
|
|
17
|
+
exports.buildEmptyBurnTxBytes = buildEmptyBurnTxBytes;
|
|
18
|
+
exports.buildEmptyBurnWitnessBytes = buildEmptyBurnWitnessBytes;
|
|
19
|
+
exports.buildEmptyTransactionBytes = buildEmptyTransactionBytes;
|
|
20
|
+
exports.buildEmptyWitnessBytes = buildEmptyWitnessBytes;
|
|
21
|
+
exports.buildEmptyBlockBody = buildEmptyBlockBody;
|
|
22
|
+
exports.assembleTxPoWEnvelope = assembleTxPoWEnvelope;
|
|
23
|
+
exports.buildBlockHeaderTail = buildBlockHeaderTail;
|
|
24
|
+
exports.computeBlockCandidateId = computeBlockCandidateId;
|
|
25
|
+
exports.isBlockWinner = isBlockWinner;
|
|
26
|
+
exports.computeSuperLevel = computeSuperLevel;
|
|
27
|
+
exports.templateFreshness = templateFreshness;
|
|
28
|
+
exports.reconstructTxPoWEnvelope = reconstructTxPoWEnvelope;
|
|
29
|
+
const core_1 = require("@totemsdk/core");
|
|
30
|
+
const core_2 = require("@totemsdk/core");
|
|
31
|
+
const constants_js_1 = require("../constants.js");
|
|
32
|
+
function hexToBytes(hex) {
|
|
33
|
+
const h = hex.startsWith('0x') ? hex.slice(2) : hex;
|
|
34
|
+
const out = new Uint8Array(h.length / 2);
|
|
35
|
+
for (let i = 0; i < h.length; i += 2) {
|
|
36
|
+
out[i / 2] = parseInt(h.slice(i, i + 2), 16);
|
|
37
|
+
}
|
|
38
|
+
return out;
|
|
39
|
+
}
|
|
40
|
+
function toHex(bytes) {
|
|
41
|
+
return Array.from(bytes).map(b => b.toString(16).padStart(2, '0')).join('');
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Serialize the super-parent RLE runs per TxHeader.writeDataStream.
|
|
45
|
+
*
|
|
46
|
+
* Consecutive equal hashes are coalesced into (count: MiniByte, hash) runs.
|
|
47
|
+
* A fresh candidate with all-distinct parents serializes as 32 runs.
|
|
48
|
+
*/
|
|
49
|
+
function serializeSuperParents(superParents) {
|
|
50
|
+
if (superParents.length !== constants_js_1.CASCADE_LEVELS) {
|
|
51
|
+
throw new Error(`superParents must have ${constants_js_1.CASCADE_LEVELS} entries, got ${superParents.length}`);
|
|
52
|
+
}
|
|
53
|
+
const parts = [];
|
|
54
|
+
let runHash = superParents[0];
|
|
55
|
+
let runCount = 1;
|
|
56
|
+
for (let i = 1; i < superParents.length; i++) {
|
|
57
|
+
if (superParents[i] === runHash) {
|
|
58
|
+
runCount++;
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
parts.push(new Uint8Array([runCount]));
|
|
62
|
+
parts.push((0, core_2.writeHashToStream)(hexToBytes(runHash)));
|
|
63
|
+
runHash = superParents[i];
|
|
64
|
+
runCount = 1;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
parts.push(new Uint8Array([runCount]));
|
|
68
|
+
parts.push((0, core_2.writeHashToStream)(hexToBytes(runHash)));
|
|
69
|
+
return (0, core_2.concat)(...parts);
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Build the empty burn transaction bytes (TxBody.writeDataStream fields 5-6).
|
|
73
|
+
*
|
|
74
|
+
* Equivalent to serializeTransaction({ linkHash: [0x00], inputs: [], outputs: [],
|
|
75
|
+
* state: [] }): 0 inputs, 0 outputs, 0 state, linkHash = ZERO_TXPOWID (1 byte).
|
|
76
|
+
*/
|
|
77
|
+
function buildEmptyBurnTxBytes() {
|
|
78
|
+
return (0, core_2.concat)((0, core_2.writeMiniNumber)(0n, 0), (0, core_2.writeMiniNumber)(0n, 0), (0, core_2.writeMiniNumber)(0n, 0), (0, core_2.writeHashToStream)(new Uint8Array([0x00])));
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Build the empty burn witness bytes (TxBody.writeDataStream field 6).
|
|
82
|
+
* 0 signatures, 0 coinproofs, 0 scriptproofs.
|
|
83
|
+
*/
|
|
84
|
+
function buildEmptyBurnWitnessBytes() {
|
|
85
|
+
return (0, core_2.concat)((0, core_2.writeMiniNumber)(0n, 0), (0, core_2.writeMiniNumber)(0n, 0), (0, core_2.writeMiniNumber)(0n, 0));
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Build the empty transaction bytes (TxBody.writeDataStream field 3).
|
|
89
|
+
* 0 inputs, 0 outputs, 0 state, linkHash = ZERO_TXPOWID (1 byte).
|
|
90
|
+
*/
|
|
91
|
+
function buildEmptyTransactionBytes() {
|
|
92
|
+
return (0, core_2.concat)((0, core_2.writeMiniNumber)(0n, 0), (0, core_2.writeMiniNumber)(0n, 0), (0, core_2.writeMiniNumber)(0n, 0), (0, core_2.writeHashToStream)(new Uint8Array([0x00])));
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Build the empty witness bytes (TxBody.writeDataStream field 4).
|
|
96
|
+
* 0 signatures, 0 coinproofs, 0 scriptproofs.
|
|
97
|
+
*/
|
|
98
|
+
function buildEmptyWitnessBytes() {
|
|
99
|
+
return (0, core_2.concat)((0, core_2.writeMiniNumber)(0n, 0), (0, core_2.writeMiniNumber)(0n, 0), (0, core_2.writeMiniNumber)(0n, 0));
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Build the serialized TxBody for a fresh block candidate.
|
|
103
|
+
*
|
|
104
|
+
* Mirrors TxBody.writeDataStream with an empty transaction and witness (the
|
|
105
|
+
* same shape Minima's MINEPULSE automine uses for block candidates):
|
|
106
|
+
* mPRNG | mTxnDifficulty | mTransaction | mWitness |
|
|
107
|
+
* mBurnTransaction | mBurnWitness | mTxPowIDList
|
|
108
|
+
*
|
|
109
|
+
* @param prng 32-byte PRNG (deterministic for tests; random otherwise).
|
|
110
|
+
* @param txnDifficulty Transaction difficulty (32-byte hex). For a block
|
|
111
|
+
* candidate this is typically the block difficulty.
|
|
112
|
+
*/
|
|
113
|
+
function buildEmptyBlockBody(prng, txnDifficulty) {
|
|
114
|
+
return (0, core_2.concat)((0, core_2.writeHashToStream)(prng), (0, core_2.writeMiniData)(hexToBytes(txnDifficulty)), buildEmptyTransactionBytes(), buildEmptyWitnessBytes(), buildEmptyBurnTxBytes(), buildEmptyBurnWitnessBytes(), (0, core_2.writeMiniNumber)(0n, 0));
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Assemble the complete Minima TxPoW wire format:
|
|
118
|
+
* TxHeader | 0x01 (hasBody) | TxBody
|
|
119
|
+
*
|
|
120
|
+
* This is the representation required for network submission of a genuine L1
|
|
121
|
+
* block candidate. A mined header alone is NOT sufficient.
|
|
122
|
+
*
|
|
123
|
+
* @param headerBytes Serialized TxHeader bytes (with the winning nonce).
|
|
124
|
+
* @param bodyBytes Serialized TxBody bytes.
|
|
125
|
+
*/
|
|
126
|
+
function assembleTxPoWEnvelope(headerBytes, bodyBytes) {
|
|
127
|
+
return (0, core_2.concat)(headerBytes, new Uint8Array([0x01]), bodyBytes);
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Build the TxHeader tail (everything after the nonce field) for a block
|
|
131
|
+
* candidate with the given customHash commitment.
|
|
132
|
+
*
|
|
133
|
+
* Field order (TxHeader.writeDataStream):
|
|
134
|
+
* mNonce | mChainID | mTimeMilli | mBlockNumber | mBlockDifficulty |
|
|
135
|
+
* super-parents RLE | mMMRRoot | mMMRTotal | mMagic | mCustomHash | mTxBodyHash
|
|
136
|
+
*
|
|
137
|
+
* @param template The current Minima work template.
|
|
138
|
+
* @param customHash The action commitment (32-byte hex) to place in mCustomHash.
|
|
139
|
+
* @param txBodyHash SHA3-256 of the serialized TxBody (32-byte hex).
|
|
140
|
+
*/
|
|
141
|
+
function buildBlockHeaderTail(template, customHash, txBodyHash) {
|
|
142
|
+
return (0, core_2.concat)((0, core_2.writeMiniData)(hexToBytes(template.chainId)), (0, core_2.writeMiniNumber)(template.timeMilli, 0), (0, core_2.writeMiniNumber)(template.blockNumber, 0), (0, core_2.writeMiniData)(hexToBytes(template.blockDifficulty)), serializeSuperParents(template.superParents), (0, core_2.writeHashToStream)(hexToBytes(template.mmrRoot)), (0, core_2.writeMiniNumber)(template.mmrTotal, 0), hexToBytes(template.magic), (0, core_2.writeHashToStream)(hexToBytes(customHash)), (0, core_2.writeHashToStream)(hexToBytes(txBodyHash)));
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Compute the TxPoW ID for a mined block-candidate header.
|
|
146
|
+
* txpowId = SHA3-256(serialized TxHeader).
|
|
147
|
+
*/
|
|
148
|
+
function computeBlockCandidateId(headerBytes) {
|
|
149
|
+
return (0, core_1.sha3_256)(headerBytes);
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Check whether a txpowId beats the block target (i.e. is a genuine Minima block).
|
|
153
|
+
* valid = txpowId < blockDifficulty (big-endian 256-bit comparison).
|
|
154
|
+
*/
|
|
155
|
+
function isBlockWinner(txpowId, blockDifficulty) {
|
|
156
|
+
const target = hexToBytes(blockDifficulty);
|
|
157
|
+
for (let i = 0; i < 32; i++) {
|
|
158
|
+
if (txpowId[i] < target[i])
|
|
159
|
+
return true;
|
|
160
|
+
if (txpowId[i] > target[i])
|
|
161
|
+
return false;
|
|
162
|
+
}
|
|
163
|
+
return false;
|
|
164
|
+
}
|
|
165
|
+
/** Java BigInteger.bitLength() for non-negative values. bitLength(0) = 0. */
|
|
166
|
+
function bigIntBitLength(x) {
|
|
167
|
+
if (x <= 0n)
|
|
168
|
+
return 0;
|
|
169
|
+
let bits = 0;
|
|
170
|
+
let v = x;
|
|
171
|
+
while (v > 0n) {
|
|
172
|
+
v >>= 1n;
|
|
173
|
+
bits++;
|
|
174
|
+
}
|
|
175
|
+
return bits;
|
|
176
|
+
}
|
|
177
|
+
function bytesToBigInt(bytes) {
|
|
178
|
+
let v = 0n;
|
|
179
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
180
|
+
v = (v << 8n) | BigInt(bytes[i]);
|
|
181
|
+
}
|
|
182
|
+
return v;
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Compute the Minima Super level for a TxPoW ID against a block difficulty,
|
|
186
|
+
* with Minima's exact integer semantics.
|
|
187
|
+
*
|
|
188
|
+
* From TxPoW.calculateTXPOWID() / getSuperLevel():
|
|
189
|
+
* quot = blockDifficulty / txpowId (unsigned BigInteger division)
|
|
190
|
+
* super = quot.bitLength() - 1 (floor(log2(quot)))
|
|
191
|
+
* if super >= MINIMA_CASCADE_LEVELS (32) → clamp to 31
|
|
192
|
+
*
|
|
193
|
+
* When the TxPoW is NOT a block (txpowId >= blockDifficulty), quot = 0,
|
|
194
|
+
* bitLength(0) = 0, so super = -1.
|
|
195
|
+
*
|
|
196
|
+
* Result:
|
|
197
|
+
* -1 — not a Minima block
|
|
198
|
+
* 0 — ordinary/base Minima block (Super-0)
|
|
199
|
+
* 1..31 — Super-1 … Super-31 (stronger blocks; 31 is the maximum represented)
|
|
200
|
+
*
|
|
201
|
+
* @param txpowId SHA3-256(header), 32 bytes.
|
|
202
|
+
* @param blockDifficulty 32-byte hex block difficulty target.
|
|
203
|
+
*/
|
|
204
|
+
function computeSuperLevel(txpowId, blockDifficulty) {
|
|
205
|
+
const diffBigInt = bytesToBigInt(hexToBytes(blockDifficulty));
|
|
206
|
+
const idBigInt = bytesToBigInt(txpowId);
|
|
207
|
+
// Not a block: quot = 0 → super = -1
|
|
208
|
+
if (idBigInt >= diffBigInt)
|
|
209
|
+
return -1;
|
|
210
|
+
const quot = diffBigInt / idBigInt;
|
|
211
|
+
let superLevel = bigIntBitLength(quot) - 1;
|
|
212
|
+
// Clamp to the maximum represented Super level, exactly as Minima does.
|
|
213
|
+
if (superLevel >= constants_js_1.CASCADE_LEVELS) {
|
|
214
|
+
superLevel = constants_js_1.CASCADE_LEVELS - 1;
|
|
215
|
+
}
|
|
216
|
+
return superLevel;
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Default staleness policy: a template is "current enough" for admission if it
|
|
220
|
+
* was captured within the window, and "current" for L1 broadcast if it matches
|
|
221
|
+
* the latest template id.
|
|
222
|
+
*/
|
|
223
|
+
function templateFreshness(template, latest, options) {
|
|
224
|
+
const now = options?.now ?? Date.now();
|
|
225
|
+
const windowMs = options?.admissionWindowMs ?? 5 * 60 * 1000;
|
|
226
|
+
const age = now - template.capturedAt;
|
|
227
|
+
const admissionValid = age >= 0 && age <= windowMs;
|
|
228
|
+
const broadcastable = latest !== null && template.templateId === latest.templateId;
|
|
229
|
+
return { admissionValid, broadcastable };
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Reconstruct the complete Minima TxPoW envelope for a proof.
|
|
233
|
+
*
|
|
234
|
+
* Rebuilds the empty block TxBody deterministically, recomputes the body hash,
|
|
235
|
+
* and reassembles header | 0x01 | body. Used by verification to confirm the
|
|
236
|
+
* proof corresponds to a complete, Minima-serializable candidate.
|
|
237
|
+
*
|
|
238
|
+
* @param proofTemplate The template the proof was mined against.
|
|
239
|
+
* @param headerBytes The mined TxHeader bytes.
|
|
240
|
+
* @param prng The PRNG used when the proof was mined (32 bytes).
|
|
241
|
+
*/
|
|
242
|
+
function reconstructTxPoWEnvelope(proofTemplate, headerBytes, prng) {
|
|
243
|
+
const body = buildEmptyBlockBody(prng, proofTemplate.blockDifficulty);
|
|
244
|
+
const bodyHash = toHex((0, core_1.sha3_256)(body));
|
|
245
|
+
const envelope = assembleTxPoWEnvelope(headerBytes, body);
|
|
246
|
+
return { envelope, bodyHash };
|
|
247
|
+
}
|