@totemsdk/core 1.3.0 → 1.4.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/LICENSE +202 -21
- package/NOTICE +60 -0
- package/dist/cjs/index.d.ts +4 -2
- package/dist/cjs/index.js +20 -8
- package/dist/cjs/mmr.d.ts +22 -0
- package/dist/cjs/mmr.js +33 -0
- package/dist/cjs/scripts/canonical.d.ts +20 -0
- package/dist/cjs/scripts/canonical.js +99 -0
- package/dist/cjs/scripts/index.d.ts +2 -1
- package/dist/cjs/scripts/index.js +4 -1
- package/dist/cjs/scripts/witness-serializer.d.ts +19 -0
- package/dist/cjs/scripts/witness-serializer.js +25 -5
- package/dist/cjs/transaction-json.d.ts +19 -0
- package/dist/cjs/transaction-json.js +114 -0
- package/dist/cjs/wasm-sync.d.ts +10 -0
- package/dist/cjs/wasm-sync.js +16 -0
- package/dist/index.d.ts +4 -2
- package/dist/index.js +7 -2
- package/dist/mmr.d.ts +22 -0
- package/dist/mmr.js +33 -1
- package/dist/scripts/canonical.d.ts +20 -0
- package/dist/scripts/canonical.js +96 -0
- package/dist/scripts/index.d.ts +2 -1
- package/dist/scripts/index.js +2 -1
- package/dist/scripts/witness-serializer.d.ts +19 -0
- package/dist/scripts/witness-serializer.js +25 -6
- package/dist/transaction-json.d.ts +19 -0
- package/dist/transaction-json.js +107 -0
- package/dist/wasm-sync.d.ts +10 -0
- package/dist/wasm-sync.js +14 -0
- package/package.json +5 -4
|
@@ -25,5 +25,24 @@ export declare function aggregateSignatures(totemSignature: {
|
|
|
25
25
|
signature: Uint8Array;
|
|
26
26
|
}, externalSignatures: ExternalSignature[], transactionDigest: Uint8Array): Uint8Array[];
|
|
27
27
|
export declare function validateExternalSignature(signature: ExternalSignature, transactionDigest: Uint8Array): boolean;
|
|
28
|
+
/**
|
|
29
|
+
* Derive a Minima script address exactly as the node does
|
|
30
|
+
* (`Address::Address(script)` / `ScriptProof::calculateAddress`):
|
|
31
|
+
*
|
|
32
|
+
* address = sha3( MiniNumber.ZERO || MiniString(script) || MiniNumber.ZERO )
|
|
33
|
+
*
|
|
34
|
+
* The script is first canonicalised (`toNodeScript`) so the address is over the
|
|
35
|
+
* same bytes the node will execute and that `buildScriptProof` attests. The
|
|
36
|
+
* previous implementation hashed `sha3(upper(script))`, which is not a Minima
|
|
37
|
+
* script address — coins sent to it could never be spent by that script
|
|
38
|
+
* ("Script Missing from TxPoW for address …").
|
|
39
|
+
*/
|
|
40
|
+
/**
|
|
41
|
+
* Address data (hex) of a WOTS public-key digest's single-key script
|
|
42
|
+
* (`RETURN SIGNEDBY(0x<pk>)`). This is the value a `VERIFYOUT` compares against
|
|
43
|
+
* (the node compares the output coin's *address data*), distinct from the WOTS
|
|
44
|
+
* public key used by `SIGNEDBY`.
|
|
45
|
+
*/
|
|
46
|
+
export declare function wotsAddressData(publicKeyDigest: string): string;
|
|
28
47
|
export declare function computeScriptAddress(script: string): string;
|
|
29
48
|
export { concat };
|
|
@@ -15,9 +15,10 @@ exports.deduplicateScriptDescriptors = deduplicateScriptDescriptors;
|
|
|
15
15
|
exports.serializeExtraScripts = serializeExtraScripts;
|
|
16
16
|
exports.aggregateSignatures = aggregateSignatures;
|
|
17
17
|
exports.validateExternalSignature = validateExternalSignature;
|
|
18
|
+
exports.wotsAddressData = wotsAddressData;
|
|
18
19
|
exports.computeScriptAddress = computeScriptAddress;
|
|
19
20
|
exports.concat = concat;
|
|
20
|
-
const
|
|
21
|
+
const canonical_js_1 = require("./canonical.js");
|
|
21
22
|
const wots_js_1 = require("../wots.js");
|
|
22
23
|
const mmr_js_1 = require("../mmr.js");
|
|
23
24
|
const minima32_js_1 = require("../minima32.js");
|
|
@@ -321,9 +322,28 @@ function validateExternalSignature(signature, transactionDigest) {
|
|
|
321
322
|
return false;
|
|
322
323
|
}
|
|
323
324
|
}
|
|
325
|
+
/**
|
|
326
|
+
* Derive a Minima script address exactly as the node does
|
|
327
|
+
* (`Address::Address(script)` / `ScriptProof::calculateAddress`):
|
|
328
|
+
*
|
|
329
|
+
* address = sha3( MiniNumber.ZERO || MiniString(script) || MiniNumber.ZERO )
|
|
330
|
+
*
|
|
331
|
+
* The script is first canonicalised (`toNodeScript`) so the address is over the
|
|
332
|
+
* same bytes the node will execute and that `buildScriptProof` attests. The
|
|
333
|
+
* previous implementation hashed `sha3(upper(script))`, which is not a Minima
|
|
334
|
+
* script address — coins sent to it could never be spent by that script
|
|
335
|
+
* ("Script Missing from TxPoW for address …").
|
|
336
|
+
*/
|
|
337
|
+
/**
|
|
338
|
+
* Address data (hex) of a WOTS public-key digest's single-key script
|
|
339
|
+
* (`RETURN SIGNEDBY(0x<pk>)`). This is the value a `VERIFYOUT` compares against
|
|
340
|
+
* (the node compares the output coin's *address data*), distinct from the WOTS
|
|
341
|
+
* public key used by `SIGNEDBY`.
|
|
342
|
+
*/
|
|
343
|
+
function wotsAddressData(publicKeyDigest) {
|
|
344
|
+
const raw = publicKeyDigest.replace(/^0x/i, '').toUpperCase();
|
|
345
|
+
return computeScriptAddress(`RETURN SIGNEDBY(0x${raw})`);
|
|
346
|
+
}
|
|
324
347
|
function computeScriptAddress(script) {
|
|
325
|
-
|
|
326
|
-
const scriptBytes = new TextEncoder().encode(cleanScript);
|
|
327
|
-
const hashBytes = (0, wasm_sync_js_1.sha3_256)(scriptBytes);
|
|
328
|
-
return bytesToHex(hashBytes);
|
|
348
|
+
return bytesToHex((0, mmr_js_1.mmrRootFromSingleLeaf)((0, canonical_js_1.toNodeScript)(script)));
|
|
329
349
|
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compatibility adapters for the legacy JSON transaction surface.
|
|
3
|
+
*
|
|
4
|
+
* The WASM bindings are retained as `*Wasm` exports, but the public
|
|
5
|
+
* `serializeTransaction` / `precomputeTransactionCoinID` names route through
|
|
6
|
+
* the TypeScript implementation. This avoids the WASM serializer crash on
|
|
7
|
+
* token+state outputs and the incorrect coin-ID precompute semantics.
|
|
8
|
+
*/
|
|
9
|
+
import { bytesToHex } from './Streamable.js';
|
|
10
|
+
import { type MinimaTransaction } from './transaction.js';
|
|
11
|
+
/** Convert the legacy JSON transaction shape into the TS object shape. */
|
|
12
|
+
export declare function minimaTransactionFromJson(json: string): MinimaTransaction;
|
|
13
|
+
/** Public serializer: accepts the TS object or the legacy JSON string. */
|
|
14
|
+
export declare function serializeTransaction(tx: MinimaTransaction | string): Uint8Array;
|
|
15
|
+
/** Coin-ID rollover: SHA3(MiniData(coinId) || MiniNumber(index)). */
|
|
16
|
+
export declare function rollCoinId(coinId: Uint8Array, outputIndex?: number): Uint8Array;
|
|
17
|
+
export declare function precomputeTransactionCoinID(tx: MinimaTransaction): void;
|
|
18
|
+
export declare function precomputeTransactionCoinID(tx: Uint8Array | string, outputIndex?: number): Uint8Array;
|
|
19
|
+
export { bytesToHex };
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Compatibility adapters for the legacy JSON transaction surface.
|
|
4
|
+
*
|
|
5
|
+
* The WASM bindings are retained as `*Wasm` exports, but the public
|
|
6
|
+
* `serializeTransaction` / `precomputeTransactionCoinID` names route through
|
|
7
|
+
* the TypeScript implementation. This avoids the WASM serializer crash on
|
|
8
|
+
* token+state outputs and the incorrect coin-ID precompute semantics.
|
|
9
|
+
*/
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
exports.bytesToHex = void 0;
|
|
12
|
+
exports.minimaTransactionFromJson = minimaTransactionFromJson;
|
|
13
|
+
exports.serializeTransaction = serializeTransaction;
|
|
14
|
+
exports.rollCoinId = rollCoinId;
|
|
15
|
+
exports.precomputeTransactionCoinID = precomputeTransactionCoinID;
|
|
16
|
+
const Streamable_js_1 = require("./Streamable.js");
|
|
17
|
+
Object.defineProperty(exports, "bytesToHex", { enumerable: true, get: function () { return Streamable_js_1.bytesToHex; } });
|
|
18
|
+
const transaction_js_1 = require("./transaction.js");
|
|
19
|
+
const Streamable_js_2 = require("./Streamable.js");
|
|
20
|
+
const wasm_sync_js_1 = require("./wasm-sync.js");
|
|
21
|
+
function bytes(value) {
|
|
22
|
+
if (!value || value === '0x')
|
|
23
|
+
return new Uint8Array([0]);
|
|
24
|
+
return (0, Streamable_js_1.hexToBytes)(value);
|
|
25
|
+
}
|
|
26
|
+
function evenHex(value) {
|
|
27
|
+
const raw = value.trim().replace(/^0x/i, '');
|
|
28
|
+
return '0x' + (raw.length % 2 === 0 ? raw : `0${raw}`);
|
|
29
|
+
}
|
|
30
|
+
function stateVariables(input) {
|
|
31
|
+
return (input ?? []).map((sv) => {
|
|
32
|
+
const rawType = (sv.svtype ?? sv.type ?? '').toLowerCase();
|
|
33
|
+
if (rawType === 'bool' || rawType === 'boolean') {
|
|
34
|
+
return { port: sv.port, type: 'bool', value: sv.data === 'true' || sv.data === '1' || sv.data === '01' };
|
|
35
|
+
}
|
|
36
|
+
if (rawType === 'number')
|
|
37
|
+
return { port: sv.port, type: 'number', value: sv.data };
|
|
38
|
+
if (rawType === 'string')
|
|
39
|
+
return { port: sv.port, type: 'string', value: sv.data };
|
|
40
|
+
return { port: sv.port, type: 'hex', value: evenHex(sv.data) };
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
function token(input) {
|
|
44
|
+
if (!input)
|
|
45
|
+
return null;
|
|
46
|
+
const parsed = (0, transaction_js_1.parseDecimalToMiniNumber)(input.totalamount ?? '0');
|
|
47
|
+
return {
|
|
48
|
+
coinId: bytes(input.coinid),
|
|
49
|
+
scale: Number(input.scale ?? 0),
|
|
50
|
+
totalAmount: parsed.unscaledValue,
|
|
51
|
+
totalAmountScale: parsed.scale,
|
|
52
|
+
name: new TextEncoder().encode(input.name ?? ''),
|
|
53
|
+
script: bytes(input.script),
|
|
54
|
+
created: BigInt(input.created ?? '0'),
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
function coin(input) {
|
|
58
|
+
return {
|
|
59
|
+
coinId: bytes(input.coinid),
|
|
60
|
+
address: bytes(input.address),
|
|
61
|
+
amount: input.amount,
|
|
62
|
+
tokenId: bytes(input.tokenid),
|
|
63
|
+
token: token(input.token),
|
|
64
|
+
storeState: input.storestate ?? false,
|
|
65
|
+
state: stateVariables(input.state),
|
|
66
|
+
mmrEntryNumber: BigInt(input.mmrentry ?? '0'),
|
|
67
|
+
spent: input.spent ?? false,
|
|
68
|
+
created: BigInt(input.created ?? '0'),
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
/** Convert the legacy JSON transaction shape into the TS object shape. */
|
|
72
|
+
function minimaTransactionFromJson(json) {
|
|
73
|
+
const parsed = JSON.parse(json);
|
|
74
|
+
return {
|
|
75
|
+
inputs: (parsed.inputs ?? []).map(coin),
|
|
76
|
+
outputs: (parsed.outputs ?? []).map(coin),
|
|
77
|
+
state: stateVariables(parsed.state),
|
|
78
|
+
linkHash: bytes(parsed.linkhash ?? '0x00'),
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
/** Public serializer: accepts the TS object or the legacy JSON string. */
|
|
82
|
+
function serializeTransaction(tx) {
|
|
83
|
+
const value = typeof tx === 'string' ? minimaTransactionFromJson(tx) : tx;
|
|
84
|
+
return (0, transaction_js_1.serializeTransaction)(value);
|
|
85
|
+
}
|
|
86
|
+
/** Coin-ID rollover: SHA3(MiniData(coinId) || MiniNumber(index)). */
|
|
87
|
+
function rollCoinId(coinId, outputIndex = 0) {
|
|
88
|
+
return (0, wasm_sync_js_1.sha3_256)((0, Streamable_js_2.concat)((0, Streamable_js_2.writeMiniData)(coinId), (0, Streamable_js_2.writeMiniNumber)(BigInt(outputIndex), 0)));
|
|
89
|
+
}
|
|
90
|
+
function precomputeTransactionCoinID(tx, outputIndex = 0) {
|
|
91
|
+
if (typeof tx === 'object' && !(tx instanceof Uint8Array)) {
|
|
92
|
+
(0, transaction_js_1.precomputeTransactionCoinID)(tx);
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
if (typeof tx === 'string') {
|
|
96
|
+
const value = minimaTransactionFromJson(tx);
|
|
97
|
+
(0, transaction_js_1.precomputeTransactionCoinID)(value);
|
|
98
|
+
return value.outputs[outputIndex]?.coinId ?? new Uint8Array();
|
|
99
|
+
}
|
|
100
|
+
// Legacy callers pass either a 32-byte input coin ID (rollover) or serialized
|
|
101
|
+
// transaction bytes (output coin ID). Prefer the transaction interpretation,
|
|
102
|
+
// but fall back to coin-ID rollover for a bare 32-byte value.
|
|
103
|
+
try {
|
|
104
|
+
const value = (0, transaction_js_1.deserializeTransaction)(tx);
|
|
105
|
+
if (value.inputs.length > 0) {
|
|
106
|
+
(0, transaction_js_1.precomputeTransactionCoinID)(value);
|
|
107
|
+
return value.outputs[outputIndex]?.coinId ?? new Uint8Array();
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
// not a serialized transaction
|
|
112
|
+
}
|
|
113
|
+
return rollCoinId(tx, outputIndex);
|
|
114
|
+
}
|
package/dist/cjs/wasm-sync.d.ts
CHANGED
|
@@ -69,3 +69,13 @@ export declare function wotsKeypairFromSeed(seed: Uint8Array, index: number): {
|
|
|
69
69
|
index: number;
|
|
70
70
|
pk: Uint8Array;
|
|
71
71
|
};
|
|
72
|
+
/**
|
|
73
|
+
* Compatibility initializer for the synchronous bridge.
|
|
74
|
+
*
|
|
75
|
+
* The CJS/sync build has already instantiated WASM at module load; this exists
|
|
76
|
+
* so the public ESM and CJS APIs expose the same `init()` contract. The ESM
|
|
77
|
+
* build rewrites this module to `wasm-async.js`, where init is the real
|
|
78
|
+
* wasm-bindgen initializer.
|
|
79
|
+
*/
|
|
80
|
+
export declare function init(): Promise<void>;
|
|
81
|
+
export declare function initSync(): void;
|
package/dist/cjs/wasm-sync.js
CHANGED
|
@@ -16,6 +16,8 @@ exports.createChallenge = createChallenge;
|
|
|
16
16
|
exports.validateChallenge = validateChallenge;
|
|
17
17
|
exports.writeMiniNumber = writeMiniNumber;
|
|
18
18
|
exports.wotsKeypairFromSeed = wotsKeypairFromSeed;
|
|
19
|
+
exports.init = init;
|
|
20
|
+
exports.initSync = initSync;
|
|
19
21
|
// The bundler target initializes WASM synchronously at import time.
|
|
20
22
|
// The nodejs target uses require() which is also synchronous.
|
|
21
23
|
// Both targets export functions that are ready to call immediately.
|
|
@@ -102,3 +104,17 @@ function wotsKeypairFromSeed(seed, index) {
|
|
|
102
104
|
const pk = (0, core_wasm_1.derive_pk_digest_wasm)(seed, index);
|
|
103
105
|
return { seed, index, pk };
|
|
104
106
|
}
|
|
107
|
+
/**
|
|
108
|
+
* Compatibility initializer for the synchronous bridge.
|
|
109
|
+
*
|
|
110
|
+
* The CJS/sync build has already instantiated WASM at module load; this exists
|
|
111
|
+
* so the public ESM and CJS APIs expose the same `init()` contract. The ESM
|
|
112
|
+
* build rewrites this module to `wasm-async.js`, where init is the real
|
|
113
|
+
* wasm-bindgen initializer.
|
|
114
|
+
*/
|
|
115
|
+
async function init() {
|
|
116
|
+
// no-op: the synchronous bridge is initialized at import time
|
|
117
|
+
}
|
|
118
|
+
function initSync() {
|
|
119
|
+
// no-op: the synchronous bridge is initialized at import time
|
|
120
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -14,12 +14,13 @@ export { bytesToHex, hexToBytes, concatBytes } from './bytes.js';
|
|
|
14
14
|
export { sha3_256 } from './wasm-sync.js';
|
|
15
15
|
export { expandPrivateKey, hashChain, derivePKdigest, deriveFullPublicKey, wotsSign, wotsVerify, wotsVerifyDigest, wotsPkFromSig, wotsPublicKeyFromSeed, wotsKeypairFromSeed, } from './wasm-sync.js';
|
|
16
16
|
export * from './params.js';
|
|
17
|
-
export { deriveChainSeedJava, derivePerAddressSeed, deriveRootPrivSeed, writeMiniNumber, writeMiniData, writeMiniString,
|
|
17
|
+
export { deriveChainSeedJava, derivePerAddressSeed, deriveRootPrivSeed, writeMiniNumber, writeMiniData, writeMiniString, init, initSync, } from './wasm-sync.js';
|
|
18
18
|
export { createUnifiedChildTreeKey, createUnifiedRootTreeKey, deriveUnifiedAddressPublicKey, verifyTreeSignature, } from './treekey.js';
|
|
19
19
|
export { mmrRootFromPublicKeys, verifyMMRProof, } from './wasm-sync.js';
|
|
20
20
|
export { phraseToSeed, generateWordList, validatePhrase, cleanSeedPhrase, } from './bip39.js';
|
|
21
21
|
export { makeMxAddress, parseMxAddress, wotsAddressFromKeypair, } from './wasm-sync.js';
|
|
22
|
-
export { serializeTransaction,
|
|
22
|
+
export { serializeTransaction, precomputeTransactionCoinID, rollCoinId, minimaTransactionFromJson, } from './transaction-json.js';
|
|
23
|
+
export { serializeTransaction as serializeTransactionWasm, precomputeTransactionCoinID as precomputeTransactionCoinIDWasm, computeTransactionDigest, } from './wasm-sync.js';
|
|
23
24
|
export { deserializeTransaction } from './transaction.js';
|
|
24
25
|
export { timingSafeEqual, createChallenge, validateChallenge, } from './wasm-sync.js';
|
|
25
26
|
export { scriptFromWotsPk } from './script.js';
|
|
@@ -28,6 +29,7 @@ export { makeMxAddress as encodeMx, parseMxAddress as decodeMx } from './wasm-sy
|
|
|
28
29
|
export declare function mxToHex(address: string): string;
|
|
29
30
|
export * from './mmr.js';
|
|
30
31
|
export * from './scripts/index.js';
|
|
32
|
+
export { toNodeScript } from './scripts/canonical.js';
|
|
31
33
|
export { scriptToAddress, addressToRoot } from './derive.js';
|
|
32
34
|
export { WORD_LIST, convertStringToSeed, convertWordListToSeed, generateSeedPhrase, } from './bip39.js';
|
|
33
35
|
export { MiniNumber } from './MiniNumber.js';
|
package/dist/index.js
CHANGED
|
@@ -27,7 +27,7 @@ export { expandPrivateKey, hashChain, derivePKdigest, deriveFullPublicKey, wotsS
|
|
|
27
27
|
// WOTS Params (from params.ts — constants, no crypto)
|
|
28
28
|
export * from './params.js';
|
|
29
29
|
// Java-Compatible Serialization
|
|
30
|
-
export { deriveChainSeedJava, derivePerAddressSeed, deriveRootPrivSeed, writeMiniNumber, writeMiniData, writeMiniString,
|
|
30
|
+
export { deriveChainSeedJava, derivePerAddressSeed, deriveRootPrivSeed, writeMiniNumber, writeMiniData, writeMiniString, init, initSync, } from './wasm-async.js';
|
|
31
31
|
// TreeKey/TreeKeyNode — WASM-backed
|
|
32
32
|
// createUnifiedChildTreeKey / createUnifiedRootTreeKey come from the TS
|
|
33
33
|
// implementation (returning a full TreeKey object with setUses/sign/getPublicKey)
|
|
@@ -45,7 +45,11 @@ export { phraseToSeed, generateWordList, validatePhrase, cleanSeedPhrase, } from
|
|
|
45
45
|
// Address Derivation
|
|
46
46
|
export { makeMxAddress, parseMxAddress, wotsAddressFromKeypair, } from './wasm-async.js';
|
|
47
47
|
// Transaction Serialization & Digest
|
|
48
|
-
|
|
48
|
+
// Public names route through the TypeScript implementation. The WASM bindings
|
|
49
|
+
// remain available explicitly for parity tests and legacy callers that
|
|
50
|
+
// intentionally want the byte-level WASM surface.
|
|
51
|
+
export { serializeTransaction, precomputeTransactionCoinID, rollCoinId, minimaTransactionFromJson, } from './transaction-json.js';
|
|
52
|
+
export { serializeTransaction as serializeTransactionWasm, precomputeTransactionCoinID as precomputeTransactionCoinIDWasm, computeTransactionDigest, } from './wasm-async.js';
|
|
49
53
|
// Transaction deserialization (byte-exact inverse of the TS serializer)
|
|
50
54
|
export { deserializeTransaction } from './transaction.js';
|
|
51
55
|
// Verification
|
|
@@ -68,6 +72,7 @@ export function mxToHex(address) {
|
|
|
68
72
|
export * from './mmr.js';
|
|
69
73
|
// Script Types, Witness Serializer, Contract Helpers
|
|
70
74
|
export * from './scripts/index.js';
|
|
75
|
+
export { toNodeScript } from './scripts/canonical.js';
|
|
71
76
|
// Address Derivation (original)
|
|
72
77
|
export { scriptToAddress, addressToRoot } from './derive.js';
|
|
73
78
|
// BIP39 — re-export word list and legacy helpers from JS
|
package/dist/mmr.d.ts
CHANGED
|
@@ -16,6 +16,28 @@ export type Bytes = Uint8Array;
|
|
|
16
16
|
export declare function mmrLeafExact(script: string): Bytes;
|
|
17
17
|
/** In a single-leaf MMR the root equals the leaf commitment. */
|
|
18
18
|
export declare function mmrRootFromSingleLeaf(script: string): Bytes;
|
|
19
|
+
/**
|
|
20
|
+
* Build the serialized Minima `ScriptProof` bytes for a single-leaf MMR.
|
|
21
|
+
*
|
|
22
|
+
* Mirrors the node's `ScriptProof(const std::string& zScript)` constructor
|
|
23
|
+
* (org/minima/objects/script_proof.cpp): the script is added as the sole leaf
|
|
24
|
+
* of a fresh MMR with sum value ZERO, so the proof chain is empty and the
|
|
25
|
+
* resulting address is the leaf commitment itself:
|
|
26
|
+
*
|
|
27
|
+
* address = sha3( MiniNumber.ZERO || MiniString(script) || MiniNumber.ZERO )
|
|
28
|
+
* = mmrLeafExact(script)
|
|
29
|
+
*
|
|
30
|
+
* This is exactly how the node derives `Address(script)`, so a witness carrying
|
|
31
|
+
* this proof satisfies `Witness::getScript(input.getAddress())` for every input
|
|
32
|
+
* paying to that script. Without it the node rejects the TxPoW with
|
|
33
|
+
* "Script Missing from TxPoW for address <addr>".
|
|
34
|
+
*
|
|
35
|
+
* Wire format (`ScriptProof::writeDataStream`):
|
|
36
|
+
* MiniString(script) || MMRProof(blockTime = 0, chunks = [])
|
|
37
|
+
*
|
|
38
|
+
* @param script - The Minima (KISSVM) script whose address the proof attests.
|
|
39
|
+
*/
|
|
40
|
+
export declare function buildScriptProof(script: string): Bytes;
|
|
19
41
|
/**
|
|
20
42
|
* MMRData structure matching Minima's MMRData.java
|
|
21
43
|
* Contains hash and value (for sum tree functionality)
|
package/dist/mmr.js
CHANGED
|
@@ -3,7 +3,10 @@
|
|
|
3
3
|
import { sha3_256 as nobleSha3 } from './wasm-async.js';
|
|
4
4
|
import { serializeMiniNumber, serializeMiniData, javaHashAllObjects } from "./javaStreamables.js";
|
|
5
5
|
// Import canonical wire serialization from Streamable.ts (single source of truth)
|
|
6
|
-
import { writeMMRProof } from "./Streamable.js";
|
|
6
|
+
import { writeMMRProof, writeMiniString } from "./Streamable.js";
|
|
7
|
+
// Canonicalise to the exact script the node will receive, so the proof's
|
|
8
|
+
// computed address matches `computeScriptAddress` (which canonicalises too).
|
|
9
|
+
import { toNodeScript } from "./scripts/canonical.js";
|
|
7
10
|
// Re-export for API compatibility (existing code imports from mmr.ts)
|
|
8
11
|
// Using a wrapper function for better compatibility with dynamic imports
|
|
9
12
|
export function serializeMMRProof(proof, blockTime = 0n) {
|
|
@@ -69,6 +72,35 @@ export function mmrLeafExact(script) {
|
|
|
69
72
|
export function mmrRootFromSingleLeaf(script) {
|
|
70
73
|
return mmrLeafExact(script);
|
|
71
74
|
}
|
|
75
|
+
/**
|
|
76
|
+
* Build the serialized Minima `ScriptProof` bytes for a single-leaf MMR.
|
|
77
|
+
*
|
|
78
|
+
* Mirrors the node's `ScriptProof(const std::string& zScript)` constructor
|
|
79
|
+
* (org/minima/objects/script_proof.cpp): the script is added as the sole leaf
|
|
80
|
+
* of a fresh MMR with sum value ZERO, so the proof chain is empty and the
|
|
81
|
+
* resulting address is the leaf commitment itself:
|
|
82
|
+
*
|
|
83
|
+
* address = sha3( MiniNumber.ZERO || MiniString(script) || MiniNumber.ZERO )
|
|
84
|
+
* = mmrLeafExact(script)
|
|
85
|
+
*
|
|
86
|
+
* This is exactly how the node derives `Address(script)`, so a witness carrying
|
|
87
|
+
* this proof satisfies `Witness::getScript(input.getAddress())` for every input
|
|
88
|
+
* paying to that script. Without it the node rejects the TxPoW with
|
|
89
|
+
* "Script Missing from TxPoW for address <addr>".
|
|
90
|
+
*
|
|
91
|
+
* Wire format (`ScriptProof::writeDataStream`):
|
|
92
|
+
* MiniString(script) || MMRProof(blockTime = 0, chunks = [])
|
|
93
|
+
*
|
|
94
|
+
* @param script - The Minima (KISSVM) script whose address the proof attests.
|
|
95
|
+
*/
|
|
96
|
+
export function buildScriptProof(script) {
|
|
97
|
+
// The node derives the proof's address as
|
|
98
|
+
// sha3( MiniNumber.ZERO || MiniString(script) || MiniNumber.ZERO )
|
|
99
|
+
// so the script bytes here must be exactly the canonical script whose address
|
|
100
|
+
// was used for the coin (see computeScriptAddress).
|
|
101
|
+
const canonical = toNodeScript(script);
|
|
102
|
+
return concat(writeMiniString(canonical), serializeMMRProof({ chunks: [] }, 0n));
|
|
103
|
+
}
|
|
72
104
|
/**
|
|
73
105
|
* Create MMRData leaf node matching Minima's MMRData.CreateMMRDataLeafNode
|
|
74
106
|
*
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `toNodeScript` — canonicalize a KISSVM script to the syntax the Minima **node**
|
|
3
|
+
* tokenizer accepts (RFC-048 §3.1 L4 finding; audit
|
|
4
|
+
* `docs/audits/kissvm-node-validity-l4-2026-10.md`).
|
|
5
|
+
*
|
|
6
|
+
* The node tokenizer requires:
|
|
7
|
+
* - user variable names `^[a-z]+$` (lowercase letters only — no digits/underscores/uppercase),
|
|
8
|
+
* - hex literals `^0x[0-9a-fA-F]+$` (lowercase `0x` prefix),
|
|
9
|
+
* - arithmetic via the `+` / `-` operators (`ADD`/`SUB` are not node functions).
|
|
10
|
+
*
|
|
11
|
+
* This is a token-level rewrite: commands, functions, operators, globals (`@…`), hex
|
|
12
|
+
* literals, numbers and `[…]` string literals are preserved; only **user identifiers**
|
|
13
|
+
* are rewritten to lowercase letters (with deterministic de-collision), `0X`→`0x`, and
|
|
14
|
+
* `ADD`/`SUB`→`+`/`-`. It is idempotent.
|
|
15
|
+
*
|
|
16
|
+
* Semantics are unchanged: the SDK evaluators normalize `+`→ADD and `-`→SUB (and vice
|
|
17
|
+
* versa), so a canonical script behaves identically to its source.
|
|
18
|
+
*/
|
|
19
|
+
/** Canonicalize a KISSVM script to node-accepted syntax. Idempotent. */
|
|
20
|
+
export declare function toNodeScript(script: string): string;
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `toNodeScript` — canonicalize a KISSVM script to the syntax the Minima **node**
|
|
3
|
+
* tokenizer accepts (RFC-048 §3.1 L4 finding; audit
|
|
4
|
+
* `docs/audits/kissvm-node-validity-l4-2026-10.md`).
|
|
5
|
+
*
|
|
6
|
+
* The node tokenizer requires:
|
|
7
|
+
* - user variable names `^[a-z]+$` (lowercase letters only — no digits/underscores/uppercase),
|
|
8
|
+
* - hex literals `^0x[0-9a-fA-F]+$` (lowercase `0x` prefix),
|
|
9
|
+
* - arithmetic via the `+` / `-` operators (`ADD`/`SUB` are not node functions).
|
|
10
|
+
*
|
|
11
|
+
* This is a token-level rewrite: commands, functions, operators, globals (`@…`), hex
|
|
12
|
+
* literals, numbers and `[…]` string literals are preserved; only **user identifiers**
|
|
13
|
+
* are rewritten to lowercase letters (with deterministic de-collision), `0X`→`0x`, and
|
|
14
|
+
* `ADD`/`SUB`→`+`/`-`. It is idempotent.
|
|
15
|
+
*
|
|
16
|
+
* Semantics are unchanged: the SDK evaluators normalize `+`→ADD and `-`→SUB (and vice
|
|
17
|
+
* versa), so a canonical script behaves identically to its source.
|
|
18
|
+
*/
|
|
19
|
+
const KEYWORDS = new Set([
|
|
20
|
+
'LET', 'IF', 'THEN', 'ELSE', 'ELSEIF', 'ENDIF', 'RETURN', 'ASSERT', 'EXEC', 'MAST',
|
|
21
|
+
'WHILE', 'DO', 'ENDWHILE', 'FOR', 'TO', 'BY', 'ENDFOR', 'FOREACH', 'IN', 'SWITCH',
|
|
22
|
+
'CASE', 'DEFAULT', 'ENDSWITCH', 'FUNC', 'ENDFUNC', 'TRUE', 'FALSE', 'STORE', 'WITH', 'FUNCTION', 'RETURN',
|
|
23
|
+
]);
|
|
24
|
+
// Word operators kept as-is (ADD/SUB are handled separately → +/−).
|
|
25
|
+
const OPS = new Set([
|
|
26
|
+
'GT', 'LT', 'EQ', 'GTE', 'LTE', 'NEQ', 'AND', 'OR', 'XOR', 'NAND', 'NOR', 'NXOR',
|
|
27
|
+
'NOT', 'NEG', 'MUL', 'DIV', 'MOD', 'LSHIFT', 'RSHIFT', 'BITAND', 'BITOR', 'BITXOR', 'BITNOT',
|
|
28
|
+
]);
|
|
29
|
+
const FUNCTIONS = new Set([
|
|
30
|
+
'SHA3', 'SHA2', 'HASH', 'PROOF', 'SIGNEDBY', 'MULTISIG', 'CHECKSIG', 'VERIFYOUT', 'VERIFYIN',
|
|
31
|
+
'GETOUTADDR', 'GETOUTAMT', 'GETOUTTOK', 'GETOUTKEEPSTATE', 'GETINADDR', 'GETINAMT', 'GETINID',
|
|
32
|
+
'GETINTOK', 'STATE', 'PREVSTATE', 'SAMESTATE', 'SAMECOINS', 'COINAGE', 'COINDATA', 'NUMBER',
|
|
33
|
+
'HEX', 'STRING', 'ASCII', 'UTF8', 'BOOL', 'CONCAT', 'LEN', 'REV', 'SUBSET', 'SETLEN',
|
|
34
|
+
'SUBSTR', 'REPLACE', 'REPLACEFIRST', 'OVERWRITE', 'ABS', 'CEIL', 'FLOOR', 'MAX', 'MIN',
|
|
35
|
+
'DEC', 'INC', 'SIGDIG', 'POW', 'SQRT', 'BITSET', 'BITGET', 'BITCOUNT', 'ADDRESS', 'EXISTS',
|
|
36
|
+
'GET', 'SUMINPUTS', 'SUMOUTPUTS', 'CHECKSIG',
|
|
37
|
+
]);
|
|
38
|
+
function classify(id) {
|
|
39
|
+
const up = id.toUpperCase();
|
|
40
|
+
if (up === 'ADD')
|
|
41
|
+
return { kind: 'keep', out: '+' };
|
|
42
|
+
if (up === 'SUB')
|
|
43
|
+
return { kind: 'keep', out: '-' };
|
|
44
|
+
if (KEYWORDS.has(up) || OPS.has(up) || FUNCTIONS.has(up))
|
|
45
|
+
return { kind: 'keep', out: up };
|
|
46
|
+
return { kind: 'var' };
|
|
47
|
+
}
|
|
48
|
+
/** a, b, …, z, aa, ab, … */
|
|
49
|
+
function letters(n) {
|
|
50
|
+
let s = '';
|
|
51
|
+
n += 1;
|
|
52
|
+
while (n > 0) {
|
|
53
|
+
const r = (n - 1) % 26;
|
|
54
|
+
s = String.fromCharCode(97 + r) + s;
|
|
55
|
+
n = Math.floor((n - 1) / 26);
|
|
56
|
+
}
|
|
57
|
+
return s;
|
|
58
|
+
}
|
|
59
|
+
const TOKEN = /(\[[^\]]*\]|0[xX][0-9a-fA-F]+|@[A-Za-z][A-Za-z0-9]*|[A-Za-z_][A-Za-z0-9_]*)/g;
|
|
60
|
+
/** Canonicalize a KISSVM script to node-accepted syntax. Idempotent. */
|
|
61
|
+
export function toNodeScript(script) {
|
|
62
|
+
// The node tokenizer has no comment syntax; strip `//` line comments.
|
|
63
|
+
script = script.replace(/\/\/[^\n]*/g, '');
|
|
64
|
+
const varMap = new Map();
|
|
65
|
+
const used = new Set();
|
|
66
|
+
// First pass: assign canonical names to user variables (in first-occurrence order).
|
|
67
|
+
let m;
|
|
68
|
+
const re1 = new RegExp(TOKEN.source, 'g');
|
|
69
|
+
while ((m = re1.exec(script))) {
|
|
70
|
+
const tok = m[0];
|
|
71
|
+
if (tok[0] === '[' || tok.startsWith('0x') || tok.startsWith('0X') || tok[0] === '@')
|
|
72
|
+
continue;
|
|
73
|
+
if (classify(tok).kind !== 'var' || varMap.has(tok))
|
|
74
|
+
continue;
|
|
75
|
+
const base = tok.toLowerCase().replace(/[^a-z]/g, '') || 'v';
|
|
76
|
+
let cand = base;
|
|
77
|
+
let i = 0;
|
|
78
|
+
while (used.has(cand))
|
|
79
|
+
cand = base + letters(i++);
|
|
80
|
+
used.add(cand);
|
|
81
|
+
varMap.set(tok, cand);
|
|
82
|
+
}
|
|
83
|
+
// Second pass: rewrite.
|
|
84
|
+
return script.replace(TOKEN, (tok) => {
|
|
85
|
+
if (tok[0] === '[')
|
|
86
|
+
return tok;
|
|
87
|
+
if (tok.startsWith('0x') || tok.startsWith('0X'))
|
|
88
|
+
return '0x' + tok.slice(2);
|
|
89
|
+
if (tok[0] === '@')
|
|
90
|
+
return '@' + tok.slice(1).toUpperCase();
|
|
91
|
+
const c = classify(tok);
|
|
92
|
+
if (c.kind === 'keep')
|
|
93
|
+
return c.out;
|
|
94
|
+
return varMap.get(tok) ?? tok;
|
|
95
|
+
});
|
|
96
|
+
}
|
package/dist/scripts/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export * from './types.js';
|
|
2
2
|
export * from './dapp-types.js';
|
|
3
|
-
export { encodeMiniNumber, encodeMiniData, encodeMiniString, serializeMMRProofChunk, serializeScriptProofWithProof, STATETYPE_HEX, STATETYPE_NUMBER, STATETYPE_STRING, STATETYPE_BOOL, encodeStateValue, serializeStateVariables, buildScriptProofFromDescriptor, deduplicateScriptDescriptors, serializeExtraScripts, aggregateSignatures, validateExternalSignature, computeScriptAddress, } from './witness-serializer.js';
|
|
3
|
+
export { encodeMiniNumber, encodeMiniData, encodeMiniString, serializeMMRProofChunk, serializeScriptProofWithProof, STATETYPE_HEX, STATETYPE_NUMBER, STATETYPE_STRING, STATETYPE_BOOL, encodeStateValue, serializeStateVariables, buildScriptProofFromDescriptor, deduplicateScriptDescriptors, serializeExtraScripts, aggregateSignatures, validateExternalSignature, computeScriptAddress, wotsAddressData, } from './witness-serializer.js';
|
|
4
4
|
export * from './contract-helpers.js';
|
|
5
|
+
export { toNodeScript } from "./canonical.js";
|
package/dist/scripts/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export * from './types.js';
|
|
2
2
|
export * from './dapp-types.js';
|
|
3
|
-
export { encodeMiniNumber, encodeMiniData, encodeMiniString, serializeMMRProofChunk, serializeScriptProofWithProof, STATETYPE_HEX, STATETYPE_NUMBER, STATETYPE_STRING, STATETYPE_BOOL, encodeStateValue, serializeStateVariables, buildScriptProofFromDescriptor, deduplicateScriptDescriptors, serializeExtraScripts, aggregateSignatures, validateExternalSignature, computeScriptAddress, } from './witness-serializer.js';
|
|
3
|
+
export { encodeMiniNumber, encodeMiniData, encodeMiniString, serializeMMRProofChunk, serializeScriptProofWithProof, STATETYPE_HEX, STATETYPE_NUMBER, STATETYPE_STRING, STATETYPE_BOOL, encodeStateValue, serializeStateVariables, buildScriptProofFromDescriptor, deduplicateScriptDescriptors, serializeExtraScripts, aggregateSignatures, validateExternalSignature, computeScriptAddress, wotsAddressData, } from './witness-serializer.js';
|
|
4
4
|
export * from './contract-helpers.js';
|
|
5
|
+
export { toNodeScript } from "./canonical.js";
|
|
@@ -25,5 +25,24 @@ export declare function aggregateSignatures(totemSignature: {
|
|
|
25
25
|
signature: Uint8Array;
|
|
26
26
|
}, externalSignatures: ExternalSignature[], transactionDigest: Uint8Array): Uint8Array[];
|
|
27
27
|
export declare function validateExternalSignature(signature: ExternalSignature, transactionDigest: Uint8Array): boolean;
|
|
28
|
+
/**
|
|
29
|
+
* Derive a Minima script address exactly as the node does
|
|
30
|
+
* (`Address::Address(script)` / `ScriptProof::calculateAddress`):
|
|
31
|
+
*
|
|
32
|
+
* address = sha3( MiniNumber.ZERO || MiniString(script) || MiniNumber.ZERO )
|
|
33
|
+
*
|
|
34
|
+
* The script is first canonicalised (`toNodeScript`) so the address is over the
|
|
35
|
+
* same bytes the node will execute and that `buildScriptProof` attests. The
|
|
36
|
+
* previous implementation hashed `sha3(upper(script))`, which is not a Minima
|
|
37
|
+
* script address — coins sent to it could never be spent by that script
|
|
38
|
+
* ("Script Missing from TxPoW for address …").
|
|
39
|
+
*/
|
|
40
|
+
/**
|
|
41
|
+
* Address data (hex) of a WOTS public-key digest's single-key script
|
|
42
|
+
* (`RETURN SIGNEDBY(0x<pk>)`). This is the value a `VERIFYOUT` compares against
|
|
43
|
+
* (the node compares the output coin's *address data*), distinct from the WOTS
|
|
44
|
+
* public key used by `SIGNEDBY`.
|
|
45
|
+
*/
|
|
46
|
+
export declare function wotsAddressData(publicKeyDigest: string): string;
|
|
28
47
|
export declare function computeScriptAddress(script: string): string;
|
|
29
48
|
export { concat };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { toNodeScript } from './canonical.js';
|
|
2
2
|
import { wotsVerifyDigest } from '../wots.js';
|
|
3
|
-
import { serializeMMRProof } from '../mmr.js';
|
|
3
|
+
import { serializeMMRProof, mmrRootFromSingleLeaf } from '../mmr.js';
|
|
4
4
|
import { mxToHex } from '../minima32.js';
|
|
5
5
|
function hexToBytes(hex) {
|
|
6
6
|
const cleanHex = hex.startsWith('0x') ? hex.slice(2) : hex;
|
|
@@ -302,10 +302,29 @@ export function validateExternalSignature(signature, transactionDigest) {
|
|
|
302
302
|
return false;
|
|
303
303
|
}
|
|
304
304
|
}
|
|
305
|
+
/**
|
|
306
|
+
* Derive a Minima script address exactly as the node does
|
|
307
|
+
* (`Address::Address(script)` / `ScriptProof::calculateAddress`):
|
|
308
|
+
*
|
|
309
|
+
* address = sha3( MiniNumber.ZERO || MiniString(script) || MiniNumber.ZERO )
|
|
310
|
+
*
|
|
311
|
+
* The script is first canonicalised (`toNodeScript`) so the address is over the
|
|
312
|
+
* same bytes the node will execute and that `buildScriptProof` attests. The
|
|
313
|
+
* previous implementation hashed `sha3(upper(script))`, which is not a Minima
|
|
314
|
+
* script address — coins sent to it could never be spent by that script
|
|
315
|
+
* ("Script Missing from TxPoW for address …").
|
|
316
|
+
*/
|
|
317
|
+
/**
|
|
318
|
+
* Address data (hex) of a WOTS public-key digest's single-key script
|
|
319
|
+
* (`RETURN SIGNEDBY(0x<pk>)`). This is the value a `VERIFYOUT` compares against
|
|
320
|
+
* (the node compares the output coin's *address data*), distinct from the WOTS
|
|
321
|
+
* public key used by `SIGNEDBY`.
|
|
322
|
+
*/
|
|
323
|
+
export function wotsAddressData(publicKeyDigest) {
|
|
324
|
+
const raw = publicKeyDigest.replace(/^0x/i, '').toUpperCase();
|
|
325
|
+
return computeScriptAddress(`RETURN SIGNEDBY(0x${raw})`);
|
|
326
|
+
}
|
|
305
327
|
export function computeScriptAddress(script) {
|
|
306
|
-
|
|
307
|
-
const scriptBytes = new TextEncoder().encode(cleanScript);
|
|
308
|
-
const hashBytes = sha3_256(scriptBytes);
|
|
309
|
-
return bytesToHex(hashBytes);
|
|
328
|
+
return bytesToHex(mmrRootFromSingleLeaf(toNodeScript(script)));
|
|
310
329
|
}
|
|
311
330
|
export { concat };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compatibility adapters for the legacy JSON transaction surface.
|
|
3
|
+
*
|
|
4
|
+
* The WASM bindings are retained as `*Wasm` exports, but the public
|
|
5
|
+
* `serializeTransaction` / `precomputeTransactionCoinID` names route through
|
|
6
|
+
* the TypeScript implementation. This avoids the WASM serializer crash on
|
|
7
|
+
* token+state outputs and the incorrect coin-ID precompute semantics.
|
|
8
|
+
*/
|
|
9
|
+
import { bytesToHex } from './Streamable.js';
|
|
10
|
+
import { type MinimaTransaction } from './transaction.js';
|
|
11
|
+
/** Convert the legacy JSON transaction shape into the TS object shape. */
|
|
12
|
+
export declare function minimaTransactionFromJson(json: string): MinimaTransaction;
|
|
13
|
+
/** Public serializer: accepts the TS object or the legacy JSON string. */
|
|
14
|
+
export declare function serializeTransaction(tx: MinimaTransaction | string): Uint8Array;
|
|
15
|
+
/** Coin-ID rollover: SHA3(MiniData(coinId) || MiniNumber(index)). */
|
|
16
|
+
export declare function rollCoinId(coinId: Uint8Array, outputIndex?: number): Uint8Array;
|
|
17
|
+
export declare function precomputeTransactionCoinID(tx: MinimaTransaction): void;
|
|
18
|
+
export declare function precomputeTransactionCoinID(tx: Uint8Array | string, outputIndex?: number): Uint8Array;
|
|
19
|
+
export { bytesToHex };
|