@totemsdk/txpow 0.1.6 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/calibrate.js CHANGED
@@ -12,7 +12,7 @@
12
12
  Object.defineProperty(exports, "__esModule", { value: true });
13
13
  exports.calibrateHashRate = calibrateHashRate;
14
14
  exports.estimateMiningCost = estimateMiningCost;
15
- const sha3_js_1 = require("@noble/hashes/sha3.js");
15
+ const core_1 = require("@totemsdk/core");
16
16
  /**
17
17
  * Run 100K trial SHA3-256 hashes and return the measured hash rate (hashes/sec).
18
18
  *
@@ -26,7 +26,7 @@ async function calibrateHashRate() {
26
26
  ? performance.now()
27
27
  : Date.now();
28
28
  for (let i = 0; i < TRIAL_COUNT; i++) {
29
- (0, sha3_js_1.sha3_256)(data);
29
+ (0, core_1.sha3_256)(data);
30
30
  }
31
31
  const elapsed = (typeof performance !== 'undefined' ? performance.now() : Date.now()) - start;
32
32
  return Math.round(TRIAL_COUNT / (elapsed / 1000));
package/dist/magic.js CHANGED
@@ -23,13 +23,13 @@ const core_1 = require("@totemsdk/core");
23
23
  const constants_js_1 = require("./constants.js");
24
24
  function serializeMagic() {
25
25
  const parts = [];
26
- parts.push((0, core_1.writeMiniNumber)(65536n));
27
- parts.push((0, core_1.writeMiniNumber)(1024n));
28
- parts.push((0, core_1.writeMiniNumber)(256n));
26
+ parts.push((0, core_1.writeMiniNumber)(65536n, 0));
27
+ parts.push((0, core_1.writeMiniNumber)(1024n, 0));
28
+ parts.push((0, core_1.writeMiniNumber)(256n, 0));
29
29
  parts.push((0, core_1.writeMiniData)(constants_js_1.MAX_HASH));
30
- parts.push((0, core_1.writeMiniNumber)(65536n));
31
- parts.push((0, core_1.writeMiniNumber)(1024n));
32
- parts.push((0, core_1.writeMiniNumber)(256n));
30
+ parts.push((0, core_1.writeMiniNumber)(65536n, 0));
31
+ parts.push((0, core_1.writeMiniNumber)(1024n, 0));
32
+ parts.push((0, core_1.writeMiniNumber)(256n, 0));
33
33
  parts.push((0, core_1.writeMiniData)(constants_js_1.MAX_HASH));
34
34
  return (0, core_1.concat)(...parts);
35
35
  }
@@ -18,7 +18,7 @@
18
18
  * npm run build (postbuild copies miner.wasm → dist/wasm/)
19
19
  *
20
20
  * ── WASM module interface (miner.wat) ──
21
- * import env.sha3_256(headerPtr, headerLen, outPtr) ← host provides via @noble/hashes
21
+ * import env.sha3_256(headerPtr, headerLen, outPtr) ← host provides via @totemsdk/core
22
22
  * export mine(headerPtr, headerLen, nonceOffset, nonceLen,
23
23
  * targetPtr, startNonce, chunkSize) → i32
24
24
  * export memory
package/dist/mine-wasm.js CHANGED
@@ -19,7 +19,7 @@
19
19
  * npm run build (postbuild copies miner.wasm → dist/wasm/)
20
20
  *
21
21
  * ── WASM module interface (miner.wat) ──
22
- * import env.sha3_256(headerPtr, headerLen, outPtr) ← host provides via @noble/hashes
22
+ * import env.sha3_256(headerPtr, headerLen, outPtr) ← host provides via @totemsdk/core
23
23
  * export mine(headerPtr, headerLen, nonceOffset, nonceLen,
24
24
  * targetPtr, startNonce, chunkSize) → i32
25
25
  * export memory
@@ -32,7 +32,7 @@ exports.setWasmUrl = setWasmUrl;
32
32
  exports.getBrowserWasmUrl = getBrowserWasmUrl;
33
33
  exports.isWasmAvailable = isWasmAvailable;
34
34
  exports.mineChunkWasm = mineChunkWasm;
35
- const sha3_js_1 = require("@noble/hashes/sha3.js");
35
+ const core_1 = require("@totemsdk/core");
36
36
  /** Byte offset in WASM linear memory where the hash output is written. */
