neozipkit 0.1.1 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +52 -105
- package/contracts/INTEGRATION.md +338 -0
- package/contracts/NETWORKS.md +156 -0
- package/contracts/README.md +152 -0
- package/{src/server/blockchain → contracts}/constants.ts +5 -22
- package/contracts/deploy-bytecode.mjs +94 -0
- package/dist/blockchain/ZipKitMinter.d.ts +106 -0
- package/dist/blockchain/ZipKitMinter.js +347 -0
- package/dist/blockchain/ZipKitNFT.d.ts +3 -0
- package/dist/blockchain/ZipKitNFT.js +7 -0
- package/dist/blockchain/ZipKitOTS.d.ts +135 -0
- package/dist/blockchain/ZipKitOTS.js +564 -0
- package/dist/blockchain/ZipKitVerifier.d.ts +69 -0
- package/dist/blockchain/ZipKitVerifier.js +289 -0
- package/dist/{server/blockchain/WalletAnalyzer.d.ts → blockchain/ZipKitWallet.d.ts} +31 -43
- package/dist/{server/blockchain/WalletAnalyzer.js → blockchain/ZipKitWallet.js} +193 -161
- package/dist/blockchain/contracts.d.ts +14 -0
- package/dist/blockchain/contracts.js +77 -0
- package/dist/blockchain/index.d.ts +7 -0
- package/dist/blockchain/index.js +50 -0
- package/dist/{server/blockchain → blockchain}/types.d.ts +12 -34
- package/dist/{server/blockchain → blockchain}/types.js +1 -22
- package/dist/browser-index.d.ts +0 -3
- package/dist/browser-index.js +4 -5
- package/dist/browser.d.ts +0 -3
- package/dist/browser.js +4 -5
- package/dist/index.d.ts +8 -15
- package/dist/index.js +12 -24
- package/dist/server/{platform/server/ZipKitServer.d.ts → ZipKitServer.d.ts} +4 -35
- package/dist/server/{platform/server/ZipKitServer.js → ZipKitServer.js} +55 -182
- package/dist/shared/ZipEntry.js +1 -1
- package/dist/shared/ZipKit.d.ts +0 -6
- package/dist/shared/ZipKit.js +1 -18
- package/dist/shared/types.js +2 -2
- package/dist/shared/version.d.ts +2 -4
- package/dist/shared/version.js +4 -5
- package/package.json +13 -3
- package/src/blockchain/README.md +229 -0
- package/src/blockchain/ZipKitMinter.ts +470 -0
- package/src/blockchain/ZipKitNFT.ts +12 -0
- package/src/blockchain/ZipKitOTS.ts +622 -0
- package/src/blockchain/ZipKitVerifier.ts +382 -0
- package/src/blockchain/ZipKitWallet.ts +338 -0
- package/src/blockchain/contracts.ts +88 -0
- package/src/blockchain/index.ts +61 -0
- package/src/blockchain/types.ts +91 -0
- package/src/browser/{TokenVerifierBrowser.ts → TokenVerifierBrowser.ts-remove} +1 -1
- package/src/browser-index.ts +3 -3
- package/src/index.ts +10 -17
- package/src/server/README.md +25 -0
- package/src/server/ZipKitServer.ts +305 -0
- package/src/shared/ZipEntry.ts +1 -1
- package/src/shared/ZipKit.ts +1 -19
- package/src/shared/types.ts +3 -3
- package/src/shared/version.ts +3 -4
- package/src/types/modules.d.ts +11 -1
- package/contracts/Lock.sol +0 -34
- package/contracts/compile-contract.js +0 -168
- package/contracts/deploy/deploy-both-contracts.js +0 -286
- package/contracts/deploy/deploy-enhanced-contract.js +0 -197
- package/contracts/deploy/deploy-private-contract.js +0 -190
- package/contracts/deploy/deploy-public-clean.js +0 -1063
- package/contracts/deploy/deploy-public-contract.js +0 -1107
- package/contracts/deployment.config.example.js +0 -25
- package/contracts/remix-compile.js +0 -222
- package/contracts/setup-deployment.js +0 -51
- package/contracts/src/ZipFileNFT-DEPRECATED-6field.sol +0 -260
- package/contracts/src/ZipFileNFT-DEPRECATED-Enhanced.sol +0 -289
- package/dist/browser/CertificateGeneratorBrowser.d.ts +0 -75
- package/dist/browser/CertificateGeneratorBrowser.js +0 -433
- package/dist/browser/TokenVerifierBrowser.d.ts +0 -76
- package/dist/browser/TokenVerifierBrowser.js +0 -299
- package/dist/server/blockchain/NFTMinter.d.ts +0 -41
- package/dist/server/blockchain/NFTMinter.js +0 -323
- package/dist/server/blockchain/TokenQuerier.d.ts +0 -31
- package/dist/server/blockchain/TokenQuerier.js +0 -259
- package/dist/server/blockchain/TokenVerifier.d.ts +0 -20
- package/dist/server/blockchain/TokenVerifier.js +0 -395
- package/dist/server/blockchain/WalletFormatter.d.ts +0 -39
- package/dist/server/blockchain/WalletFormatter.js +0 -196
- package/dist/server/blockchain/WalletManager.d.ts +0 -46
- package/dist/server/blockchain/WalletManager.js +0 -294
- package/dist/server/blockchain/ZipMetadata.d.ts +0 -27
- package/dist/server/blockchain/ZipMetadata.js +0 -131
- package/dist/server/blockchain/ZipTokenProcessor.d.ts +0 -44
- package/dist/server/blockchain/ZipTokenProcessor.js +0 -204
- package/dist/server/blockchain/constants.d.ts +0 -102
- package/dist/server/blockchain/constants.js +0 -968
- package/dist/server/blockchain/index.d.ts +0 -9
- package/dist/server/blockchain/index.js +0 -44
- package/dist/server/blockchain/test-certificate.d.ts +0 -8
- package/dist/server/blockchain/test-certificate.js +0 -100
- package/dist/server/platform/servercomponents/ZipKitOTS.d.ts +0 -91
- package/dist/server/platform/servercomponents/ZipKitOTS.js +0 -271
- package/examples/unzip-token.ts +0 -488
- package/examples/zip-token.ts +0 -604
- package/src/server/blockchain/NFTMinter.ts +0 -357
- package/src/server/blockchain/TokenQuerier.ts +0 -287
- package/src/server/blockchain/TokenVerifier.ts +0 -453
- package/src/server/blockchain/WalletAnalyzer.ts +0 -397
- package/src/server/blockchain/WalletFormatter.ts +0 -206
- package/src/server/blockchain/WalletManager.ts +0 -294
- package/src/server/blockchain/ZipMetadata.ts +0 -117
- package/src/server/blockchain/ZipTokenProcessor.ts +0 -234
- package/src/server/blockchain/index.ts +0 -42
- package/src/server/blockchain/test-certificate.ts +0 -137
- package/src/server/blockchain/types.ts +0 -134
- package/src/server/platform/server/ZipKitServer.ts +0 -459
- package/src/server/platform/servercomponents/ZipKitOTS.ts +0 -313
- package/src/server/platform/servercomponents/opentimestamps.d.ts +0 -23
- /package/contracts/{bytecode.txt → NZIP-NFT-bytecode.txt} +0 -0
- /package/dist/{server/blockchain → blockchain}/CertificateToken.d.ts +0 -0
- /package/dist/{server/blockchain → blockchain}/CertificateToken.js +0 -0
- /package/src/{server/blockchain → blockchain}/CertificateToken.ts +0 -0
- /package/src/browser/{CertificateGeneratorBrowser.ts → CertificateGeneratorBrowser.ts-remove} +0 -0
|
@@ -0,0 +1,564 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ======================================
|
|
3
|
+
* ZipKitOTS.ts - OpenTimestamps Integration
|
|
4
|
+
* ======================================
|
|
5
|
+
*
|
|
6
|
+
* This module provides comprehensive OpenTimestamps (OTS) functionality for ZIP files,
|
|
7
|
+
* including timestamp creation, verification, certificate generation, and proof upgrades.
|
|
8
|
+
*
|
|
9
|
+
* Key Features:
|
|
10
|
+
* - Bitcoin blockchain timestamping via OpenTimestamps
|
|
11
|
+
* - Merkle root calculation and verification
|
|
12
|
+
* - OTS proof upgrade capabilities
|
|
13
|
+
* - Certificate generation for verified timestamps
|
|
14
|
+
* - Integration with ZIP file metadata
|
|
15
|
+
*
|
|
16
|
+
* @fileoverview OpenTimestamps utilities for blockchain-enabled ZIP files
|
|
17
|
+
* @version 0.1.2
|
|
18
|
+
* @author NeoWare Inc.
|
|
19
|
+
*/
|
|
20
|
+
"use server";
|
|
21
|
+
"use strict";
|
|
22
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
23
|
+
if (k2 === undefined) k2 = k;
|
|
24
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
25
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
26
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
27
|
+
}
|
|
28
|
+
Object.defineProperty(o, k2, desc);
|
|
29
|
+
}) : (function(o, m, k, k2) {
|
|
30
|
+
if (k2 === undefined) k2 = k;
|
|
31
|
+
o[k2] = m[k];
|
|
32
|
+
}));
|
|
33
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
34
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
35
|
+
}) : function(o, v) {
|
|
36
|
+
o["default"] = v;
|
|
37
|
+
});
|
|
38
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
39
|
+
var ownKeys = function(o) {
|
|
40
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
41
|
+
var ar = [];
|
|
42
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
43
|
+
return ar;
|
|
44
|
+
};
|
|
45
|
+
return ownKeys(o);
|
|
46
|
+
};
|
|
47
|
+
return function (mod) {
|
|
48
|
+
if (mod && mod.__esModule) return mod;
|
|
49
|
+
var result = {};
|
|
50
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
51
|
+
__setModuleDefault(result, mod);
|
|
52
|
+
return result;
|
|
53
|
+
};
|
|
54
|
+
})();
|
|
55
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
56
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
57
|
+
};
|
|
58
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
59
|
+
exports.verifyOts = verifyOts;
|
|
60
|
+
exports.bufferToArrayBuffer = bufferToArrayBuffer;
|
|
61
|
+
exports.getOtsEntry = getOtsEntry;
|
|
62
|
+
exports.getOtsBuffer = getOtsBuffer;
|
|
63
|
+
exports.getMerkleRootSafe = getMerkleRootSafe;
|
|
64
|
+
exports.verifyOtsZip = verifyOtsZip;
|
|
65
|
+
exports.createTimestamp = createTimestamp;
|
|
66
|
+
exports.deserializeOts = deserializeOts;
|
|
67
|
+
exports.parseVerifyResult = parseVerifyResult;
|
|
68
|
+
exports.createCertificateOts = createCertificateOts;
|
|
69
|
+
exports.upgradeOTS = upgradeOTS;
|
|
70
|
+
exports.createOtsMetadataEntry = createOtsMetadataEntry;
|
|
71
|
+
// ===== Imports =====
|
|
72
|
+
const moment_timezone_1 = __importDefault(require("moment-timezone"));
|
|
73
|
+
const fs = __importStar(require("fs"));
|
|
74
|
+
const Errors_1 = __importDefault(require("../shared/constants/Errors"));
|
|
75
|
+
const CertificateOTS_1 = __importDefault(require("../shared/components/CertificateOTS"));
|
|
76
|
+
const ZipKit_1 = __importDefault(require("../shared/ZipKit"));
|
|
77
|
+
const Headers_1 = require("../shared/constants/Headers");
|
|
78
|
+
const Util_1 = require("../shared/components/Util");
|
|
79
|
+
if (globalThis._bitcore)
|
|
80
|
+
delete globalThis._bitcore;
|
|
81
|
+
let OTSInstance = null;
|
|
82
|
+
/**
|
|
83
|
+
* Lazily loads the OpenTimestamps module to avoid import conflicts.
|
|
84
|
+
* @returns Promise resolving to OpenTimestamps module instance
|
|
85
|
+
*/
|
|
86
|
+
async function getOpenTimestamps() {
|
|
87
|
+
if (!OTSInstance) {
|
|
88
|
+
OTSInstance = (await Promise.resolve().then(() => __importStar(require('opentimestamps')))).default;
|
|
89
|
+
}
|
|
90
|
+
return OTSInstance;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Executes a function with OpenTimestamps logging control.
|
|
94
|
+
* @param debug - If true, keeps logging enabled; if false, suppresses console output
|
|
95
|
+
* @param fn - Function to execute with controlled logging
|
|
96
|
+
* @returns Promise resolving to function result
|
|
97
|
+
*/
|
|
98
|
+
async function withOtsLogging(debug, fn) {
|
|
99
|
+
if (debug)
|
|
100
|
+
return fn();
|
|
101
|
+
const originalLog = console.log;
|
|
102
|
+
const originalDebug = console.debug;
|
|
103
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
104
|
+
console.log = () => { };
|
|
105
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
106
|
+
console.debug = () => { };
|
|
107
|
+
try {
|
|
108
|
+
return await fn();
|
|
109
|
+
}
|
|
110
|
+
finally {
|
|
111
|
+
console.log = originalLog;
|
|
112
|
+
console.debug = originalDebug;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Verifies an OpenTimestamps proof against a hash digest.
|
|
117
|
+
* @param hashDigest - SHA256 hash as hex string to verify
|
|
118
|
+
* @param ots - OpenTimestamps proof data
|
|
119
|
+
* @param options - Configuration options
|
|
120
|
+
* @param options.debug - Enable debug logging
|
|
121
|
+
* @returns Promise resolving to verification result, error object, or null if inputs invalid
|
|
122
|
+
*/
|
|
123
|
+
async function verifyOts(hashDigest, ots, options) {
|
|
124
|
+
if (!hashDigest || !ots)
|
|
125
|
+
return null;
|
|
126
|
+
const tsResult = {
|
|
127
|
+
merkleRoot: hashDigest,
|
|
128
|
+
ots,
|
|
129
|
+
verified: false,
|
|
130
|
+
results: null,
|
|
131
|
+
error: null,
|
|
132
|
+
attestations: null,
|
|
133
|
+
upgradedOts: null,
|
|
134
|
+
};
|
|
135
|
+
try {
|
|
136
|
+
return await withOtsLogging(options?.debug, async () => {
|
|
137
|
+
const OpenTimestamps = await getOpenTimestamps();
|
|
138
|
+
const _otsBuffer = Buffer.from(ots);
|
|
139
|
+
const detachedOts = OpenTimestamps.DetachedTimestampFile.deserialize(_otsBuffer);
|
|
140
|
+
const digest = Buffer.from(hashDigest, 'hex');
|
|
141
|
+
const hashOts = OpenTimestamps.DetachedTimestampFile.fromHash(new OpenTimestamps.Ops.OpSHA256(), digest);
|
|
142
|
+
const upgrade = await OpenTimestamps.upgrade(detachedOts);
|
|
143
|
+
if (upgrade) {
|
|
144
|
+
console.log('verifyOts === Timestamp should be upgraded!');
|
|
145
|
+
tsResult.upgradedOts = detachedOts.serializeToBytes();
|
|
146
|
+
}
|
|
147
|
+
tsResult.results = await OpenTimestamps.verify(detachedOts, hashOts, {
|
|
148
|
+
ignoreBitcoinNode: true,
|
|
149
|
+
timeout: 5000,
|
|
150
|
+
});
|
|
151
|
+
const _attest = [];
|
|
152
|
+
detachedOts.timestamp.allAttestations().forEach((attestation) => {
|
|
153
|
+
if (!(attestation instanceof OpenTimestamps.Notary.UnknownAttestation)) {
|
|
154
|
+
_attest.push(attestation);
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
tsResult.attestations = JSON.stringify(_attest);
|
|
158
|
+
tsResult.verified = !!tsResult.results && Object.keys(tsResult.results).length > 0;
|
|
159
|
+
if (!tsResult.verified)
|
|
160
|
+
tsResult.results = null;
|
|
161
|
+
console.log('verifyOts === tsResult: ', tsResult);
|
|
162
|
+
return tsResult;
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
catch (error) {
|
|
166
|
+
let errMsg = error?.message || 'Timestamp Verification Failed!';
|
|
167
|
+
if (errMsg.includes('ESOCKETTIMEDOUT'))
|
|
168
|
+
errMsg = 'Server timed out, Try again in a few minutes.';
|
|
169
|
+
else if (errMsg.includes('502'))
|
|
170
|
+
errMsg = 'Server failed to respond, Try again in a few minutes.';
|
|
171
|
+
else if (errMsg.includes('ECONNRESET'))
|
|
172
|
+
errMsg = 'Server connection reset, Try again in a few minutes.';
|
|
173
|
+
return { error: errMsg };
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Converts a Node.js Buffer to an ArrayBuffer.
|
|
178
|
+
* @param buf - Buffer to convert
|
|
179
|
+
* @returns ArrayBuffer with same data
|
|
180
|
+
*/
|
|
181
|
+
function bufferToArrayBuffer(buf) {
|
|
182
|
+
const ab = new ArrayBuffer(buf.byteLength);
|
|
183
|
+
const view = new Uint8Array(ab);
|
|
184
|
+
view.set(buf);
|
|
185
|
+
return ab;
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Finds the OpenTimestamps entry in a ZIP file.
|
|
189
|
+
* Searches for TIMESTAMP_METADATA first, then TIMESTAMP_SUBMITTED.
|
|
190
|
+
* @param zip - ZipKit instance to search
|
|
191
|
+
* @returns ZIP entry containing OTS proof, or null if not found
|
|
192
|
+
*/
|
|
193
|
+
function getOtsEntry(zip) {
|
|
194
|
+
try {
|
|
195
|
+
// Include metadata entries in the directory
|
|
196
|
+
const entries = zip.getDirectory?.(true) || [];
|
|
197
|
+
// Prefer upgraded metadata entry first
|
|
198
|
+
let entry = entries.find((e) => e && e.filename === Headers_1.TIMESTAMP_METADATA);
|
|
199
|
+
if (entry)
|
|
200
|
+
return entry;
|
|
201
|
+
// Fallback to submitted proof entry
|
|
202
|
+
entry = entries.find((e) => e && e.filename === Headers_1.TIMESTAMP_SUBMITTED);
|
|
203
|
+
if (entry)
|
|
204
|
+
return entry;
|
|
205
|
+
}
|
|
206
|
+
catch {
|
|
207
|
+
return null;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Extracts OTS proof data from a ZIP entry.
|
|
212
|
+
* @param zip - ZipKit instance
|
|
213
|
+
* @param otsEntry - ZIP entry containing OTS data
|
|
214
|
+
* @returns Promise resolving to OTS proof buffer, or null if extraction fails
|
|
215
|
+
*/
|
|
216
|
+
async function getOtsBuffer(zip, otsEntry) {
|
|
217
|
+
try {
|
|
218
|
+
const data = await zip.extract(otsEntry, true);
|
|
219
|
+
return Buffer.isBuffer(data) ? data : null;
|
|
220
|
+
}
|
|
221
|
+
catch {
|
|
222
|
+
return null;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Safely extracts merkle root from a ZIP file.
|
|
227
|
+
* @param zip - ZipKit instance
|
|
228
|
+
* @returns Merkle root hash string, or null if not available
|
|
229
|
+
*/
|
|
230
|
+
function getMerkleRootSafe(zip) {
|
|
231
|
+
try {
|
|
232
|
+
const mr = zip.getMerkleRoot?.();
|
|
233
|
+
return (typeof mr === 'string' && mr.length > 0) ? mr : null;
|
|
234
|
+
}
|
|
235
|
+
catch {
|
|
236
|
+
return null;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Converts various date formats to a Date object.
|
|
241
|
+
* @param val - Date value (number timestamp, string, or Date)
|
|
242
|
+
* @returns Date object or undefined if invalid
|
|
243
|
+
*/
|
|
244
|
+
function coerceDate(val) {
|
|
245
|
+
if (!val)
|
|
246
|
+
return undefined;
|
|
247
|
+
if (typeof val === 'number')
|
|
248
|
+
return new Date(val * (val > 10000000000 ? 1 : 1000));
|
|
249
|
+
if (typeof val === 'string')
|
|
250
|
+
return new Date(val);
|
|
251
|
+
return undefined;
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Verifies OpenTimestamps proof within a ZIP file.
|
|
255
|
+
* Extracts merkle root and OTS proof, then verifies against Bitcoin blockchain.
|
|
256
|
+
* @param zip - ZipKit instance containing files and OTS proof
|
|
257
|
+
* @returns Promise resolving to verification result with status: 'none', 'error', 'pending', or 'valid'
|
|
258
|
+
*/
|
|
259
|
+
async function verifyOtsZip(zip) {
|
|
260
|
+
const mr = getMerkleRootSafe(zip);
|
|
261
|
+
const entry = getOtsEntry(zip);
|
|
262
|
+
if (!entry)
|
|
263
|
+
return { status: 'none' };
|
|
264
|
+
const otsBuf = await getOtsBuffer(zip, entry);
|
|
265
|
+
if (!otsBuf || !mr)
|
|
266
|
+
return { status: 'error' };
|
|
267
|
+
try {
|
|
268
|
+
const result = await verifyOts(mr, bufferToArrayBuffer(otsBuf));
|
|
269
|
+
if (result?.verified === true) {
|
|
270
|
+
const bitcoin = result?.results?.bitcoin || {};
|
|
271
|
+
const blockHeight = result.blockHeight ?? result.block ?? result.height ?? bitcoin.height;
|
|
272
|
+
const attestedAt = coerceDate(result.attestedAt ?? result.attested ?? result.time ?? bitcoin.timestamp);
|
|
273
|
+
const upgraded = Boolean(result.upgraded || result.upgradedOts);
|
|
274
|
+
const upgradedOts = result.upgradedOts ? Buffer.from(result.upgradedOts) : undefined;
|
|
275
|
+
return { status: 'valid', blockHeight, attestedAt, upgraded, upgradedOts };
|
|
276
|
+
}
|
|
277
|
+
return { status: 'pending' };
|
|
278
|
+
}
|
|
279
|
+
catch (e) {
|
|
280
|
+
return { status: 'pending', message: e?.message };
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* Creates an OpenTimestamps proof for a hash digest.
|
|
285
|
+
* Submits hash to OpenTimestamps servers for Bitcoin blockchain anchoring.
|
|
286
|
+
* @param hashDigest - SHA256 hash as hex string to timestamp
|
|
287
|
+
* @param options - Configuration options
|
|
288
|
+
* @param options.debug - Enable debug logging
|
|
289
|
+
* @returns Promise resolving to OTS proof buffer, or null if creation failed
|
|
290
|
+
*/
|
|
291
|
+
async function createTimestamp(hashDigest, options) {
|
|
292
|
+
if (!hashDigest)
|
|
293
|
+
return null;
|
|
294
|
+
try {
|
|
295
|
+
return await withOtsLogging(options?.debug, async () => {
|
|
296
|
+
const OpenTimestamps = await getOpenTimestamps();
|
|
297
|
+
const hashBuffer = Buffer.from(hashDigest, 'hex');
|
|
298
|
+
const detached = OpenTimestamps.DetachedTimestampFile.fromHash(new OpenTimestamps.Ops.OpSHA256(), hashBuffer);
|
|
299
|
+
await OpenTimestamps.stamp(detached);
|
|
300
|
+
return Buffer.from(detached.serializeToBytes());
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
catch (error) {
|
|
304
|
+
console.error('Error creating timestamp:', error);
|
|
305
|
+
throw new Error('Error creating timestamp');
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* Deserializes OpenTimestamps verification result into JSON format.
|
|
310
|
+
* Extracts blockchain attestation data and transaction details.
|
|
311
|
+
* @param tsResult - Timestamp verification result to deserialize
|
|
312
|
+
* @param options - Configuration options
|
|
313
|
+
* @param options.debug - Enable debug logging
|
|
314
|
+
* @returns Promise resolving to JSON string with attestation data, or null if failed
|
|
315
|
+
*/
|
|
316
|
+
async function deserializeOts(tsResult, options) {
|
|
317
|
+
let result = {
|
|
318
|
+
attestStr: null,
|
|
319
|
+
needsUpdateMsg: null,
|
|
320
|
+
attestationValues: [],
|
|
321
|
+
};
|
|
322
|
+
for (const chain in tsResult.results) {
|
|
323
|
+
const _results = tsResult.results[chain];
|
|
324
|
+
const _attestDate = (0, moment_timezone_1.default)(_results.timestamp * 1000).tz(moment_timezone_1.default.tz.guess()).format('MM-DD-YYYY hh:mma z');
|
|
325
|
+
const blockchain = chain.charAt(0).toUpperCase() + chain.slice(1);
|
|
326
|
+
result.attestStr = `<b>${blockchain} block ${_results.height}</b> attests existence as of <b>${_attestDate}</b>`;
|
|
327
|
+
}
|
|
328
|
+
let timestamp = tsResult.ots;
|
|
329
|
+
if (tsResult.upgradedOts != null) {
|
|
330
|
+
result.needsUpdateMsg = 'This Zip file requires updated Timestamp Metadata!';
|
|
331
|
+
timestamp = tsResult.upgradedOts;
|
|
332
|
+
}
|
|
333
|
+
if (!timestamp)
|
|
334
|
+
return null;
|
|
335
|
+
const infoResult = await withOtsLogging(options?.debug, async () => {
|
|
336
|
+
const OpenTimestamps = await getOpenTimestamps();
|
|
337
|
+
const detached = OpenTimestamps.DetachedTimestampFile.deserialize(Buffer.from(timestamp));
|
|
338
|
+
return OpenTimestamps.info(detached);
|
|
339
|
+
});
|
|
340
|
+
const transactionIdMatches = [...infoResult.matchAll(/transaction id ([a-f0-9]+)/gi)];
|
|
341
|
+
const transactionIds = transactionIdMatches.map((m) => m[1]);
|
|
342
|
+
const blockHeightMatches = [...infoResult.matchAll(/verify BitcoinBlockHeaderAttestation\((\d+)\)/g)];
|
|
343
|
+
const blockHeights = blockHeightMatches.map((m) => m[1]);
|
|
344
|
+
const merkleRootMatches = [...infoResult.matchAll(/block merkle root ([a-f0-9]+)/gi)];
|
|
345
|
+
const merkleRoots = merkleRootMatches.map((m) => m[1]);
|
|
346
|
+
result.attestationValues = [];
|
|
347
|
+
if (transactionIds.length === blockHeights.length && transactionIds.length === merkleRoots.length) {
|
|
348
|
+
for (let i = 0; i < transactionIds.length; i++) {
|
|
349
|
+
result.attestationValues.push({
|
|
350
|
+
blockHeight: blockHeights[i],
|
|
351
|
+
transactionId: transactionIds[i],
|
|
352
|
+
merkleRoot: merkleRoots[i],
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
result.attestationValues.sort((a, b) => a.blockHeight - b.blockHeight);
|
|
356
|
+
}
|
|
357
|
+
return JSON.stringify(result);
|
|
358
|
+
}
|
|
359
|
+
/**
|
|
360
|
+
* Parses timestamp verification result into readable information.
|
|
361
|
+
* Formats blockchain attestation data with human-readable messages and dates.
|
|
362
|
+
* @param verifyResult - Timestamp verification result to parse
|
|
363
|
+
* @returns Promise resolving to formatted timestamp information
|
|
364
|
+
*/
|
|
365
|
+
async function parseVerifyResult(verifyResult) {
|
|
366
|
+
let tsInfo = {
|
|
367
|
+
message: '',
|
|
368
|
+
results: [],
|
|
369
|
+
attestDate: null,
|
|
370
|
+
attestHeight: null,
|
|
371
|
+
submittedUri: null,
|
|
372
|
+
otsUpgraded: false,
|
|
373
|
+
};
|
|
374
|
+
for (const chain in verifyResult?.results) {
|
|
375
|
+
const _results = verifyResult.results[chain];
|
|
376
|
+
const _date = (0, moment_timezone_1.default)(_results.timestamp * 1000).tz('UTC').format('MM-DD-YYYY hh:mma z');
|
|
377
|
+
const _blockchain = chain.charAt(0).toUpperCase() + chain.slice(1);
|
|
378
|
+
tsInfo.message = `Validated - ${_blockchain} block ${_results.height} attests existence as of ${_date}`;
|
|
379
|
+
if (tsInfo.attestHeight == null) {
|
|
380
|
+
tsInfo.attestDate = new Date(_results.timestamp * 1000);
|
|
381
|
+
tsInfo.attestHeight = _results.height;
|
|
382
|
+
}
|
|
383
|
+
tsInfo.results.push(tsInfo.message);
|
|
384
|
+
}
|
|
385
|
+
if (verifyResult?.upgradedOts) {
|
|
386
|
+
tsInfo.otsUpgraded = true;
|
|
387
|
+
tsInfo.results.push('Notice: Please select "Upgrade Timestamp" to save the upgraded timestamp.');
|
|
388
|
+
}
|
|
389
|
+
return tsInfo;
|
|
390
|
+
}
|
|
391
|
+
// =========================
|
|
392
|
+
// Server-side certificate generation (HTML)
|
|
393
|
+
// =========================
|
|
394
|
+
/**
|
|
395
|
+
* Creates an HTML certificate for OpenTimestamps verification.
|
|
396
|
+
* Generates a certificate file documenting the timestamp verification results.
|
|
397
|
+
* @param fileName - Base name for the ZIP file (used for certificate filename)
|
|
398
|
+
* @param zipEntries - Array of ZIP entries to include in certificate
|
|
399
|
+
* @param timestampResult - Verification result to document
|
|
400
|
+
* @returns Promise resolving to success message with certificate filename, or error message
|
|
401
|
+
*/
|
|
402
|
+
async function createCertificateOts(fileName, zipEntries, timestampResult) {
|
|
403
|
+
if (!timestampResult || !timestampResult.merkleRoot || !fileName) {
|
|
404
|
+
return Errors_1.default.CERTIFICATE_OTS_BAD;
|
|
405
|
+
}
|
|
406
|
+
const rsp = await deserializeOts(timestampResult);
|
|
407
|
+
if (!rsp)
|
|
408
|
+
return Errors_1.default.CERTIFICATE_OTS_FAILED;
|
|
409
|
+
const result = JSON.parse(rsp);
|
|
410
|
+
const cert = (0, CertificateOTS_1.default)(result.attestStr, fileName, timestampResult.merkleRoot, result.needsUpdateMsg, result.attestationValues, zipEntries, false);
|
|
411
|
+
if (!cert)
|
|
412
|
+
return Errors_1.default.CERTIFICATE_OTS_FAILED;
|
|
413
|
+
const certFileName = fileName + '.certificate.html';
|
|
414
|
+
try {
|
|
415
|
+
fs.writeFileSync(certFileName, cert, { encoding: 'utf8' });
|
|
416
|
+
return `Certificate created as ${certFileName}`;
|
|
417
|
+
}
|
|
418
|
+
catch (error) {
|
|
419
|
+
console.error(`Failed to write certificate file: ${error}`);
|
|
420
|
+
return Errors_1.default.CERTIFICATE_OTS_FAILED;
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
/**
|
|
424
|
+
* Upgrades OpenTimestamps proof in an existing ZIP file.
|
|
425
|
+
* Replaces old timestamp metadata with upgraded OTS proof data.
|
|
426
|
+
* @param zipFilePath - Path to ZIP file to upgrade
|
|
427
|
+
* @param upgradedOts - Buffer containing upgraded OTS proof
|
|
428
|
+
* @returns Promise that resolves when upgrade is complete
|
|
429
|
+
* @throws Error if upgrade fails
|
|
430
|
+
*/
|
|
431
|
+
async function upgradeOTS(zipFilePath, upgradedOts) {
|
|
432
|
+
if (!fs.existsSync(zipFilePath)) {
|
|
433
|
+
throw new Error(`Zip file not found: ${zipFilePath}`);
|
|
434
|
+
}
|
|
435
|
+
if (!upgradedOts || upgradedOts.length === 0) {
|
|
436
|
+
throw new Error('Invalid upgraded OTS data provided');
|
|
437
|
+
}
|
|
438
|
+
// Create a temporary file for the upgrade process
|
|
439
|
+
const tempPath = `${zipFilePath}.upgrading.${Date.now()}.${process.pid}`;
|
|
440
|
+
try {
|
|
441
|
+
// Load the existing zip file
|
|
442
|
+
const zipKit = new ZipKit_1.default();
|
|
443
|
+
const zipData = fs.readFileSync(zipFilePath);
|
|
444
|
+
zipKit.loadZip(zipData);
|
|
445
|
+
const zipEntries = zipKit.getDirectory(false) || [];
|
|
446
|
+
if (!zipEntries || zipEntries.length === 0) {
|
|
447
|
+
throw new Error('No entries found in zip file or file is corrupted');
|
|
448
|
+
}
|
|
449
|
+
// Prepare buffers for the new zip file
|
|
450
|
+
const buffers = [];
|
|
451
|
+
let offset = 0;
|
|
452
|
+
const keptEntries = [];
|
|
453
|
+
// Process existing entries, excluding old timestamp entries
|
|
454
|
+
for (const entry of zipEntries) {
|
|
455
|
+
if (entry.filename === Headers_1.TIMESTAMP_METADATA ||
|
|
456
|
+
entry.filename === Headers_1.TIMESTAMP_SUBMITTED) {
|
|
457
|
+
// Skip old timestamp entries - they will be replaced
|
|
458
|
+
continue;
|
|
459
|
+
}
|
|
460
|
+
// Copy the entry data
|
|
461
|
+
const entryData = zipKit.copyEntry(entry);
|
|
462
|
+
if (!entryData) {
|
|
463
|
+
throw new Error(`Failed to copy entry data for: ${entry.filename}`);
|
|
464
|
+
}
|
|
465
|
+
// Update the entry's local header offset
|
|
466
|
+
entry.localHdrOffset = offset;
|
|
467
|
+
offset += entryData.length;
|
|
468
|
+
buffers.push(entryData);
|
|
469
|
+
keptEntries.push(entry);
|
|
470
|
+
}
|
|
471
|
+
// Create the new upgraded timestamp entry with standard filename
|
|
472
|
+
const tsEntry = zipKit.createZipEntry(Headers_1.TIMESTAMP_METADATA);
|
|
473
|
+
if (!tsEntry) {
|
|
474
|
+
throw new Error('Failed to create timestamp metadata entry');
|
|
475
|
+
}
|
|
476
|
+
// Configure the timestamp entry
|
|
477
|
+
tsEntry.timeDateDOS = tsEntry.setDateTime(new Date());
|
|
478
|
+
tsEntry.compressedSize = upgradedOts.length;
|
|
479
|
+
tsEntry.uncompressedSize = upgradedOts.length;
|
|
480
|
+
tsEntry.cmpMethod = Headers_1.CMP_METHOD.STORED; // Store without compression
|
|
481
|
+
tsEntry.crc = (0, Util_1.crc32)(upgradedOts);
|
|
482
|
+
tsEntry.fileData = upgradedOts; // Cast to any to avoid type issues
|
|
483
|
+
// Create and add the local header for the timestamp entry
|
|
484
|
+
const localHdr = tsEntry.createLocalHdr();
|
|
485
|
+
if (!localHdr) {
|
|
486
|
+
throw new Error('Failed to create local header for timestamp entry');
|
|
487
|
+
}
|
|
488
|
+
tsEntry.localHdrOffset = offset;
|
|
489
|
+
offset += localHdr.length + tsEntry.compressedSize;
|
|
490
|
+
buffers.push(localHdr);
|
|
491
|
+
buffers.push(upgradedOts);
|
|
492
|
+
// Add the timestamp entry to our list of entries
|
|
493
|
+
keptEntries.push(tsEntry);
|
|
494
|
+
// Create central directory
|
|
495
|
+
const centralDirOffset = offset;
|
|
496
|
+
let centralDirSize = 0;
|
|
497
|
+
for (const entry of keptEntries) {
|
|
498
|
+
const centralDirEntry = entry.centralDirEntry();
|
|
499
|
+
if (!centralDirEntry) {
|
|
500
|
+
throw new Error(`Failed to create central directory entry for: ${entry.filename}`);
|
|
501
|
+
}
|
|
502
|
+
centralDirSize += centralDirEntry.length;
|
|
503
|
+
buffers.push(centralDirEntry);
|
|
504
|
+
}
|
|
505
|
+
// Create end of central directory record
|
|
506
|
+
const endOfCentralDir = Buffer.alloc(22);
|
|
507
|
+
endOfCentralDir.writeUInt32LE(0x06054b50, 0); // End of central dir signature
|
|
508
|
+
endOfCentralDir.writeUInt16LE(0, 4); // Number of this disk
|
|
509
|
+
endOfCentralDir.writeUInt16LE(0, 6); // Disk where central directory starts
|
|
510
|
+
endOfCentralDir.writeUInt16LE(keptEntries.length, 8); // Number of central directory records on this disk
|
|
511
|
+
endOfCentralDir.writeUInt16LE(keptEntries.length, 10); // Total number of central directory records
|
|
512
|
+
endOfCentralDir.writeUInt32LE(centralDirSize, 12); // Size of central directory
|
|
513
|
+
endOfCentralDir.writeUInt32LE(centralDirOffset, 16); // Offset of start of central directory
|
|
514
|
+
endOfCentralDir.writeUInt16LE(0, 20); // ZIP file comment length
|
|
515
|
+
buffers.push(endOfCentralDir);
|
|
516
|
+
// Write the complete new zip file to temporary location
|
|
517
|
+
const finalBuffer = Buffer.concat(buffers);
|
|
518
|
+
fs.writeFileSync(tempPath, finalBuffer);
|
|
519
|
+
// Validate the temporary file was created successfully
|
|
520
|
+
if (!fs.existsSync(tempPath)) {
|
|
521
|
+
throw new Error('Failed to create temporary upgraded file');
|
|
522
|
+
}
|
|
523
|
+
const tempStats = fs.statSync(tempPath);
|
|
524
|
+
if (tempStats.size === 0) {
|
|
525
|
+
throw new Error('Temporary upgraded file is empty');
|
|
526
|
+
}
|
|
527
|
+
// Replace the original file with the upgraded version
|
|
528
|
+
fs.unlinkSync(zipFilePath);
|
|
529
|
+
fs.renameSync(tempPath, zipFilePath);
|
|
530
|
+
}
|
|
531
|
+
catch (error) {
|
|
532
|
+
// Clean up temporary file if it exists
|
|
533
|
+
if (fs.existsSync(tempPath)) {
|
|
534
|
+
try {
|
|
535
|
+
fs.unlinkSync(tempPath);
|
|
536
|
+
}
|
|
537
|
+
catch (cleanupError) {
|
|
538
|
+
// Log cleanup error but don't throw - main error is more important
|
|
539
|
+
console.warn(`Warning: Could not clean up temporary file ${tempPath}: ${cleanupError}`);
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
// Re-throw the original error
|
|
543
|
+
throw error;
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
/**
|
|
547
|
+
* Creates a ZIP metadata entry containing OpenTimestamps proof.
|
|
548
|
+
* Creates a special ZIP entry marked as metadata that stores OTS proof data.
|
|
549
|
+
* @param zipKit - ZipKit instance to add metadata entry to
|
|
550
|
+
* @param ots - Buffer containing OpenTimestamps proof data
|
|
551
|
+
* @returns ZIP entry containing OTS metadata, or null if creation failed
|
|
552
|
+
*/
|
|
553
|
+
function createOtsMetadataEntry(zipKit, ots) {
|
|
554
|
+
if (!ots || !zipKit)
|
|
555
|
+
return null;
|
|
556
|
+
const tsEntry = zipKit.createZipEntry(Headers_1.TIMESTAMP_SUBMITTED);
|
|
557
|
+
tsEntry.crc = (0, Util_1.crc32)(ots);
|
|
558
|
+
tsEntry.uncompressedSize = ots.length;
|
|
559
|
+
tsEntry.compressedSize = ots.length;
|
|
560
|
+
tsEntry.cmpMethod = Headers_1.CMP_METHOD.STORED;
|
|
561
|
+
tsEntry.fileBuffer = ots;
|
|
562
|
+
tsEntry.isMetaData = true;
|
|
563
|
+
return tsEntry;
|
|
564
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* ZipKitVerifier - Handles NFT token verification for ZIP files
|
|
4
|
+
* Verifier API for NZIP-NFT smart contract.
|
|
5
|
+
*/
|
|
6
|
+
import type { TokenMetadata } from './types';
|
|
7
|
+
export interface VerificationOptions {
|
|
8
|
+
debug?: boolean;
|
|
9
|
+
skipHash?: boolean;
|
|
10
|
+
}
|
|
11
|
+
export interface VerificationResult {
|
|
12
|
+
success: boolean;
|
|
13
|
+
message: string;
|
|
14
|
+
tokenMetadata?: TokenMetadata;
|
|
15
|
+
verificationDetails?: {
|
|
16
|
+
tokenId: string;
|
|
17
|
+
network: string;
|
|
18
|
+
contractAddress: string;
|
|
19
|
+
merkleRoot: string;
|
|
20
|
+
mintDate: string;
|
|
21
|
+
calculatedMerkleRoot?: string;
|
|
22
|
+
declaredMerkleRoot?: string;
|
|
23
|
+
merkleRootMatch?: boolean;
|
|
24
|
+
onChainValid?: boolean;
|
|
25
|
+
};
|
|
26
|
+
errorDetails?: {
|
|
27
|
+
errorType: 'METADATA_ERROR' | 'MERKLE_ERROR' | 'NETWORK_ERROR' | 'CONTRACT_ERROR';
|
|
28
|
+
networkName?: string;
|
|
29
|
+
rpcUrl?: string;
|
|
30
|
+
contractAddress?: string;
|
|
31
|
+
tokenId?: string;
|
|
32
|
+
merkleRoot?: string;
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Blockchain Token Verifier for ZIP files
|
|
37
|
+
*/
|
|
38
|
+
export declare class ZipKitVerifier {
|
|
39
|
+
private debug;
|
|
40
|
+
constructor(options?: VerificationOptions);
|
|
41
|
+
/**
|
|
42
|
+
* Extract and validate token metadata from ZIP
|
|
43
|
+
*/
|
|
44
|
+
extractTokenMetadata(tokenBuffer: Buffer): Promise<{
|
|
45
|
+
success: boolean;
|
|
46
|
+
metadata?: TokenMetadata;
|
|
47
|
+
error?: string;
|
|
48
|
+
}>;
|
|
49
|
+
/**
|
|
50
|
+
* Map network name to RPC configuration
|
|
51
|
+
*/
|
|
52
|
+
private getNetworkConfig;
|
|
53
|
+
/**
|
|
54
|
+
* Perform merkle root comparison
|
|
55
|
+
*/
|
|
56
|
+
private compareMerkleRoots;
|
|
57
|
+
/**
|
|
58
|
+
* Verify token against blockchain
|
|
59
|
+
*/
|
|
60
|
+
verifyOnChain(tokenId: string, contractAddress: string, networkConfig: any, merkleRoot: string): Promise<{
|
|
61
|
+
success: boolean;
|
|
62
|
+
isValid?: boolean;
|
|
63
|
+
error?: string;
|
|
64
|
+
}>;
|
|
65
|
+
/**
|
|
66
|
+
* Complete verification process
|
|
67
|
+
*/
|
|
68
|
+
verifyToken(tokenMetadata: TokenMetadata, calculatedMerkleRoot: string | null, options?: VerificationOptions): Promise<VerificationResult>;
|
|
69
|
+
}
|