Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit b17bfc1

Browse files
committedSep 6, 2024·
Support for compressed base64 wasm files
Upgrade to use WebAssembly.instantiateStreaming
1 parent ade0151 commit b17bfc1

12 files changed

+95
-22
lines changed
 

‎.eslintrc

+2-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@
77
"node_modules",
88
"build",
99
"coverage",
10-
"src/lib/schema/ajv/*.js"
10+
"src/lib/schema/ajv/*.js",
11+
"src/lib/bin/secp256k1/secp256k1.js"
1112
],
1213
"extends": ["bitauth"],
1314
// "globals": { "BigInt": true, "console": true, "WebAssembly": true },

‎src/lib/bin/hashes.ts

+39-1
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,47 @@
1+
import { binsAreEqual } from '../format/hex.js';
2+
13
export type HashFunction = {
24
final: (rawState: Uint8Array) => Uint8Array;
35
hash: (input: Uint8Array) => Uint8Array;
46
init: () => Uint8Array;
57
update: (rawState: Uint8Array, input: Uint8Array) => Uint8Array;
68
};
79

10+
/* eslint-disable @typescript-eslint/require-await, functional/no-return-void, functional/no-expression-statements, @typescript-eslint/no-magic-numbers, @typescript-eslint/naming-convention */
11+
/**
12+
* Reads in a wasm binary as an ArrayBuffer, checks if it was compressed and
13+
* decompresses it if necessary. Returns a Response object with the wasm binary compatible with WebAssembly.instantiateStreaming.
14+
*/
15+
export const streamWasmArrayBuffer = async (
16+
wasmArrayBuffer: ArrayBuffer,
17+
): Promise<Response> => {
18+
// currently, we consume the data in a single chunk, but when ReadableStream.from() becomes widely available, we can switch to it
19+
const wasmStream = new ReadableStream({
20+
start(controller): void {
21+
controller.enqueue(new Uint8Array(wasmArrayBuffer));
22+
controller.close();
23+
},
24+
});
25+
26+
// if source is uncompressed, then return as is
27+
if (
28+
binsAreEqual(
29+
new Uint8Array(wasmArrayBuffer, 0, 4),
30+
new Uint8Array([0x00, 0x61, 0x73, 0x6d]),
31+
)
32+
) {
33+
return new Response(wasmStream, {
34+
headers: new Headers({ 'content-type': 'application/wasm' }),
35+
});
36+
}
37+
38+
// otherwise, decompress the source
39+
return new Response(wasmStream.pipeThrough(new DecompressionStream('gzip')), {
40+
headers: new Headers({ 'content-type': 'application/wasm' }),
41+
});
42+
};
43+
/* eslint-enable @typescript-eslint/require-await, functional/no-return-void, functional/no-expression-statements, @typescript-eslint/no-magic-numbers, @typescript-eslint/naming-convention */
44+
845
/* eslint-disable functional/no-conditional-statements, functional/no-let, functional/no-expression-statements, no-underscore-dangle, functional/no-try-statements, @typescript-eslint/no-magic-numbers, @typescript-eslint/max-params, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-non-null-assertion */
946
/**
1047
* Note, most of this method is translated and boiled-down from the wasm-pack
@@ -19,8 +56,9 @@ export const instantiateRustWasm = async (
1956
updateExportName: string,
2057
finalExportName: string,
2158
): Promise<HashFunction> => {
59+
const webassemblyByteStream = await streamWasmArrayBuffer(webassemblyBytes);
2260
const wasm = (
23-
await WebAssembly.instantiate(webassemblyBytes, {
61+
await WebAssembly.instantiateStreaming(webassemblyByteStream, {
2462
[expectedImportModuleName]: {
2563
/**
2664
* This would only be called in cases where a `__wbindgen_malloc` failed.

‎src/lib/bin/ripemd160/ripemd160.base64.ts

+1-1
Large diffs are not rendered by default.

‎src/lib/bin/secp256k1/secp256k1-wasm.spec.ts

+28-2
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
getEmbeddedSecp256k1Binary,
1313
instantiateSecp256k1Wasm,
1414
instantiateSecp256k1WasmBytes,
15+
streamWasmArrayBuffer,
1516
} from '../../lib.js';
1617

1718
// test vectors from `zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo wrong` (`xprv9s21ZrQH143K2PfMvkNViFc1fgumGqBew45JD8SxA59Jc5M66n3diqb92JjvaR61zT9P89Grys12kdtV4EFVo6tMwER7U2hcUmZ9VfMYPLC`), m/0 and m/1:
@@ -491,10 +492,35 @@ const testSecp256k1Wasm = (
491492

492493
const binary = getEmbeddedSecp256k1Binary();
493494

494-
test('[crypto] getEmbeddedSecp256k1Binary returns the proper binary', (t) => {
495+
test('[crypto] getEmbeddedSecp256k1Binary returns the proper binary', async (t) => {
495496
const path = join(new URL('.', import.meta.url).pathname, 'secp256k1.wasm');
496497
const binaryFromDisk = readFileSync(path).buffer;
497-
t.deepEqual(binary, binaryFromDisk);
498+
const eventuallyDecompressedBinary = await (
499+
await streamWasmArrayBuffer(binary)
500+
).arrayBuffer();
501+
t.deepEqual(eventuallyDecompressedBinary, binaryFromDisk);
502+
503+
// compress the wasm binary
504+
const stream = new ReadableStream({
505+
start(controller): void {
506+
controller.enqueue(new Uint8Array(binaryFromDisk));
507+
controller.close();
508+
},
509+
}).pipeThrough(new CompressionStream('gzip'));
510+
511+
const compressedBinary = await new Response(stream).arrayBuffer();
512+
513+
// decompress the compressed wasm binary
514+
const decompressedBinary = await (
515+
await streamWasmArrayBuffer(compressedBinary)
516+
).arrayBuffer();
517+
t.deepEqual(decompressedBinary, binaryFromDisk);
518+
519+
// check that the uncompressed binary produced by `streamWasmArrayBuffer` is the same as the binary from disk
520+
const uncompressedBinary = await (
521+
await streamWasmArrayBuffer(binaryFromDisk)
522+
).arrayBuffer();
523+
t.deepEqual(uncompressedBinary, binaryFromDisk);
498524
});
499525

500526
test('[crypto] Secp256k1Wasm instantiated with embedded binary', async (t) => {

‎src/lib/bin/secp256k1/secp256k1-wasm.ts

+10-5
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
/* eslint-disable no-underscore-dangle, @typescript-eslint/max-params, @typescript-eslint/naming-convention */
22
// cSpell:ignore memcpy, anyfunc
33
import { base64ToBin } from '../../format/format.js';
4+
import { streamWasmArrayBuffer } from '../hashes.js';
45

