chadtmiller.com/pds.js
An atproto PDS in JavaScript, for Cloudflare Workers and Node
0
0
// 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<Uint8Array>}
*/
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);
});
});