if you want a perfectly uniform distribution just replace it by a shuffled array of all numbers from 0-N.
Results will be the same.
Your script is biased in the following ways:1- It does not use H160.
2- It loads the entire set, which means it does not take into account the computational power saved in operations on the curve, double SHA256, and RIPEMD-160 when searching statistically.
3- It does not apply a viable prefix logic.
However, here is this slightly adjusted script, using short prefixes and skipping the computational power saved in operations on the curve and double-SHA256.
It's not 100% fair for what you want in a search by prefixes since it’s implemented at half-capacity, but at least you’ll be able to verify that it’s not as useless as it might seem.
const { performance } = require("perf_hooks");
const crypto = require("crypto");
const TOTAL_SIZE = 100_000;
const RANGE_SIZE = 5_000;
const PREFIX_LENGTH = 3;
const PREFIX_THRESHOLD = 1;
const NUMBER_SIMULATIONS = 500;
const SKIP_SIZE = Math.round(4096 * 0.3);
console.log(`
=== Configuration ===
Total numbers: ${TOTAL_SIZE.toLocaleString()}
Block size: ${RANGE_SIZE.toLocaleString()}
Prefix: ${PREFIX_LENGTH} chars (${16 ** PREFIX_LENGTH} comb.)
Skip ${SKIP_SIZE} keys per prefix match
Threshold: ${PREFIX_THRESHOLD} matches/block
Simulations: ${NUMBER_SIMULATIONS}
`);
function generateH160(data) {
return crypto.createHash("ripemd160").update(data.toString()).digest("hex");
}
function shuffledRange(n) {
const arr = Array.from({ length: n + 1 }, (_, i) => i);
for (let i = arr.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[arr[i], arr[j]] = [arr[j], arr[i]];
}
return arr;
}
function sequentialRandom(totalSize, rangeSize, targetHash) {
const numRanges = Math.floor(totalSize / rangeSize);
const rangeOrder = shuffledRange(numRanges - 1);
let keyChecks = 0;
for (const blockIndex of rangeOrder) {
const start = blockIndex * rangeSize;
const end = (blockIndex + 1) * rangeSize;
for (let num = start; num < end; num++) {
const hash = generateH160(num);
keyChecks++;
if (hash === targetHash) return keyChecks;
}
}
return keyChecks;
}
function preciseSkipSearch(totalSize, rangeSize, prefixLength, targetHash) {
const numRanges = Math.floor(totalSize / rangeSize);
const rangeOrder = shuffledRange(numRanges - 1);
let keyChecks = 0;
const targetPrefix = targetHash.substring(0, prefixLength);
const skippedRanges = [];
for (const blockIndex of rangeOrder) {
const start = blockIndex * rangeSize;
const end = (blockIndex + 1) * rangeSize;
let prefixCount = 0;
let current = start;
while (current < end && prefixCount < PREFIX_THRESHOLD) {
const hash = generateH160(current);
keyChecks++;
if (hash === targetHash) return keyChecks;
if (hash.startsWith(targetPrefix)) {
prefixCount++;
const skipStart = current + 1;
const skipEnd = Math.min(current + SKIP_SIZE, end);
if (skipStart < skipEnd) {
skippedRanges.push({ blockIndex, start: skipStart, end: skipEnd });
}
current = skipEnd;
} else {
current++;
}
}
if (prefixCount >= PREFIX_THRESHOLD) {
skippedRanges.push({
blockIndex,
start: current + 1,
end: end - 1
});
}
}
const shuffledIndices = shuffledRange(skippedRanges.length - 1);
for (const i of shuffledIndices) {
const { start, end } = skippedRanges[i];
for (let num = start; num <= end; num++) {
const hash = generateH160(num);
keyChecks++;
if (hash === targetHash) return keyChecks;
}
}
return keyChecks;
}
async function compareMethods() {
console.log("Warm-up...");
for (let i = 0; i < 5; i++) generateH160(i);
const results = {
sequential: { checks: [], times: [] },
precise: { checks: [], times: [] }
};
console.log("\nStarting comparison...");
const startTime = performance.now();
for (let i = 0; i < NUMBER_SIMULATIONS; i++) {
const targetNum = Math.floor(Math.random() * TOTAL_SIZE);
const targetHash = generateH160(targetNum);
if (i % 2 === 0) {
const startSeq = performance.now();
results.sequential.checks.push(sequentialRandom(TOTAL_SIZE, RANGE_SIZE, targetHash));
results.sequential.times.push(performance.now() - startSeq);
const startPrecise = performance.now();
results.precise.checks.push(preciseSkipSearch(TOTAL_SIZE, RANGE_SIZE, PREFIX_LENGTH, targetHash));
results.precise.times.push(performance.now() - startPrecise);
} else {
const startPrecise = performance.now();
results.precise.checks.push(preciseSkipSearch(TOTAL_SIZE, RANGE_SIZE, PREFIX_LENGTH, targetHash));
results.precise.times.push(performance.now() - startPrecise);
const startSeq = performance.now();
results.sequential.checks.push(sequentialRandom(TOTAL_SIZE, RANGE_SIZE, targetHash));
results.sequential.times.push(performance.now() - startSeq);
}
if ((i + 1) % 10 === 0) console.log(`Test ${i + 1}/${NUMBER_SIMULATIONS}`);
}
const calculateStats = (data) => {
const avg = data.reduce((a, b) => a + b, 0) / data.length;
const squaredDiffs = data.map(x => Math.pow(x - avg, 2));
const variance = squaredDiffs.reduce((a, b) => a + b, 0) / data.length;
const stdDev = Math.sqrt(variance);
return { avg, min: Math.min(...data), max: Math.max(...data), stdDev };
};
const seqStats = calculateStats(results.sequential.checks);
const preciseStats = calculateStats(results.precise.checks);
const seqTime = results.sequential.times.reduce((a, b) => a + b, 0) / NUMBER_SIMULATIONS;
const preciseTime = results.precise.times.reduce((a, b) => a + b, 0) / NUMBER_SIMULATIONS;
const improvement = ((seqStats.avg - preciseStats.avg) / seqStats.avg * 100).toFixed(2);
const timeImprovement = ((seqTime - preciseTime) / seqTime * 100).toFixed(2);
console.log(`
=== FINAL RESULTS ===
Random Sequential:
- Average comparisons: ${seqStats.avg.toFixed(0)}
- Average time: ${(seqTime / 1000).toFixed(3)}s
- Range: [${seqStats.min} - ${seqStats.max}]
Prefixes Search (${SKIP_SIZE} keys skip + threshold ${PREFIX_THRESHOLD}):
- Average comparisons: ${preciseStats.avg.toFixed(0)}
- Average time: ${(preciseTime / 1000).toFixed(3)}s
- Range: [${preciseStats.min} - ${preciseStats.max}]
=== CONCLUSION ===
The ${preciseStats.avg < seqStats.avg ? 'PREFIXES' : 'RANDOM+SEQUENTIAL'} method is more efficient:
- ${improvement}% fewer comparisons
- ${timeImprovement}% faster
`);
console.log(`Total time: ${((performance.now() - startTime)/1000).toFixed(1)} seconds`);
}
compareMethods();