56
import type { Secp256k1Wasm } from './secp256k1-wasm-types.js';
67
import { CompressionFlag, ContextFlag } from './secp256k1-wasm-types.js';
@@ -352,11 +353,15 @@ export const instantiateSecp256k1WasmBytes = async (
352353
global: { Infinity, NaN },
353354
};
354355

355-
return WebAssembly.instantiate(webassemblyBytes, info).then((result) => {
356-
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-assignment
357-
getErrNoLocation = result.instance.exports['___errno_location'] as any;
358-
return wrapSecp256k1Wasm(result.instance, heapU8, heapU32);
359-
});
356+
const webassemblyByteStream = await streamWasmArrayBuffer(webassemblyBytes);
357+
358+
return WebAssembly.instantiateStreaming(webassemblyByteStream, info).then(
359+
(result) => {
360+
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-assignment
361+
getErrNoLocation = result.instance.exports['___errno_location'] as any;
362+
return wrapSecp256k1Wasm(result.instance, heapU8, heapU32);
363+
},
364+
);
360365
};
361366
/* eslint-enable functional/immutable-data, functional/no-expression-statements, @typescript-eslint/no-magic-numbers, functional/no-conditional-statements, no-bitwise, functional/no-throw-statements */
362367

‎src/lib/bin/secp256k1/secp256k1.base64.ts

+1-1
Large diffs are not rendered by default.

‎src/lib/bin/sha1/sha1.base64.ts

+1-1
Large diffs are not rendered by default.

‎src/lib/bin/sha256/sha256.base64.ts

