73 lines
2.6 KiB
TypeScript
73 lines
2.6 KiB
TypeScript
|
|
import { describe, test, expect } from 'bun:test';
|
||
|
|
import { StreamingSha256, sha256Once } from '../src/index.js';
|
||
|
|
|
||
|
|
function hex(bytes: Uint8Array): string {
|
||
|
|
return Array.from(bytes, (b) => b.toString(16).padStart(2, '0')).join('');
|
||
|
|
}
|
||
|
|
|
||
|
|
async function subtleHashHex(data: Uint8Array): Promise<string> {
|
||
|
|
const buf = await globalThis.crypto.subtle.digest('SHA-256', data as unknown as ArrayBuffer);
|
||
|
|
return hex(new Uint8Array(buf));
|
||
|
|
}
|
||
|
|
|
||
|
|
describe('StreamingSha256', () => {
|
||
|
|
test('digest of empty input matches the well-known SHA-256 zero hash', () => {
|
||
|
|
const h = new StreamingSha256().digest();
|
||
|
|
expect(hex(h)).toBe('e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855');
|
||
|
|
});
|
||
|
|
|
||
|
|
test('matches one-shot sha256 over the same bytes', () => {
|
||
|
|
const data = new TextEncoder().encode('the quick brown fox');
|
||
|
|
const streaming = new StreamingSha256().update(data).digest();
|
||
|
|
expect(hex(streaming)).toBe(hex(sha256Once(data)));
|
||
|
|
});
|
||
|
|
|
||
|
|
test('matches SubtleCrypto digest over the same bytes', async () => {
|
||
|
|
const data = new TextEncoder().encode('cross-impl parity check');
|
||
|
|
const streaming = new StreamingSha256().update(data).digest();
|
||
|
|
expect(hex(streaming)).toBe(await subtleHashHex(data));
|
||
|
|
});
|
||
|
|
|
||
|
|
test('chunked updates produce identical digest to a single update', () => {
|
||
|
|
const buf = new Uint8Array(4096);
|
||
|
|
for (let i = 0; i < buf.length; i++) buf[i] = i & 0xff;
|
||
|
|
const a = new StreamingSha256().update(buf).digest();
|
||
|
|
const b = new StreamingSha256();
|
||
|
|
for (let off = 0; off < buf.length; off += 137) {
|
||
|
|
b.update(buf.slice(off, Math.min(off + 137, buf.length)));
|
||
|
|
}
|
||
|
|
expect(hex(a)).toBe(hex(b.digest()));
|
||
|
|
});
|
||
|
|
|
||
|
|
test('handles multi-megabyte inputs (memory-bounded streaming)', () => {
|
||
|
|
const chunk = new Uint8Array(1024 * 1024);
|
||
|
|
for (let i = 0; i < chunk.length; i++) chunk[i] = (i * 31) & 0xff;
|
||
|
|
const h = new StreamingSha256();
|
||
|
|
for (let i = 0; i < 4; i++) h.update(chunk);
|
||
|
|
const digest = h.digest();
|
||
|
|
expect(digest.length).toBe(32);
|
||
|
|
});
|
||
|
|
|
||
|
|
test('throws on update after digest()', () => {
|
||
|
|
const h = new StreamingSha256();
|
||
|
|
h.digest();
|
||
|
|
expect(() => h.update(new Uint8Array([1]))).toThrow();
|
||
|
|
});
|
||
|
|
|
||
|
|
test('isFinalized reflects digest()', () => {
|
||
|
|
const h = new StreamingSha256();
|
||
|
|
expect(h.isFinalized).toBe(false);
|
||
|
|
h.digest();
|
||
|
|
expect(h.isFinalized).toBe(true);
|
||
|
|
});
|
||
|
|
|
||
|
|
test('skips no-op empty updates', () => {
|
||
|
|
const h = new StreamingSha256();
|
||
|
|
h.update(new Uint8Array(0));
|
||
|
|
h.update(new Uint8Array(0));
|
||
|
|
expect(hex(h.digest())).toBe(
|
||
|
|
'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855',
|
||
|
|
);
|
||
|
|
});
|
||
|
|
});
|