moolb-js

Bloom filter for javascript with pluggable hasher backend
git clone git://git.defalsify.org/moolb-js.git
Log | Files | Refs | README

commit 8543b07c1ca2a68f756e1f878b92463d38732a46
parent 7cd7011ab5041fb81667c514a86cab12a113a4ed
Author: nolash <dev@holbrook.no>
Date:   Fri, 30 Oct 2020 20:15:54 +0100

Change inputs to uint8array, move execution code to mocha test

Diffstat:
Dbloom.js | 80-------------------------------------------------------------------------------
Amoolb/moolb.js | 108+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Apackage.json | 27+++++++++++++++++++++++++++
3 files changed, 135 insertions(+), 80 deletions(-)

diff --git a/bloom.js b/bloom.js @@ -1,80 +0,0 @@ -let sha = require('jssha'); - -// block numbers 6000000 -// false positive probability 2% -// -// m = ceil((n * log(p)) / log(1 / pow(2, log(2)))); -// m = ceil((6000000 * log(0.1)) / log(1 / pow(2, log(2)))) -// = 3917675 -//var bloomBits = 29600000; -var bloomBits = 8192 * 8; -var bloomBytes = bloomBits / 8; - -// hashing rounds to match a filter entry -var bloomRounds = 3; - -// Creates a new bloom object. -// \param bytes optional, set the filter to Uint8Array of bytes of length according to global var bloomBytes. If not set, creates a new empty filter -function Bloom(bytes) { - if (bytes !== undefined) { - if (bytes.length != bloomBytes) { - console.error("bloom bytes must be length " + bloomBytes + ", have " + bytes.length); - return false; - } - this.filter = new Uint8Array(bytes); - } else { - this.filter = new Uint8Array(bloomBytes); - } -} - -// add entry to bloom filter -// \param blockNumber block number to add -Bloom.prototype.add = function(blockNumber) { - for (var i = 0; i < bloomRounds; i++) { - var result = hashBloom(blockNumber, i); - console.log(result) - var bytepos = parseInt(parseInt(result, 10) / 8, 10); - var bitpos = parseInt(result, 10) % 8; - this.filter[bytepos] |= 1 << bitpos; - console.debug("setpos ", bytepos, bitpos); - } -}; - -// checks if the block number has been added to the bloom filter -// \param blockNumber block number to check -// \return false if not found in filter -// \todo check if we can merely return on false -Bloom.prototype.check = function(blockNumber) { - var ok = true; - for (var i = 0; i < bloomRounds; i++) { - var result = hashBloom(blockNumber, i); - var bytepos = parseInt(parseInt(result, 10) / 8, 10); - if (this.filter[bytepos] == undefined) { - ok = false; - } else { - var test = 1 << (0xff & (parseInt(result, 10) % 8)); - if ((this.filter[bytepos] & test) == 0) { - ok = false; - } - } - } - return ok; -}; - - -// a single bloom hash round -function hashBloom(data, salt) { - var h = new sha("SHA-256", "TEXT"); - console.log('hashing ', data.toString(), salt.toString()); - h.update(data.toString()); - h.update(salt.toString()); - hx = "0x"+h.getHash("HEX") - console.log('hx', hx) - return parseInt(BigInt(hx) % BigInt(bloomBits), 10); -} - -a = new Uint8Array(bloomBytes); -b = new Bloom(a); -b.add(1024); -console.log(b.check(1024)); -console.log(b.check(1023)); diff --git a/moolb/moolb.js b/moolb/moolb.js @@ -0,0 +1,108 @@ +module.exports = { + 'Bloom': Bloom, +} + +let crypto = require('crypto'); + +// block numbers 6000000 +// false positive probability 2% +// +// m = ceil((n * log(p)) / log(1 / pow(2, log(2)))); +// m = ceil((6000000 * log(0.1)) / log(1 / pow(2, log(2)))) +// = 3917675 + +// Creates a new bloom object. +// \param size of filter in bits, aligned to byte boundary +// \param number of rounds to hash +// \param hasher function, which must take two Uint8Array parameters 'data' and 'salt'. If not sef, hashBloomDefault will be used. +function Bloom(bits, rounds, hasher) { + this.bits = bits; + this.bytes = parseInt(bits / 8, 10); + this.rounds = rounds; + if (this.hasher === undefined) { + this.hasher = hashBloomDefault; + } + if (this.bytes * 8 != this.bits) { + console.error('number of bits must be on byte boundary'); + return false; + } else { + this.filter = new Uint8Array(this.bytes); + } +} + +// add entry to bloom filter +// \param value to add +Bloom.prototype.add = function(v) { + a = new ArrayBuffer(4); + iw = new DataView(a); + for (var i = 0; i < this.rounds; i++) { + iw.setUint32(0, i); + let result = this.hasher(v, iw); + console.debug(result) + let resultHex = Array.prototype.map.call(new Uint8Array(result), x => ('00' + x.toString(16)).slice(-2)).join(''); + let resultInt = parseInt(BigInt('0x'+resultHex) % BigInt(this.bits), 10); + let bytepos = parseInt(resultInt / 8, 10); + let bitpos = parseInt(resultInt, 10) % 8; + this.filter[bytepos] |= 1 << bitpos; + console.debug("setpos ", bytepos, bitpos); + } +}; + +// checks if the block number has been added to the bloom filter +// \param value to check for +// \return false if not found in filter +Bloom.prototype.check = function(v) { + var ok = true; + a = new ArrayBuffer(4); + iw = new DataView(a); + for (let i = 0; i < this.rounds; i++) { + iw.setUint32(0, i); + let result = this.hasher(v, iw); + let resultHex = Array.prototype.map.call(new Uint8Array(result), x => ('00' + x.toString(16)).slice(-2)).join(''); + let resultInt = parseInt(BigInt('0x'+resultHex) % BigInt(this.bits), 10); + let bytepos = parseInt(resultInt / 8, 10); + if (this.filter[bytepos] == undefined) { + console.error('byte pos ' + bytepos + ' is undefined!'); + return false; + //ok = false; + } else { + let test = 1 << (0xff & (resultInt % 8)); + if ((this.filter[bytepos] & test) == 0) { + return false; + //ok = false; + } + } + } + return ok; +}; + +// return the verbatim filter +// \return Uint8Array filter +Bloom.prototype.bytes = function() { + return this.filter; +}; + + +// Default hashing function used in Bloom.add() - sha256 +// \param data to insert in filter +// \param salt, typically a sequence number +// \return Uint8Array digest +function hashBloomDefault(data, salt) { + h = crypto.createHash('sha256'); + h.update(data); + h.update(salt); + //return Uint8Array.from(h.digest()); + return h.digest(); +} + + +// a single bloom hash round +function hashBloom(data, salt) { + var h = new sha("SHA-256", "TEXT"); + console.log('hashing ', data.toString(), salt.toString()); + h.update(data.toString()); + h.update(salt.toString()); + hx = "0x"+h.getHash("HEX") + console.log('hx', hx) + return parseInt(BigInt(hx) % BigInt(bloomBits), 10); +} diff --git a/package.json b/package.json @@ -0,0 +1,27 @@ +{ + "name": "moolb", + "version": "0.0.4", + "description": "Simple bloom filter with pluggable hash backend", + "main": "dist/index.js", + "directories": { + "tests": "new_test" + }, + "files": [ + "moolb" + ], + "dependencies": { + "mocha": "^7.2.0" + }, + "scripts": { + "test": "mocha --reporter spec ./tests" + }, + "repository": { + "type": "git", + "url": "https://gitlab.com/nolash/block-syncer-js" + }, + "author": "Louis Holbrook <npm@holbrook.no> (https://holbrook.no)", + "license": "GPL-3.0-or-later", + "engine": { + "node": "^12.0.0" + } +}