+1-1
Large diffs are not rendered by default.

‎src/lib/bin/sha512/sha512.base64.ts

+1-1
Large diffs are not rendered by default.

‎src/lib/crypto/hash.spec.helper.ts

+6-3
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { join } from 'path';
77
import test from 'ava';
88

99
import type { HashFunction } from '../lib.js';
10-
import { utf8ToBin } from '../lib.js';
10+
import { streamWasmArrayBuffer, utf8ToBin } from '../lib.js';
1111

1212
import fc from 'fast-check';
1313
import hashJs from 'hash.js';
@@ -43,7 +43,7 @@ export const testHashFunction = <T extends HashFunction>({
4343
nodeJsAlgorithm: 'ripemd160' | 'sha1' | 'sha256' | 'sha512';
4444
}) => {
4545
const binary = getEmbeddedBinary();
46-
test(`[crypto] ${hashFunctionName} getEmbeddedBinary returns the proper binary`, (t) => {
46+
test(`[crypto] ${hashFunctionName} getEmbeddedBinary returns the proper binary`, async (t) => {
4747
const path = join(
4848
new URL('.', import.meta.url).pathname,
4949
'..',
@@ -52,7 +52,10 @@ export const testHashFunction = <T extends HashFunction>({
5252
`${hashFunctionName}.wasm`,
5353
);
5454
const binaryFromDisk = readFileSync(path).buffer;
55-
t.deepEqual(binary, binaryFromDisk);
55+
const eventuallyDecompressedBinary = await (
56+
await streamWasmArrayBuffer(binary)
57+
).arrayBuffer();
58+
t.deepEqual(eventuallyDecompressedBinary, binaryFromDisk);
5659
});
5760

5861
test(`[crypto] ${hashFunctionName} instantiated with embedded binary`, async (t) => {

‎wasm/docker/hashes.Dockerfile

+4-4
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ RUN /binaryen/bin/wasm-opt -O3 pkg/ripemd160_bg.wasm -o pkg/ripemd160.wasm
2222
RUN cp pkg/ripemd160.wasm out/ripemd160
2323
RUN cp pkg/ripemd160.d.ts out/ripemd160
2424
RUN cp pkg/ripemd160.js out/ripemd160
25-
RUN OUTPUT_TS_FILE=out/ripemd160/ripemd160.base64.ts; printf "/**\n * @hidden\n */\n// prettier-ignore\nexport const ripemd160Base64Bytes =\n '" > $OUTPUT_TS_FILE && base64 -w 0 pkg/ripemd160.wasm >> $OUTPUT_TS_FILE && printf "';\n" >> $OUTPUT_TS_FILE
25+
RUN OUTPUT_TS_FILE=out/ripemd160/ripemd160.base64.ts; printf "/**\n * @hidden\n */\n// prettier-ignore\nexport const ripemd160Base64Bytes =\n '" > $OUTPUT_TS_FILE && cat pkg/ripemd160.wasm | gzip | base64 -w 0 >> $OUTPUT_TS_FILE && printf "';\n" >> $OUTPUT_TS_FILE
2626
RUN cp -r /libauth/wasm/hashes/ripemd160/out/ripemd160 /libauth/bin
2727

2828
# sha256
@@ -34,7 +34,7 @@ RUN /binaryen/bin/wasm-opt -O3 pkg/sha256_bg.wasm -o pkg/sha256.wasm
3434
RUN cp pkg/sha256.wasm out/sha256
3535
RUN cp pkg/sha256.d.ts out/sha256
3636
RUN cp pkg/sha256.js out/sha256
37-
RUN OUTPUT_TS_FILE=out/sha256/sha256.base64.ts; printf "/**\n * @hidden\n */\n// prettier-ignore\nexport const sha256Base64Bytes =\n '" > $OUTPUT_TS_FILE && base64 -w 0 pkg/sha256.wasm >> $OUTPUT_TS_FILE && printf "';\n" >> $OUTPUT_TS_FILE
37+
RUN OUTPUT_TS_FILE=out/sha256/sha256.base64.ts; printf "/**\n * @hidden\n */\n// prettier-ignore\nexport const sha256Base64Bytes =\n '" > $OUTPUT_TS_FILE && cat pkg/sha256.wasm | gzip | base64 -w 0 >> $OUTPUT_TS_FILE && printf "';\n" >> $OUTPUT_TS_FILE
3838
RUN cp -r /libauth/wasm/hashes/sha256/out/sha256 /libauth/bin
3939

4040
# sha512
@@ -46,7 +46,7 @@ RUN /binaryen/bin/wasm-opt -O3 pkg/sha512_bg.wasm -o pkg/sha512.wasm
4646
RUN cp pkg/sha512.wasm out/sha512
4747
RUN cp pkg/sha512.d.ts out/sha512
4848
RUN cp pkg/sha512.js out/sha512
49-
RUN OUTPUT_TS_FILE=out/sha512/sha512.base64.ts; printf "/**\n * @hidden\n */\n// prettier-ignore\nexport const sha512Base64Bytes =\n '" > $OUTPUT_TS_FILE && base64 -w 0 pkg/sha512.wasm >> $OUTPUT_TS_FILE && printf "';\n" >> $OUTPUT_TS_FILE
49+
RUN OUTPUT_TS_FILE=out/sha512/sha512.base64.ts; printf "/**\n * @hidden\n */\n// prettier-ignore\nexport const sha512Base64Bytes =\n '" > $OUTPUT_TS_FILE && cat pkg/sha512.wasm | gzip | base64 -w 0 >> $OUTPUT_TS_FILE && printf "';\n" >> $OUTPUT_TS_FILE
5050
RUN cp -r /libauth/wasm/hashes/sha512/out/sha512 /libauth/bin
5151

5252
# sha1
@@ -58,7 +58,7 @@ RUN /binaryen/bin/wasm-opt -O3 pkg/sha1_bg.wasm -o pkg/sha1.wasm
5858
RUN cp pkg/sha1.wasm out/sha1
5959
RUN cp pkg/sha1.d.ts out/sha1
6060
RUN cp pkg/sha1.js out/sha1
61-
RUN OUTPUT_TS_FILE=out/sha1/sha1.base64.ts; printf "/**\n * @hidden\n */\n// prettier-ignore\nexport const sha1Base64Bytes =\n '" > $OUTPUT_TS_FILE && base64 -w 0 pkg/sha1.wasm >> $OUTPUT_TS_FILE && printf "';\n" >> $OUTPUT_TS_FILE
61+
RUN OUTPUT_TS_FILE=out/sha1/sha1.base64.ts; printf "/**\n * @hidden\n */\n// prettier-ignore\nexport const sha1Base64Bytes =\n '" > $OUTPUT_TS_FILE && cat pkg/sha1.wasm | gzip | base64 -w 0 >> $OUTPUT_TS_FILE && printf "';\n" >> $OUTPUT_TS_FILE
6262
RUN cp -r /libauth/wasm/hashes/sha1/out/sha1 /libauth/bin
6363

6464
WORKDIR /libauth/wasm/hashes/

‎wasm/docker/secp256k1.Dockerfile

+1-1
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ RUN emcc src/libsecp256k1_la-secp256k1.o \
5959
]' \
6060
-o out/secp256k1/secp256k1.js
6161

62-
RUN OUTPUT_TS_FILE=out/secp256k1/secp256k1.base64.ts; printf "/**\n * @hidden\n */\n// prettier-ignore\nexport const secp256k1Base64Bytes =\n '" > $OUTPUT_TS_FILE && base64 -w 0 out/secp256k1/secp256k1.wasm >> $OUTPUT_TS_FILE && printf "';\n" >> $OUTPUT_TS_FILE
62+
RUN OUTPUT_TS_FILE=out/secp256k1/secp256k1.base64.ts; printf "/**\n * @hidden\n */\n// prettier-ignore\nexport const secp256k1Base64Bytes =\n '" > $OUTPUT_TS_FILE && cat out/secp256k1/secp256k1.wasm | gzip | base64 -w 0 >> $OUTPUT_TS_FILE && printf "';\n" >> $OUTPUT_TS_FILE
6363

6464
RUN cp -r /libauth/wasm/secp256k1/out /libauth/bin
6565

0 commit comments

Comments
 (0)
Please sign in to comment.