37
37
  const HASH_OUT_OFFSET = 0;
38
38
  /** First byte of the header region (after the 32-byte hash output buffer). */
@@ -74,7 +74,7 @@ function getBrowserWasmUrl() {
74
74
  let _instance = null;
75
75
  let _memory = null;
76
76
  let _loadAttempted = false;
77
- /** Build the WebAssembly import object, wiring sha3_256 from @noble/hashes. */
77
+ /** Build the WebAssembly import object, wiring sha3_256 from @totemsdk/core. */
78
78
  function buildImports() {
79
79
  return {
80
80
  env: {
@@ -89,7 +89,7 @@ function buildImports() {
89
89
  const memView = new Uint8Array(_memory.buffer);
90
90
  // Slice to get a clean copy unaffected by future mutations
91
91
  const input = memView.slice(headerPtr, headerPtr + headerLen);
92
- const hash = (0, sha3_js_1.sha3_256)(input);
92
+ const hash = (0, core_1.sha3_256)(input);
93
93
  memView.set(hash, outPtr);
94
94
  },
95
95
  },
package/dist/mine.js CHANGED
@@ -32,8 +32,8 @@ exports.isLessThan = isLessThan;
32
32
  exports.setBrowserWorkerUrl = setBrowserWorkerUrl;
33
33
  exports.mineTxPoWInProcess = mineTxPoWInProcess;
34
34
  exports.mineTxPoW = mineTxPoW;
35
- const sha3_js_1 = require("@noble/hashes/sha3.js");
36
35
  const core_1 = require("@totemsdk/core");
36
+ const core_2 = require("@totemsdk/core");
37
37
  const magic_js_1 = require("./magic.js");
38
38
  const constants_js_1 = require("./constants.js");
39
39
  const mine_wasm_js_1 = require("./mine-wasm.js");
@@ -63,7 +63,7 @@ function isLessThan(a, b) {
63
63
  * k=3: 2^23 = 8388608
64
64
  */
65
65
  function nextNonceBoundary(nonce) {
66
- const nonceValueLen = (0, core_1.writeMiniNumber)(nonce).length - 2;
66
+ const nonceValueLen = (0, core_2.writeMiniNumber)(nonce, 0).length - 2;
67
67
  return 2n ** BigInt(8 * nonceValueLen - 1);
68
68
  }
69
69
  /**
@@ -72,7 +72,7 @@ function nextNonceBoundary(nonce) {
72
72
  * This is computed once at the start of a mine and never changes during it.
73
73
  */
74
74
  function buildHeaderTail(txBodyHash, timeMilli) {
75
- return (0, core_1.concat)((0, core_1.writeMiniData)(constants_js_1.MAIN_NET_CHAIN_ID), (0, core_1.writeMiniNumber)(timeMilli), (0, core_1.writeMiniNumber)(0n), (0, core_1.writeMiniData)(constants_js_1.MAX_HASH), new Uint8Array([constants_js_1.CASCADE_LEVELS]), (0, core_1.writeHashToStream)(constants_js_1.ZERO_HASH), (0, core_1.writeHashToStream)(constants_js_1.ZERO_HASH), (0, core_1.writeMiniNumber)(0n), (0, magic_js_1.serializeMagic)(), (0, core_1.writeHashToStream)(constants_js_1.ZERO_HASH), (0, core_1.writeHashToStream)(txBodyHash));
75
+ return (0, core_2.concat)((0, core_2.writeMiniData)(constants_js_1.MAIN_NET_CHAIN_ID), (0, core_2.writeMiniNumber)(timeMilli, 0), (0, core_2.writeMiniNumber)(0n, 0), (0, core_2.writeMiniData)(constants_js_1.MAX_HASH), new Uint8Array([constants_js_1.CASCADE_LEVELS]), (0, core_2.writeHashToStream)(constants_js_1.ZERO_HASH), (0, core_2.writeHashToStream)(constants_js_1.ZERO_HASH), (0, core_2.writeMiniNumber)(0n, 0), (0, magic_js_1.serializeMagic)(), (0, core_2.writeHashToStream)(constants_js_1.ZERO_HASH), (0, core_2.writeHashToStream)(txBodyHash));
76
76
  }
77
77
  /**
78
78
  * Synchronously mine a chunk of nonces within a fixed encoding boundary.
@@ -88,7 +88,7 @@ function mineChunkJs(headerBuf, nonceValueLen, target, startNonce, endNonce) {
88
88
  headerBuf[nonceValueOffset + i] = Number(n & 0xffn);
89
89
  n >>= 8n;
90
90
  }
91
- const hash = (0, sha3_js_1.sha3_256)(headerBuf);
91
+ const hash = (0, core_1.sha3_256)(headerBuf);
92
92
  if (isLessThan(hash, target))
93
93
  return nonce;
94
94
  }
@@ -294,7 +294,7 @@ async function mineTxPoWInProcess(txBodyBytes, txnDifficulty, options) {
294
294
  const signal = options?.signal;
295
295
  const timeMilli = options?.timeMilli ?? BigInt(Date.now());
296
296
  const forceJs = options?.forceJs ?? false;
297
- const bodyHash = (0, sha3_js_1.sha3_256)(txBodyBytes);
297
+ const bodyHash = (0, core_1.sha3_256)(txBodyBytes);
298
298
  const headerTail = buildHeaderTail(bodyHash, timeMilli);
299
299
  const useWasm = !forceJs && (await (0, mine_wasm_js_1.isWasmAvailable)());
300
300
  const startTime = Date.now();
@@ -309,9 +309,9 @@ async function mineTxPoWInProcess(txBodyBytes, txnDifficulty, options) {
309
309
  }
310
310
  const boundary = nextNonceBoundary(nonce);
311
311
  const chunkEnd = nonce + chunkSize < boundary ? nonce + chunkSize : boundary;
312
- const nonceBytes = (0, core_1.writeMiniNumber)(nonce);
312
+ const nonceBytes = (0, core_2.writeMiniNumber)(nonce, 0);
313
313
  const nonceValueLen = nonceBytes.length - 2;
314
- const headerBuf = (0, core_1.concat)(nonceBytes, headerTail);
314
+ const headerBuf = (0, core_2.concat)(nonceBytes, headerTail);
315
315
  // ── CRITICAL: WASM and JS are mutually exclusive per chunk ──────────────
316
316
  // When WASM returns null (no nonce in chunk), the chunk has been fully
317
317
  // scanned by WASM — we do NOT re-scan it with JS. Doing so would double
@@ -326,9 +326,9 @@ async function mineTxPoWInProcess(txBodyBytes, txnDifficulty, options) {
326
326
  }
327
327
  // ── End mutually exclusive section ───────────────────────────────────────
328
328
  if (found !== null) {
329
- const finalNonceBytes = (0, core_1.writeMiniNumber)(found);
330
- const finalHeader = (0, core_1.concat)(finalNonceBytes, headerTail);
331
- const txpowId = (0, sha3_js_1.sha3_256)(finalHeader);
329
+ const finalNonceBytes = (0, core_2.writeMiniNumber)(found, 0);
330
+ const finalHeader = (0, core_2.concat)(finalNonceBytes, headerTail);
331
+ const txpowId = (0, core_1.sha3_256)(finalHeader);
332
332
  return {
333
333
  minedHeaderBytes: finalHeader,
334
334
  txpowId,
@@ -56,8 +56,8 @@ exports.serializeTxHeader = serializeTxHeader;
56
56
  exports.computeTxPoWId = computeTxPoWId;
57
57
  exports.serializeTxBody = serializeTxBody;
58
58
  exports.serializeTxPoW = serializeTxPoW;
59
- const sha3_js_1 = require("@noble/hashes/sha3.js");
60
59
  const core_1 = require("@totemsdk/core");
60
+ const core_2 = require("@totemsdk/core");
61
61
  const magic_js_1 = require("./magic.js");
62
62
  const constants_js_1 = require("./constants.js");
63
63
  /**
@@ -70,26 +70,26 @@ function serializeTxHeader(txBodyHash, options) {
70
70
  const nonce = options?.nonce ?? 0n;
71
71
  const timeMilli = options?.timeMilli ?? BigInt(Date.now());
72
72
  const parts = [];
73
- parts.push((0, core_1.writeMiniNumber)(nonce));
74
- parts.push((0, core_1.writeMiniData)(constants_js_1.MAIN_NET_CHAIN_ID));
75
- parts.push((0, core_1.writeMiniNumber)(timeMilli));
76
- parts.push((0, core_1.writeMiniNumber)(0n));
77
- parts.push((0, core_1.writeMiniData)(constants_js_1.MAX_HASH));
73
+ parts.push((0, core_2.writeMiniNumber)(nonce, 0));
74
+ parts.push((0, core_2.writeMiniData)(constants_js_1.MAIN_NET_CHAIN_ID));
75
+ parts.push((0, core_2.writeMiniNumber)(timeMilli, 0));
76
+ parts.push((0, core_2.writeMiniNumber)(0n, 0));
77
+ parts.push((0, core_2.writeMiniData)(constants_js_1.MAX_HASH));
78
78
  parts.push(new Uint8Array([constants_js_1.CASCADE_LEVELS]));
79
- parts.push((0, core_1.writeHashToStream)(constants_js_1.ZERO_HASH));
80
- parts.push((0, core_1.writeHashToStream)(constants_js_1.ZERO_HASH));
81
- parts.push((0, core_1.writeMiniNumber)(0n));
79
+ parts.push((0, core_2.writeHashToStream)(constants_js_1.ZERO_HASH));
80
+ parts.push((0, core_2.writeHashToStream)(constants_js_1.ZERO_HASH));
81
+ parts.push((0, core_2.writeMiniNumber)(0n, 0));
82
82
  parts.push((0, magic_js_1.serializeMagic)());
83
- parts.push((0, core_1.writeHashToStream)(constants_js_1.ZERO_HASH));
84
- parts.push((0, core_1.writeHashToStream)(txBodyHash));
85
- return (0, core_1.concat)(...parts);
83
+ parts.push((0, core_2.writeHashToStream)(constants_js_1.ZERO_HASH));
84
+ parts.push((0, core_2.writeHashToStream)(txBodyHash));
85
+ return (0, core_2.concat)(...parts);
86
86
  }
87
87
  /**
88
88
  * Compute TxPoW ID = SHA3-256(TxHeader bytes).
89
89
  * Matches Java: Crypto.getInstance().hashObject(mHeader) via SHA3Digest(256).
90
90
  */
91
91
  function computeTxPoWId(headerBytes) {
92
- return (0, sha3_js_1.sha3_256)(headerBytes);
92
+ return (0, core_1.sha3_256)(headerBytes);
93
93
  }
94
94
  /**
95
95
  * Build the empty burn transaction bytes.
@@ -103,7 +103,7 @@ function computeTxPoWId(headerBytes) {
103
103
  * writeHashToStream([0x00]) linkHash = ZERO_TXPOWID (1 byte)
104
104
  */
105
105
  function buildEmptyBurnTxBytes() {
106
- return (0, core_1.concat)((0, core_1.writeMiniNumber)(0n), (0, core_1.writeMiniNumber)(0n), (0, core_1.writeMiniNumber)(0n), (0, core_1.writeHashToStream)(new Uint8Array([0x00])));
106
+ 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])));
107
107
  }
