UNPKG

@webbuf/blake3

Version:

Rust/wasm optimized blake3 hash & mac for the web, node.js, deno, and bun.

408 lines (377 loc) 12.4 kB
/** * Audit tests for @webbuf/blake3 * * These tests verify the BLAKE3 implementation against official test vectors * from https://github.com/BLAKE3-team/BLAKE3/blob/master/test_vectors/test_vectors.json * * The test input is a repeating pattern of bytes 0-250 (251 bytes cycling). */ import { describe, it, expect } from "vitest"; import { blake3Hash, doubleBlake3Hash, blake3Mac } from "../src/index.js"; import { WebBuf } from "@webbuf/webbuf"; import { FixedBuf } from "@webbuf/fixedbuf"; /** * Generate the test input pattern used by official BLAKE3 test vectors. * The pattern is a repeating sequence of bytes 0, 1, 2, ..., 250, 0, 1, 2, ... */ function generateTestInput(length: number): WebBuf { const buf = WebBuf.alloc(length); for (let i = 0; i < length; i++) { buf[i] = i % 251; } return buf; } /** * The key used in official BLAKE3 keyed_hash test vectors. * "whats the Elvish word for friend" (32 bytes) */ const OFFICIAL_TEST_KEY = WebBuf.fromUtf8("whats the Elvish word for friend"); describe("Audit: Official BLAKE3 test vectors - hash mode", () => { // Official test vectors from BLAKE3 repository const hashTestVectors: { inputLen: number; expectedHash: string }[] = [ { inputLen: 0, expectedHash: "af1349b9f5f9a1a6a0404dea36dcc9499bcb25c9adc112b7cc9a93cae41f3262", }, { inputLen: 1, expectedHash: "2d3adedff11b61f14c886e35afa036736dcd87a74d27b5c1510225d0f592e213", }, { inputLen: 2, expectedHash: "7b7015bb92cf0b318037702a6cdd81dee41224f734684c2c122cd6359cb1ee63", }, { inputLen: 3, expectedHash: "e1be4d7a8ab5560aa4199eea339849ba8e293d55ca0a81006726d184519e647f", }, { inputLen: 4, expectedHash: "f30f5ab28fe047904037f77b6da4fea1e27241c5d132638d8bedce9d40494f32", }, { inputLen: 5, expectedHash: "b40b44dfd97e7a84a996a91af8b85188c66c126940ba7aad2e7ae6b385402aa2", }, { inputLen: 6, expectedHash: "06c4e8ffb6872fad96f9aaca5eee1553eb62aed0ad7198cef42e87f6a616c844", }, { inputLen: 7, expectedHash: "3f8770f387faad08faa9d8414e9f449ac68e6ff0417f673f602a646a891419fe", }, { inputLen: 8, expectedHash: "2351207d04fc16ade43ccab08600939c7c1fa70a5c0aaca76063d04c3228eaeb", }, { inputLen: 63, expectedHash: "e9bc37a594daad83be9470df7f7b3798297c3d834ce80ba85d6e207627b7db7b", }, { inputLen: 64, expectedHash: "4eed7141ea4a5cd4b788606bd23f46e212af9cacebacdc7d1f4c6dc7f2511b98", }, { inputLen: 65, expectedHash: "de1e5fa0be70df6d2be8fffd0e99ceaa8eb6e8c93a63f2d8d1c30ecb6b263dee", }, { inputLen: 127, expectedHash: "d81293fda863f008c09e92fc382a81f5a0b4a1251cba1634016a0f86a6bd640d", }, { inputLen: 128, expectedHash: "f17e570564b26578c33bb7f44643f539624b05df1a76c81f30acd548c44b45ef", }, { inputLen: 129, expectedHash: "683aaae9f3c5ba37eaaf072aed0f9e30bac0865137bae68b1fde4ca2aebdcb12", }, { inputLen: 1023, expectedHash: "10108970eeda3eb932baac1428c7a2163b0e924c9a9e25b35bba72b28f70bd11", }, { inputLen: 1024, expectedHash: "42214739f095a406f3fc83deb889744ac00df831c10daa55189b5d121c855af7", }, { inputLen: 1025, expectedHash: "d00278ae47eb27b34faecf67b4fe263f82d5412916c1ffd97c8cb7fb814b8444", }, ]; for (const { inputLen, expectedHash } of hashTestVectors) { it(`should match official test vector for ${String(inputLen)} byte input`, () => { const input = generateTestInput(inputLen); const result = blake3Hash(input); expect(result.toHex()).toBe(expectedHash); }); } }); describe("Audit: Official BLAKE3 test vectors - keyed_hash mode (MAC)", () => { // Official test vectors for keyed_hash mode const keyedHashTestVectors: { inputLen: number; expectedHash: string; }[] = [ { inputLen: 0, expectedHash: "92b2b75604ed3c761f9d6f62392c8a9227ad0ea3f09573e783f1498a4ed60d26", }, { inputLen: 1, expectedHash: "6d7878dfff2f485635d39013278ae14f1454b8c0a3a2d34bc1ab38228a80c95b", }, { inputLen: 2, expectedHash: "5392ddae0e0a69d5f40160462cbd9bd889375082ff224ac9c758802b7a6fd20a", }, { inputLen: 3, expectedHash: "39e67b76b5a007d4921969779fe666da67b5213b096084ab674742f0d5ec62b9", }, { inputLen: 4, expectedHash: "7671dde590c95d5ac9616651ff5aa0a27bee5913a348e053b8aa9108917fe070", }, { inputLen: 5, expectedHash: "73ac69eecf286894d8102018a6fc729f4b1f4247d3703f69bdc6a5fe3e0c8461", }, { inputLen: 6, expectedHash: "82d3199d0013035682cc7f2a399d4c212544376a839aa863a0f4c91220ca7a6d", }, { inputLen: 7, expectedHash: "af0a7ec382aedc0cfd626e49e7628bc7a353a4cb108855541a5651bf64fbb28a", }, { inputLen: 8, expectedHash: "be2f5495c61cba1bb348a34948c004045e3bd4dae8f0fe82bf44d0da245a0600", }, { inputLen: 63, expectedHash: "bb1eb5d4afa793c1ebdd9fb08def6c36d10096986ae0cfe148cd101170ce37ae", }, { inputLen: 64, expectedHash: "ba8ced36f327700d213f120b1a207a3b8c04330528586f414d09f2f7d9ccb7e6", }, { inputLen: 65, expectedHash: "c0a4edefa2d2accb9277c371ac12fcdbb52988a86edc54f0716e1591b4326e72", }, { inputLen: 127, expectedHash: "c64200ae7dfaf35577ac5a9521c47863fb71514a3bcad18819218b818de85818", }, { inputLen: 128, expectedHash: "b04fe15577457267ff3b6f3c947d93be581e7e3a4b018679125eaf86f6a628ec", }, { inputLen: 129, expectedHash: "d4a64dae6cdccbac1e5287f54f17c5f985105457c1a2ec1878ebd4b57e20d38f", }, { inputLen: 1023, expectedHash: "c951ecdf03288d0fcc96ee3413563d8a6d3589547f2c2fb36d9786470f1b9d6e", }, { inputLen: 1024, expectedHash: "75c46f6f3d9eb4f55ecaaee480db732e6c2105546f1e675003687c31719c7ba4", }, { inputLen: 1025, expectedHash: "357dc55de0c7e382c900fd6e320acc04146be01db6a8ce7210b7189bd664ea69", }, ]; // The official test key is exactly 32 bytes const testKey = FixedBuf.fromBuf(32, OFFICIAL_TEST_KEY); for (const { inputLen, expectedHash } of keyedHashTestVectors) { it(`should match official keyed_hash test vector for ${String(inputLen)} byte input`, () => { const input = generateTestInput(inputLen); const result = blake3Mac(testKey, input); expect(result.toHex()).toBe(expectedHash); }); } }); describe("Audit: doubleBlake3Hash correctness", () => { it("should produce BLAKE3(BLAKE3(input))", () => { const input = WebBuf.fromUtf8("test input"); // Manual double hash const firstHash = blake3Hash(input); const manualDoubleHash = blake3Hash(firstHash.buf); // Using the convenience function const doubleHash = doubleBlake3Hash(input); expect(doubleHash.toHex()).toBe(manualDoubleHash.toHex()); }); it("should produce different output than single hash", () => { const input = WebBuf.fromUtf8("test"); const singleHash = blake3Hash(input); const doubleHash = doubleBlake3Hash(input); expect(singleHash.toHex()).not.toBe(doubleHash.toHex()); }); it("should match expected double hash for empty input", () => { const input = WebBuf.alloc(0); const firstHash = blake3Hash(input); const expectedDoubleHash = blake3Hash(firstHash.buf); const result = doubleBlake3Hash(input); expect(result.toHex()).toBe(expectedDoubleHash.toHex()); }); }); describe("Audit: BLAKE3 properties", () => { describe("output size", () => { it("should always produce 32-byte output for hash", () => { const testLengths = [0, 1, 32, 64, 100, 1000, 10000]; for (const len of testLengths) { const input = generateTestInput(len); const result = blake3Hash(input); expect(result.buf.length).toBe(32); } }); it("should always produce 32-byte output for MAC", () => { const key = FixedBuf.fromRandom(32); const testLengths = [0, 1, 32, 64, 100, 1000, 10000]; for (const len of testLengths) { const input = generateTestInput(len); const result = blake3Mac(key, input); expect(result.buf.length).toBe(32); } }); }); describe("determinism", () => { it("should produce same hash for same input", () => { const input = WebBuf.fromUtf8("deterministic test"); const hash1 = blake3Hash(input); const hash2 = blake3Hash(input); expect(hash1.toHex()).toBe(hash2.toHex()); }); it("should produce same MAC for same key and message", () => { const key = FixedBuf.fromRandom(32); const message = WebBuf.fromUtf8("deterministic test"); const mac1 = blake3Mac(key, message); const mac2 = blake3Mac(key, message); expect(mac1.toHex()).toBe(mac2.toHex()); }); }); describe("collision resistance (basic)", () => { it("should produce different hashes for different inputs", () => { const input1 = WebBuf.fromUtf8("input 1"); const input2 = WebBuf.fromUtf8("input 2"); const hash1 = blake3Hash(input1); const hash2 = blake3Hash(input2); expect(hash1.toHex()).not.toBe(hash2.toHex()); }); it("should produce different hashes for inputs differing by one bit", () => { const input1 = WebBuf.from([0x00]); const input2 = WebBuf.from([0x01]); const hash1 = blake3Hash(input1); const hash2 = blake3Hash(input2); expect(hash1.toHex()).not.toBe(hash2.toHex()); }); it("should produce different hashes for inputs differing by length only", () => { const input1 = WebBuf.from([0x00]); const input2 = WebBuf.from([0x00, 0x00]); const hash1 = blake3Hash(input1); const hash2 = blake3Hash(input2); expect(hash1.toHex()).not.toBe(hash2.toHex()); }); }); describe("MAC key sensitivity", () => { it("should produce different MACs for different keys", () => { const key1 = FixedBuf.fromHex( 32, "0000000000000000000000000000000000000000000000000000000000000000", ); const key2 = FixedBuf.fromHex( 32, "0000000000000000000000000000000000000000000000000000000000000001", ); const message = WebBuf.fromUtf8("test message"); const mac1 = blake3Mac(key1, message); const mac2 = blake3Mac(key2, message); expect(mac1.toHex()).not.toBe(mac2.toHex()); }); it("should produce different MACs for different messages with same key", () => { const key = FixedBuf.fromRandom(32); const message1 = WebBuf.fromUtf8("message 1"); const message2 = WebBuf.fromUtf8("message 2"); const mac1 = blake3Mac(key, message1); const mac2 = blake3Mac(key, message2); expect(mac1.toHex()).not.toBe(mac2.toHex()); }); it("MAC should differ from hash of same input", () => { const key = FixedBuf.fromRandom(32); const message = WebBuf.fromUtf8("test"); const hash = blake3Hash(message); const mac = blake3Mac(key, message); expect(hash.toHex()).not.toBe(mac.toHex()); }); }); }); describe("Audit: Edge cases", () => { it("should handle empty input", () => { const empty = WebBuf.alloc(0); const hash = blake3Hash(empty); expect(hash.buf.length).toBe(32); // Official empty hash from test vectors expect(hash.toHex()).toBe( "af1349b9f5f9a1a6a0404dea36dcc9499bcb25c9adc112b7cc9a93cae41f3262", ); }); it("should handle large input (100KB)", () => { const large = generateTestInput(100 * 1024); const hash = blake3Hash(large); expect(hash.buf.length).toBe(32); }); it("should handle input with all zero bytes", () => { const zeros = WebBuf.alloc(64); const hash = blake3Hash(zeros); expect(hash.buf.length).toBe(32); }); it("should handle input with all 0xFF bytes", () => { const ones = WebBuf.alloc(64, 0xff); const hash = blake3Hash(ones); expect(hash.buf.length).toBe(32); }); });