@totemsdk/core 1.3.0 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -28,6 +28,7 @@ export { makeMxAddress as encodeMx, parseMxAddress as decodeMx } from './wasm-sy
28
28
  export declare function mxToHex(address: string): string;
29
29
  export * from './mmr.js';
30
30
  export * from './scripts/index.js';
31
+ export { toNodeScript } from './scripts/canonical.js';
31
32
  export { scriptToAddress, addressToRoot } from './derive.js';
32
33
  export { WORD_LIST, convertStringToSeed, convertWordListToSeed, generateSeedPhrase, } from './bip39.js';
33
34
  export { MiniNumber } from './MiniNumber.js';
package/dist/cjs/index.js CHANGED
@@ -22,8 +22,8 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
22
22
  };
23
23
  Object.defineProperty(exports, "__esModule", { value: true });
24
24
  exports.createChallenge = exports.timingSafeEqual = exports.deserializeTransaction = exports.computeTransactionDigest = exports.serializeTransaction = exports.wotsAddressFromKeypair = exports.parseMxAddress = exports.makeMxAddress = exports.cleanSeedPhrase = exports.validatePhrase = exports.generateWordList = exports.phraseToSeed = exports.verifyMMRProof = exports.mmrRootFromPublicKeys = exports.verifyTreeSignature = exports.deriveUnifiedAddressPublicKey = exports.createUnifiedRootTreeKey = exports.createUnifiedChildTreeKey = exports.precomputeTransactionCoinID = exports.writeMiniString = exports.writeMiniData = exports.writeMiniNumber = exports.deriveRootPrivSeed = exports.derivePerAddressSeed = exports.deriveChainSeedJava = exports.wotsKeypairFromSeed = exports.wotsPublicKeyFromSeed = exports.wotsPkFromSig = exports.wotsVerifyDigest = exports.wotsVerify = exports.wotsSign = exports.deriveFullPublicKey = exports.derivePKdigest = exports.hashChain = exports.expandPrivateKey = exports.sha3_256 = exports.concatBytes = exports.hexToBytes = exports.bytesToHex = exports.TransactionReceiptStore = exports.WatermarkExhaustedError = exports.TransactionLifecycleError = exports.TransactionLifecycle = exports.TransactionService = exports.flatIndexFromLanes = exports.finalizeLease = exports.prepareLease = exports.LeaseMonitor = exports.WatermarkStore = exports.LeaseStore = void 0;