108
108
  /**
109
109
  * Build the empty burn witness bytes.
@@ -114,7 +114,7 @@ function buildEmptyBurnTxBytes() {
114
114
  * writeMiniNumber(0n) scriptproof count
115
115
  */
116
116
  function buildEmptyBurnWitnessBytes() {
117
- return (0, core_1.concat)((0, core_1.writeMiniNumber)(0n), (0, core_1.writeMiniNumber)(0n), (0, core_1.writeMiniNumber)(0n));
117
+ return (0, core_2.concat)((0, core_2.writeMiniNumber)(0n, 0), (0, core_2.writeMiniNumber)(0n, 0), (0, core_2.writeMiniNumber)(0n, 0));
118
118
  }
119
119
  /**
120
120
  * Serialize a TxBody per Minima's TxBody.writeDataStream().
@@ -130,13 +130,14 @@ function serializeTxBody(txBytes, witnessBytes, options) {
130
130
  prng = options.prng;
131
131
  }
132
132
  else {
133
- prng = new Uint8Array(32);
133
+ const fresh = new Uint8Array(32);
134
134
  if (typeof crypto === 'undefined' || !crypto.getRandomValues) {
135
135
  throw new Error('SECURITY: crypto.getRandomValues unavailable — cannot generate secure random bytes for transaction PRNG');
136
136
  }
137
- crypto.getRandomValues(prng);
137
+ crypto.getRandomValues(fresh);
138
+ prng = fresh;
138
139
  }
139
- return (0, core_1.concat)((0, core_1.writeHashToStream)(prng), (0, core_1.writeMiniData)(txnDifficulty), txBytes, witnessBytes, buildEmptyBurnTxBytes(), buildEmptyBurnWitnessBytes(), (0, core_1.writeMiniNumber)(0n));
140
+ return (0, core_2.concat)((0, core_2.writeHashToStream)(prng), (0, core_2.writeMiniData)(txnDifficulty), txBytes, witnessBytes, buildEmptyBurnTxBytes(), buildEmptyBurnWitnessBytes(), (0, core_2.writeMiniNumber)(0n, 0));
140
141
  }
141
142
  /**
142
143
  * Serialize a complete TxPoW per Minima's TxPoW.writeDataStream().
@@ -154,6 +155,6 @@ function serializeTxBody(txBytes, witnessBytes, options) {
154
155
  */
