From df854d3e3a96f7b00a1bbaa0aad140184a88c262 Mon Sep 17 00:00:00 2001 From: Kevin Jahns Date: Wed, 21 Jun 2023 14:33:55 +0200 Subject: [PATCH] [fingerprint] almost full test coverage --- crypto/gc2-polynomial.js | 95 ++++++++++------------------------- crypto/gc2-polynomial.test.js | 60 +++++++++++++++++++--- number.test.js | 7 ++- 3 files changed, 83 insertions(+), 79 deletions(-) diff --git a/crypto/gc2-polynomial.js b/crypto/gc2-polynomial.js index 1f4c1c9..d208d74 100644 --- a/crypto/gc2-polynomial.js +++ b/crypto/gc2-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 error from '../error.js' import * as map from '../map.js' /** @@ -26,6 +25,8 @@ export class GC2Polynomial { } /** + * From Uint8Array (MSB). + * * @param {Uint8Array} bytes */ export const createFromBytes = bytes => { @@ -43,25 +44,8 @@ export const createFromBytes = bytes => { } /** - * Least-significant-byte-first + * Transform to Uint8Array (MSB). * - * @param {Uint8Array} bytes - */ -export const createFromBytesLsb = bytes => { - const p = new GC2Polynomial() - for (let bsi = 0, currDegree = 0; bsi < bytes.length; bsi++) { - const currByte = bytes[bsi] - for (let i = 0; i < 8; i++) { - if (((currByte >>> i) & 1) === 1) { - p.degrees.add(currDegree) - } - currDegree++ - } - } - return p -} - -/** * @param {GC2Polynomial} p * @param {number} byteLength */ @@ -78,23 +62,6 @@ export const toUint8Array = (p, byteLength = _degreeToMinByteLength(getHighestDe return buf } -/** - * @param {GC2Polynomial} p - * @param {number} byteLength - */ -export const toUint8ArrayLsb = (p, byteLength) => { - const buf = buffer.createUint8ArrayFromLen(byteLength) - /** - * @param {number} i - */ - const setBit = i => { - const bi = math.floor(i / 8) - buf[bi] |= (1 << (i % 8)) - } - p.degrees.forEach(setBit) - return buf -} - /** * Create from unsigned integer (max 32bit uint) - read most-significant-byte first. * @@ -472,7 +439,7 @@ export class EfficientFingerprintEncoder { this.add(_shiftBsLeft(this.m, i)) } } - if (this.bs[this.bpos] !== 0) { error.unexpectedCase() } + // if (this.bs[this.bpos] !== 0) { error.unexpectedCase() } // assert(this.bs[this.bpos] === 0) } @@ -485,53 +452,33 @@ export class EfficientFingerprintEncoder { } } -/** - * @param {Uint8Array} bs1 - * @param {Uint8Array} bs2 - */ -export const xorBuffers = (bs1, bs2) => { - const res = new Uint8Array(bs1.byteLength) - if (bs1.byteLength !== bs2.byteLength) error.unexpectedCase() - for (let i = 0; i < res.byteLength; i++) { - res[i] = bs1[i] ^ bs2[i] - } - return res -} - /** * Maps from a modulo to the precomputed values. * - * @type {Map} + * @type {Map} */ const _precomputedFingerprintCache = new Map() /** * @param {Uint8Array} m */ -const ensureCache = m => map.setIfUndefined(_precomputedFingerprintCache, m, () => { +const ensureCache = m => map.setIfUndefined(_precomputedFingerprintCache, buffer.toBase64(m), () => { const byteLen = m.byteLength const cache = new Uint8Array(256 * byteLen) - /** - * @todo not necessary, can be written directly - * @param {number} msb - * @param {Uint8Array} result - */ - const writeCacheResult = (msb, result) => { - for (let i = 0; i < result.byteLength; i++) { - cache[msb * byteLen + i] = result[i] - } - } - writeCacheResult(1, m) // can be written using a native function - // 10101010 + // 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 rest = (bitShifted | j) ^ mBitShifted[0] - // @todo xorBuffers (and creating views) is not necessary - writeCacheResult(bitShifted | j, xorBuffers(cache.slice(rest * byteLen, rest * byteLen + byteLen), mBitShifted)) - if (cache[(bitShifted | j) * byteLen] !== (bitShifted | j)) { error.unexpectedCase() } + 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 @@ -566,7 +513,7 @@ export class CachedEfficientFingerprintEncoder { 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() } + // if (this.bs[this.bpos] !== 0) { error.unexpectedCase() } } getFingerprint () { @@ -577,3 +524,13 @@ export class CachedEfficientFingerprintEncoder { 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/crypto/gc2-polynomial.test.js b/crypto/gc2-polynomial.test.js index f1f4822..6ba6e35 100644 --- a/crypto/gc2-polynomial.test.js +++ b/crypto/gc2-polynomial.test.js @@ -37,8 +37,8 @@ export const testIrreducibleInput = _tc => { * @param {t.TestCase} _tc */ export const testIrreducibleSpread = _tc => { - const degree = 53 - const N = 200 + const degree = 32 + const N = 400 const avgSpread = getSpreadAverage(degree, N) const diffSpread = math.abs(avgSpread - degree) t.info(`Average spread for degree ${degree} at ${N} repetitions: ${avgSpread}`) @@ -64,24 +64,57 @@ const getSpreadAverage = (degree, tests) => { return array.fold(spreads, 0, math.add) / tests } +/** + * @param {t.TestCase} _tc + */ +export const testGenerateIrreducibles = _tc => { + /** + * @param {number} byteLen + */ + 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) + console.log(`K = ${K}`, irrBs) + t.assert(irrBs[0] === 1) + t.assert(irrBs.byteLength === byteLen + 1) + } + testIrreducibleGen(1) + testIrreducibleGen(2) + 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)) +} + /** * @param {t.TestCase} tc + * @param {number} K */ -export const testFingerprint = tc => { +export const _testFingerprintK = (tc, K) => { /** * @type {Array} */ const dataObjects = [] - const N = 3000 - const K = 32 + const N = 300 const MSIZE = 130 t.info(`N=${N} K=${K} MSIZE=${MSIZE}`) /** * @type {gc2.GC2Polynomial} */ let irreducible + /** + * @type {Uint8Array} + */ + let irreducibleBuffer t.measureTime(`find irreducible of ${K}`, () => { irreducible = gc2.createIrreducible(K) + irreducibleBuffer = gc2.toUint8Array(irreducible) }) for (let i = 0; i < N; i++) { dataObjects.push(prng.uint8Array(tc.prng, MSIZE)) @@ -94,7 +127,7 @@ export const testFingerprint = tc => { fingerprints1 = dataObjects.map((o, _index) => gc2.fingerprint(o, irreducible)) }) const testSet = new Set(fingerprints1.map(buffer.toBase64)) - t.assert(testSet.size === N) + t.assert(K < 32 || testSet.size === N) /** * @type {Array} */ @@ -115,7 +148,7 @@ export const testFingerprint = tc => { let fingerprints3 = [] t.measureTime('polynomial incremental (efficent))', () => { fingerprints3 = dataObjects.map((o, _index) => { - const encoder = new gc2.EfficientFingerprintEncoder(gc2.toUint8Array(irreducible)) + const encoder = new gc2.EfficientFingerprintEncoder(irreducibleBuffer) for (let i = 0; i < o.byteLength; i++) { encoder.write(o[i]) } @@ -129,7 +162,7 @@ export const testFingerprint = tc => { let fingerprints4 = [] t.measureTime('polynomial incremental (efficent & cached))', () => { fingerprints4 = dataObjects.map((o, _index) => { - const encoder = new gc2.CachedEfficientFingerprintEncoder(gc2.toUint8Array(irreducible)) + const encoder = new gc2.CachedEfficientFingerprintEncoder(irreducibleBuffer) for (let i = 0; i < o.byteLength; i++) { encoder.write(o[i]) } @@ -138,3 +171,14 @@ export const testFingerprint = tc => { }) t.compare(fingerprints1, fingerprints4) } + +/** + * @param {t.TestCase} tc + */ +export const testFingerprint = tc => { + _testFingerprintK(tc, 8) + _testFingerprintK(tc, 16) + _testFingerprintK(tc, 32) + _testFingerprintK(tc, 64) + _testFingerprintK(tc, 128) +} diff --git a/number.test.js b/number.test.js index 0f6bdd2..bf8c42b 100644 --- a/number.test.js +++ b/number.test.js @@ -4,9 +4,9 @@ import * as random from './random.js' import * as math from './math.js' /** - * @param {t.TestCase} tc + * @param {t.TestCase} _tc */ -export const testNumber = tc => { +export const testNumber = _tc => { t.describe('isNaN') t.assert(number.isNaN(NaN)) t.assert(!number.isNaN(1 / 0)) @@ -18,6 +18,9 @@ export const testNumber = tc => { t.assert(!number.isInteger(NaN)) t.assert(number.isInteger(0)) t.assert(number.isInteger(-1)) + t.assert(number.countBits(1) === 1) + t.assert(number.countBits(3) === 2) + t.assert(number.countBits(128 + 3) === 3) } /**