diff --git a/buffer.js b/buffer.js index 57eded4..3d50186 100644 --- a/buffer.js +++ b/buffer.js @@ -113,3 +113,20 @@ export const encodeAny = data => { * @return {any} */ export const decodeAny = buf => decoding.readAny(decoding.createDecoder(buf)) + +/** + * Shift Byte Array {N} bits to the left. Does not expand byte array. + * + * @param {Uint8Array} bs + * @param {number} N should be in the range of [0-7] + */ +export const shiftNBitsLeft = (bs, N) => { + if (N === 0) return bs + bs = new Uint8Array(bs) + bs[0] <<= N + for (let i = 1; i < bs.length; i++) { + bs[i - 1] |= bs[i] >>> (8 - N) + bs[i] <<= N + } + return bs +} diff --git a/crypto/gc2-polynomial.js b/hash/rabin-gf2-polynomial.js similarity index 65% rename from crypto/gc2-polynomial.js rename to hash/rabin-gf2-polynomial.js index 2267880..5bc90e5 100644 --- a/crypto/gc2-polynomial.js +++ b/hash/rabin-gf2-polynomial.js @@ -2,7 +2,6 @@ import * as math from '../math.js' import * as webcrypto from 'lib0/webcrypto' import * as array from '../array.js' import * as buffer from '../buffer.js' -import * as map from '../map.js' /** * @param {number} degree @@ -92,7 +91,6 @@ export const createRandom = degree => { return createFromBytes(bs) } - /** * @param {GC2Polynomial} p * @return number @@ -345,7 +343,7 @@ export const createIrreducible = degree => { */ export const fingerprint = (buf, m) => toUint8Array(mod(createFromBytes(buf), m), _degreeToMinByteLength(getHighestDegree(m) - 1)) -export class FingerprintEncoder { +export class RabinPolynomialEncoder { /** * @param {GC2Polynomial} m The irreducible polynomial */ @@ -368,162 +366,3 @@ export class FingerprintEncoder { return toUint8Array(this.fingerprint, _degreeToMinByteLength(getHighestDegree(this.m) - 1)) } } - -/** - * Shift modulo polynomial i bits to the left. Expect that bs[0] === 1. - * - * @param {Uint8Array} bs - * @param {number} lshift - */ -const _shiftBsLeft = (bs, lshift) => { - if (lshift === 0) return bs - bs = new Uint8Array(bs) - bs[0] <<= lshift - for (let i = 1; i < bs.length; i++) { - bs[i - 1] |= bs[i] >>> (8 - lshift) - bs[i] <<= lshift - } - return bs -} - -export class EfficientFingerprintEncoder { - /** - * @param {Uint8Array} m assert(m[0] === 1) - */ - constructor (m) { - this.m = m - this.blen = m.byteLength - this.bs = new Uint8Array(this.blen) - /** - * This describes the position of the most significant byte (starts with 0 and increases with - * shift) - */ - this.bpos = 0 - } - - /** - * Add/Xor/Substract bytes. - * - * Discards bytes that are out of range. - * @todo put this in function or inline - * - * @param {Uint8Array} cs - */ - add (cs) { - const copyLen = math.min(this.blen, cs.byteLength) - // copy from right to left until max is reached - for (let i = 0; i < copyLen; i++) { - this.bs[(this.bpos + this.blen - i - 1) % this.blen] ^= cs[cs.byteLength - i - 1] - } - } - - /** - * @param {number} byte - */ - write (byte) { - // [0,m1,m2,b] - // x <- bpos - // Shift one byte to the left, add b - this.bs[this.bpos] = byte - this.bpos = (this.bpos + 1) % this.blen - // mod - for (let i = 7; i >= 0; i--) { - if (((this.bs[this.bpos] >>> i) & 1) === 1) { - this.add(_shiftBsLeft(this.m, i)) - } - } - // if (this.bs[this.bpos] !== 0) { error.unexpectedCase() } - // assert(this.bs[this.bpos] === 0) - } - - getFingerprint () { - const result = new Uint8Array(this.blen - 1) - for (let i = 0; i < result.byteLength; i++) { - result[i] = this.bs[(this.bpos + i + 1) % this.blen] - } - return result - } -} - -/** - * Maps from a modulo to the precomputed values. - * - * @type {Map} - */ -const _precomputedFingerprintCache = new Map() - -/** - * @param {Uint8Array} m - */ -const ensureCache = m => map.setIfUndefined(_precomputedFingerprintCache, buffer.toBase64(m), () => { - const byteLen = m.byteLength - const cache = new Uint8Array(256 * byteLen) - // Use dynamic computing to compute the cached results. - // Starting values: cache(0) = 0; cache(1) = m - cache.set(m, byteLen) - for (let bit = 1; bit < 8; bit++) { - const mBitShifted = _shiftBsLeft(m, bit) - const bitShifted = 1 << bit - for (let j = 0; j < bitShifted; j++) { - // rest is already precomputed - const msb = bitShifted | j - const rest = msb ^ mBitShifted[0] - for (let i = 0; i < byteLen; i++) { - cache[msb * byteLen + i] = cache[rest * byteLen + i] ^ mBitShifted[i] - } - // if (cache[(bitShifted | j) * byteLen] !== (bitShifted | j)) { error.unexpectedCase() } - } - } - return cache -}) - -export class CachedEfficientFingerprintEncoder { - /** - * @param {Uint8Array} m assert(m[0] === 1) - */ - constructor (m) { - this.m = m - this.blen = m.byteLength - this.bs = new Uint8Array(this.blen) - this.cache = ensureCache(m) - /** - * This describes the position of the most significant byte (starts with 0 and increases with - * shift) - */ - this.bpos = 0 - } - - /** - * @param {number} byte - */ - write (byte) { - // [0,m1,m2,b] - // x <- bpos - // Shift one byte to the left, add b - this.bs[this.bpos] = byte - this.bpos = (this.bpos + 1) % this.blen - const msb = this.bs[this.bpos] - for (let i = 0; i < this.blen; i++) { - this.bs[(this.bpos + i) % this.blen] ^= this.cache[msb * this.blen + i] - } - // if (this.bs[this.bpos] !== 0) { error.unexpectedCase() } - } - - getFingerprint () { - const result = new Uint8Array(this.blen - 1) - for (let i = 0; i < result.byteLength; i++) { - result[i] = this.bs[(this.bpos + i + 1) % this.blen] - } - return result - } -} - -export const StandardIrreducible8 = new Uint8Array([1, 189]) - -export const StandardIrreducible16 = new Uint8Array([1, 244, 157]) - -export const StandardIrreducible32 = new Uint8Array([1, 149, 183, 205, 191]) - -export const StandardIrreducible64 = new Uint8Array([1, 133, 250, 114, 193, 250, 28, 193, 231]) - -export const StandardIrreducible128 = new Uint8Array([1, 94, 109, 166, 228, 6, 222, 102, 239, 27, 128, 184, 13, 50, 112, 169, 199]) diff --git a/hash/rabin-uncached.js b/hash/rabin-uncached.js new file mode 100644 index 0000000..11ddd50 --- /dev/null +++ b/hash/rabin-uncached.js @@ -0,0 +1,61 @@ +import * as math from '../math.js' +import * as buffer from '../buffer.js' + +export class RabinUncachedEncoder { + /** + * @param {Uint8Array} m assert(m[0] === 1) + */ + constructor (m) { + this.m = m + this.blen = m.byteLength + this.bs = new Uint8Array(this.blen) + /** + * This describes the position of the most significant byte (starts with 0 and increases with + * shift) + */ + this.bpos = 0 + } + + /** + * Add/Xor/Substract bytes. + * + * Discards bytes that are out of range. + * @todo put this in function or inline + * + * @param {Uint8Array} cs + */ + add (cs) { + const copyLen = math.min(this.blen, cs.byteLength) + // copy from right to left until max is reached + for (let i = 0; i < copyLen; i++) { + this.bs[(this.bpos + this.blen - i - 1) % this.blen] ^= cs[cs.byteLength - i - 1] + } + } + + /** + * @param {number} byte + */ + write (byte) { + // [0,m1,m2,b] + // x <- bpos + // Shift one byte to the left, add b + this.bs[this.bpos] = byte + this.bpos = (this.bpos + 1) % this.blen + // mod + for (let i = 7; i >= 0; i--) { + if (((this.bs[this.bpos] >>> i) & 1) === 1) { + this.add(buffer.shiftNBitsLeft(this.m, i)) + } + } + // if (this.bs[this.bpos] !== 0) { error.unexpectedCase() } + // assert(this.bs[this.bpos] === 0) + } + + getFingerprint () { + const result = new Uint8Array(this.blen - 1) + for (let i = 0; i < result.byteLength; i++) { + result[i] = this.bs[(this.bpos + i + 1) % this.blen] + } + return result + } +} diff --git a/hash/rabin.js b/hash/rabin.js new file mode 100644 index 0000000..e84c349 --- /dev/null +++ b/hash/rabin.js @@ -0,0 +1,80 @@ +import * as buffer from '../buffer.js' +import * as map from '../map.js' + +export const StandardIrreducible8 = new Uint8Array([1, 189]) +export const StandardIrreducible16 = new Uint8Array([1, 244, 157]) +export const StandardIrreducible32 = new Uint8Array([1, 149, 183, 205, 191]) +export const StandardIrreducible64 = new Uint8Array([1, 133, 250, 114, 193, 250, 28, 193, 231]) +export const StandardIrreducible128 = new Uint8Array([1, 94, 109, 166, 228, 6, 222, 102, 239, 27, 128, 184, 13, 50, 112, 169, 199]) + +/** + * Maps from a modulo to the precomputed values. + * + * @type {Map} + */ +const _precomputedFingerprintCache = new Map() + +/** + * @param {Uint8Array} m + */ +const ensureCache = m => map.setIfUndefined(_precomputedFingerprintCache, buffer.toBase64(m), () => { + const byteLen = m.byteLength + const cache = new Uint8Array(256 * byteLen) + // Use dynamic computing to compute the cached results. + // Starting values: cache(0) = 0; cache(1) = m + cache.set(m, byteLen) + for (let bit = 1; bit < 8; bit++) { + const mBitShifted = buffer.shiftNBitsLeft(m, bit) + const bitShifted = 1 << bit + for (let j = 0; j < bitShifted; j++) { + // rest is already precomputed + const msb = bitShifted | j + const rest = msb ^ mBitShifted[0] + for (let i = 0; i < byteLen; i++) { + cache[msb * byteLen + i] = cache[rest * byteLen + i] ^ mBitShifted[i] + } + // if (cache[(bitShifted | j) * byteLen] !== (bitShifted | j)) { error.unexpectedCase() } + } + } + return cache +}) + +export class RabinEncoder { + /** + * @param {Uint8Array} m assert(m[0] === 1) + */ + constructor (m) { + this.m = m + this.blen = m.byteLength + this.bs = new Uint8Array(this.blen) + this.cache = ensureCache(m) + /** + * This describes the position of the most significant byte (starts with 0 and increases with + * shift) + */ + this.bpos = 0 + } + + /** + * @param {number} byte + */ + write (byte) { + // assert(this.bs[0] === 0) + // Shift one byte to the left, add b + this.bs[this.bpos] = byte + this.bpos = (this.bpos + 1) % this.blen + const msb = this.bs[this.bpos] + for (let i = 0; i < this.blen; i++) { + this.bs[(this.bpos + i) % this.blen] ^= this.cache[msb * this.blen + i] + } + // assert(this.bs[this.bpos] !== 0) + } + + getFingerprint () { + const result = new Uint8Array(this.blen - 1) + for (let i = 0; i < result.byteLength; i++) { + result[i] = this.bs[(this.bpos + i + 1) % this.blen] + } + return result + } +} diff --git a/crypto/gc2-polynomial.test.js b/hash/rabin.test.js similarity index 67% rename from crypto/gc2-polynomial.test.js rename to hash/rabin.test.js index 6ba6e35..d802b9a 100644 --- a/crypto/gc2-polynomial.test.js +++ b/hash/rabin.test.js @@ -1,5 +1,7 @@ import * as t from '../testing.js' -import * as gc2 from './gc2-polynomial.js' +import * as gf2 from './rabin-gf2-polynomial.js' +import { RabinUncachedEncoder } from './rabin-uncached.js' +import * as rabin from './rabin.js' import * as math from '../math.js' import * as array from '../array.js' import * as prng from '../prng.js' @@ -10,7 +12,7 @@ import * as buffer from '../buffer.js' */ export const testPolynomialBasics = _tc => { const bs = new Uint8Array([1, 11]) - const p = gc2.createFromBytes(bs) + const p = gf2.createFromBytes(bs) t.assert(p.degrees.has(3)) t.assert(p.degrees.has(1)) t.assert(p.degrees.has(0)) @@ -21,16 +23,16 @@ export const testPolynomialBasics = _tc => { * @param {t.TestCase} _tc */ export const testIrreducibleInput = _tc => { - const pa = gc2.createFromUint(0x53) - const pb = gc2.createFromUint(0xCA) - const pm = gc2.createFromUint(0x11B) - const px = gc2.multiply(pa, pb) - t.compare(new Uint8Array([0x53]), gc2.toUint8Array(pa)) - t.compare(new Uint8Array([0xCA]), gc2.toUint8Array(pb)) - t.assert(gc2.equals(gc2.createFromUint(0x3F7E), px)) - t.compare(new Uint8Array([0x3F, 0x7E]), gc2.toUint8Array(px)) - const pabm = gc2.mod(px, pm) - t.compare(new Uint8Array([0x1]), gc2.toUint8Array(pabm)) + const pa = gf2.createFromUint(0x53) + const pb = gf2.createFromUint(0xCA) + const pm = gf2.createFromUint(0x11B) + const px = gf2.multiply(pa, pb) + t.compare(new Uint8Array([0x53]), gf2.toUint8Array(pa)) + t.compare(new Uint8Array([0xCA]), gf2.toUint8Array(pb)) + t.assert(gf2.equals(gf2.createFromUint(0x3F7E), px)) + t.compare(new Uint8Array([0x3F, 0x7E]), gf2.toUint8Array(px)) + const pabm = gf2.mod(px, pm) + t.compare(new Uint8Array([0x1]), gf2.toUint8Array(pabm)) } /** @@ -52,9 +54,9 @@ export const testIrreducibleSpread = _tc => { const getSpreadAverage = (degree, tests) => { const spreads = [] for (let i = 0, test = 0, lastI = 0; test < tests; i++) { - const f = gc2.createRandom(degree) - t.assert(gc2.getHighestDegree(f) === degree) - if (gc2.isIrreducibleBenOr(f)) { + const f = gf2.createRandom(degree) + t.assert(gf2.getHighestDegree(f) === degree) + if (gf2.isIrreducibleBenOr(f)) { const spread = i - lastI spreads.push(spread) lastI = i @@ -73,9 +75,9 @@ export const testGenerateIrreducibles = _tc => { */ const testIrreducibleGen = byteLen => { const K = byteLen * 8 - const irr = gc2.createIrreducible(K) - t.assert(gc2.getHighestDegree(irr) === K, 'degree equals K') - const irrBs = gc2.toUint8Array(irr) + const irr = gf2.createIrreducible(K) + t.assert(gf2.getHighestDegree(irr) === K, 'degree equals K') + const irrBs = gf2.toUint8Array(irr) console.log(`K = ${K}`, irrBs) t.assert(irrBs[0] === 1) t.assert(irrBs.byteLength === byteLen + 1) @@ -85,18 +87,18 @@ export const testGenerateIrreducibles = _tc => { testIrreducibleGen(4) testIrreducibleGen(8) testIrreducibleGen(16) - gc2.isIrreducibleBenOr(gc2.createFromBytes(gc2.StandardIrreducible8)) - gc2.isIrreducibleBenOr(gc2.createFromBytes(gc2.StandardIrreducible16)) - gc2.isIrreducibleBenOr(gc2.createFromBytes(gc2.StandardIrreducible32)) - gc2.isIrreducibleBenOr(gc2.createFromBytes(gc2.StandardIrreducible64)) - gc2.isIrreducibleBenOr(gc2.createFromBytes(gc2.StandardIrreducible128)) + gf2.isIrreducibleBenOr(gf2.createFromBytes(rabin.StandardIrreducible8)) + gf2.isIrreducibleBenOr(gf2.createFromBytes(rabin.StandardIrreducible16)) + gf2.isIrreducibleBenOr(gf2.createFromBytes(rabin.StandardIrreducible32)) + gf2.isIrreducibleBenOr(gf2.createFromBytes(rabin.StandardIrreducible64)) + gf2.isIrreducibleBenOr(gf2.createFromBytes(rabin.StandardIrreducible128)) } /** * @param {t.TestCase} tc * @param {number} K */ -export const _testFingerprintK = (tc, K) => { +const _testFingerprintK = (tc, K) => { /** * @type {Array} */ @@ -105,7 +107,7 @@ export const _testFingerprintK = (tc, K) => { const MSIZE = 130 t.info(`N=${N} K=${K} MSIZE=${MSIZE}`) /** - * @type {gc2.GC2Polynomial} + * @type {gf2.GC2Polynomial} */ let irreducible /** @@ -113,8 +115,8 @@ export const _testFingerprintK = (tc, K) => { */ let irreducibleBuffer t.measureTime(`find irreducible of ${K}`, () => { - irreducible = gc2.createIrreducible(K) - irreducibleBuffer = gc2.toUint8Array(irreducible) + irreducible = gf2.createIrreducible(K) + irreducibleBuffer = gf2.toUint8Array(irreducible) }) for (let i = 0; i < N; i++) { dataObjects.push(prng.uint8Array(tc.prng, MSIZE)) @@ -124,7 +126,7 @@ export const _testFingerprintK = (tc, K) => { */ let fingerprints1 = [] t.measureTime('polynomial direct', () => { - fingerprints1 = dataObjects.map((o, _index) => gc2.fingerprint(o, irreducible)) + fingerprints1 = dataObjects.map((o, _index) => gf2.fingerprint(o, irreducible)) }) const testSet = new Set(fingerprints1.map(buffer.toBase64)) t.assert(K < 32 || testSet.size === N) @@ -134,7 +136,7 @@ export const _testFingerprintK = (tc, K) => { let fingerprints2 = [] t.measureTime('polynomial incremental', () => { fingerprints2 = dataObjects.map((o, _index) => { - const encoder = new gc2.FingerprintEncoder(irreducible) + const encoder = new gf2.RabinPolynomialEncoder(irreducible) for (let i = 0; i < o.byteLength; i++) { encoder.write(o[i]) } @@ -148,7 +150,7 @@ export const _testFingerprintK = (tc, K) => { let fingerprints3 = [] t.measureTime('polynomial incremental (efficent))', () => { fingerprints3 = dataObjects.map((o, _index) => { - const encoder = new gc2.EfficientFingerprintEncoder(irreducibleBuffer) + const encoder = new RabinUncachedEncoder(irreducibleBuffer) for (let i = 0; i < o.byteLength; i++) { encoder.write(o[i]) } @@ -162,7 +164,7 @@ export const _testFingerprintK = (tc, K) => { let fingerprints4 = [] t.measureTime('polynomial incremental (efficent & cached))', () => { fingerprints4 = dataObjects.map((o, _index) => { - const encoder = new gc2.CachedEfficientFingerprintEncoder(irreducibleBuffer) + const encoder = new rabin.RabinEncoder(irreducibleBuffer) for (let i = 0; i < o.byteLength; i++) { encoder.write(o[i]) } diff --git a/package.json b/package.json index 5570fef..c4be3d7 100644 --- a/package.json +++ b/package.json @@ -83,19 +83,25 @@ "types": "./crypto/aes-gcm.d.ts", "module": "./crypto/aes-gcm.js", "import": "./crypto/aes-gcm.js", - "require": "./dist/crypto/aes-gcm.cjs" + "require": "./dist/aes-gcm.cjs" }, "./crypto/ecdsa": { "types": "./crypto/ecdsa.d.ts", "module": "./crypto/ecdsa.js", "import": "./crypto/ecdsa.js", - "require": "./dist/crypto/ecdsa.cjs" + "require": "./dist/ecdsa.cjs" }, "./crypto/rsa-oaep": { "types": "./crypto/rsa-oaep.d.ts", "module": "./crypto/rsa-oaep.js", "import": "./crypto/rsa-oaep.js", - "require": "./dist/crypto/rsa-oaep.cjs" + "require": "./dist/rsa-oaep.cjs" + }, + "./hash/rabin": { + "types": "./hash/rabin.d.ts", + "module": "./hash/rabin.js", + "import": "./hash/rabin.js", + "require": "./dist/rabin.cjs" }, "./decoding.js": "./decoding.js", "./dist/decoding.cjs": "./dist/decoding.cjs", diff --git a/rollup.config.js b/rollup.config.js index 3fa4f52..6dea38c 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -1,6 +1,9 @@ import * as fs from 'fs' -const files = fs.readdirSync('./').filter(file => /(? fs.readdirSync(root).map(f => root + f)).flat().filter(file => /(?