95 lines
2.6 KiB
JavaScript
95 lines
2.6 KiB
JavaScript
#!/usr/bin/env node
|
|
|
|
const fs = require('fs');
|
|
const crypto = require('crypto');
|
|
|
|
function computeMerkleRoot(filename = null, blockSize = 1024 * 1024) {
|
|
let data;
|
|
|
|
if (filename) {
|
|
// Read file synchronously
|
|
data = fs.readFileSync(filename);
|
|
} else {
|
|
// Read from stdin
|
|
data = fs.readFileSync(0); // 0 is stdin file descriptor
|
|
}
|
|
|
|
const stack = [];
|
|
let offset = 0;
|
|
|
|
// Process blocks
|
|
while (offset < data.length) {
|
|
const block = data.slice(offset, offset + blockSize);
|
|
|
|
// Create a node: [tree-level, hash]
|
|
const hash = crypto.createHash('sha256').update(block).digest();
|
|
const node = [0, hash];
|
|
stack.push(node);
|
|
|
|
// Concatenate adjacent pairs at the same level
|
|
while (stack.length >= 2 && stack[stack.length - 2][0] === stack[stack.length - 1][0]) {
|
|
const a = stack[stack.length - 2];
|
|
const b = stack[stack.length - 1];
|
|
const level = a[0];
|
|
|
|
// Combine hashes
|
|
const combinedHash = crypto.createHash('sha256')
|
|
.update(Buffer.concat([a[1], b[1]]))
|
|
.digest();
|
|
|
|
// Replace last two elements with combined node
|
|
stack.splice(-2, 2, [level + 1, combinedHash]);
|
|
}
|
|
|
|
offset += blockSize;
|
|
}
|
|
|
|
// Combine remaining nodes even across different levels
|
|
while (stack.length > 1) {
|
|
const a = stack[stack.length - 2];
|
|
const b = stack[stack.length - 1];
|
|
const al = a[0];
|
|
const bl = b[0];
|
|
|
|
const combinedHash = crypto.createHash('sha256')
|
|
.update(Buffer.concat([a[1], b[1]]))
|
|
.digest();
|
|
|
|
// Replace last two elements with combined node
|
|
stack.splice(-2, 2, [Math.max(al, bl) + 1, combinedHash]);
|
|
}
|
|
|
|
return stack[0][1].toString('hex');
|
|
}
|
|
|
|
// Command line interface
|
|
function main() {
|
|
const args = process.argv.slice(2);
|
|
let filename = null;
|
|
let blockSize = 1024 * 1024; // Default 1MB
|
|
|
|
// Simple argument parsing
|
|
for (let i = 0; i < args.length; i++) {
|
|
if (args[i] === '--block-size' && i + 1 < args.length) {
|
|
blockSize = parseInt(args[i + 1]);
|
|
i++; // Skip next argument
|
|
} else if (!args[i].startsWith('--')) {
|
|
filename = args[i];
|
|
}
|
|
}
|
|
|
|
try {
|
|
const result = computeMerkleRoot(filename, blockSize);
|
|
console.log(result);
|
|
} catch (error) {
|
|
console.error('Error:', error.message);
|
|
process.exit(1);
|
|
}
|
|
}
|
|
|
|
// Run if this script is executed directly
|
|
if (require.main === module) {
|
|
main();
|
|
}
|
|
|
|
module.exports = { computeMerkleRoot };
|