155
156
  function serializeTxPoW(txBytes, witnessBytes, options) {
156
157
  const txBody = serializeTxBody(txBytes, witnessBytes, options);
157
- const txBodyHash = (0, sha3_js_1.sha3_256)(txBody);
158
- return (0, core_1.concat)(serializeTxHeader(txBodyHash, options), new Uint8Array([0x01]), txBody);
158
+ const txBodyHash = (0, core_1.sha3_256)(txBody);
159
+ return (0, core_2.concat)(serializeTxHeader(txBodyHash, options), new Uint8Array([0x01]), txBody);
159
160
  }
package/dist/verify.js CHANGED
@@ -34,7 +34,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
34
34
  exports.verifyProofOfWork = verifyProofOfWork;
35
35
  exports.verifyTxPoWParts = verifyTxPoWParts;
36
36
  exports.verifyTxPoWWork = verifyTxPoWWork;
37
- const sha3_js_1 = require("@noble/hashes/sha3.js");
37
+ const core_1 = require("@totemsdk/core");
38
38
  const mine_js_1 = require("./mine.js");
39
39
  const serialization_js_1 = require("./serialization.js");
40
40
  const constants_js_1 = require("./constants.js");
@@ -129,7 +129,7 @@ function tryParseTxPoW(bytes) {
129
129
  }
