import { readFileSync } from 'node:fs'; import { describe, expect, it } from 'vitest'; import { LTHASH_STATE_BYTES, LtHash } from '../src/lthash.js'; const vectors = JSON.parse( readFileSync( new URL('../../../test/fixtures/space-vectors.json', import.meta.url), 'utf8', ), ); const toHex = (/** @type {Uint8Array} */ bytes) => Array.from(bytes) .map((b) => b.toString(16).padStart(2, '0')) .join(''); describe('LtHash', () => { it('starts as 2048 zero bytes', () => { const h = new LtHash(); expect(h.state()).toEqual(new Uint8Array(LTHASH_STATE_BYTES)); expect(h.isEmpty()).toBe(true); }); it('rejects a state of the wrong length', () => { expect(() => new LtHash(new Uint8Array(10))).toThrow(/2048/); }); it('add then remove returns to the zero state', () => { const h = new LtHash().add('a'); expect(h.isEmpty()).toBe(false); h.remove('a'); expect(h.isEmpty()).toBe(true); }); it('is order-independent', async () => { const a = new LtHash().add('a').add('b'); const b = new LtHash().add('b').add('a'); expect(a.equals(b)).toBe(true); expect(await a.digest()).toEqual(await b.digest()); }); it('distinguishes different elements', () => { expect(new LtHash().add('a').equals(new LtHash().add('b'))).toBe(false); }); it('round-trips through state()', () => { const h = new LtHash().add('a').add('b'); expect(new LtHash(h.state()).equals(h)).toBe(true); }); it('matches the reference empty state', async () => { const h = new LtHash(); expect(toHex(h.state())).toBe(vectors.lthash.empty.stateHex); expect(toHex(await h.digest())).toBe(vectors.lthash.empty.digestHex); }); it('matches the reference for single elements', async () => { for (const c of vectors.lthash.single) { const h = new LtHash().add(c.element); expect(toHex(h.state()), c.element).toBe(c.stateHex); expect(toHex(await h.digest()), c.element).toBe(c.digestHex); } }); it('matches the reference for element sets', async () => { for (const c of vectors.lthash.sets) { const h = new LtHash(); for (const e of c.elements) h.add(e); expect(toHex(await h.digest()), c.elements.join(',')).toBe(c.digestHex); } }); });