// The incremental hasher against WebCrypto, across block boundaries and // chunkings. 55, 56, 63, 64 and 65 bytes surround the two padding cases: a // length that fits after the 0x80 marker and one that forces an extra block. import { describe, expect, it } from 'vitest'; import { blobCidFromDigest, cidToString, createBlobCid } from '../src/repo.js'; import { createSha256 } from '../src/sha256.js'; /** * @param {number} size * @returns {Uint8Array} */ function bytes(size) { const out = new Uint8Array(size); for (let i = 0; i < size; i += 1) out[i] = (i * 31 + 7) % 256; return out; } /** * @param {Uint8Array} data * @returns {Promise} */ async function reference(data) { return new Uint8Array( await crypto.subtle.digest('SHA-256', /** @type {BufferSource} */ (data)), ); } describe('createSha256', () => { const sizes = [0, 1, 3, 55, 56, 57, 63, 64, 65, 127, 128, 1000, 70000]; for (const size of sizes) { it(`matches WebCrypto for ${size} bytes in one update`, async () => { const data = bytes(size); const hasher = createSha256(); hasher.update(data); expect(await hasher.digest()).toEqual(await reference(data)); }); } it('matches WebCrypto when fed a byte at a time', async () => { const data = bytes(200); const hasher = createSha256(); for (const b of data) hasher.update(new Uint8Array([b])); expect(await hasher.digest()).toEqual(await reference(data)); }); it('matches WebCrypto across uneven chunk splits', async () => { const data = bytes(10_000); for (const split of [1, 7, 63, 64, 65, 4096]) { const hasher = createSha256(); for (let at = 0; at < data.length; at += split) { hasher.update(data.subarray(at, at + split)); } expect(await hasher.digest()).toEqual(await reference(data)); } }); it('hashes nothing at all', async () => { const hasher = createSha256(); expect(await hasher.digest()).toEqual(await reference(new Uint8Array(0))); }); it('answers the same digest twice', async () => { const hasher = createSha256(); hasher.update(bytes(100)); const first = await hasher.digest(); expect(await hasher.digest()).toBe(first); }); it('refuses an update after the digest', async () => { const hasher = createSha256(); await hasher.digest(); expect(() => hasher.update(bytes(1))).toThrow(/final/); }); }); describe('blobCidFromDigest', () => { it('builds the same CID the buffered path builds', async () => { const data = bytes(500); const hasher = createSha256(); hasher.update(data); const streamed = cidToString(blobCidFromDigest(await hasher.digest())); const buffered = cidToString(await createBlobCid(data)); expect(streamed).toBe(buffered); }); });