130
130
  const embeddedBodyHash = headerBytes.slice(hashFieldStart + 4);
131
131
  const bodyBytes = bytes.slice(i + 1);
132
- const actualBodyHash = (0, sha3_js_1.sha3_256)(bodyBytes);
132
+ const actualBodyHash = (0, core_1.sha3_256)(bodyBytes);
133
133
  if (actualBodyHash.every((b, j) => b === embeddedBodyHash[j])) {
134
134
  return { headerBytes, bodyBytes };
135
135
  }
@@ -12,22 +12,4 @@
12
12
  * to avoid structured-clone limitations on older Node.js versions.
13
13
  * Uint8Arrays are transmitted as number[] for reliable structured-clone transfer.
14
14
  */
15
- declare const workerData: any, parentPort: import("worker_threads").MessagePort | null;
16
- declare const mineTxPoWInProcess: typeof import("../mine").mineTxPoWInProcess;
17
- interface NodeWorkerInput {
18
- txBodyBytes: number[];
19
- txnDifficulty: number[];
20
- chunkSize?: number;
21
- maxIterations?: string;
22
- timeMilli?: string;
23
- forceJs?: boolean;
24
- }
25
- interface NodeWorkerOutput {
26
- minedHeaderBytes?: number[];
27
- txpowId?: number[];
28
- nonce?: string;
29
- elapsedMs?: number;
30
- source?: 'wasm' | 'js';
31
- error?: string;
32
- }
33
- declare function run(): Promise<void>;
15
+ export {};
@@ -13,6 +13,7 @@
13
13
  * to avoid structured-clone limitations on older Node.js versions.
