Skip to content

Commit

Permalink
[fingerprint] almost full test coverage
Browse files Browse the repository at this point in the history
  • Loading branch information
dmonad committed Jun 21, 2023
1 parent 690cd69 commit df854d3
Show file tree
Hide file tree
Showing 3 changed files with 83 additions and 79 deletions.
95 changes: 26 additions & 69 deletions crypto/gc2-polynomial.js
Expand Up @@ -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'

/**
Expand All @@ -26,6 +25,8 @@ export class GC2Polynomial {
}

/**
* From Uint8Array (MSB).
*
* @param {Uint8Array} bytes
*/
export const createFromBytes = bytes => {
Expand All @@ -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
*/
Expand All @@ -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.
*
Expand Down Expand Up @@ -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)
}

Expand All @@ -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<Uint8Array,Uint8Array>}
* @type {Map<string,Uint8Array>}
*/
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
Expand Down Expand Up @@ -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 () {
Expand All @@ -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])
60 changes: 52 additions & 8 deletions crypto/gc2-polynomial.test.js
Expand Up @@ -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}`)
Expand All @@ -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<Uint8Array>}
*/
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))
Expand All @@ -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<Uint8Array>}
*/
Expand All @@ -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])
}
Expand All @@ -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])
}
Expand All @@ -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)
}
7 changes: 5 additions & 2 deletions number.test.js
Expand Up @@ -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))
Expand All @@ -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)
}

/**
Expand Down

0 comments on commit df854d3

Please sign in to comment.