25
- exports.TreeKeyNode = exports.TreeKey = exports.serializeMMREntry = exports.serializeMMRData = exports.serializeMMREntryNumber = exports.createMMREntryNumber = exports.deriveUnifiedChildSeed = exports.indexToMiniDataBytes = exports.javaHashAllObjects = exports.writeHashToStream = exports.serializeMiniNumberONE = exports.serializeMiniNumberZERO = exports.hashObject = exports.deriveChildTreeSeedJava = exports.hashAllObjects = exports.serializeMiniData = exports.serializeMiniNumber = exports.bigIntToByteArray = exports.concat = exports.writeSignatureProof = exports.writeSignature = exports.writeMMREntryNumber = exports.writeMiniByte = exports.verifyDeterministicAddress = exports.deriveAddress = exports.deriveUnifiedAddress = exports.deriveDeterministicAddress = exports.normalizeHex = exports.deriveAddressFromPublicKey = exports.verifyTreeSignatureDetailed = exports.verifySignatureDetailed = exports.verifySignature = exports.buildMinimaCoin = exports.createDefaultTransaction = exports.serializeTokenDescriptor = exports.computeTokenId = exports.serializeTransactionObject = exports.serializeCoin = exports.parseDecimalToMiniNumber = exports.MiniNumber = exports.generateSeedPhrase = exports.convertWordListToSeed = exports.convertStringToSeed = exports.WORD_LIST = exports.addressToRoot = exports.scriptToAddress = exports.decodeMx = exports.encodeMx = exports.scriptFromWotsPk = exports.validateChallenge = void 0;
26
- exports.MINIMA_CONSTANTS = exports.bytesToUtf8 = exports.utf8ToBytes = exports.hashCanonical = exports.canonicalJson = exports.toHex = exports.wotsSignLegacy = exports.baseWWithChecksum = exports.toWinternitzDigits = exports.prfChainSeed = exports.h = exports.assert32 = exports.u32be = exports.u16be = exports.disableWotsLogger = exports.setWotsLogger = exports.fromHex = exports.hex = exports.F = exports.disableTreeKeyLogger = exports.setTreeKeyLogger = exports.createPerAddressTreeKeyAsync = exports.createPerAddressTreeKey = exports.createUnifiedChildTreeKeyAsync = exports.DEFAULT_LEVELS = exports.DEFAULT_KEYS_PER_LEVEL = exports.getRootPublicKey = exports.deserializeTreeSignature = exports.serializeTreeSignature = void 0;
25
+ exports.TreeKey = exports.serializeMMREntry = exports.serializeMMRData = exports.serializeMMREntryNumber = exports.createMMREntryNumber = exports.deriveUnifiedChildSeed = exports.indexToMiniDataBytes = exports.javaHashAllObjects = exports.writeHashToStream = exports.serializeMiniNumberONE = exports.serializeMiniNumberZERO = exports.hashObject = exports.deriveChildTreeSeedJava = exports.hashAllObjects = exports.serializeMiniData = exports.serializeMiniNumber = exports.bigIntToByteArray = exports.concat = exports.writeSignatureProof = exports.writeSignature = exports.writeMMREntryNumber = exports.writeMiniByte = exports.verifyDeterministicAddress = exports.deriveAddress = exports.deriveUnifiedAddress = exports.deriveDeterministicAddress = exports.normalizeHex = exports.deriveAddressFromPublicKey = exports.verifyTreeSignatureDetailed = exports.verifySignatureDetailed = exports.verifySignature = exports.buildMinimaCoin = exports.createDefaultTransaction = exports.serializeTokenDescriptor = exports.computeTokenId = exports.serializeTransactionObject = exports.serializeCoin = exports.parseDecimalToMiniNumber = exports.MiniNumber = exports.generateSeedPhrase = exports.convertWordListToSeed = exports.convertStringToSeed = exports.WORD_LIST = exports.addressToRoot = exports.scriptToAddress = exports.toNodeScript = exports.decodeMx = exports.encodeMx = exports.scriptFromWotsPk = exports.validateChallenge = void 0;
26
+ exports.MINIMA_CONSTANTS = exports.bytesToUtf8 = exports.utf8ToBytes = exports.hashCanonical = exports.canonicalJson = exports.toHex = exports.wotsSignLegacy = exports.baseWWithChecksum = exports.toWinternitzDigits = exports.prfChainSeed = exports.h = exports.assert32 = exports.u32be = exports.u16be = exports.disableWotsLogger = exports.setWotsLogger = exports.fromHex = exports.hex = exports.F = exports.disableTreeKeyLogger = exports.setTreeKeyLogger = exports.createPerAddressTreeKeyAsync = exports.createPerAddressTreeKey = exports.createUnifiedChildTreeKeyAsync = exports.DEFAULT_LEVELS = exports.DEFAULT_KEYS_PER_LEVEL = exports.getRootPublicKey = exports.deserializeTreeSignature = exports.serializeTreeSignature = exports.TreeKeyNode = void 0;
27
27
  exports.mxToHex = mxToHex;
28
28
  // Version information for bundle duplication detection
29
29
  __exportStar(require("./version.js"), exports);