14
14
  * Uint8Arrays are transmitted as number[] for reliable structured-clone transfer.
15
15
  */
16
+ Object.defineProperty(exports, "__esModule", { value: true });
16
17
  /* eslint-disable @typescript-eslint/no-var-requires */
17
18
  const { workerData, parentPort } = require('worker_threads');
18
19
  const { mineTxPoWInProcess } = require('../mine');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@totemsdk/txpow",
3
- "version": "0.1.6",
3
+ "version": "1.0.0",
4
4
  "description": "TxPoW envelope serialization and proof-of-work mining for the Minima protocol",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -13,28 +13,18 @@
13
13
  },
14
14
  "files": [
15
15
  "dist",
16
- "src/wasm/miner.wasm",
17
16
  "README.md",
18
17
  "LICENSE"
19
18
  ],
20
19
  "dependencies": {
21
- "@totemsdk/core": "^1.1.0"
22
- },
23
- "peerDependencies": {
24
- "@noble/hashes": "^2.0.0"
25
- },
26
- "peerDependenciesMeta": {
27
- "@noble/hashes": {
28
- "optional": false
29
- }
20
+ "@totemsdk/core": "^1.2.9"
30
21
  },
31
22
  "devDependencies": {
32
- "@noble/hashes": "^2.2.0",
33
- "@types/jest": "^29.0.0",
23
+ "@types/jest": "^30.0.0",
34
24
  "@types/node": "^20.0.0",
35
- "jest": "^29.0.0",
25
+ "jest": "^30.4.2",
36
26
  "ts-jest": "^29.0.0",
37
- "typescript": "^5.0.0"
27
+ "typescript": "^7.0.2"
38
28
  },
39
29
  "engines": {
40
30
  "node": ">=16.0.0"
@@ -44,14 +34,14 @@
44
34
  },
45
35
  "license": "MIT",
46
36
  "author": "Totem SDK",
47
- "homepage": "https://totemsdk.com",
37
+ "homepage": "https://totem.ing",
48
38
  "bugs": {
49
- "url": "https://github.com/MrGheek/axia-totem/issues"
39
+ "url": "https://github.com/Totem-Edge/totem-sdk/issues"
50
40
  },
51
41
  "repository": {
52
42
  "type": "git",
53
- "url": "git+https://github.com/MrGheek/axia-totem.git",
54
- "directory": "packages/totem-sdk/packages/txpow"
43
+ "url": "git+https://github.com/Totem-Edge/totem-sdk.git",
44
+ "directory": "packages/txpow"
55
45
  },
56
46
  "keywords": [
57
47
  "totem",
@@ -60,15 +50,14 @@
60
50
  "blockchain",
61
51
  "quantum-resistant",
62
52
  "wots",
63
- "kissvm",
64
53
  "utxo"
65
54
  ],
66
55
  "scripts": {
67
- "build": "tsc",
56
+ "build": "npm run build:wasm && tsc",
68
57
  "postbuild": "node -e \"const fs=require('fs'),p=require('path');const src=p.join(__dirname,'src','wasm','miner.wasm');const dst=p.join(__dirname,'dist','wasm','miner.wasm');if(fs.existsSync(src)){fs.mkdirSync(p.dirname(dst),{recursive:true});fs.copyFileSync(src,dst);console.log('[@totemsdk/txpow] miner.wasm → dist/wasm/')}else{console.log('[@totemsdk/txpow] Note: miner.wasm not compiled — JS fallback only. Run npm run build:wasm with Emscripten to enable WASM acceleration.')}\"",
69
58
  "compile:wasm": "node scripts/compile-wasm.js",
70
- "build:wasm": "npm run compile:wasm && npm run build",
59
+ "build:wasm": "npm run compile:wasm",
71
60
  "clean": "rm -rf dist",
72
- "test": "jest --passWithNoTests"
61
+ "test": "jest"
73
62
  }
74
63
  }
Binary file