From 7de74169b0c8b054e9e68719cbbb68834941f934 Mon Sep 17 00:00:00 2001 From: tamarafinogina Date: Tue, 5 Aug 2025 12:21:31 +0200 Subject: [PATCH] add deriveKeyBLAKE3 --- lib/WASMInterface.ts | 17 +++ lib/blake3.ts | 42 +++++++ src/blake3.c | 16 +++ test/blake3-derive.test.ts | 248 +++++++++++++++++++++++++++++++++++++ 4 files changed, 323 insertions(+) create mode 100644 test/blake3-derive.test.ts diff --git a/lib/WASMInterface.ts b/lib/WASMInterface.ts index 1982937..5e6cbfa 100644 --- a/lib/WASMInterface.ts +++ b/lib/WASMInterface.ts @@ -136,6 +136,11 @@ export async function WASMInterface(binary: IEmbeddedWasm, hashLength: number) { wasmInstance.exports.Hash_Init(bits); }; + const init_derive_key = (context: string) => { + initialized = true; + wasmInstance.exports.Hash_Init_Derive_Key(context.length); + }; + const updateUInt8Array = (data: Uint8Array): void => { let read = 0; while (read < data.length) { @@ -290,6 +295,17 @@ export async function WASMInterface(binary: IEmbeddedWasm, hashLength: number) { return getDigestHex(digestChars, memoryView, hashLength); }; + const derive = ( + context: string, + key: IDataType, + outputType: "hex" | "binary" = "hex", + digestParam = null, + ): Uint8Array | string => { + init_derive_key(context); + update(key); + return digest(outputType, digestParam); + }; + await setupInterface(); return { @@ -303,6 +319,7 @@ export async function WASMInterface(binary: IEmbeddedWasm, hashLength: number) { save, load, calculate, + derive, hashLength, }; } diff --git a/lib/blake3.ts b/lib/blake3.ts index a8c2e9c..0dac60e 100644 --- a/lib/blake3.ts +++ b/lib/blake3.ts @@ -69,6 +69,48 @@ export function blake3( } } +/** + * Derives new keys from the key material (NOT password!) using BLAKE3. For passwords use Argon2. + * @param context Context string of any length. + * Should be hardcoded, globally unique, and application-specific. + * Example: `"[application] [commit timestamp] [purpose]"` + * @param key Key material (not a password) of any length. (string, Buffer or TypedArray). + * @param bits Number of output bits, which has to be a number + * divisible by 8. Defaults to 256. + * @param outputType Type of the output ("hex" | "binary"): HEX string or binary. Default is HEX. + * @returns Computed a derived key as a hexadecimal string + */ +export function deriveKeyBLAKE3( + context: string, + key: IDataType, + bits = 256, + outputType: "hex" | "binary" = "hex", +): Promise { + if (validateBits(bits)) { + return Promise.reject(validateBits(bits)); + } + const hashLength = bits / 8; + const digestParam = hashLength; + + const contextBuffer = getUInt8Buffer(context); + + if (wasmCache === null || wasmCache.hashLength !== hashLength) { + return lockedCreate(mutex, wasmJson, hashLength).then((wasm) => { + wasmCache = wasm; + wasmCache.writeMemory(contextBuffer); + return wasmCache.derive(context, key, outputType, digestParam); + }); + } + + try { + wasmCache.writeMemory(contextBuffer); + const hash = wasmCache.derive(context, key, outputType, digestParam); + return Promise.resolve(hash); + } catch (err) { + return Promise.reject(err); + } +} + /** * Creates a new BLAKE3 hash instance * @param bits Number of output bits, which has to be a number diff --git a/src/blake3.c b/src/blake3.c index b234a87..ffb42ce 100644 --- a/src/blake3.c +++ b/src/blake3.c @@ -829,6 +829,17 @@ void blake3_hasher_finalize(const blake3_hasher *self, uint8_t *out, size_t out_ blake3_hasher_finalize_seek(self, 0, out, out_len); } +void blake3_hasher_init_derive_key_raw(blake3_hasher *self, const void *context, size_t context_len) { + blake3_hasher context_hasher; + hasher_init_base(&context_hasher, IV, DERIVE_KEY_CONTEXT); + blake3_hasher_update(&context_hasher, context, context_len); + uint8_t context_key[BLAKE3_KEY_LEN]; + blake3_hasher_finalize(&context_hasher, context_key, BLAKE3_KEY_LEN); + uint32_t context_key_words[8]; + load_key_words(context_key, context_key_words); + hasher_init_base(self, context_key_words, DERIVE_KEY_MATERIAL); +} + blake3_hasher hasher; WASM_EXPORT @@ -840,6 +851,11 @@ void Hash_Init(uint32_t keyLen) { } } +WASM_EXPORT +void Hash_Init_Derive_Key(size_t context_len) { + blake3_hasher_init_derive_key_raw(&hasher, main_buffer, context_len); +} + WASM_EXPORT void Hash_Update(uint32_t len) { blake3_hasher_update(&hasher, main_buffer, len); diff --git a/test/blake3-derive.test.ts b/test/blake3-derive.test.ts new file mode 100644 index 0000000..00a77b9 --- /dev/null +++ b/test/blake3-derive.test.ts @@ -0,0 +1,248 @@ +import { deriveKeyBLAKE3 } from "../lib"; +describe("Test deriveKeyBLAKE3", () => { + const testCases = [ + { + input_len: 0, + derive_key: + "2cc39783c223154fea8dfb7c1b1660f2ac2dcbd1c1de8277b0b0dd39b7e50d7d905630c8be290dfcf3e6842f13bddd573c098c3f17361f1f206b8cad9d088aa4a3f746752c6b0ce6a83b0da81d59649257cdf8eb3e9f7d4998e41021fac119deefb896224ac99f860011f73609e6e0e4540f93b273e56547dfd3aa1a035ba6689d89a0", + }, + { + input_len: 1, + derive_key: + "b3e2e340a117a499c6cf2398a19ee0d29cca2bb7404c73063382693bf66cb06c5827b91bf889b6b97c5477f535361caefca0b5d8c4746441c57617111933158950670f9aa8a05d791daae10ac683cbef8faf897c84e6114a59d2173c3f417023a35d6983f2c7dfa57e7fc559ad751dbfb9ffab39c2ef8c4aafebc9ae973a64f0c76551", + }, + { + input_len: 2, + derive_key: + "1f166565a7df0098ee65922d7fea425fb18b9943f19d6161e2d17939356168e6daa59cae19892b2d54f6fc9f475d26031fd1c22ae0a3e8ef7bdb23f452a15e0027629d2e867b1bb1e6ab21c71297377750826c404dfccc2406bd57a83775f89e0b075e59a7732326715ef912078e213944f490ad68037557518b79c0086de6d6f6cdd2", + }, + { + input_len: 3, + derive_key: + "440aba35cb006b61fc17c0529255de438efc06a8c9ebf3f2ddac3b5a86705797f27e2e914574f4d87ec04c379e12789eccbfbc15892626042707802dbe4e97c3ff59dca80c1e54246b6d055154f7348a39b7d098b2b4824ebe90e104e763b2a447512132cede16243484a55a4e40a85790038bb0dcf762e8c053cabae41bbe22a5bff7", + }, + { + input_len: 4, + derive_key: + "f46085c8190d69022369ce1a18880e9b369c135eb93f3c63550d3e7630e91060fbd7d8f4258bec9da4e05044f88b91944f7cab317a2f0c18279629a3867fad0662c9ad4d42c6f27e5b124da17c8c4f3a94a025ba5d1b623686c6099d202a7317a82e3d95dae46a87de0555d727a5df55de44dab799a20dffe239594d6e99ed17950910", + }, + { + input_len: 5, + derive_key: + "1f24eda69dbcb752847ec3ebb5dd42836d86e58500c7c98d906ecd82ed9ae47f6f48a3f67e4e43329c9a89b1ca526b9b35cbf7d25c1e353baffb590fd79be58ddb6c711f1a6b60e98620b851c688670412fcb0435657ba6b638d21f0f2a04f2f6b0bd8834837b10e438d5f4c7c2c71299cf7586ea9144ed09253d51f8f54dd6bff719d", + }, + { + input_len: 6, + derive_key: + "be96b30b37919fe4379dfbe752ae77b4f7e2ab92f7ff27435f76f2f065f6a5f435ae01a1d14bd5a6b3b69d8cbd35f0b01ef2173ff6f9b640ca0bd4748efa398bf9a9c0acd6a66d9332fdc9b47ffe28ba7ab6090c26747b85f4fab22f936b71eb3f64613d8bd9dfabe9bb68da19de78321b481e5297df9e40ec8a3d662f3e1479c65de0", + }, + { + input_len: 7, + derive_key: + "dc3b6485f9d94935329442916b0d059685ba815a1fa2a14107217453a7fc9f0e66266db2ea7c96843f9d8208e600a73f7f45b2f55b9e6d6a7ccf05daae63a3fdd10b25ac0bd2e224ce8291f88c05976d575df998477db86fb2cfbbf91725d62cb57acfeb3c2d973b89b503c2b60dde85a7802b69dc1ac2007d5623cbea8cbfb6b181f5", + }, + { + input_len: 8, + derive_key: + "2b166978cef14d9d438046c720519d8b1cad707e199746f1562d0c87fbd32940f0e2545a96693a66654225ebbaac76d093bfa9cd8f525a53acb92a861a98c42e7d1c4ae82e68ab691d510012edd2a728f98cd4794ef757e94d6546961b4f280a51aac339cc95b64a92b83cc3f26d8af8dfb4c091c240acdb4d47728d23e7148720ef04", + }, + { + input_len: 63, + derive_key: + "b6451e30b953c206e34644c6803724e9d2725e0893039cfc49584f991f451af3b89e8ff572d3da4f4022199b9563b9d70ebb616efff0763e9abec71b550f1371e233319c4c4e74da936ba8e5bbb29a598e007a0bbfa929c99738ca2cc098d59134d11ff300c39f82e2fce9f7f0fa266459503f64ab9913befc65fddc474f6dc1c67669", + }, + { + input_len: 64, + derive_key: + "a5c4a7053fa86b64746d4bb688d06ad1f02a18fce9afd3e818fefaa7126bf73e9b9493a9befebe0bf0c9509fb3105cfa0e262cde141aa8e3f2c2f77890bb64a4cca96922a21ead111f6338ad5244f2c15c44cb595443ac2ac294231e31be4a4307d0a91e874d36fc9852aeb1265c09b6e0cda7c37ef686fbbcab97e8ff66718be048bb", + }, + { + input_len: 65, + derive_key: + "51fd05c3c1cfbc8ed67d139ad76f5cf8236cd2acd26627a30c104dfd9d3ff8a82b02e8bd36d8498a75ad8c8e9b15eb386970283d6dd42c8ae7911cc592887fdbe26a0a5f0bf821cd92986c60b2502c9be3f98a9c133a7e8045ea867e0828c7252e739321f7c2d65daee4468eb4429efae469a42763f1f94977435d10dccae3e3dce88d", + }, + { + input_len: 127, + derive_key: + "c91c090ceee3a3ac81902da31838012625bbcd73fcb92e7d7e56f78deba4f0c3feeb3974306966ccb3e3c69c337ef8a45660ad02526306fd685c88542ad00f759af6dd1adc2e50c2b8aac9f0c5221ff481565cf6455b772515a69463223202e5c371743e35210bbbbabd89651684107fd9fe493c937be16e39cfa7084a36207c99bea3", + }, + { + input_len: 128, + derive_key: + "81720f34452f58a0120a58b6b4608384b5c51d11f39ce97161a0c0e442ca022550e7cd651e312f0b4c6afb3c348ae5dd17d2b29fab3b894d9a0034c7b04fd9190cbd90043ff65d1657bbc05bfdecf2897dd894c7a1b54656d59a50b51190a9da44db426266ad6ce7c173a8c0bbe091b75e734b4dadb59b2861cd2518b4e7591e4b83c9", + }, + { + input_len: 129, + derive_key: + "938d2d4435be30eafdbb2b7031f7857c98b04881227391dc40db3c7b21f41fc18d72d0f9c1de5760e1941aebf3100b51d64644cb459eb5d20258e233892805eb98b07570ef2a1787cd48e117c8d6a63a68fd8fc8e59e79dbe63129e88352865721c8d5f0cf183f85e0609860472b0d6087cefdd186d984b21542c1c780684ed6832d8d", + }, + { + input_len: 1023, + derive_key: + "74a16c1c3d44368a86e1ca6df64be6a2f64cce8f09220787450722d85725dea59c413264404661e9e4d955409dfe4ad3aa487871bcd454ed12abfe2c2b1eb7757588cf6cb18d2eccad49e018c0d0fec323bec82bf1644c6325717d13ea712e6840d3e6e730d35553f59eff5377a9c350bcc1556694b924b858f329c44ee64b884ef00d", + }, + { + input_len: 1024, + derive_key: + "7356cd7720d5b66b6d0697eb3177d9f8d73a4a5c5e968896eb6a6896843027066c23b601d3ddfb391e90d5c8eccdef4ae2a264bce9e612ba15e2bc9d654af1481b2e75dbabe615974f1070bba84d56853265a34330b4766f8e75edd1f4a1650476c10802f22b64bd3919d246ba20a17558bc51c199efdec67e80a227251808d8ce5bad", + }, + { + input_len: 1025, + derive_key: + "effaa245f065fbf82ac186839a249707c3bddf6d3fdda22d1b95a3c970379bcb5d31013a167509e9066273ab6e2123bc835b408b067d88f96addb550d96b6852dad38e320b9d940f86db74d398c770f462118b35d2724efa13da97194491d96dd37c3c09cbef665953f2ee85ec83d88b88d11547a6f911c8217cca46defa2751e7f3ad", + }, + { + input_len: 2048, + derive_key: + "7b2945cb4fef70885cc5d78a87bf6f6207dd901ff239201351ffac04e1088a23e2c11a1ebffcea4d80447867b61badb1383d842d4e79645d48dd82ccba290769caa7af8eaa1bd78a2a5e6e94fbdab78d9c7b74e894879f6a515257ccf6f95056f4e25390f24f6b35ffbb74b766202569b1d797f2d4bd9d17524c720107f985f4ddc583", + }, + { + input_len: 2049, + derive_key: + "2ea477c5515cc3dd606512ee72bb3e0e758cfae7232826f35fb98ca1bcbdf27316d8e9e79081a80b046b60f6a263616f33ca464bd78d79fa18200d06c7fc9bffd808cc4755277a7d5e09da0f29ed150f6537ea9bed946227ff184cc66a72a5f8c1e4bd8b04e81cf40fe6dc4427ad5678311a61f4ffc39d195589bdbc670f63ae70f4b6", + }, + { + input_len: 3072, + derive_key: + "050df97f8c2ead654d9bb3ab8c9178edcd902a32f8495949feadcc1e0480c46b3604131bbd6e3ba573b6dd682fa0a63e5b165d39fc43a625d00207607a2bfeb65ff1d29292152e26b298868e3b87be95d6458f6f2ce6118437b632415abe6ad522874bcd79e4030a5e7bad2efa90a7a7c67e93f0a18fb28369d0a9329ab5c24134ccb0", + }, + { + input_len: 3073, + derive_key: + "72613c9ec9ff7e40f8f5c173784c532ad852e827dba2bf85b2ab4b76f7079081576288e552647a9d86481c2cae75c2dd4e7c5195fb9ada1ef50e9c5098c249d743929191441301c69e1f48505a4305ec1778450ee48b8e69dc23a25960fe33070ea549119599760a8a2d28aeca06b8c5e9ba58bc19e11fe57b6ee98aa44b2a8e6b14a5", + }, + { + input_len: 4096, + derive_key: + "1e0d7f3db8c414c97c6307cbda6cd27ac3b030949da8e23be1a1a924ad2f25b9d78038f7b198596c6cc4a9ccf93223c08722d684f240ff6569075ed81591fd93f9fff1110b3a75bc67e426012e5588959cc5a4c192173a03c00731cf84544f65a2fb9378989f72e9694a6a394a8a30997c2e67f95a504e631cd2c5f55246024761b245", + }, + { + input_len: 4097, + derive_key: + "aca51029626b55fda7117b42a7c211f8c6e9ba4fe5b7a8ca922f34299500ead8a897f66a400fed9198fd61dd2d58d382458e64e100128075fc54b860934e8de2e84170734b06e1d212a117100820dbc48292d148afa50567b8b84b1ec336ae10d40c8c975a624996e12de31abbe135d9d159375739c333798a80c64ae895e51e22f3ad", + }, + { + input_len: 5120, + derive_key: + "7a7acac8a02adcf3038d74cdd1d34527de8a0fcc0ee3399d1262397ce5817f6055d0cefd84d9d57fe792d65a278fd20384ac6c30fdb340092f1a74a92ace99c482b28f0fc0ef3b923e56ade20c6dba47e49227166251337d80a037e987ad3a7f728b5ab6dfafd6e2ab1bd583a95d9c895ba9c2422c24ea0f62961f0dca45cad47bfa0d", + }, + { + input_len: 5121, + derive_key: + "b07f01e518e702f7ccb44a267e9e112d403a7b3f4883a47ffbed4b48339b3c341a0add0ac032ab5aaea1e4e5b004707ec5681ae0fcbe3796974c0b1cf31a194740c14519273eedaabec832e8a784b6e7cfc2c5952677e6c3f2c3914454082d7eb1ce1766ac7d75a4d3001fc89544dd46b5147382240d689bbbaefc359fb6ae30263165", + }, + { + input_len: 6144, + derive_key: + "2a95beae63ddce523762355cf4b9c1d8f131465780a391286a5d01abb5683a1597099e3c6488aab6c48f3c15dbe1942d21dbcdc12115d19a8b8465fb54e9053323a9178e4275647f1a9927f6439e52b7031a0b465c861a3fc531527f7758b2b888cf2f20582e9e2c593709c0a44f9c6e0f8b963994882ea4168827823eef1f64169fef", + }, + { + input_len: 6145, + derive_key: + "379bcc61d0051dd489f686c13de00d5b14c505245103dc040d9e4dd1facab8e5114493d029bdbd295aaa744a59e31f35c7f52dba9c3642f773dd0b4262a9980a2aef811697e1305d37ba9d8b6d850ef07fe41108993180cf779aeece363704c76483458603bbeeb693cffbbe5588d1f3535dcad888893e53d977424bb707201569a8d2", + }, + { + input_len: 7168, + derive_key: + "11c37a112765370c94a51415d0d651190c288566e295d505defdad895dae223730d5a5175a38841693020669c7638f40b9bc1f9f39cf98bda7a5b54ae24218a800a2116b34665aa95d846d97ea988bfcb53dd9c055d588fa21ba78996776ea6c40bc428b53c62b5f3ccf200f647a5aae8067f0ea1976391fcc72af1945100e2a6dcb88", + }, + { + input_len: 7169, + derive_key: + "554b0a5efea9ef183f2f9b931b7497995d9eb26f5c5c6dad2b97d62fc5ac31d99b20652c016d88ba2a611bbd761668d5eda3e568e940faae24b0d9991c3bd25a65f770b89fdcadabcb3d1a9c1cb63e69721cacf1ae69fefdcef1e3ef41bc5312ccc17222199e47a26552c6adc460cf47a72319cb5039369d0060eaea59d6c65130f1dd", + }, + { + input_len: 8192, + derive_key: + "ad01d7ae4ad059b0d33baa3c01319dcf8088094d0359e5fd45d6aeaa8b2d0c3d4c9e58958553513b67f84f8eac653aeeb02ae1d5672dcecf91cd9985a0e67f4501910ecba25555395427ccc7241d70dc21c190e2aadee875e5aae6bf1912837e53411dabf7a56cbf8e4fb780432b0d7fe6cec45024a0788cf5874616407757e9e6bef7", + }, + { + input_len: 8193, + derive_key: + "af1e0346e389b17c23200270a64aa4e1ead98c61695d917de7d5b00491c9b0f12f20a01d6d622edf3de026a4db4e4526225debb93c1237934d71c7340bb5916158cbdafe9ac3225476b6ab57a12357db3abbad7a26c6e66290e44034fb08a20a8d0ec264f309994d2810c49cfba6989d7abb095897459f5425adb48aba07c5fb3c83c0", + }, + { + input_len: 16384, + derive_key: + "160e18b5878cd0df1c3af85eb25a0db5344d43a6fbd7a8ef4ed98d0714c3f7e160dc0b1f09caa35f2f417b9ef309dfe5ebd67f4c9507995a531374d099cf8ae317542e885ec6f589378864d3ea98716b3bbb65ef4ab5e0ab5bb298a501f19a41ec19af84a5e6b428ecd813b1a47ed91c9657c3fba11c406bc316768b58f6802c9e9b57", + }, + { + input_len: 31744, + derive_key: + "39772aef80e0ebe60596361e45b061e8f417429d529171b6764468c22928e28e9759adeb797a3fbf771b1bcea30150a020e317982bf0d6e7d14dd9f064bc11025c25f31e81bd78a921db0174f03dd481d30e93fd8e90f8b2fee209f849f2d2a52f31719a490fb0ba7aea1e09814ee912eba111a9fde9d5c274185f7bae8ba85d300a2b", + }, + { + input_len: 102400, + derive_key: + "4652cff7a3f385a6103b5c260fc1593e13c778dbe608efb092fe7ee69df6e9c6d83a3e041bc3a48df2879f4a0a3ed40e7c961c73eff740f3117a0504c2dff4786d44fb17f1549eb0ba585e40ec29bf7732f0b7e286ff8acddc4cb1e23b87ff5d824a986458dcc6a04ac83969b80637562953df51ed1a7e90a7926924d2763778be8560", + }, + ]; + + function createTestInput(size) { + const result = new Uint8Array(size); + for (let i = 0; i < size; i++) { + result[i] = i % 251; + } + return result; + } + + const context = "BLAKE3 2019-12-27 16:29:52 test vectors context"; + for (const { input_len, derive_key } of testCases) { + test(`blake3 test vector with input_len= ${input_len}`, async () => { + await expect( + deriveKeyBLAKE3(context, createTestInput(input_len), 1048), + ).resolves.toBe(derive_key); + }); + } + + test("invalid parameters", async () => { + const invalidLength = [-1, "a", 223, 0, 127, 257, null]; + + for (const len of invalidLength) { + await expect(deriveKeyBLAKE3(context, "", len as any)).rejects.toThrow(); + } + + const invalidKey = [0, 1, Number(1), {}, [], null, undefined]; + + for (const key of invalidKey) { + await expect(deriveKeyBLAKE3(context, key as any)).rejects.toThrow(); + } + }); + + test("default value for key length", async () => { + const key = await deriveKeyBLAKE3(context, "", 256); + const default_key = await deriveKeyBLAKE3(context, ""); + expect(key).toBe(default_key); + }); + + test("different context gives different derived keys", async () => { + const diff_context = + "BLAKE3 2019-12-27 16:29:52 test vectors context different"; + const key1 = await deriveKeyBLAKE3(context, ""); + const key2 = await deriveKeyBLAKE3(diff_context, ""); + expect(key1).not.toBe(key2); + }); + + test("different key material gives different derived keys", async () => { + const key1 = await deriveKeyBLAKE3(context, ""); + const key2 = await deriveKeyBLAKE3(context, "different key material"); + expect(key1).not.toBe(key2); + }); + + test("same key material and same context gives same derived keys", async () => { + const key_material = createTestInput(256); + const len = 128; + const key1 = await deriveKeyBLAKE3(context, key_material, len); + const key2 = await deriveKeyBLAKE3(context, key_material, len); + expect(key1).toBe(key2); + }); + + test("works for different output types", async () => { + const key1 = await deriveKeyBLAKE3(context, "", 128, "binary"); + const key2 = await deriveKeyBLAKE3(context, "", 128, "hex"); + expect(key1).toBeInstanceOf(Uint8Array); + expect(typeof key2).toBe("string"); + const key2Array = new Uint8Array(Buffer.from(key2 as string, "hex")); + expect(key1).toStrictEqual(key2Array); + }); +});