neozip-cli 0.75.2-beta → 0.90.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/CHANGELOG.md +78 -0
- package/DOCUMENTATION.md +20 -9
- package/README.md +55 -31
- package/dist/src/commands/mintTimestampProof.js +335 -0
- package/dist/src/commands/verifyEmail.js +146 -0
- package/dist/src/config/ConfigSetup.js +50 -20
- package/dist/src/config/ConfigStore.js +36 -3
- package/dist/src/index.js +1 -1
- package/dist/src/neolist.js +25 -11
- package/dist/src/neounzip.js +324 -66
- package/dist/src/neozip/blockchain.js +5 -5
- package/dist/src/neozip/createZip.js +211 -44
- package/dist/src/neozip/upgradeZip.js +182 -0
- package/dist/src/neozip.js +160 -24
- package/env.example +10 -0
- package/package.json +97 -82
- package/dist/neozipkit-bundles/blockchain.js +0 -13725
- package/dist/neozipkit-bundles/browser.js +0 -6186
- package/dist/neozipkit-bundles/core.js +0 -3839
- package/dist/neozipkit-bundles/node.js +0 -17730
- package/dist/neozipkit-wrappers/blockchain/core/contracts.js +0 -16
- package/dist/neozipkit-wrappers/blockchain/index.js +0 -2
- package/dist/neozipkit-wrappers/core/ZipDecompress.js +0 -2
- package/dist/neozipkit-wrappers/core/components/HashCalculator.js +0 -2
- package/dist/neozipkit-wrappers/core/components/Logger.js +0 -2
- package/dist/neozipkit-wrappers/core/constants/Errors.js +0 -2
- package/dist/neozipkit-wrappers/core/constants/Headers.js +0 -2
- package/dist/neozipkit-wrappers/core/encryption/ZipCrypto.js +0 -7
- package/dist/neozipkit-wrappers/core/index.js +0 -3
- package/dist/neozipkit-wrappers/index.js +0 -13
- package/dist/neozipkit-wrappers/node/index.js +0 -2
package/dist/src/neounzip.js
CHANGED
|
@@ -48,8 +48,10 @@ exports.showExtendedHelp = showExtendedHelp;
|
|
|
48
48
|
const fs = __importStar(require("fs"));
|
|
49
49
|
const path = __importStar(require("path"));
|
|
50
50
|
const neozipkit_1 = require("neozipkit");
|
|
51
|
-
const blockchain_1 = require("neozipkit/blockchain");
|
|
52
51
|
const node_1 = __importDefault(require("neozipkit/node"));
|
|
52
|
+
const neozip_blockchain_1 = require("neozip-blockchain");
|
|
53
|
+
const ots_1 = require("neozip-blockchain/ots");
|
|
54
|
+
const zipstamp_server_1 = require("neozip-blockchain/zipstamp-server");
|
|
53
55
|
const readline = __importStar(require("readline"));
|
|
54
56
|
const version_1 = require("./version");
|
|
55
57
|
const exit_codes_1 = require("./exit-codes");
|
|
@@ -283,6 +285,34 @@ async function promptForTimeoutAction(networkConfig, currentRpcIndex, nonInterac
|
|
|
283
285
|
});
|
|
284
286
|
});
|
|
285
287
|
}
|
|
288
|
+
/**
|
|
289
|
+
* Perform Zipstamp upgrade on the archive (TS-SUBMIT.NZIP -> TIMESTAMP.NZIP)
|
|
290
|
+
*/
|
|
291
|
+
async function performZipstampUpgrade(archivePath) {
|
|
292
|
+
try {
|
|
293
|
+
// Dynamic import: use path without .js for ts-node; tsc emits .js for dist
|
|
294
|
+
// @ts-expect-error TS2835 - node16 requires .js but ts-node resolves .ts from source
|
|
295
|
+
const { upgradeZipForTimestamp } = await import('./neozip/upgradeZip');
|
|
296
|
+
await upgradeZipForTimestamp(archivePath, undefined, { verbose: false, debug: false });
|
|
297
|
+
console.log(' - Upgraded archive saved. Re-run neounzip to extract.');
|
|
298
|
+
}
|
|
299
|
+
catch (error) {
|
|
300
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
301
|
+
console.log(` - Upgrade failed: ${msg}`);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* Prompt user for Zipstamp upgrade confirmation
|
|
306
|
+
*/
|
|
307
|
+
function promptForZipstampUpgrade(archiveName) {
|
|
308
|
+
return new Promise((resolve) => {
|
|
309
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
310
|
+
rl.question(` - Batch confirmed. Upgrade archive to TIMESTAMP.NZIP? (y/N): `, (answer) => {
|
|
311
|
+
rl.close();
|
|
312
|
+
resolve(answer.trim().toLowerCase() === 'y' || answer.trim().toLowerCase() === 'yes');
|
|
313
|
+
});
|
|
314
|
+
});
|
|
315
|
+
}
|
|
286
316
|
/**
|
|
287
317
|
* Perform OTS upgrade on the archive
|
|
288
318
|
*/
|
|
@@ -303,7 +333,7 @@ async function performOtsUpgrade(archivePath, upgradedOts) {
|
|
|
303
333
|
fs.copyFileSync(archivePath, upgradedPath);
|
|
304
334
|
console.log(` - Copied original file to upgrade location`);
|
|
305
335
|
// Use the upgradeOTS function
|
|
306
|
-
await (0,
|
|
336
|
+
await (0, ots_1.upgradeOTS)(upgradedPath, upgradedOts);
|
|
307
337
|
console.log(' - Upgrade: Applied successfully');
|
|
308
338
|
console.log(` - Note: Upgraded timestamp saved to ${upgradedPath}`);
|
|
309
339
|
console.log(` - Original file ${archivePath} remains unchanged`);
|
|
@@ -549,9 +579,11 @@ async function testArchive(zip, options) {
|
|
|
549
579
|
// Filter out metadata files for merkle root calculation
|
|
550
580
|
const contentEntriesForMerkle = entries.filter(entry => {
|
|
551
581
|
const filename = entry.filename || '';
|
|
552
|
-
return filename !==
|
|
553
|
-
filename !==
|
|
554
|
-
filename !==
|
|
582
|
+
return filename !== neozip_blockchain_1.TOKENIZED_METADATA &&
|
|
583
|
+
filename !== ots_1.TIMESTAMP_METADATA &&
|
|
584
|
+
filename !== ots_1.TIMESTAMP_SUBMITTED &&
|
|
585
|
+
filename !== zipstamp_server_1.SUBMIT_METADATA &&
|
|
586
|
+
filename !== zipstamp_server_1.TIMESTAMP_METADATA &&
|
|
555
587
|
!entry.isDirectory;
|
|
556
588
|
});
|
|
557
589
|
for (const entry of entries) {
|
|
@@ -576,9 +608,11 @@ async function testArchive(zip, options) {
|
|
|
576
608
|
});
|
|
577
609
|
// Collect verified SHA-256 hash for merkle root calculation if available
|
|
578
610
|
if (testResult.verifiedHash && !options.skipBlockchain) {
|
|
579
|
-
const isContentFile = name !==
|
|
580
|
-
name !==
|
|
581
|
-
name !==
|
|
611
|
+
const isContentFile = name !== neozip_blockchain_1.TOKENIZED_METADATA &&
|
|
612
|
+
name !== ots_1.TIMESTAMP_METADATA &&
|
|
613
|
+
name !== ots_1.TIMESTAMP_SUBMITTED &&
|
|
614
|
+
name !== zipstamp_server_1.SUBMIT_METADATA &&
|
|
615
|
+
name !== zipstamp_server_1.TIMESTAMP_METADATA &&
|
|
582
616
|
!entry.isDirectory;
|
|
583
617
|
if (isContentFile) {
|
|
584
618
|
// Use the verified hash returned from testEntry()
|
|
@@ -591,14 +625,17 @@ async function testArchive(zip, options) {
|
|
|
591
625
|
throw new Error('ZIP file not loaded. Cannot determine backend type.');
|
|
592
626
|
}
|
|
593
627
|
// Report success
|
|
628
|
+
const encTag = entry.isEncrypted
|
|
629
|
+
? (entry.aesVersion > 0 ? ' [AES-256]' : ' [PKZIP]')
|
|
630
|
+
: '';
|
|
594
631
|
if (entry.sha256 && !options.skipBlockchain) {
|
|
595
|
-
console.log(`testing: ${name} ...OK SHA-256`);
|
|
632
|
+
console.log(`testing: ${name} ...OK SHA-256${encTag}`);
|
|
596
633
|
}
|
|
597
634
|
else if (entry.sha256 && options.skipBlockchain) {
|
|
598
|
-
console.log(`testing: ${name} ...OK (SHA-256 skipped)`);
|
|
635
|
+
console.log(`testing: ${name} ...OK (SHA-256 skipped)${encTag}`);
|
|
599
636
|
}
|
|
600
637
|
else {
|
|
601
|
-
console.log(`testing: ${name} ...OK`);
|
|
638
|
+
console.log(`testing: ${name} ...OK${encTag}`);
|
|
602
639
|
}
|
|
603
640
|
}
|
|
604
641
|
catch (err) {
|
|
@@ -608,9 +645,11 @@ async function testArchive(zip, options) {
|
|
|
608
645
|
// Check bitFlags for encryption flag (bit 0)
|
|
609
646
|
const isEncrypted = entry.isEncrypted || (entry.bitFlags & 0x01) !== 0;
|
|
610
647
|
// Track failed files for merkle root calculation
|
|
611
|
-
const isContentFile = name !==
|
|
612
|
-
name !==
|
|
613
|
-
name !==
|
|
648
|
+
const isContentFile = name !== neozip_blockchain_1.TOKENIZED_METADATA &&
|
|
649
|
+
name !== ots_1.TIMESTAMP_METADATA &&
|
|
650
|
+
name !== ots_1.TIMESTAMP_SUBMITTED &&
|
|
651
|
+
name !== zipstamp_server_1.SUBMIT_METADATA &&
|
|
652
|
+
name !== zipstamp_server_1.TIMESTAMP_METADATA &&
|
|
614
653
|
!entry.isDirectory;
|
|
615
654
|
if (isEncrypted) {
|
|
616
655
|
// For encrypted files, decompression errors after decryption usually mean wrong password
|
|
@@ -730,7 +769,7 @@ async function preVerifyArchive(zip, archiveName, options) {
|
|
|
730
769
|
let verificationSuccess = false;
|
|
731
770
|
let verificationError;
|
|
732
771
|
try {
|
|
733
|
-
const verifier = new
|
|
772
|
+
const verifier = new neozip_blockchain_1.ZipkitVerifier({
|
|
734
773
|
debug: options.verbose,
|
|
735
774
|
skipHash: options.skipBlockchain
|
|
736
775
|
});
|
|
@@ -791,21 +830,21 @@ async function preVerifyArchive(zip, archiveName, options) {
|
|
|
791
830
|
const chainId = tokenMetadata.networkChainId;
|
|
792
831
|
let networkConfig = null;
|
|
793
832
|
if (chainId) {
|
|
794
|
-
networkConfig = (0,
|
|
833
|
+
networkConfig = (0, neozip_blockchain_1.getContractConfig)(chainId);
|
|
795
834
|
}
|
|
796
835
|
else {
|
|
797
836
|
const networkName = tokenMetadata.network.toLowerCase();
|
|
798
837
|
if (networkName.includes('base sepolia') || networkName === 'base-sepolia') {
|
|
799
|
-
networkConfig = (0,
|
|
838
|
+
networkConfig = (0, neozip_blockchain_1.getContractConfig)(84532);
|
|
800
839
|
}
|
|
801
840
|
else if (networkName.includes('base mainnet') || networkName === 'base-mainnet' || networkName === 'base') {
|
|
802
|
-
networkConfig = (0,
|
|
841
|
+
networkConfig = (0, neozip_blockchain_1.getContractConfig)(8453);
|
|
803
842
|
}
|
|
804
843
|
else if (networkName.includes('arbitrum sepolia') || networkName === 'arbitrum-sepolia' || (networkName.includes('arbitrum') && networkName.includes('sepolia'))) {
|
|
805
|
-
networkConfig = (0,
|
|
844
|
+
networkConfig = (0, neozip_blockchain_1.getContractConfig)(421614);
|
|
806
845
|
}
|
|
807
846
|
else if (networkName.includes('sepolia testnet') || networkName === 'sepolia-testnet' || (networkName.includes('sepolia') && !networkName.includes('base') && !networkName.includes('arbitrum'))) {
|
|
808
|
-
networkConfig = (0,
|
|
847
|
+
networkConfig = (0, neozip_blockchain_1.getContractConfig)(11155111);
|
|
809
848
|
}
|
|
810
849
|
}
|
|
811
850
|
// Perform verification (non-interactive mode for pre-verify)
|
|
@@ -926,9 +965,11 @@ async function extractArchive(zip, destination, options) {
|
|
|
926
965
|
// Filter out metadata files for merkle root calculation
|
|
927
966
|
const contentEntriesForMerkle = filteredEntries.filter(entry => {
|
|
928
967
|
const filename = entry.filename || '';
|
|
929
|
-
return filename !==
|
|
930
|
-
filename !==
|
|
931
|
-
filename !==
|
|
968
|
+
return filename !== neozip_blockchain_1.TOKENIZED_METADATA &&
|
|
969
|
+
filename !== ots_1.TIMESTAMP_METADATA &&
|
|
970
|
+
filename !== ots_1.TIMESTAMP_SUBMITTED &&
|
|
971
|
+
filename !== zipstamp_server_1.SUBMIT_METADATA &&
|
|
972
|
+
filename !== zipstamp_server_1.TIMESTAMP_METADATA &&
|
|
932
973
|
!entry.isDirectory;
|
|
933
974
|
});
|
|
934
975
|
for (const entry of filteredEntries) {
|
|
@@ -998,16 +1039,21 @@ async function extractArchive(zip, destination, options) {
|
|
|
998
1039
|
totalBytes += result.bytesExtracted;
|
|
999
1040
|
const hasSha = entry.sha256 ? true : false;
|
|
1000
1041
|
const suffix = hasSha ? (options.skipBlockchain ? ' (SHA-256 skipped)' : ' (SHA-256 OK)') : '';
|
|
1042
|
+
const encTag = entry.isEncrypted
|
|
1043
|
+
? (entry.aesVersion > 0 ? ' [AES-256]' : ' [PKZIP]')
|
|
1044
|
+
: '';
|
|
1001
1045
|
if (options.verbose)
|
|
1002
|
-
log(`extracting: ${filename} (${result.bytesExtracted} bytes)${suffix}`, options);
|
|
1046
|
+
log(`extracting: ${filename} (${result.bytesExtracted} bytes)${suffix}${encTag}`, options);
|
|
1003
1047
|
else
|
|
1004
|
-
log(`extracting: ${filename}${suffix}`, options);
|
|
1048
|
+
log(`extracting: ${filename}${suffix}${encTag}`, options);
|
|
1005
1049
|
// Collect verified SHA-256 hash for merkle root calculation (single pass - no double check)
|
|
1006
1050
|
// Only if this is a content file (not metadata) and SHA-256 verification was performed
|
|
1007
1051
|
if (!options.skipBlockchain && hasSha) {
|
|
1008
|
-
const isContentFile = filename !==
|
|
1009
|
-
filename !==
|
|
1010
|
-
filename !==
|
|
1052
|
+
const isContentFile = filename !== neozip_blockchain_1.TOKENIZED_METADATA &&
|
|
1053
|
+
filename !== ots_1.TIMESTAMP_METADATA &&
|
|
1054
|
+
filename !== ots_1.TIMESTAMP_SUBMITTED &&
|
|
1055
|
+
filename !== zipstamp_server_1.SUBMIT_METADATA &&
|
|
1056
|
+
filename !== zipstamp_server_1.TIMESTAMP_METADATA &&
|
|
1011
1057
|
!entry.isDirectory;
|
|
1012
1058
|
if (isContentFile && entry.sha256) {
|
|
1013
1059
|
// Use the verified hash from the entry (already verified during extraction)
|
|
@@ -1019,9 +1065,11 @@ async function extractArchive(zip, destination, options) {
|
|
|
1019
1065
|
else {
|
|
1020
1066
|
log(`error: ${filename} (${result.error || 'unknown error'})`, options);
|
|
1021
1067
|
// Track failed files for merkle root calculation
|
|
1022
|
-
const isContentFile = filename !==
|
|
1023
|
-
filename !==
|
|
1024
|
-
filename !==
|
|
1068
|
+
const isContentFile = filename !== neozip_blockchain_1.TOKENIZED_METADATA &&
|
|
1069
|
+
filename !== ots_1.TIMESTAMP_METADATA &&
|
|
1070
|
+
filename !== ots_1.TIMESTAMP_SUBMITTED &&
|
|
1071
|
+
filename !== zipstamp_server_1.SUBMIT_METADATA &&
|
|
1072
|
+
filename !== zipstamp_server_1.TIMESTAMP_METADATA &&
|
|
1025
1073
|
!entry.isDirectory;
|
|
1026
1074
|
if (isContentFile && !options.skipBlockchain) {
|
|
1027
1075
|
// Check if failure was due to SHA-256 mismatch
|
|
@@ -1051,9 +1099,12 @@ async function extractArchive(zip, destination, options) {
|
|
|
1051
1099
|
async function verifyTokenization(zip, archiveName, options, extractionResult) {
|
|
1052
1100
|
try {
|
|
1053
1101
|
const entries = await getDirectory(zip, false);
|
|
1054
|
-
const
|
|
1102
|
+
const tokenEntryLegacy = entries.find((e) => (e.filename || '') === 'META-INF/NZIP.TOKEN');
|
|
1103
|
+
const tokenEntryNzip = entries.find((e) => (e.filename || '') === 'META-INF/TOKEN.NZIP');
|
|
1104
|
+
const tokenEntry = tokenEntryNzip || tokenEntryLegacy;
|
|
1105
|
+
const tokenMetaFile = tokenEntry ? (tokenEntry.filename || 'META-INF/TOKEN.NZIP') : '';
|
|
1055
1106
|
if (tokenEntry) {
|
|
1056
|
-
console.log(
|
|
1107
|
+
console.log(`\nTokenization detected (${tokenMetaFile})`);
|
|
1057
1108
|
}
|
|
1058
1109
|
// Only do token verification if there's a token entry
|
|
1059
1110
|
if (tokenEntry) {
|
|
@@ -1063,7 +1114,7 @@ async function verifyTokenization(zip, archiveName, options, extractionResult) {
|
|
|
1063
1114
|
return;
|
|
1064
1115
|
}
|
|
1065
1116
|
// Initialize blockchain verifier
|
|
1066
|
-
const verifier = new
|
|
1117
|
+
const verifier = new neozip_blockchain_1.ZipkitVerifier({
|
|
1067
1118
|
debug: options.verbose, // Enable debug output in verbose mode
|
|
1068
1119
|
skipHash: options.skipBlockchain
|
|
1069
1120
|
});
|
|
@@ -1155,9 +1206,11 @@ async function verifyTokenization(zip, archiveName, options, extractionResult) {
|
|
|
1155
1206
|
// Filter out blockchain metadata files to ensure consistent Merkle Root calculation
|
|
1156
1207
|
const contentEntries = entries.filter(entry => {
|
|
1157
1208
|
const filename = entry.filename || '';
|
|
1158
|
-
return filename !==
|
|
1159
|
-
filename !==
|
|
1160
|
-
filename !==
|
|
1209
|
+
return filename !== neozip_blockchain_1.TOKENIZED_METADATA &&
|
|
1210
|
+
filename !== ots_1.TIMESTAMP_METADATA &&
|
|
1211
|
+
filename !== ots_1.TIMESTAMP_SUBMITTED &&
|
|
1212
|
+
filename !== zipstamp_server_1.SUBMIT_METADATA &&
|
|
1213
|
+
filename !== zipstamp_server_1.TIMESTAMP_METADATA &&
|
|
1161
1214
|
!entry.isDirectory; // Skip directories
|
|
1162
1215
|
});
|
|
1163
1216
|
// Extract each file once and verify SHA-256 during extraction
|
|
@@ -1270,22 +1323,22 @@ async function verifyTokenization(zip, archiveName, options, extractionResult) {
|
|
|
1270
1323
|
const chainId = tokenMetadata.networkChainId;
|
|
1271
1324
|
let networkConfig = null;
|
|
1272
1325
|
if (chainId) {
|
|
1273
|
-
networkConfig = (0,
|
|
1326
|
+
networkConfig = (0, neozip_blockchain_1.getContractConfig)(chainId);
|
|
1274
1327
|
}
|
|
1275
1328
|
else {
|
|
1276
1329
|
// Fallback: try to determine chain ID from network name
|
|
1277
1330
|
const networkName = tokenMetadata.network.toLowerCase();
|
|
1278
1331
|
if (networkName.includes('base sepolia') || networkName === 'base-sepolia') {
|
|
1279
|
-
networkConfig = (0,
|
|
1332
|
+
networkConfig = (0, neozip_blockchain_1.getContractConfig)(84532);
|
|
1280
1333
|
}
|
|
1281
1334
|
else if (networkName.includes('base mainnet') || networkName === 'base-mainnet' || networkName === 'base') {
|
|
1282
|
-
networkConfig = (0,
|
|
1335
|
+
networkConfig = (0, neozip_blockchain_1.getContractConfig)(8453);
|
|
1283
1336
|
}
|
|
1284
1337
|
else if (networkName.includes('arbitrum sepolia') || networkName === 'arbitrum-sepolia' || (networkName.includes('arbitrum') && networkName.includes('sepolia'))) {
|
|
1285
|
-
networkConfig = (0,
|
|
1338
|
+
networkConfig = (0, neozip_blockchain_1.getContractConfig)(421614);
|
|
1286
1339
|
}
|
|
1287
1340
|
else if (networkName.includes('sepolia testnet') || networkName === 'sepolia-testnet' || (networkName.includes('sepolia') && !networkName.includes('base') && !networkName.includes('arbitrum'))) {
|
|
1288
|
-
networkConfig = (0,
|
|
1341
|
+
networkConfig = (0, neozip_blockchain_1.getContractConfig)(11155111);
|
|
1289
1342
|
}
|
|
1290
1343
|
}
|
|
1291
1344
|
let verificationResult;
|
|
@@ -1437,26 +1490,36 @@ async function verifyTokenization(zip, archiveName, options, extractionResult) {
|
|
|
1437
1490
|
}
|
|
1438
1491
|
// Display results based on verification outcome
|
|
1439
1492
|
if (verificationResult.success && verificationResult.verificationDetails) {
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
}
|
|
1493
|
+
const d = verificationResult.verificationDetails;
|
|
1494
|
+
const meta = verificationResult.tokenMetadata;
|
|
1495
|
+
// Stamp type and summary
|
|
1496
|
+
console.log('\n🔐 Tokenization (NFT) Verification:');
|
|
1497
|
+
console.log(' - Status: ✅ VERIFIED');
|
|
1498
|
+
console.log(' - Stamp type: NZIP-NFT (tokenized archive)');
|
|
1499
|
+
if (d.contractAddress) {
|
|
1500
|
+
console.log(` - Contract: ${d.contractAddress}`);
|
|
1449
1501
|
}
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1502
|
+
if (meta?.contractVersion) {
|
|
1503
|
+
console.log(` - Contract version: ${meta.contractVersion}`);
|
|
1504
|
+
}
|
|
1505
|
+
if (d.network) {
|
|
1506
|
+
console.log(` - Network: ${d.network}`);
|
|
1507
|
+
}
|
|
1508
|
+
if (d.tokenId) {
|
|
1509
|
+
console.log(` - Token ID: ${d.tokenId}`);
|
|
1510
|
+
}
|
|
1511
|
+
if (d.merkleRoot && d.calculatedMerkleRoot) {
|
|
1512
|
+
const rootsMatch = d.merkleRoot === d.calculatedMerkleRoot;
|
|
1513
|
+
console.log(` - Merkle root: ${d.merkleRoot}`);
|
|
1514
|
+
console.log(` ${rootsMatch ? '✓' : '✗'} Contract Merkle Root ${rootsMatch ? 'matches' : 'does not match'} calculated Merkle Root`);
|
|
1515
|
+
}
|
|
1516
|
+
if (d.mintDate) {
|
|
1517
|
+
console.log(` - Minted: ${d.mintDate}`);
|
|
1518
|
+
}
|
|
1519
|
+
console.log(`✅ ${verificationResult.message}`);
|
|
1520
|
+
console.log(`The archive, tokenized as NFT #${d.tokenId} on the ${d.network}, was minted on ${d.mintDate}.`);
|
|
1521
|
+
if (options.verbose && d.declaredMerkleRoot) {
|
|
1522
|
+
console.log(` Declared Merkle Root: ${d.declaredMerkleRoot}`);
|
|
1460
1523
|
}
|
|
1461
1524
|
}
|
|
1462
1525
|
else {
|
|
@@ -1497,6 +1560,8 @@ async function verifyTokenization(zip, archiveName, options, extractionResult) {
|
|
|
1497
1560
|
} // End of token verification if statement
|
|
1498
1561
|
// Check for OpenTimestamp verification and upgrade
|
|
1499
1562
|
await verifyAndUpgradeOts(zip, archiveName, options);
|
|
1563
|
+
// Check for Zipstamp verification
|
|
1564
|
+
await verifyZipstampTimestamp(zip, archiveName, options, extractionResult);
|
|
1500
1565
|
}
|
|
1501
1566
|
catch (e) {
|
|
1502
1567
|
const msg = e instanceof Error ? e.message : String(e);
|
|
@@ -1520,7 +1585,7 @@ async function verifyAndUpgradeOts(zip, archiveName, options) {
|
|
|
1520
1585
|
if (!otsEntry) {
|
|
1521
1586
|
return;
|
|
1522
1587
|
}
|
|
1523
|
-
const res = await (0,
|
|
1588
|
+
const res = await (0, ots_1.verifyOtsZip)(zip);
|
|
1524
1589
|
console.log('\n🕐 OpenTimestamps Verification:');
|
|
1525
1590
|
if (res.status === 'none') {
|
|
1526
1591
|
console.log(' - Status: No OpenTimestamps found');
|
|
@@ -1539,10 +1604,12 @@ async function verifyAndUpgradeOts(zip, archiveName, options) {
|
|
|
1539
1604
|
hour12: false
|
|
1540
1605
|
});
|
|
1541
1606
|
console.log(` - Status: ✅ VERIFIED`);
|
|
1607
|
+
console.log(` - Stamp type: OpenTimestamps (Bitcoin)`);
|
|
1542
1608
|
console.log(` - Attested: Bitcoin block ${res.blockHeight} on ${date} ${time}`);
|
|
1543
1609
|
}
|
|
1544
1610
|
else {
|
|
1545
1611
|
console.log(' - Status: ✅ VERIFIED');
|
|
1612
|
+
console.log(' - Stamp type: OpenTimestamps (Bitcoin)');
|
|
1546
1613
|
}
|
|
1547
1614
|
if (res.upgraded && res.upgradedOts) {
|
|
1548
1615
|
let doUpgrade = false;
|
|
@@ -1605,6 +1672,182 @@ async function verifyAndUpgradeOts(zip, archiveName, options) {
|
|
|
1605
1672
|
}
|
|
1606
1673
|
}
|
|
1607
1674
|
}
|
|
1675
|
+
/**
|
|
1676
|
+
* Verify Zipstamp timestamp if present
|
|
1677
|
+
* Uses our extraction logic (extractEntry/extractToFile) for compatibility with file-based ZIPs
|
|
1678
|
+
* When batch is confirmed but archive has TS-SUBMIT.NZIP, offers upgrade (prompt/auto/skip)
|
|
1679
|
+
*/
|
|
1680
|
+
async function verifyZipstampTimestamp(zip, archiveName, options, extractionResult) {
|
|
1681
|
+
try {
|
|
1682
|
+
if (options.skipBlockchain) {
|
|
1683
|
+
return;
|
|
1684
|
+
}
|
|
1685
|
+
const entries = await getDirectory(zip, false);
|
|
1686
|
+
const zipstampEntry = entries.find((e) => (e.filename || '') === 'META-INF/TS-SUBMIT.NZIP' ||
|
|
1687
|
+
(e.filename || '') === 'META-INF/TIMESTAMP.NZIP');
|
|
1688
|
+
if (!zipstampEntry) {
|
|
1689
|
+
return;
|
|
1690
|
+
}
|
|
1691
|
+
// Extract timestamp metadata using our extraction (works for both buffer and file-based)
|
|
1692
|
+
let timestampData = null;
|
|
1693
|
+
const bufferBased = isBufferBased(zip);
|
|
1694
|
+
const fileBased = isFileBased(zip);
|
|
1695
|
+
if (bufferBased) {
|
|
1696
|
+
const buf = await extractEntry(zip, zipstampEntry, true);
|
|
1697
|
+
if (buf) {
|
|
1698
|
+
try {
|
|
1699
|
+
timestampData = JSON.parse(buf.toString('utf-8'));
|
|
1700
|
+
}
|
|
1701
|
+
catch {
|
|
1702
|
+
timestampData = null;
|
|
1703
|
+
}
|
|
1704
|
+
}
|
|
1705
|
+
}
|
|
1706
|
+
else if (fileBased) {
|
|
1707
|
+
const tmpPath = path.join(require('os').tmpdir(), `neounzip-zipstamp-${Date.now()}-${(zipstampEntry.filename || 'meta').replace(/[^a-zA-Z0-9]/g, '_')}`);
|
|
1708
|
+
try {
|
|
1709
|
+
await zip.extractToFile(zipstampEntry, tmpPath, { skipHashCheck: true });
|
|
1710
|
+
const buf = fs.readFileSync(tmpPath);
|
|
1711
|
+
timestampData = JSON.parse(buf.toString('utf-8'));
|
|
1712
|
+
}
|
|
1713
|
+
catch {
|
|
1714
|
+
timestampData = null;
|
|
1715
|
+
}
|
|
1716
|
+
finally {
|
|
1717
|
+
if (fs.existsSync(tmpPath)) {
|
|
1718
|
+
try {
|
|
1719
|
+
fs.unlinkSync(tmpPath);
|
|
1720
|
+
}
|
|
1721
|
+
catch {
|
|
1722
|
+
/* ignore */
|
|
1723
|
+
}
|
|
1724
|
+
}
|
|
1725
|
+
}
|
|
1726
|
+
}
|
|
1727
|
+
if (!timestampData || !timestampData.digest) {
|
|
1728
|
+
console.log('\n🔗 Zipstamp Verification:');
|
|
1729
|
+
console.log(' - Status: ❌ ERROR');
|
|
1730
|
+
console.log(' - Error: Failed to extract timestamp metadata');
|
|
1731
|
+
return;
|
|
1732
|
+
}
|
|
1733
|
+
const merkleRoot = extractionResult?.merkleRoot ??
|
|
1734
|
+
zip.getMerkleRoot?.() ??
|
|
1735
|
+
null;
|
|
1736
|
+
if (!merkleRoot) {
|
|
1737
|
+
console.log('\n🔗 Zipstamp Verification:');
|
|
1738
|
+
console.log(' - Status: ❌ ERROR');
|
|
1739
|
+
console.log(' - Error: Merkle root not found in ZIP file');
|
|
1740
|
+
return;
|
|
1741
|
+
}
|
|
1742
|
+
if (timestampData.digest.toLowerCase() !== merkleRoot.toLowerCase()) {
|
|
1743
|
+
console.log('\n🔗 Zipstamp Verification:');
|
|
1744
|
+
console.log(' - Status: ❌ ERROR');
|
|
1745
|
+
console.log(' - Error: Timestamp digest does not match ZIP merkle root');
|
|
1746
|
+
return;
|
|
1747
|
+
}
|
|
1748
|
+
// Use verifyDigest directly - pass undefined for chainId when 0/invalid (server may reject chainId 0)
|
|
1749
|
+
const chainId = timestampData.chainId && timestampData.chainId > 0 ? timestampData.chainId : undefined;
|
|
1750
|
+
const batchId = timestampData.batchId || undefined;
|
|
1751
|
+
const response = await (0, zipstamp_server_1.verifyDigest)(merkleRoot, chainId, batchId, undefined, {
|
|
1752
|
+
debug: options.debug ?? false,
|
|
1753
|
+
});
|
|
1754
|
+
const res = response.success
|
|
1755
|
+
? response.verified
|
|
1756
|
+
? {
|
|
1757
|
+
status: 'valid',
|
|
1758
|
+
transactionHash: response.transactionHash,
|
|
1759
|
+
blockNumber: response.blockNumber,
|
|
1760
|
+
timestamp: response.timestamp,
|
|
1761
|
+
network: response.network,
|
|
1762
|
+
contractAddress: response.contractAddress,
|
|
1763
|
+
chainId: response.chainId,
|
|
1764
|
+
}
|
|
1765
|
+
: { status: 'pending', message: 'Timestamp is pending confirmation' }
|
|
1766
|
+
: { status: 'error', message: response.error || 'Verification failed' };
|
|
1767
|
+
console.log('\n🔗 Zipstamp Verification:');
|
|
1768
|
+
if (res.status === 'valid') {
|
|
1769
|
+
console.log(' - Status: ✅ VERIFIED');
|
|
1770
|
+
console.log(' - Stamp type: Zipstamp (blockchain timestamp)');
|
|
1771
|
+
if (res.network) {
|
|
1772
|
+
console.log(` - Network: ${res.network}`);
|
|
1773
|
+
}
|
|
1774
|
+
if (res.contractAddress) {
|
|
1775
|
+
console.log(` - Contract: ${res.contractAddress}`);
|
|
1776
|
+
}
|
|
1777
|
+
if (res.transactionHash) {
|
|
1778
|
+
console.log(` - Transaction: ${res.transactionHash}`);
|
|
1779
|
+
}
|
|
1780
|
+
if (res.blockNumber !== undefined) {
|
|
1781
|
+
console.log(` - Block: ${res.blockNumber}`);
|
|
1782
|
+
}
|
|
1783
|
+
if (res.timestamp !== undefined && res.timestamp != null) {
|
|
1784
|
+
const blockDate = new Date(res.timestamp * 1000);
|
|
1785
|
+
const mintedStr = blockDate.toLocaleString(undefined, {
|
|
1786
|
+
month: '2-digit',
|
|
1787
|
+
day: '2-digit',
|
|
1788
|
+
year: 'numeric',
|
|
1789
|
+
hour: '2-digit',
|
|
1790
|
+
minute: '2-digit',
|
|
1791
|
+
second: '2-digit',
|
|
1792
|
+
hour12: true,
|
|
1793
|
+
});
|
|
1794
|
+
console.log(` - Block timestamp (minted): ${mintedStr}`);
|
|
1795
|
+
}
|
|
1796
|
+
// Offer upgrade when archive has TS-SUBMIT.NZIP but batch is confirmed
|
|
1797
|
+
const hasPendingMetadata = (zipstampEntry.filename || '') === 'META-INF/TS-SUBMIT.NZIP';
|
|
1798
|
+
if (hasPendingMetadata) {
|
|
1799
|
+
let doUpgrade = false;
|
|
1800
|
+
if (options.zipstampSkipUpgrade) {
|
|
1801
|
+
doUpgrade = false;
|
|
1802
|
+
console.log(' - Upgrade: Skipped (--zipstamp-skip-upgrade)');
|
|
1803
|
+
}
|
|
1804
|
+
else if (options.zipstampAutoUpgrade) {
|
|
1805
|
+
doUpgrade = true;
|
|
1806
|
+
console.log(' - Upgrade: Automatically applying (--zipstamp-auto-upgrade)');
|
|
1807
|
+
}
|
|
1808
|
+
else {
|
|
1809
|
+
doUpgrade = await promptForZipstampUpgrade(archiveName);
|
|
1810
|
+
}
|
|
1811
|
+
if (doUpgrade) {
|
|
1812
|
+
await performZipstampUpgrade(archiveName);
|
|
1813
|
+
}
|
|
1814
|
+
else if (!options.zipstampSkipUpgrade && !options.zipstampAutoUpgrade) {
|
|
1815
|
+
console.log(' - Note: Run "neozip upgrade <archive>" to upgrade when ready');
|
|
1816
|
+
}
|
|
1817
|
+
}
|
|
1818
|
+
return;
|
|
1819
|
+
}
|
|
1820
|
+
if (res.status === 'pending') {
|
|
1821
|
+
console.log(' - Status: ⏳ PENDING (batch not yet confirmed on blockchain)');
|
|
1822
|
+
if (res.message) {
|
|
1823
|
+
console.log(` - Info: ${res.message}`);
|
|
1824
|
+
}
|
|
1825
|
+
console.log(' - Note: Run "neozip upgrade <archive>" to upgrade when confirmed');
|
|
1826
|
+
return;
|
|
1827
|
+
}
|
|
1828
|
+
if (res.status === 'error') {
|
|
1829
|
+
console.log(' - Status: ❌ ERROR');
|
|
1830
|
+
if (res.message) {
|
|
1831
|
+
console.log(` - Error: ${res.message}`);
|
|
1832
|
+
}
|
|
1833
|
+
return;
|
|
1834
|
+
}
|
|
1835
|
+
}
|
|
1836
|
+
catch (error) {
|
|
1837
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
1838
|
+
console.log('\n🔗 Zipstamp Verification:');
|
|
1839
|
+
if (errorMessage.includes('ESOCKETTIMEDOUT') || errorMessage.includes('ECONNRESET') ||
|
|
1840
|
+
errorMessage.includes('ENOTFOUND') || errorMessage.includes('ECONNREFUSED') ||
|
|
1841
|
+
errorMessage.includes('timeout') || errorMessage.includes('network')) {
|
|
1842
|
+
console.log(' - Status: 📡 COMMUNICATION ERROR (unable to contact Zipstamp server)');
|
|
1843
|
+
console.log(` - Error: ${errorMessage}`);
|
|
1844
|
+
}
|
|
1845
|
+
else {
|
|
1846
|
+
console.log(' - Status: ❌ ERROR during verification');
|
|
1847
|
+
console.log(` - Error: ${errorMessage}`);
|
|
1848
|
+
}
|
|
1849
|
+
}
|
|
1850
|
+
}
|
|
1608
1851
|
/**
|
|
1609
1852
|
* Parse command line arguments
|
|
1610
1853
|
*/
|
|
@@ -1772,6 +2015,12 @@ function parseArgs(args) {
|
|
|
1772
2015
|
case '--ots-skip-upgrade':
|
|
1773
2016
|
options.otsSkipUpgrade = true;
|
|
1774
2017
|
break;
|
|
2018
|
+
case '--zipstamp-auto-upgrade':
|
|
2019
|
+
options.zipstampAutoUpgrade = true;
|
|
2020
|
+
break;
|
|
2021
|
+
case '--zipstamp-skip-upgrade':
|
|
2022
|
+
options.zipstampSkipUpgrade = true;
|
|
2023
|
+
break;
|
|
1775
2024
|
case '--in-memory':
|
|
1776
2025
|
options.inMemory = true;
|
|
1777
2026
|
break;
|
|
@@ -1843,7 +2092,7 @@ Options:
|
|
|
1843
2092
|
--progress Enable enhanced progress reporting
|
|
1844
2093
|
--debug Enable debug output
|
|
1845
2094
|
-sf, --show-files Show files as they are extracted
|
|
1846
|
-
-P, --password [pwd] Password for encrypted files
|
|
2095
|
+
-P, --password [pwd] Password for encrypted files (AES-256 and PKZIP supported)
|
|
1847
2096
|
-x, --exclude <pattern> Exclude files matching pattern (can be used multiple times)
|
|
1848
2097
|
-i, --include <pattern> Include only files matching pattern (can be used multiple times)
|
|
1849
2098
|
-j, --junk-paths Extract files without directory structure (flatten to destination)
|
|
@@ -1854,6 +2103,8 @@ Options:
|
|
|
1854
2103
|
-H, --hard-links Restore hard links efficiently (recreate link relationships)
|
|
1855
2104
|
--ots-auto-upgrade Automatically upgrade OTS timestamps when available
|
|
1856
2105
|
--ots-skip-upgrade Skip OTS timestamp upgrades (no prompt)
|
|
2106
|
+
--zipstamp-auto-upgrade Automatically upgrade Zipstamp timestamps when confirmed
|
|
2107
|
+
--zipstamp-skip-upgrade Skip Zipstamp timestamp upgrades (no prompt)
|
|
1857
2108
|
--in-memory Force in-memory processing mode (browser compatible)
|
|
1858
2109
|
|
|
1859
2110
|
Arguments:
|
|
@@ -1861,9 +2112,9 @@ Arguments:
|
|
|
1861
2112
|
[destination] Output directory (default: current directory)
|
|
1862
2113
|
|
|
1863
2114
|
Examples:
|
|
1864
|
-
neounzip output/calgary.nzip
|
|
1865
|
-
neounzip -t output/calgary.nzip
|
|
1866
|
-
neounzip -l output/calgary.nzip
|
|
2115
|
+
neounzip tests/output/calgary.nzip tests/extracted
|
|
2116
|
+
neounzip -t tests/output/calgary.nzip
|
|
2117
|
+
neounzip -l tests/output/calgary.nzip
|
|
1867
2118
|
`);
|
|
1868
2119
|
}
|
|
1869
2120
|
/**
|
|
@@ -2025,6 +2276,13 @@ async function main() {
|
|
|
2025
2276
|
log(`🚀 NEOUNZIP v${version_1.APP_VERSION} (${version_1.APP_RELEASE_DATE})`, options);
|
|
2026
2277
|
log(`📦 Archive: ${archive}`, options);
|
|
2027
2278
|
log(`📁 Destination: ${extractDestination}`, options);
|
|
2279
|
+
const allEntries = zip.getDirectory();
|
|
2280
|
+
const hasEncrypted = allEntries.some((e) => e.isEncrypted);
|
|
2281
|
+
if (hasEncrypted) {
|
|
2282
|
+
const hasAes = allEntries.some((e) => e.isEncrypted && e.aesVersion > 0);
|
|
2283
|
+
const encLabel = hasAes ? 'AES-256' : 'PKZIP';
|
|
2284
|
+
log(`🔐 Encryption: ${encLabel}`, options);
|
|
2285
|
+
}
|
|
2028
2286
|
if (options.debug) {
|
|
2029
2287
|
// Set global Logger level to 'debug' for debug output
|
|
2030
2288
|
// Note: Individual class logging is controlled by their loggingEnabled static property
|
|
@@ -41,14 +41,14 @@ exports.getWalletPasskey = getWalletPasskey;
|
|
|
41
41
|
exports.addTokenMetaToZip = addTokenMetaToZip;
|
|
42
42
|
exports.handleTokenMinting = handleTokenMinting;
|
|
43
43
|
const neozipkit_1 = require("neozipkit");
|
|
44
|
-
const
|
|
44
|
+
const neozip_blockchain_1 = require("neozip-blockchain");
|
|
45
45
|
const ConfigStore_1 = require("../config/ConfigStore");
|
|
46
46
|
const readline = __importStar(require("readline"));
|
|
47
47
|
/**
|
|
48
48
|
* Check if the network is supported (uses nameAliases from CONTRACT_CONFIGS)
|
|
49
49
|
*/
|
|
50
50
|
function isSupportedNetwork(network) {
|
|
51
|
-
return (0,
|
|
51
|
+
return (0, neozip_blockchain_1.getChainIdByName)(network) !== null;
|
|
52
52
|
}
|
|
53
53
|
/**
|
|
54
54
|
* Check if the required wallet passkey is available and valid
|
|
@@ -78,7 +78,7 @@ function getWalletPasskey(cliWalletKey, showError = true) {
|
|
|
78
78
|
return null;
|
|
79
79
|
}
|
|
80
80
|
// Validate the private key format
|
|
81
|
-
if (!(0,
|
|
81
|
+
if (!(0, neozip_blockchain_1.validatePrivateKey)(passkey)) {
|
|
82
82
|
if (showError) {
|
|
83
83
|
console.error('❌ Error: Invalid private key format');
|
|
84
84
|
console.error(' Private key should be a 64-character hex string starting with 0x');
|
|
@@ -94,7 +94,7 @@ function getWalletPasskey(cliWalletKey, showError = true) {
|
|
|
94
94
|
async function addTokenMetaToZip(zip, tokenMeta) {
|
|
95
95
|
const tokenContent = JSON.stringify(tokenMeta, null, 2);
|
|
96
96
|
const tokenBuffer = Buffer.from(tokenContent, 'utf8');
|
|
97
|
-
const tokenPath =
|
|
97
|
+
const tokenPath = neozip_blockchain_1.TOKENIZED_METADATA;
|
|
98
98
|
// Create new token entry
|
|
99
99
|
const tokenEntry = zip.createZipEntry(tokenPath);
|
|
100
100
|
tokenEntry.timeDateDOS = tokenEntry.setDateTime(new Date());
|
|
@@ -193,7 +193,7 @@ async function handleTokenMinting(initialMinter, zip, nonInteractive = false, in
|
|
|
193
193
|
else {
|
|
194
194
|
// ZipkitMinter uses getChainIdByName internally which handles all nameAliases
|
|
195
195
|
// So we can pass the original network name directly
|
|
196
|
-
minter = new
|
|
196
|
+
minter = new neozip_blockchain_1.ZipkitMinter(merkleRoot, {
|
|
197
197
|
network: network,
|
|
198
198
|
walletPrivateKey: walletPrivateKey,
|
|
199
199
|
verbose: verbose,
|