From 00f67349add0cc8f9fbf833f269d52d78508efc6 Mon Sep 17 00:00:00 2001 From: Hugo Heuzard Date: Wed, 20 Dec 2023 11:14:36 +0100 Subject: [PATCH] Runtime: support for blake2b --- .ocamlformat | 2 +- .../js_of_ocaml_compiler_runtime_files.ml | 1 + compiler/lib-runtime-files/tests/all.ml | 2 + compiler/tests-full/stdlib.cma.expected.js | 15 +- compiler/tests-ocaml/lib-digest/digests.ml | 76 ++++ compiler/tests-ocaml/lib-digest/dune | 8 +- runtime/blake2.js | 343 ++++++++++++++++++ runtime/mlBytes.js | 2 +- 8 files changed, 440 insertions(+), 9 deletions(-) create mode 100644 compiler/tests-ocaml/lib-digest/digests.ml create mode 100644 runtime/blake2.js diff --git a/.ocamlformat b/.ocamlformat index d0f1561795..61aefc0fab 100644 --- a/.ocamlformat +++ b/.ocamlformat @@ -10,5 +10,5 @@ break-separators=before dock-collection-brackets=false margin=90 module-item-spacing=sparse -version=0.26.0 +version=0.26.1 ocaml-version=4.08.0 diff --git a/compiler/lib-runtime-files/js_of_ocaml_compiler_runtime_files.ml b/compiler/lib-runtime-files/js_of_ocaml_compiler_runtime_files.ml index 4bb63e3b1a..3f3b9dc06d 100644 --- a/compiler/lib-runtime-files/js_of_ocaml_compiler_runtime_files.ml +++ b/compiler/lib-runtime-files/js_of_ocaml_compiler_runtime_files.ml @@ -57,6 +57,7 @@ let runtime = ; effect ; zstd ; runtime_events + ; blake2 ] include Files diff --git a/compiler/lib-runtime-files/tests/all.ml b/compiler/lib-runtime-files/tests/all.ml index ad89353fa9..282ecce3fc 100644 --- a/compiler/lib-runtime-files/tests/all.ml +++ b/compiler/lib-runtime-files/tests/all.ml @@ -15,6 +15,7 @@ let%expect_test _ = +backtrace.js +bigarray.js +bigstring.js + +blake2.js +compare.js +domain.js +dynlink.js @@ -59,6 +60,7 @@ let%expect_test _ = +backtrace.js +bigarray.js +bigstring.js + +blake2.js +compare.js +domain.js +effect.js diff --git a/compiler/tests-full/stdlib.cma.expected.js b/compiler/tests-full/stdlib.cma.expected.js index 1d3955adcc..f52cd86da8 100644 --- a/compiler/tests-full/stdlib.cma.expected.js +++ b/compiler/tests-full/stdlib.cma.expected.js @@ -24020,6 +24020,9 @@ cst_Digest_of_hex$1 = "Digest.of_hex", cst_Digest_substring$1 = "Digest.substring", cst_Digest_to_hex$1 = "Digest.to_hex", + caml_blake2_final = runtime.caml_blake2_final, + caml_blake2_string = runtime.caml_blake2_string, + caml_blake2_update = runtime.caml_blake2_update, caml_bytes_unsafe_set = runtime.caml_bytes_unsafe_set, caml_create_bytes = runtime.caml_create_bytes, caml_maybe_attach_backtrace = runtime.caml_maybe_attach_backtrace, @@ -24122,7 +24125,7 @@ compare = Stdlib_String[10], equal = Stdlib_String[9]; function string(str){ - /*<>*/ return /*<>*/ runtime.caml_blake2_string + /*<>*/ return /*<>*/ caml_blake2_string (hash_length, cst, str, 0, caml_ml_string_length(str)); /*<>*/ } function bytes(b){ @@ -24140,7 +24143,7 @@ if(_g_) /*<>*/ /*<>*/ caml_call1 (Stdlib[1], cst_Digest_substring); - /*<>*/ return /*<>*/ runtime.caml_blake2_string + /*<>*/ return /*<>*/ caml_blake2_string (hash_length, cst, str, ofs, len); /*<>*/ } function subbytes(b, ofs, len){ @@ -24160,7 +24163,7 @@ var toread$0 = toread; /*<>*/ for(;;){ if(0 === toread$0) - /*<>*/ return /*<>*/ runtime.caml_blake2_final + /*<>*/ return /*<>*/ caml_blake2_final (ctx, hash_length); /*<>*/ var /*<>*/ _e_ = @@ -24172,7 +24175,7 @@ /*<>*/ if(0 === n) /*<>*/ throw /*<>*/ caml_maybe_attach_backtrace (Stdlib[12], 1); - /*<>*/ /*<>*/ runtime.caml_blake2_update + /*<>*/ /*<>*/ caml_blake2_update (ctx, /*<>*/ caml_call1(Stdlib_Bytes[44], buf), 0, @@ -24187,9 +24190,9 @@ /*<>*/ caml_call4 (Stdlib_In_channel[16], ic, buf, 0, buf_size); /*<>*/ if(0 === n$0) - /*<>*/ return /*<>*/ runtime.caml_blake2_final + /*<>*/ return /*<>*/ caml_blake2_final (ctx, hash_length); - /*<>*/ /*<>*/ runtime.caml_blake2_update + /*<>*/ /*<>*/ caml_blake2_update (ctx, /*<>*/ caml_call1(Stdlib_Bytes[44], buf), 0, diff --git a/compiler/tests-ocaml/lib-digest/digests.ml b/compiler/tests-ocaml/lib-digest/digests.ml new file mode 100644 index 0000000000..2d0f653c26 --- /dev/null +++ b/compiler/tests-ocaml/lib-digest/digests.ml @@ -0,0 +1,76 @@ +(* TEST +*) + +module Test(H: Digest.S) = struct + + let string (msg, hh) = + if not ( (H.(equal (string msg) (of_hex hh)))) + then ( + Printf.printf "Expecting %S\ + \nGot %S\n" hh (H.to_hex (H.string msg)); assert false) + + let file wlen rlen = + let data = String.init wlen Char.unsafe_chr in + Out_channel.with_open_bin "data.tmp" + (fun oc -> Out_channel.output_string oc data); + let h1 = H.file "data.tmp" in + assert (H.equal h1 (H.string data)); + let h2 = + In_channel.with_open_bin "data.tmp" + (fun ic -> H.channel ic rlen) in + assert (H.equal h2 (H.substring data 0 rlen)); + Sys.remove "data.tmp" + + let run_tests tests = + List.iter string tests; + file 100 99; + file 100_000 10_000 +end + +(* Test inputs *) + +let in1 = "" +let in2 = "a" +let in3 = "abc" +let in4 = "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmno\ + ijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu" +let in5 = String.make 100_000 'a' + +(* Test vectors *) + +module TestMD5 = Test(Digest.MD5) +let _ = TestMD5.run_tests + [in1, "d41d8cd98f00b204e9800998ecf8427e"; + in2, "0cc175b9c0f1b6a831c399e269772661"; + in3, "900150983cd24fb0d6963f7d28e17f72"; + in4, "03dd8807a93175fb062dfb55dc7d359c"; + in5, "1af6d6f2f682f76f80e606aeaaee1680"] + +module TestBLAKE512 = Test(Digest.BLAKE512) +let _ = TestBLAKE512.run_tests + [in1, "786a02f742015903c6c6fd852552d272912f4740e15847618a86e217f71f5419\ + d25e1031afee585313896444934eb04b903a685b1448b755d56f701afe9be2ce"; + in2, "333fcb4ee1aa7c115355ec66ceac917c8bfd815bf7587d325aec1864edd24e34\ + d5abe2c6b1b5ee3face62fed78dbef802f2a85cb91d455a8f5249d330853cb3c"; + in3, "ba80a53f981c4d0d6a2797b69f12f6e94c212f14685ac4b74b12bb6fdbffa2d1\ + 7d87c5392aab792dc252d5de4533cc9518d38aa8dbf1925ab92386edd4009923"; + in4, "ce741ac5930fe346811175c5227bb7bfcd47f42612fae46c0809514f9e0e3a11\ + ee1773287147cdeaeedff50709aa716341fe65240f4ad6777d6bfaf9726e5e52"; + in5, "fe89a110a412012e7cc5c0e05b03b48a6b9d0ba108187826c5ac82ce7aa45e7e\ + 31b054979ec8ca5acd0bcc85f379d848f90f9d1593358cba8d88c7cd94ea8eee"] + +module TestBLAKE256 = Test(Digest.BLAKE256) +let _ = TestBLAKE256.run_tests + [in1, "0e5751c026e543b2e8ab2eb06099daa1d1e5df47778f7787faab45cdf12fe3a8"; + in2, "8928aae63c84d87ea098564d1e03ad813f107add474e56aedd286349c0c03ea4"; + in3, "bddd813c634239723171ef3fee98579b94964e3bb1cb3e427262c8c068d52319"; + in4, "90a0bcf5e5a67ac1578c2754617994cfc248109275a809a0721feebd1e918738"; + in5, "b717c86cf745507ec5373f12f21350eb8550039b4263f7ba6e8df9030b5673c6"] + +module TestBLAKE128 = Test(Digest.BLAKE128) +let _ = TestBLAKE128.run_tests + [in1, "cae66941d9efbd404e4d88758ea67670"; + in2, "27c35e6e9373877f29e562464e46497e"; + in3, "cf4ab791c62b8d2b2109c90275287816"; + in4, "8fa81cd08c10a6e4dd94583e6fb48c2f"; + in5, "5c4b4b762807b3290e7eee0aa9b18655"] diff --git a/compiler/tests-ocaml/lib-digest/dune b/compiler/tests-ocaml/lib-digest/dune index 61a173c088..c97124bba1 100644 --- a/compiler/tests-ocaml/lib-digest/dune +++ b/compiler/tests-ocaml/lib-digest/dune @@ -1,5 +1,5 @@ (executables - (names md5) + (names md5 digests) (libraries) (modes js)) @@ -16,3 +16,9 @@ (deps md5.reference md5.referencejs) (action (diff md5.reference md5.referencejs))) + +(rule + (alias runtest) + (deps digests.bc.js) + (action + (run node ./digests.bc.js))) diff --git a/runtime/blake2.js b/runtime/blake2.js new file mode 100644 index 0000000000..4e76dc25b9 --- /dev/null +++ b/runtime/blake2.js @@ -0,0 +1,343 @@ +//Provides: blake2b +var blake2b = (function () { +// Blake2B in pure Javascript +// Adapted from the reference implementation in RFC7693 +// Ported to Javascript by DC - https://github.com/dcposch + +// 64-bit unsigned addition +// Sets v[a,a+1] += v[b,b+1] +// v should be a Uint32Array +function ADD64AA (v, a, b) { + const o0 = v[a] + v[b] + let o1 = v[a + 1] + v[b + 1] + if (o0 >= 0x100000000) { + o1++ + } + v[a] = o0 + v[a + 1] = o1 +} + +// 64-bit unsigned addition +// Sets v[a,a+1] += b +// b0 is the low 32 bits of b, b1 represents the high 32 bits +function ADD64AC (v, a, b0, b1) { + let o0 = v[a] + b0 + if (b0 < 0) { + o0 += 0x100000000 + } + let o1 = v[a + 1] + b1 + if (o0 >= 0x100000000) { + o1++ + } + v[a] = o0 + v[a + 1] = o1 +} + +// Little-endian byte access +function B2B_GET32 (arr, i) { + return arr[i] ^ (arr[i + 1] << 8) ^ (arr[i + 2] << 16) ^ (arr[i + 3] << 24) +} + +// G Mixing function +// The ROTRs are inlined for speed +function B2B_G (a, b, c, d, ix, iy) { + const x0 = m[ix] + const x1 = m[ix + 1] + const y0 = m[iy] + const y1 = m[iy + 1] + + ADD64AA(v, a, b) // v[a,a+1] += v[b,b+1] ... in JS we must store a uint64 as two uint32s + ADD64AC(v, a, x0, x1) // v[a, a+1] += x ... x0 is the low 32 bits of x, x1 is the high 32 bits + + // v[d,d+1] = (v[d,d+1] xor v[a,a+1]) rotated to the right by 32 bits + let xor0 = v[d] ^ v[a] + let xor1 = v[d + 1] ^ v[a + 1] + v[d] = xor1 + v[d + 1] = xor0 + + ADD64AA(v, c, d) + + // v[b,b+1] = (v[b,b+1] xor v[c,c+1]) rotated right by 24 bits + xor0 = v[b] ^ v[c] + xor1 = v[b + 1] ^ v[c + 1] + v[b] = (xor0 >>> 24) ^ (xor1 << 8) + v[b + 1] = (xor1 >>> 24) ^ (xor0 << 8) + + ADD64AA(v, a, b) + ADD64AC(v, a, y0, y1) + + // v[d,d+1] = (v[d,d+1] xor v[a,a+1]) rotated right by 16 bits + xor0 = v[d] ^ v[a] + xor1 = v[d + 1] ^ v[a + 1] + v[d] = (xor0 >>> 16) ^ (xor1 << 16) + v[d + 1] = (xor1 >>> 16) ^ (xor0 << 16) + + ADD64AA(v, c, d) + + // v[b,b+1] = (v[b,b+1] xor v[c,c+1]) rotated right by 63 bits + xor0 = v[b] ^ v[c] + xor1 = v[b + 1] ^ v[c + 1] + v[b] = (xor1 >>> 31) ^ (xor0 << 1) + v[b + 1] = (xor0 >>> 31) ^ (xor1 << 1) +} + +// Initialization Vector +const BLAKE2B_IV32 = new Uint32Array([ + 0xf3bcc908, 0x6a09e667, 0x84caa73b, 0xbb67ae85, 0xfe94f82b, 0x3c6ef372, + 0x5f1d36f1, 0xa54ff53a, 0xade682d1, 0x510e527f, 0x2b3e6c1f, 0x9b05688c, + 0xfb41bd6b, 0x1f83d9ab, 0x137e2179, 0x5be0cd19 +]) + +const SIGMA8 = [ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 14, 10, 4, 8, 9, 15, 13, + 6, 1, 12, 0, 2, 11, 7, 5, 3, 11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, + 9, 4, 7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8, 9, 0, 5, 7, 2, 4, + 10, 15, 14, 1, 11, 12, 6, 8, 3, 13, 2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, + 15, 14, 1, 9, 12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11, 13, 11, 7, + 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10, 6, 15, 14, 9, 11, 3, 0, 8, 12, 2, + 13, 7, 1, 4, 10, 5, 10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13, 0, 0, + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 14, 10, 4, 8, 9, 15, 13, 6, + 1, 12, 0, 2, 11, 7, 5, 3 +] + +// These are offsets into a uint64 buffer. +// Multiply them all by 2 to make them offsets into a uint32 buffer, +// because this is Javascript and we don't have uint64s +const SIGMA82 = new Uint8Array( + SIGMA8.map(function (x) { + return x * 2 + }) +) + +// Compression function. 'last' flag indicates last block. +// Note we're representing 16 uint64s as 32 uint32s +const v = new Uint32Array(32) +const m = new Uint32Array(32) +function blake2bCompress (ctx, last) { + let i = 0 + + // init work variables + for (i = 0; i < 16; i++) { + v[i] = ctx.h[i] + v[i + 16] = BLAKE2B_IV32[i] + } + + // low 64 bits of offset + v[24] = v[24] ^ ctx.t + v[25] = v[25] ^ (ctx.t / 0x100000000) + // high 64 bits not supported, offset may not be higher than 2**53-1 + + // last block flag set ? + if (last) { + v[28] = ~v[28] + v[29] = ~v[29] + } + + // get little-endian words + for (i = 0; i < 32; i++) { + m[i] = B2B_GET32(ctx.b, 4 * i) + } + + // twelve rounds of mixing + // uncomment the DebugPrint calls to log the computation + // and match the RFC sample documentation + for (i = 0; i < 12; i++) { + B2B_G(0, 8, 16, 24, SIGMA82[i * 16 + 0], SIGMA82[i * 16 + 1]) + B2B_G(2, 10, 18, 26, SIGMA82[i * 16 + 2], SIGMA82[i * 16 + 3]) + B2B_G(4, 12, 20, 28, SIGMA82[i * 16 + 4], SIGMA82[i * 16 + 5]) + B2B_G(6, 14, 22, 30, SIGMA82[i * 16 + 6], SIGMA82[i * 16 + 7]) + B2B_G(0, 10, 20, 30, SIGMA82[i * 16 + 8], SIGMA82[i * 16 + 9]) + B2B_G(2, 12, 22, 24, SIGMA82[i * 16 + 10], SIGMA82[i * 16 + 11]) + B2B_G(4, 14, 16, 26, SIGMA82[i * 16 + 12], SIGMA82[i * 16 + 13]) + B2B_G(6, 8, 18, 28, SIGMA82[i * 16 + 14], SIGMA82[i * 16 + 15]) + } + + for (i = 0; i < 16; i++) { + ctx.h[i] = ctx.h[i] ^ v[i] ^ v[i + 16] + } +} + +// reusable parameterBlock +const parameterBlock = new Uint8Array([ + 0, + 0, + 0, + 0, // 0: outlen, keylen, fanout, depth + 0, + 0, + 0, + 0, // 4: leaf length, sequential mode + 0, + 0, + 0, + 0, // 8: node offset + 0, + 0, + 0, + 0, // 12: node offset + 0, + 0, + 0, + 0, // 16: node depth, inner length, rfu + 0, + 0, + 0, + 0, // 20: rfu + 0, + 0, + 0, + 0, // 24: rfu + 0, + 0, + 0, + 0, // 28: rfu + 0, + 0, + 0, + 0, // 32: salt + 0, + 0, + 0, + 0, // 36: salt + 0, + 0, + 0, + 0, // 40: salt + 0, + 0, + 0, + 0, // 44: salt + 0, + 0, + 0, + 0, // 48: personal + 0, + 0, + 0, + 0, // 52: personal + 0, + 0, + 0, + 0, // 56: personal + 0, + 0, + 0, + 0 // 60: personal +]) + +// Creates a BLAKE2b hashing context +// Requires an output length between 1 and 64 bytes +// Takes an optional Uint8Array key +function blake2bInit (outlen, key) { + if (outlen === 0 || outlen > 64) { + throw new Error('Illegal output length, expected 0 < length <= 64') + } + if (key.length > 64) { + throw new Error('Illegal key, expected Uint8Array with 0 < length <= 64') + } + + // state, 'param block' + const ctx = { + b: new Uint8Array(128), + h: new Uint32Array(16), + t: 0, // input count + c: 0, // pointer within buffer + outlen: outlen // output length in bytes + } + + // initialize parameterBlock before usage + parameterBlock.fill(0) + parameterBlock[0] = outlen + parameterBlock[1] = key.length + parameterBlock[2] = 1 // fanout + parameterBlock[3] = 1 // depth + + // initialize hash state + for (let i = 0; i < 16; i++) { + ctx.h[i] = BLAKE2B_IV32[i] ^ B2B_GET32(parameterBlock, i * 4) + } + + + + if(key.length > 0){ + blake2bUpdate(ctx, key) + // at the end + ctx.c = 128 + } + + return ctx +} + +// Updates a BLAKE2b streaming hash +// Requires hash context and Uint8Array (byte array) +function blake2bUpdate (ctx, input) { + for (let i = 0; i < input.length; i++) { + if (ctx.c === 128) { + // buffer full ? + ctx.t += ctx.c // add counters + blake2bCompress(ctx, false) // compress (not last) + ctx.c = 0 // counter to zero + } + ctx.b[ctx.c++] = input[i] + } +} + +// Completes a BLAKE2b streaming hash +// Returns a Uint8Array containing the message digest +function blake2bFinal (ctx) { + ctx.t += ctx.c // mark last block offset + + while (ctx.c < 128) { + // fill up with zeros + ctx.b[ctx.c++] = 0 + } + blake2bCompress(ctx, true) // final block flag = 1 + + // little endian convert and store + const out = new Uint8Array(ctx.outlen) + for (let i = 0; i < ctx.outlen; i++) { + out[i] = ctx.h[i >> 2] >> (8 * (i & 3)) + } + return out +} + return {Init:blake2bInit, Update:blake2bUpdate, Final:blake2bFinal} +})() + +//Provides: caml_blake2_create +//Requires: caml_uint8_array_of_string +//Requires: blake2b +function caml_blake2_create(hashlen, key){ + key = caml_uint8_array_of_string(key); + if(key.length > 64) { + key.subarray(0,64); + } + return blake2b.Init(hashlen, key); +} + +//Provides: caml_blake2_final +//Requires: caml_string_of_array +//Requires: blake2b +function caml_blake2_final(ctx, hashlen) { + var r = blake2b.Final(ctx); + return caml_string_of_array(r); +} + +//Provides: caml_blake2_update +//Requires: blake2b +//Requires: caml_uint8_array_of_string +function caml_blake2_update(ctx, buf, ofs, len){ + var input = caml_uint8_array_of_string(buf); + input = input.subarray(ofs, ofs + len); + blake2b.Update(ctx, input); + return 0 +} + +//Provides: caml_blake2_string +//Requires: caml_blake2_create +//Requires: caml_blake2_update +//Requires: caml_blake2_final +function caml_blake2_string(hashlen, key, buf, ofs, len) { + var ctx = caml_blake2_create (hashlen, key); + caml_blake2_update(ctx, buf, ofs, len); + return caml_blake2_final(ctx, hashlen); +} diff --git a/runtime/mlBytes.js b/runtime/mlBytes.js index c6dbf95e3e..fb1bb955d4 100644 --- a/runtime/mlBytes.js +++ b/runtime/mlBytes.js @@ -473,7 +473,7 @@ function caml_uint8_array_of_bytes (s) { //Requires: caml_ml_string_length, caml_string_unsafe_get function caml_uint8_array_of_string (s) { var l = caml_ml_string_length(s); - var a = new Array(l); + var a = new Uint8Array(l); var i = 0; for (; i < l; i++) a[i] = caml_string_unsafe_get(s,i); return a;