neozip-cli 0.75.1-beta → 0.80.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 +30 -0
- package/DOCUMENTATION.md +9 -1
- package/README.md +22 -10
- 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 +18 -10
- package/dist/src/neounzip.js +305 -60
- package/dist/src/neozip/blockchain.js +5 -5
- package/dist/src/neozip/createZip.js +207 -41
- package/dist/src/neozip/upgradeZip.js +182 -0
- package/dist/src/neozip.js +143 -8
- package/env.example +10 -0
- package/package.json +91 -85
- 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()
|
|
@@ -608,9 +642,11 @@ async function testArchive(zip, options) {
|
|
|
608
642
|
// Check bitFlags for encryption flag (bit 0)
|
|
609
643
|
const isEncrypted = entry.isEncrypted || (entry.bitFlags & 0x01) !== 0;
|
|
610
644
|
// Track failed files for merkle root calculation
|
|
611
|
-
const isContentFile = name !==
|
|
612
|
-
name !==
|
|
613
|
-
name !==
|
|
645
|
+
const isContentFile = name !== neozip_blockchain_1.TOKENIZED_METADATA &&
|
|
646
|
+
name !== ots_1.TIMESTAMP_METADATA &&
|
|
647
|
+
name !== ots_1.TIMESTAMP_SUBMITTED &&
|
|
648
|
+
name !== zipstamp_server_1.SUBMIT_METADATA &&
|
|
649
|
+
name !== zipstamp_server_1.TIMESTAMP_METADATA &&
|
|
614
650
|
!entry.isDirectory;
|
|
615
651
|
if (isEncrypted) {
|
|
616
652
|
// For encrypted files, decompression errors after decryption usually mean wrong password
|
|
@@ -730,7 +766,7 @@ async function preVerifyArchive(zip, archiveName, options) {
|
|
|
730
766
|
let verificationSuccess = false;
|
|
731
767
|
let verificationError;
|
|
732
768
|
try {
|
|
733
|
-
const verifier = new
|
|
769
|
+
const verifier = new neozip_blockchain_1.ZipkitVerifier({
|
|
734
770
|
debug: options.verbose,
|
|
735
771
|
skipHash: options.skipBlockchain
|
|
736
772
|
});
|
|
@@ -791,21 +827,21 @@ async function preVerifyArchive(zip, archiveName, options) {
|
|
|
791
827
|
const chainId = tokenMetadata.networkChainId;
|
|
792
828
|
let networkConfig = null;
|
|
793
829
|
if (chainId) {
|
|
794
|
-
networkConfig = (0,
|
|
830
|
+
networkConfig = (0, neozip_blockchain_1.getContractConfig)(chainId);
|
|
795
831
|
}
|
|
796
832
|
else {
|
|
797
833
|
const networkName = tokenMetadata.network.toLowerCase();
|
|
798
834
|
if (networkName.includes('base sepolia') || networkName === 'base-sepolia') {
|
|
799
|
-
networkConfig = (0,
|
|
835
|
+
networkConfig = (0, neozip_blockchain_1.getContractConfig)(84532);
|
|
800
836
|
}
|
|
801
837
|
else if (networkName.includes('base mainnet') || networkName === 'base-mainnet' || networkName === 'base') {
|
|
802
|
-
networkConfig = (0,
|
|
838
|
+
networkConfig = (0, neozip_blockchain_1.getContractConfig)(8453);
|
|
803
839
|
}
|
|
804
840
|
else if (networkName.includes('arbitrum sepolia') || networkName === 'arbitrum-sepolia' || (networkName.includes('arbitrum') && networkName.includes('sepolia'))) {
|
|
805
|
-
networkConfig = (0,
|
|
841
|
+
networkConfig = (0, neozip_blockchain_1.getContractConfig)(421614);
|
|
806
842
|
}
|
|
807
843
|
else if (networkName.includes('sepolia testnet') || networkName === 'sepolia-testnet' || (networkName.includes('sepolia') && !networkName.includes('base') && !networkName.includes('arbitrum'))) {
|
|
808
|
-
networkConfig = (0,
|
|
844
|
+
networkConfig = (0, neozip_blockchain_1.getContractConfig)(11155111);
|
|
809
845
|
}
|
|
810
846
|
}
|
|
811
847
|
// Perform verification (non-interactive mode for pre-verify)
|
|
@@ -926,9 +962,11 @@ async function extractArchive(zip, destination, options) {
|
|
|
926
962
|
// Filter out metadata files for merkle root calculation
|
|
927
963
|
const contentEntriesForMerkle = filteredEntries.filter(entry => {
|
|
928
964
|
const filename = entry.filename || '';
|
|
929
|
-
return filename !==
|
|
930
|
-
filename !==
|
|
931
|
-
filename !==
|
|
965
|
+
return filename !== neozip_blockchain_1.TOKENIZED_METADATA &&
|
|
966
|
+
filename !== ots_1.TIMESTAMP_METADATA &&
|
|
967
|
+
filename !== ots_1.TIMESTAMP_SUBMITTED &&
|
|
968
|
+
filename !== zipstamp_server_1.SUBMIT_METADATA &&
|
|
969
|
+
filename !== zipstamp_server_1.TIMESTAMP_METADATA &&
|
|
932
970
|
!entry.isDirectory;
|
|
933
971
|
});
|
|
934
972
|
for (const entry of filteredEntries) {
|
|
@@ -1005,9 +1043,11 @@ async function extractArchive(zip, destination, options) {
|
|
|
1005
1043
|
// Collect verified SHA-256 hash for merkle root calculation (single pass - no double check)
|
|
1006
1044
|
// Only if this is a content file (not metadata) and SHA-256 verification was performed
|
|
1007
1045
|
if (!options.skipBlockchain && hasSha) {
|
|
1008
|
-
const isContentFile = filename !==
|
|
1009
|
-
filename !==
|
|
1010
|
-
filename !==
|
|
1046
|
+
const isContentFile = filename !== neozip_blockchain_1.TOKENIZED_METADATA &&
|
|
1047
|
+
filename !== ots_1.TIMESTAMP_METADATA &&
|
|
1048
|
+
filename !== ots_1.TIMESTAMP_SUBMITTED &&
|
|
1049
|
+
filename !== zipstamp_server_1.SUBMIT_METADATA &&
|
|
1050
|
+
filename !== zipstamp_server_1.TIMESTAMP_METADATA &&
|
|
1011
1051
|
!entry.isDirectory;
|
|
1012
1052
|
if (isContentFile && entry.sha256) {
|
|
1013
1053
|
// Use the verified hash from the entry (already verified during extraction)
|
|
@@ -1019,9 +1059,11 @@ async function extractArchive(zip, destination, options) {
|
|
|
1019
1059
|
else {
|
|
1020
1060
|
log(`error: ${filename} (${result.error || 'unknown error'})`, options);
|
|
1021
1061
|
// Track failed files for merkle root calculation
|
|
1022
|
-
const isContentFile = filename !==
|
|
1023
|
-
filename !==
|
|
1024
|
-
filename !==
|
|
1062
|
+
const isContentFile = filename !== neozip_blockchain_1.TOKENIZED_METADATA &&
|
|
1063
|
+
filename !== ots_1.TIMESTAMP_METADATA &&
|
|
1064
|
+
filename !== ots_1.TIMESTAMP_SUBMITTED &&
|
|
1065
|
+
filename !== zipstamp_server_1.SUBMIT_METADATA &&
|
|
1066
|
+
filename !== zipstamp_server_1.TIMESTAMP_METADATA &&
|
|
1025
1067
|
!entry.isDirectory;
|
|
1026
1068
|
if (isContentFile && !options.skipBlockchain) {
|
|
1027
1069
|
// Check if failure was due to SHA-256 mismatch
|
|
@@ -1051,9 +1093,12 @@ async function extractArchive(zip, destination, options) {
|
|
|
1051
1093
|
async function verifyTokenization(zip, archiveName, options, extractionResult) {
|
|
1052
1094
|
try {
|
|
1053
1095
|
const entries = await getDirectory(zip, false);
|
|
1054
|
-
const
|
|
1096
|
+
const tokenEntryLegacy = entries.find((e) => (e.filename || '') === 'META-INF/NZIP.TOKEN');
|
|
1097
|
+
const tokenEntryNzip = entries.find((e) => (e.filename || '') === 'META-INF/TOKEN.NZIP');
|
|
1098
|
+
const tokenEntry = tokenEntryNzip || tokenEntryLegacy;
|
|
1099
|
+
const tokenMetaFile = tokenEntry ? (tokenEntry.filename || 'META-INF/TOKEN.NZIP') : '';
|
|
1055
1100
|
if (tokenEntry) {
|
|
1056
|
-
console.log(
|
|
1101
|
+
console.log(`\nTokenization detected (${tokenMetaFile})`);
|
|
1057
1102
|
}
|
|
1058
1103
|
// Only do token verification if there's a token entry
|
|
1059
1104
|
if (tokenEntry) {
|
|
@@ -1063,7 +1108,7 @@ async function verifyTokenization(zip, archiveName, options, extractionResult) {
|
|
|
1063
1108
|
return;
|
|
1064
1109
|
}
|
|
1065
1110
|
// Initialize blockchain verifier
|
|
1066
|
-
const verifier = new
|
|
1111
|
+
const verifier = new neozip_blockchain_1.ZipkitVerifier({
|
|
1067
1112
|
debug: options.verbose, // Enable debug output in verbose mode
|
|
1068
1113
|
skipHash: options.skipBlockchain
|
|
1069
1114
|
});
|
|
@@ -1155,9 +1200,11 @@ async function verifyTokenization(zip, archiveName, options, extractionResult) {
|
|
|
1155
1200
|
// Filter out blockchain metadata files to ensure consistent Merkle Root calculation
|
|
1156
1201
|
const contentEntries = entries.filter(entry => {
|
|
1157
1202
|
const filename = entry.filename || '';
|
|
1158
|
-
return filename !==
|
|
1159
|
-
filename !==
|
|
1160
|
-
filename !==
|
|
1203
|
+
return filename !== neozip_blockchain_1.TOKENIZED_METADATA &&
|
|
1204
|
+
filename !== ots_1.TIMESTAMP_METADATA &&
|
|
1205
|
+
filename !== ots_1.TIMESTAMP_SUBMITTED &&
|
|
1206
|
+
filename !== zipstamp_server_1.SUBMIT_METADATA &&
|
|
1207
|
+
filename !== zipstamp_server_1.TIMESTAMP_METADATA &&
|
|
1161
1208
|
!entry.isDirectory; // Skip directories
|
|
1162
1209
|
});
|
|
1163
1210
|
// Extract each file once and verify SHA-256 during extraction
|
|
@@ -1270,22 +1317,22 @@ async function verifyTokenization(zip, archiveName, options, extractionResult) {
|
|
|
1270
1317
|
const chainId = tokenMetadata.networkChainId;
|
|
1271
1318
|
let networkConfig = null;
|
|
1272
1319
|
if (chainId) {
|
|
1273
|
-
networkConfig = (0,
|
|
1320
|
+
networkConfig = (0, neozip_blockchain_1.getContractConfig)(chainId);
|
|
1274
1321
|
}
|
|
1275
1322
|
else {
|
|
1276
1323
|
// Fallback: try to determine chain ID from network name
|
|
1277
1324
|
const networkName = tokenMetadata.network.toLowerCase();
|
|
1278
1325
|
if (networkName.includes('base sepolia') || networkName === 'base-sepolia') {
|
|
1279
|
-
networkConfig = (0,
|
|
1326
|
+
networkConfig = (0, neozip_blockchain_1.getContractConfig)(84532);
|
|
1280
1327
|
}
|
|
1281
1328
|
else if (networkName.includes('base mainnet') || networkName === 'base-mainnet' || networkName === 'base') {
|
|
1282
|
-
networkConfig = (0,
|
|
1329
|
+
networkConfig = (0, neozip_blockchain_1.getContractConfig)(8453);
|
|
1283
1330
|
}
|
|
1284
1331
|
else if (networkName.includes('arbitrum sepolia') || networkName === 'arbitrum-sepolia' || (networkName.includes('arbitrum') && networkName.includes('sepolia'))) {
|
|
1285
|
-
networkConfig = (0,
|
|
1332
|
+
networkConfig = (0, neozip_blockchain_1.getContractConfig)(421614);
|
|
1286
1333
|
}
|
|
1287
1334
|
else if (networkName.includes('sepolia testnet') || networkName === 'sepolia-testnet' || (networkName.includes('sepolia') && !networkName.includes('base') && !networkName.includes('arbitrum'))) {
|
|
1288
|
-
networkConfig = (0,
|
|
1335
|
+
networkConfig = (0, neozip_blockchain_1.getContractConfig)(11155111);
|
|
1289
1336
|
}
|
|
1290
1337
|
}
|
|
1291
1338
|
let verificationResult;
|
|
@@ -1437,26 +1484,36 @@ async function verifyTokenization(zip, archiveName, options, extractionResult) {
|
|
|
1437
1484
|
}
|
|
1438
1485
|
// Display results based on verification outcome
|
|
1439
1486
|
if (verificationResult.success && verificationResult.verificationDetails) {
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
}
|
|
1487
|
+
const d = verificationResult.verificationDetails;
|
|
1488
|
+
const meta = verificationResult.tokenMetadata;
|
|
1489
|
+
// Stamp type and summary
|
|
1490
|
+
console.log('\n🔐 Tokenization (NFT) Verification:');
|
|
1491
|
+
console.log(' - Status: ✅ VERIFIED');
|
|
1492
|
+
console.log(' - Stamp type: NZIP-NFT (tokenized archive)');
|
|
1493
|
+
if (d.contractAddress) {
|
|
1494
|
+
console.log(` - Contract: ${d.contractAddress}`);
|
|
1449
1495
|
}
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1496
|
+
if (meta?.contractVersion) {
|
|
1497
|
+
console.log(` - Contract version: ${meta.contractVersion}`);
|
|
1498
|
+
}
|
|
1499
|
+
if (d.network) {
|
|
1500
|
+
console.log(` - Network: ${d.network}`);
|
|
1501
|
+
}
|
|
1502
|
+
if (d.tokenId) {
|
|
1503
|
+
console.log(` - Token ID: ${d.tokenId}`);
|
|
1504
|
+
}
|
|
1505
|
+
if (d.merkleRoot && d.calculatedMerkleRoot) {
|
|
1506
|
+
const rootsMatch = d.merkleRoot === d.calculatedMerkleRoot;
|
|
1507
|
+
console.log(` - Merkle root: ${d.merkleRoot}`);
|
|
1508
|
+
console.log(` ${rootsMatch ? '✓' : '✗'} Contract Merkle Root ${rootsMatch ? 'matches' : 'does not match'} calculated Merkle Root`);
|
|
1509
|
+
}
|
|
1510
|
+
if (d.mintDate) {
|
|
1511
|
+
console.log(` - Minted: ${d.mintDate}`);
|
|
1512
|
+
}
|
|
1513
|
+
console.log(`✅ ${verificationResult.message}`);
|
|
1514
|
+
console.log(`The archive, tokenized as NFT #${d.tokenId} on the ${d.network}, was minted on ${d.mintDate}.`);
|
|
1515
|
+
if (options.verbose && d.declaredMerkleRoot) {
|
|
1516
|
+
console.log(` Declared Merkle Root: ${d.declaredMerkleRoot}`);
|
|
1460
1517
|
}
|
|
1461
1518
|
}
|
|
1462
1519
|
else {
|
|
@@ -1497,6 +1554,8 @@ async function verifyTokenization(zip, archiveName, options, extractionResult) {
|
|
|
1497
1554
|
} // End of token verification if statement
|
|
1498
1555
|
// Check for OpenTimestamp verification and upgrade
|
|
1499
1556
|
await verifyAndUpgradeOts(zip, archiveName, options);
|
|
1557
|
+
// Check for Zipstamp verification
|
|
1558
|
+
await verifyZipstampTimestamp(zip, archiveName, options, extractionResult);
|
|
1500
1559
|
}
|
|
1501
1560
|
catch (e) {
|
|
1502
1561
|
const msg = e instanceof Error ? e.message : String(e);
|
|
@@ -1520,7 +1579,7 @@ async function verifyAndUpgradeOts(zip, archiveName, options) {
|
|
|
1520
1579
|
if (!otsEntry) {
|
|
1521
1580
|
return;
|
|
1522
1581
|
}
|
|
1523
|
-
const res = await (0,
|
|
1582
|
+
const res = await (0, ots_1.verifyOtsZip)(zip);
|
|
1524
1583
|
console.log('\n🕐 OpenTimestamps Verification:');
|
|
1525
1584
|
if (res.status === 'none') {
|
|
1526
1585
|
console.log(' - Status: No OpenTimestamps found');
|
|
@@ -1539,10 +1598,12 @@ async function verifyAndUpgradeOts(zip, archiveName, options) {
|
|
|
1539
1598
|
hour12: false
|
|
1540
1599
|
});
|
|
1541
1600
|
console.log(` - Status: ✅ VERIFIED`);
|
|
1601
|
+
console.log(` - Stamp type: OpenTimestamps (Bitcoin)`);
|
|
1542
1602
|
console.log(` - Attested: Bitcoin block ${res.blockHeight} on ${date} ${time}`);
|
|
1543
1603
|
}
|
|
1544
1604
|
else {
|
|
1545
1605
|
console.log(' - Status: ✅ VERIFIED');
|
|
1606
|
+
console.log(' - Stamp type: OpenTimestamps (Bitcoin)');
|
|
1546
1607
|
}
|
|
1547
1608
|
if (res.upgraded && res.upgradedOts) {
|
|
1548
1609
|
let doUpgrade = false;
|
|
@@ -1605,6 +1666,182 @@ async function verifyAndUpgradeOts(zip, archiveName, options) {
|
|
|
1605
1666
|
}
|
|
1606
1667
|
}
|
|
1607
1668
|
}
|
|
1669
|
+
/**
|
|
1670
|
+
* Verify Zipstamp timestamp if present
|
|
1671
|
+
* Uses our extraction logic (extractEntry/extractToFile) for compatibility with file-based ZIPs
|
|
1672
|
+
* When batch is confirmed but archive has TS-SUBMIT.NZIP, offers upgrade (prompt/auto/skip)
|
|
1673
|
+
*/
|
|
1674
|
+
async function verifyZipstampTimestamp(zip, archiveName, options, extractionResult) {
|
|
1675
|
+
try {
|
|
1676
|
+
if (options.skipBlockchain) {
|
|
1677
|
+
return;
|
|
1678
|
+
}
|
|
1679
|
+
const entries = await getDirectory(zip, false);
|
|
1680
|
+
const zipstampEntry = entries.find((e) => (e.filename || '') === 'META-INF/TS-SUBMIT.NZIP' ||
|
|
1681
|
+
(e.filename || '') === 'META-INF/TIMESTAMP.NZIP');
|
|
1682
|
+
if (!zipstampEntry) {
|
|
1683
|
+
return;
|
|
1684
|
+
}
|
|
1685
|
+
// Extract timestamp metadata using our extraction (works for both buffer and file-based)
|
|
1686
|
+
let timestampData = null;
|
|
1687
|
+
const bufferBased = isBufferBased(zip);
|
|
1688
|
+
const fileBased = isFileBased(zip);
|
|
1689
|
+
if (bufferBased) {
|
|
1690
|
+
const buf = await extractEntry(zip, zipstampEntry, true);
|
|
1691
|
+
if (buf) {
|
|
1692
|
+
try {
|
|
1693
|
+
timestampData = JSON.parse(buf.toString('utf-8'));
|
|
1694
|
+
}
|
|
1695
|
+
catch {
|
|
1696
|
+
timestampData = null;
|
|
1697
|
+
}
|
|
1698
|
+
}
|
|
1699
|
+
}
|
|
1700
|
+
else if (fileBased) {
|
|
1701
|
+
const tmpPath = path.join(require('os').tmpdir(), `neounzip-zipstamp-${Date.now()}-${(zipstampEntry.filename || 'meta').replace(/[^a-zA-Z0-9]/g, '_')}`);
|
|
1702
|
+
try {
|
|
1703
|
+
await zip.extractToFile(zipstampEntry, tmpPath, { skipHashCheck: true });
|
|
1704
|
+
const buf = fs.readFileSync(tmpPath);
|
|
1705
|
+
timestampData = JSON.parse(buf.toString('utf-8'));
|
|
1706
|
+
}
|
|
1707
|
+
catch {
|
|
1708
|
+
timestampData = null;
|
|
1709
|
+
}
|
|
1710
|
+
finally {
|
|
1711
|
+
if (fs.existsSync(tmpPath)) {
|
|
1712
|
+
try {
|
|
1713
|
+
fs.unlinkSync(tmpPath);
|
|
1714
|
+
}
|
|
1715
|
+
catch {
|
|
1716
|
+
/* ignore */
|
|
1717
|
+
}
|
|
1718
|
+
}
|
|
1719
|
+
}
|
|
1720
|
+
}
|
|
1721
|
+
if (!timestampData || !timestampData.digest) {
|
|
1722
|
+
console.log('\n🔗 Zipstamp Verification:');
|
|
1723
|
+
console.log(' - Status: ❌ ERROR');
|
|
1724
|
+
console.log(' - Error: Failed to extract timestamp metadata');
|
|
1725
|
+
return;
|
|
1726
|
+
}
|
|
1727
|
+
const merkleRoot = extractionResult?.merkleRoot ??
|
|
1728
|
+
zip.getMerkleRoot?.() ??
|
|
1729
|
+
null;
|
|
1730
|
+
if (!merkleRoot) {
|
|
1731
|
+
console.log('\n🔗 Zipstamp Verification:');
|
|
1732
|
+
console.log(' - Status: ❌ ERROR');
|
|
1733
|
+
console.log(' - Error: Merkle root not found in ZIP file');
|
|
1734
|
+
return;
|
|
1735
|
+
}
|
|
1736
|
+
if (timestampData.digest.toLowerCase() !== merkleRoot.toLowerCase()) {
|
|
1737
|
+
console.log('\n🔗 Zipstamp Verification:');
|
|
1738
|
+
console.log(' - Status: ❌ ERROR');
|
|
1739
|
+
console.log(' - Error: Timestamp digest does not match ZIP merkle root');
|
|
1740
|
+
return;
|
|
1741
|
+
}
|
|
1742
|
+
// Use verifyDigest directly - pass undefined for chainId when 0/invalid (server may reject chainId 0)
|
|
1743
|
+
const chainId = timestampData.chainId && timestampData.chainId > 0 ? timestampData.chainId : undefined;
|
|
1744
|
+
const batchId = timestampData.batchId || undefined;
|
|
1745
|
+
const response = await (0, zipstamp_server_1.verifyDigest)(merkleRoot, chainId, batchId, undefined, {
|
|
1746
|
+
debug: options.debug ?? false,
|
|
1747
|
+
});
|
|
1748
|
+
const res = response.success
|
|
1749
|
+
? response.verified
|
|
1750
|
+
? {
|
|
1751
|
+
status: 'valid',
|
|
1752
|
+
transactionHash: response.transactionHash,
|
|
1753
|
+
blockNumber: response.blockNumber,
|
|
1754
|
+
timestamp: response.timestamp,
|
|
1755
|
+
network: response.network,
|
|
1756
|
+
contractAddress: response.contractAddress,
|
|
1757
|
+
chainId: response.chainId,
|
|
1758
|
+
}
|
|
1759
|
+
: { status: 'pending', message: 'Timestamp is pending confirmation' }
|
|
1760
|
+
: { status: 'error', message: response.error || 'Verification failed' };
|
|
1761
|
+
console.log('\n🔗 Zipstamp Verification:');
|
|
1762
|
+
if (res.status === 'valid') {
|
|
1763
|
+
console.log(' - Status: ✅ VERIFIED');
|
|
1764
|
+
console.log(' - Stamp type: Zipstamp (blockchain timestamp)');
|
|
1765
|
+
if (res.network) {
|
|
1766
|
+
console.log(` - Network: ${res.network}`);
|
|
1767
|
+
}
|
|
1768
|
+
if (res.contractAddress) {
|
|
1769
|
+
console.log(` - Contract: ${res.contractAddress}`);
|
|
1770
|
+
}
|
|
1771
|
+
if (res.transactionHash) {
|
|
1772
|
+
console.log(` - Transaction: ${res.transactionHash}`);
|
|
1773
|
+
}
|
|
1774
|
+
if (res.blockNumber !== undefined) {
|
|
1775
|
+
console.log(` - Block: ${res.blockNumber}`);
|
|
1776
|
+
}
|
|
1777
|
+
if (res.timestamp !== undefined && res.timestamp != null) {
|
|
1778
|
+
const blockDate = new Date(res.timestamp * 1000);
|
|
1779
|
+
const mintedStr = blockDate.toLocaleString(undefined, {
|
|
1780
|
+
month: '2-digit',
|
|
1781
|
+
day: '2-digit',
|
|
1782
|
+
year: 'numeric',
|
|
1783
|
+
hour: '2-digit',
|
|
1784
|
+
minute: '2-digit',
|
|
1785
|
+
second: '2-digit',
|
|
1786
|
+
hour12: true,
|
|
1787
|
+
});
|
|
1788
|
+
console.log(` - Block timestamp (minted): ${mintedStr}`);
|
|
1789
|
+
}
|
|
1790
|
+
// Offer upgrade when archive has TS-SUBMIT.NZIP but batch is confirmed
|
|
1791
|
+
const hasPendingMetadata = (zipstampEntry.filename || '') === 'META-INF/TS-SUBMIT.NZIP';
|
|
1792
|
+
if (hasPendingMetadata) {
|
|
1793
|
+
let doUpgrade = false;
|
|
1794
|
+
if (options.zipstampSkipUpgrade) {
|
|
1795
|
+
doUpgrade = false;
|
|
1796
|
+
console.log(' - Upgrade: Skipped (--zipstamp-skip-upgrade)');
|
|
1797
|
+
}
|
|
1798
|
+
else if (options.zipstampAutoUpgrade) {
|
|
1799
|
+
doUpgrade = true;
|
|
1800
|
+
console.log(' - Upgrade: Automatically applying (--zipstamp-auto-upgrade)');
|
|
1801
|
+
}
|
|
1802
|
+
else {
|
|
1803
|
+
doUpgrade = await promptForZipstampUpgrade(archiveName);
|
|
1804
|
+
}
|
|
1805
|
+
if (doUpgrade) {
|
|
1806
|
+
await performZipstampUpgrade(archiveName);
|
|
1807
|
+
}
|
|
1808
|
+
else if (!options.zipstampSkipUpgrade && !options.zipstampAutoUpgrade) {
|
|
1809
|
+
console.log(' - Note: Run "neozip upgrade <archive>" to upgrade when ready');
|
|
1810
|
+
}
|
|
1811
|
+
}
|
|
1812
|
+
return;
|
|
1813
|
+
}
|
|
1814
|
+
if (res.status === 'pending') {
|
|
1815
|
+
console.log(' - Status: ⏳ PENDING (batch not yet confirmed on blockchain)');
|
|
1816
|
+
if (res.message) {
|
|
1817
|
+
console.log(` - Info: ${res.message}`);
|
|
1818
|
+
}
|
|
1819
|
+
console.log(' - Note: Run "neozip upgrade <archive>" to upgrade when confirmed');
|
|
1820
|
+
return;
|
|
1821
|
+
}
|
|
1822
|
+
if (res.status === 'error') {
|
|
1823
|
+
console.log(' - Status: ❌ ERROR');
|
|
1824
|
+
if (res.message) {
|
|
1825
|
+
console.log(` - Error: ${res.message}`);
|
|
1826
|
+
}
|
|
1827
|
+
return;
|
|
1828
|
+
}
|
|
1829
|
+
}
|
|
1830
|
+
catch (error) {
|
|
1831
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
1832
|
+
console.log('\n🔗 Zipstamp Verification:');
|
|
1833
|
+
if (errorMessage.includes('ESOCKETTIMEDOUT') || errorMessage.includes('ECONNRESET') ||
|
|
1834
|
+
errorMessage.includes('ENOTFOUND') || errorMessage.includes('ECONNREFUSED') ||
|
|
1835
|
+
errorMessage.includes('timeout') || errorMessage.includes('network')) {
|
|
1836
|
+
console.log(' - Status: 📡 COMMUNICATION ERROR (unable to contact Zipstamp server)');
|
|
1837
|
+
console.log(` - Error: ${errorMessage}`);
|
|
1838
|
+
}
|
|
1839
|
+
else {
|
|
1840
|
+
console.log(' - Status: ❌ ERROR during verification');
|
|
1841
|
+
console.log(` - Error: ${errorMessage}`);
|
|
1842
|
+
}
|
|
1843
|
+
}
|
|
1844
|
+
}
|
|
1608
1845
|
/**
|
|
1609
1846
|
* Parse command line arguments
|
|
1610
1847
|
*/
|
|
@@ -1772,6 +2009,12 @@ function parseArgs(args) {
|
|
|
1772
2009
|
case '--ots-skip-upgrade':
|
|
1773
2010
|
options.otsSkipUpgrade = true;
|
|
1774
2011
|
break;
|
|
2012
|
+
case '--zipstamp-auto-upgrade':
|
|
2013
|
+
options.zipstampAutoUpgrade = true;
|
|
2014
|
+
break;
|
|
2015
|
+
case '--zipstamp-skip-upgrade':
|
|
2016
|
+
options.zipstampSkipUpgrade = true;
|
|
2017
|
+
break;
|
|
1775
2018
|
case '--in-memory':
|
|
1776
2019
|
options.inMemory = true;
|
|
1777
2020
|
break;
|
|
@@ -1854,6 +2097,8 @@ Options:
|
|
|
1854
2097
|
-H, --hard-links Restore hard links efficiently (recreate link relationships)
|
|
1855
2098
|
--ots-auto-upgrade Automatically upgrade OTS timestamps when available
|
|
1856
2099
|
--ots-skip-upgrade Skip OTS timestamp upgrades (no prompt)
|
|
2100
|
+
--zipstamp-auto-upgrade Automatically upgrade Zipstamp timestamps when confirmed
|
|
2101
|
+
--zipstamp-skip-upgrade Skip Zipstamp timestamp upgrades (no prompt)
|
|
1857
2102
|
--in-memory Force in-memory processing mode (browser compatible)
|
|
1858
2103
|
|
|
1859
2104
|
Arguments:
|
|
@@ -1861,9 +2106,9 @@ Arguments:
|
|
|
1861
2106
|
[destination] Output directory (default: current directory)
|
|
1862
2107
|
|
|
1863
2108
|
Examples:
|
|
1864
|
-
neounzip output/calgary.nzip
|
|
1865
|
-
neounzip -t output/calgary.nzip
|
|
1866
|
-
neounzip -l output/calgary.nzip
|
|
2109
|
+
neounzip tests/output/calgary.nzip tests/extracted
|
|
2110
|
+
neounzip -t tests/output/calgary.nzip
|
|
2111
|
+
neounzip -l tests/output/calgary.nzip
|
|
1867
2112
|
`);
|
|
1868
2113
|
}
|
|
1869
2114
|
/**
|
|
@@ -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,
|