@@ -142,6 +142,8 @@ function mxToHex(address) {
142
142
  __exportStar(require("./mmr.js"), exports);
143
143
  // Script Types, Witness Serializer, Contract Helpers
144
144
  __exportStar(require("./scripts/index.js"), exports);
145
+ var canonical_js_1 = require("./scripts/canonical.js");
146
+ Object.defineProperty(exports, "toNodeScript", { enumerable: true, get: function () { return canonical_js_1.toNodeScript; } });
145
147
  // Address Derivation (original)
146
148
  var derive_js_1 = require("./derive.js");
147
149
  Object.defineProperty(exports, "scriptToAddress", { enumerable: true, get: function () { return derive_js_1.scriptToAddress; } });
@@ -231,9 +233,9 @@ Object.defineProperty(exports, "toWinternitzDigits", { enumerable: true, get: fu
231
233
  Object.defineProperty(exports, "baseWWithChecksum", { enumerable: true, get: function () { return wots_js_1.baseWWithChecksum; } });
232
234
  Object.defineProperty(exports, "wotsSignLegacy", { enumerable: true, get: function () { return wots_js_1.wotsSignLegacy; } });
233
235
  // Canonical helpers (deterministic JSON + hashing)
234
- var canonical_js_1 = require("./canonical.js");
235
- Object.defineProperty(exports, "toHex", { enumerable: true, get: function () { return canonical_js_1.toHex; } });
236
- Object.defineProperty(exports, "canonicalJson", { enumerable: true, get: function () { return canonical_js_1.canonicalJson; } });
236
+ var canonical_js_2 = require("./canonical.js");
237
+ Object.defineProperty(exports, "toHex", { enumerable: true, get: function () { return canonical_js_2.toHex; } });
238
+ Object.defineProperty(exports, "canonicalJson", { enumerable: true, get: function () { return canonical_js_2.canonicalJson; } });
237
239
  var canonical_hash_js_1 = require("./canonical-hash.js");
238
240
  Object.defineProperty(exports, "hashCanonical", { enumerable: true, get: function () { return canonical_hash_js_1.hashCanonical; } });
239
241
  // Byte/string utilities
package/dist/cjs/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/cjs/mmr.js CHANGED
@@ -4,6 +4,7 @@ exports.deserializeMMRProof = exports.MMRTree = exports.serializeRealMMRProof =
4
4
  exports.serializeMMRProof = serializeMMRProof;
5
5
  exports.mmrLeafExact = mmrLeafExact;
6
6
  exports.mmrRootFromSingleLeaf = mmrRootFromSingleLeaf;
7
+ exports.buildScriptProof = buildScriptProof;
7
8
  exports.createMMRDataLeafNode = createMMRDataLeafNode;
8
9
  exports.createMMRDataParentNode = createMMRDataParentNode;
9
10
  exports.calculateProofRoot = calculateProofRoot;
@@ -15,6 +16,9 @@ const wasm_sync_js_1 = require("./wasm-sync.js");
15
16
  const javaStreamables_js_1 = require("./javaStreamables.js");
16
17
  // Import canonical wire serialization from Streamable.ts (single source of truth)
17
18
  const Streamable_js_1 = require("./Streamable.js");
19
+ // Canonicalise to the exact script the node will receive, so the proof's
20
+ // computed address matches `computeScriptAddress` (which canonicalises too).
21
+ const canonical_js_1 = require("./scripts/canonical.js");
18
22
  // Re-export for API compatibility (existing code imports from mmr.ts)
19
23
  // Using a wrapper function for better compatibility with dynamic imports
20
24
  function serializeMMRProof(proof, blockTime = 0n) {
@@ -80,6 +84,35 @@ function mmrLeafExact(script) {
80
84
  function mmrRootFromSingleLeaf(script) {
81
85
  return mmrLeafExact(script);
82
86
  }
87
+ /**
88
+ * Build the serialized Minima `ScriptProof` bytes for a single-leaf MMR.
89
+ *
90
+ * Mirrors the node's `ScriptProof(const std::string& zScript)` constructor
91
+ * (org/minima/objects/script_proof.cpp): the script is added as the sole leaf
92
+ * of a fresh MMR with sum value ZERO, so the proof chain is empty and the
93
+ * resulting address is the leaf commitment itself:
94
+ *
95
+ * address = sha3( MiniNumber.ZERO || MiniString(script) || MiniNumber.ZERO )
96
+ * = mmrLeafExact(script)
97
+ *
98
+ * This is exactly how the node derives `Address(script)`, so a witness carrying
99
+ * this proof satisfies `Witness::getScript(input.getAddress())` for every input
100
+ * paying to that script. Without it the node rejects the TxPoW with
101
+ * "Script Missing from TxPoW for address <addr>".
102
+ *
103
+ * Wire format (`ScriptProof::writeDataStream`):
104
+ * MiniString(script) || MMRProof(blockTime = 0, chunks = [])
105
+ *
106
+ * @param script - The Minima (KISSVM) script whose address the proof attests.
107
+ */
108
+ function buildScriptProof(script) {
109
+ // The node derives the proof's address as
110
+ // sha3( MiniNumber.ZERO || MiniString(script) || MiniNumber.ZERO )
111
+ // so the script bytes here must be exactly the canonical script whose address
112
+ // was used for the coin (see computeScriptAddress).
113
+ const canonical = (0, canonical_js_1.toNodeScript)(script);
114
+ return concat((0, Streamable_js_1.writeMiniString)(canonical), serializeMMRProof({ chunks: [] }, 0n));
115
+ }
83
116
  /**
84
117
  * Create MMRData leaf node matching Minima's MMRData.CreateMMRDataLeafNode
85
118
  *
@@ -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,99 @@
1
+ "use strict";
2
+ /**
3
+ * `toNodeScript` — canonicalize a KISSVM script to the syntax the Minima **node**
4
+ * tokenizer accepts (RFC-048 §3.1 L4 finding; audit
5
+ * `docs/audits/kissvm-node-validity-l4-2026-10.md`).
6
+ *
7
+ * The node tokenizer requires:
8
+ * - user variable names `^[a-z]+$` (lowercase letters only — no digits/underscores/uppercase),
9
+ * - hex literals `^0x[0-9a-fA-F]+$` (lowercase `0x` prefix),
10
+ * - arithmetic via the `+` / `-` operators (`ADD`/`SUB` are not node functions).
11
+ *
12
+ * This is a token-level rewrite: commands, functions, operators, globals (`@…`), hex
13
+ * literals, numbers and `[…]` string literals are preserved; only **user identifiers**
14
+ * are rewritten to lowercase letters (with deterministic de-collision), `0X`→`0x`, and
15
+ * `ADD`/`SUB`→`+`/`-`. It is idempotent.
16
+ *
17
+ * Semantics are unchanged: the SDK evaluators normalize `+`→ADD and `-`→SUB (and vice
18
+ * versa), so a canonical script behaves identically to its source.
19
+ */
20
+ Object.defineProperty(exports, "__esModule", { value: true });
21
+ exports.toNodeScript = toNodeScript;
22
+ const KEYWORDS = new Set([
23
+ 'LET', 'IF', 'THEN', 'ELSE', 'ELSEIF', 'ENDIF', 'RETURN', 'ASSERT', 'EXEC', 'MAST',
24
+ 'WHILE', 'DO', 'ENDWHILE', 'FOR', 'TO', 'BY', 'ENDFOR', 'FOREACH', 'IN', 'SWITCH',
25
+ 'CASE', 'DEFAULT', 'ENDSWITCH', 'FUNC', 'ENDFUNC', 'TRUE', 'FALSE', 'STORE', 'WITH', 'FUNCTION', 'RETURN',
26
+ ]);
27
+ // Word operators kept as-is (ADD/SUB are handled separately → +/−).
28
+ const OPS = new Set([
29
+ 'GT', 'LT', 'EQ', 'GTE', 'LTE', 'NEQ', 'AND', 'OR', 'XOR', 'NAND', 'NOR', 'NXOR',
30
+ 'NOT', 'NEG', 'MUL', 'DIV', 'MOD', 'LSHIFT', 'RSHIFT', 'BITAND', 'BITOR', 'BITXOR', 'BITNOT',
31
+ ]);
32
+ const FUNCTIONS = new Set([
33
+ 'SHA3', 'SHA2', 'HASH', 'PROOF', 'SIGNEDBY', 'MULTISIG', 'CHECKSIG', 'VERIFYOUT', 'VERIFYIN',
34
+ 'GETOUTADDR', 'GETOUTAMT', 'GETOUTTOK', 'GETOUTKEEPSTATE', 'GETINADDR', 'GETINAMT', 'GETINID',
35
+ 'GETINTOK', 'STATE', 'PREVSTATE', 'SAMESTATE', 'SAMECOINS', 'COINAGE', 'COINDATA', 'NUMBER',
36
+ 'HEX', 'STRING', 'ASCII', 'UTF8', 'BOOL', 'CONCAT', 'LEN', 'REV', 'SUBSET', 'SETLEN',
37
+ 'SUBSTR', 'REPLACE', 'REPLACEFIRST', 'OVERWRITE', 'ABS', 'CEIL', 'FLOOR', 'MAX', 'MIN',
38
+ 'DEC', 'INC', 'SIGDIG', 'POW', 'SQRT', 'BITSET', 'BITGET', 'BITCOUNT', 'ADDRESS', 'EXISTS',
39
+ 'GET', 'SUMINPUTS', 'SUMOUTPUTS', 'CHECKSIG',
40
+ ]);
41
+ function classify(id) {
42
+ const up = id.toUpperCase();
43
+ if (up === 'ADD')
44
+ return { kind: 'keep', out: '+' };
45
+ if (up === 'SUB')
46
+ return { kind: 'keep', out: '-' };
47
+ if (KEYWORDS.has(up) || OPS.has(up) || FUNCTIONS.has(up))
48
+ return { kind: 'keep', out: up };
49
+ return { kind: 'var' };
50
+ }
51
+ /** a, b, …, z, aa, ab, … */
52
+ function letters(n) {
53
+ let s = '';
54
+ n += 1;
55
+ while (n > 0) {
56
+ const r = (n - 1) % 26;
57
+ s = String.fromCharCode(97 + r) + s;
58
+ n = Math.floor((n - 1) / 26);
59
+ }
60
+ return s;
61
+ }
62
+ const TOKEN = /(\[[^\]]*\]|0[xX][0-9a-fA-F]+|@[A-Za-z][A-Za-z0-9]*|[A-Za-z_][A-Za-z0-9_]*)/g;
63
+ /** Canonicalize a KISSVM script to node-accepted syntax. Idempotent. */
64
+ function toNodeScript(script) {
65
+ // The node tokenizer has no comment syntax; strip `//` line comments.
66
+ script = script.replace(/\/\/[^\n]*/g, '');
67
+ const varMap = new Map();
68
+ const used = new Set();
69
+ // First pass: assign canonical names to user variables (in first-occurrence order).
70
+ let m;
71
+ const re1 = new RegExp(TOKEN.source, 'g');
72
+ while ((m = re1.exec(script))) {
73
+ const tok = m[0];
74
+ if (tok[0] === '[' || tok.startsWith('0x') || tok.startsWith('0X') || tok[0] === '@')
75
+ continue;
76
+ if (classify(tok).kind !== 'var' || varMap.has(tok))
77
+ continue;
78
+ const base = tok.toLowerCase().replace(/[^a-z]/g, '') || 'v';
79
+ let cand = base;
80
+ let i = 0;
81
+ while (used.has(cand))
82
+ cand = base + letters(i++);
83
+ used.add(cand);
84
+ varMap.set(tok, cand);
85
+ }
86
+ // Second pass: rewrite.
87
+ return script.replace(TOKEN, (tok) => {
88
+ if (tok[0] === '[')
89
+ return tok;
90
+ if (tok.startsWith('0x') || tok.startsWith('0X'))
91
+ return '0x' + tok.slice(2);
92
+ if (tok[0] === '@')
93
+ return '@' + tok.slice(1).toUpperCase();
94
+ const c = classify(tok);
95
+ if (c.kind === 'keep')
96
+ return c.out;
97
+ return varMap.get(tok) ?? tok;
98
+ });
99
+ }
@@ -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";
@@ -14,7 +14,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
- exports.computeScriptAddress = exports.validateExternalSignature = exports.aggregateSignatures = exports.serializeExtraScripts = exports.deduplicateScriptDescriptors = exports.buildScriptProofFromDescriptor = exports.serializeStateVariables = exports.encodeStateValue = exports.STATETYPE_BOOL = exports.STATETYPE_STRING = exports.STATETYPE_NUMBER = exports.STATETYPE_HEX = exports.serializeScriptProofWithProof = exports.serializeMMRProofChunk = exports.encodeMiniString = exports.encodeMiniData = exports.encodeMiniNumber = void 0;
17
+ exports.toNodeScript = exports.wotsAddressData = exports.computeScriptAddress = exports.validateExternalSignature = exports.aggregateSignatures = exports.serializeExtraScripts = exports.deduplicateScriptDescriptors = exports.buildScriptProofFromDescriptor = exports.serializeStateVariables = exports.encodeStateValue = exports.STATETYPE_BOOL = exports.STATETYPE_STRING = exports.STATETYPE_NUMBER = exports.STATETYPE_HEX = exports.serializeScriptProofWithProof = exports.serializeMMRProofChunk = exports.encodeMiniString = exports.encodeMiniData = exports.encodeMiniNumber = void 0;
18
18
  __exportStar(require("./types.js"), exports);
19
19
  __exportStar(require("./dapp-types.js"), exports);
20
20
  var witness_serializer_js_1 = require("./witness-serializer.js");
@@ -35,4 +35,7 @@ Object.defineProperty(exports, "serializeExtraScripts", { enumerable: true, get:
35
35
  Object.defineProperty(exports, "aggregateSignatures", { enumerable: true, get: function () { return witness_serializer_js_1.aggregateSignatures; } });
36
36
  Object.defineProperty(exports, "validateExternalSignature", { enumerable: true, get: function () { return witness_serializer_js_1.validateExternalSignature; } });
37
37
  Object.defineProperty(exports, "computeScriptAddress", { enumerable: true, get: function () { return witness_serializer_js_1.computeScriptAddress; } });
38
+ Object.defineProperty(exports, "wotsAddressData", { enumerable: true, get: function () { return witness_serializer_js_1.wotsAddressData; } });
38
39
  __exportStar(require("./contract-helpers.js"), exports);
40
+ var canonical_js_1 = require("./canonical.js");
41
+ Object.defineProperty(exports, "toNodeScript", { enumerable: true, get: function () { return canonical_js_1.toNodeScript; } });
@@ -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 wasm_sync_js_1 = require("../wasm-sync.js");
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
- const cleanScript = script.trim().toUpperCase();
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
  }
package/dist/index.d.ts CHANGED
@@ -28,6 +28,7 @@ export { makeMxAddress as encodeMx, parseMxAddress as decodeMx } from './wasm-sy
28
28
  export declare function mxToHex(address: string): string;
29
29
  export * from './mmr.js';
30
30
  export * from './scripts/index.js';
31
+ export { toNodeScript } from './scripts/canonical.js';
31
32
  export { scriptToAddress, addressToRoot } from './derive.js';
32
33
  export { WORD_LIST, convertStringToSeed, convertWordListToSeed, generateSeedPhrase, } from './bip39.js';
33
34
  export { MiniNumber } from './MiniNumber.js';
package/dist/index.js CHANGED
@@ -68,6 +68,7 @@ export function mxToHex(address) {
68
68
  export * from './mmr.js';
69
69
  // Script Types, Witness Serializer, Contract Helpers
70
70
  export * from './scripts/index.js';
71
+ export { toNodeScript } from './scripts/canonical.js';
71
72
  // Address Derivation (original)
72
73
  export { scriptToAddress, addressToRoot } from './derive.js';
73
74
  // 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
+ }
@@ -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";
@@ -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 { sha3_256 } from '../wasm-async.js';
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
- const cleanScript = script.trim().toUpperCase();
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 };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@totemsdk/core",
3
- "version": "1.3.0",
3
+ "version": "1.4.0",
4
4
  "description": "Core cryptographic primitives for Totem SDK — backed by Rust/WASM",
5
5
  "type": "module",
6
6
  "main": "dist/cjs/index.js",