nostr-tools
Version:
Tools for making a Nostr client.
184 lines (182 loc) • 5.45 kB
JavaScript
;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// nip45.ts
var nip45_exports = {};
__export(nip45_exports, {
computeOffset: () => computeOffset,
estimateCount: () => estimateCount,
feedEvent: () => feedEvent,
feedPubkey: () => feedPubkey,
getCountManyFilter: () => getCountManyFilter,
getFilterFirstTagValue: () => getFilterFirstTagValue,
hllDecode: () => hllDecode,
hllEncode: () => hllEncode,
mergeHll: () => mergeHll,
newHll: () => newHll
});
module.exports = __toCommonJS(nip45_exports);
var import_sha2 = require("@noble/hashes/sha2.js");
var import_utils = require("@noble/hashes/utils.js");
var M = 256;
var HLL_HEX_LENGTH = M * 2;
var utf8Encoder = new TextEncoder();
function getCountManyFilter(target, directive) {
switch (directive) {
case "reactions":
return { "#e": [target], kinds: [7] };
case "reposts":
return { "#e": [target], kinds: [6] };
case "quotes":
return { "#q": [target], kinds: [1, 1111] };
case "replies":
return { "#e": [target], kinds: [1] };
case "comments":
return { "#E": [target], kinds: [1111] };
case "followers":
return { "#p": [target], kinds: [3] };
}
}
function newHll() {
return new Uint8Array(M);
}
function hllDecode(hex) {
if (hex.length !== HLL_HEX_LENGTH || !/^[0-9a-fA-F]+$/.test(hex))
return void 0;
const registers = new Uint8Array(M);
for (let i = 0; i < M; i++) {
registers[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
}
return registers;
}
function hllEncode(registers) {
if (registers.length !== M)
throw new Error(`invalid number of registers ${registers.length}`);
let hex = "";
for (let i = 0; i < M; i++) {
hex += registers[i].toString(16).padStart(2, "0");
}
return hex;
}
function computeOffset(filterFirstTagValue) {
let hex = filterFirstTagValue;
if (!isHex64(hex)) {
const parts = hex.split(":");
if (parts.length === 3 && isHex64(parts[1])) {
hex = parts[1];
} else {
hex = (0, import_utils.bytesToHex)((0, import_sha2.sha256)(utf8Encoder.encode(filterFirstTagValue)));
}
}
return parseInt(hex[32], 16) + 8;
}
function getFilterFirstTagValue(filter) {
for (const key in filter) {
if (key[0] !== "#")
continue;
const values = filter[key];
if (Array.isArray(values) && typeof values[0] === "string")
return values[0];
}
return void 0;
}
function feedPubkey(hll, pubkey, offset) {
if (offset < 0 || offset > 24)
throw new Error(`invalid offset ${offset}`);
if (!isHex64(pubkey))
throw new Error("pubkey must be 32-byte hex");
if (hll.length === 0)
hll = newHll();
if (hll.length !== M)
throw new Error(`invalid number of registers ${hll.length}`);
const ri = parseInt(pubkey.slice(offset * 2, offset * 2 + 2), 16);
const value = countLeadingZeroBitsAfterOffset(pubkey, offset) + 1;
if (value > hll[ri])
hll[ri] = value;
return hll;
}
function feedEvent(hll, event, offset) {
return feedPubkey(hll, event.pubkey, offset);
}
function mergeHll(target, source) {
if (target.length === 0)
target = newHll();
if (target.length !== M)
throw new Error(`invalid number of registers ${target.length}`);
if (source.length !== M)
throw new Error(`invalid number of registers ${source.length}`);
for (let i = 0; i < M; i++) {
if (source[i] > target[i])
target[i] = source[i];
}
return target;
}
function estimateCount(hll) {
if (hll.length === 0)
return 0;
if (hll.length !== M)
throw new Error(`invalid number of registers ${hll.length}`);
const v = countZeros(hll);
if (v !== 0) {
const lc = linearCounting(M, v);
if (lc <= 220)
return Math.floor(lc);
}
const estimate = calculateEstimate(hll);
if (estimate <= M * 3 && v !== 0)
return Math.floor(linearCounting(M, v));
return Math.floor(estimate);
}
function isHex64(value) {
return /^[0-9a-fA-F]{64}$/.test(value);
}
function countLeadingZeroBitsAfterOffset(pubkey, offset) {
let zeroBits = 0;
for (let i = offset + 1; i < offset + 8; i++) {
const byte = parseInt(pubkey.slice(i * 2, i * 2 + 2), 16);
if (byte === 0) {
zeroBits += 8;
continue;
}
let mask = 128;
while ((byte & mask) === 0) {
zeroBits++;
mask >>= 1;
}
break;
}
return zeroBits;
}
function countZeros(registers) {
let count = 0;
for (let i = 0; i < M; i++) {
if (registers[i] === 0)
count++;
}
return count;
}
function linearCounting(m, v) {
return m * Math.log(m / v);
}
function calculateEstimate(registers) {
let sum = 0;
for (let i = 0; i < M; i++) {
sum += 1 / 2 ** registers[i];
}
return 0.7182725932495458 * M * M / sum;
}