mirror of
https://github.com/zhigang1992/mtcute.git
synced 2026-04-29 04:35:08 +08:00
chore(wasm): dual-build to support non-simd environments
This commit is contained in:
@@ -16,11 +16,10 @@ import {
|
||||
|
||||
// we currently prefer wasm for ctr because bun mostly uses browserify polyfills for node:crypto
|
||||
// which are slow AND semi-broken
|
||||
// native node-api addon is broken on macos so we don't support it either
|
||||
|
||||
export class BunCryptoProvider extends BaseCryptoProvider implements ICryptoProvider {
|
||||
async initialize(): Promise<void> {
|
||||
const wasmFile = require.resolve('@mtcute/wasm/mtcute.wasm')
|
||||
const wasmFile = require.resolve('@mtcute/wasm/mtcute-simd.wasm')
|
||||
const wasm = await readFile(wasmFile)
|
||||
initSync(wasm)
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import { createRequire } from 'node:module'
|
||||
|
||||
import { deflateSync, gunzipSync } from 'node:zlib'
|
||||
import { BaseCryptoProvider } from '@mtcute/core/utils.js'
|
||||
import { ige256Decrypt, ige256Encrypt, initSync } from '@mtcute/wasm'
|
||||
import { ige256Decrypt, ige256Encrypt, initSync, SIMD_AVAILABLE } from '@mtcute/wasm'
|
||||
|
||||
export class NodeCryptoProvider extends BaseCryptoProvider {
|
||||
createAesCtr(key: Uint8Array, iv: Uint8Array): IAesCtr {
|
||||
@@ -69,7 +69,8 @@ export class NodeCryptoProvider extends BaseCryptoProvider {
|
||||
|
||||
async initialize(): Promise<void> {
|
||||
const require = createRequire(import.meta.url)
|
||||
const wasmFile = require.resolve('@mtcute/wasm/mtcute.wasm')
|
||||
const file = SIMD_AVAILABLE ? 'mtcute-simd.wasm' : 'mtcute.wasm'
|
||||
const wasmFile = require.resolve(`@mtcute/wasm/${file}`)
|
||||
const wasm = await readFile(wasmFile)
|
||||
initSync(wasm)
|
||||
}
|
||||
|
||||
@@ -14,9 +14,11 @@ export default () => {
|
||||
],
|
||||
preparePackageJson({ packageJson }) {
|
||||
packageJson.exports['./mtcute.wasm'] = './mtcute.wasm'
|
||||
packageJson.exports['./mtcute-simd.wasm'] = './mtcute-simd.wasm'
|
||||
},
|
||||
finalize({ packageDir, outDir }) {
|
||||
fs.cpSync(resolve(packageDir, 'src/mtcute.wasm'), resolve(outDir, 'mtcute.wasm'))
|
||||
fs.cpSync(resolve(packageDir, 'src/mtcute-simd.wasm'), resolve(outDir, 'mtcute-simd.wasm'))
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,3 +13,4 @@ RUN make
|
||||
|
||||
FROM scratch AS binaries
|
||||
COPY --from=build /src/mtcute.wasm ../
|
||||
COPY --from=build /src/mtcute-simd.wasm ../
|
||||
|
||||
@@ -20,7 +20,6 @@ CFLAGS_WASM := \
|
||||
-target wasm32-unknown-unknown \
|
||||
-nostdlib -ffreestanding -DFREESTANDING \
|
||||
-mbulk-memory \
|
||||
-msimd128 \
|
||||
-Wl,--no-entry,--export-dynamic,--lto-O3
|
||||
|
||||
CFLAGS := $(CFLAGS_WASM) \
|
||||
@@ -44,12 +43,15 @@ ifneq ($(OS),Windows_NT)
|
||||
endif
|
||||
endif
|
||||
|
||||
OUT := ../mtcute.wasm
|
||||
OUT := ../src/mtcute.wasm
|
||||
OUT_SIMD := ../src/mtcute-simd.wasm
|
||||
|
||||
$(OUT): $(SOURCES)
|
||||
$(CC) $(CFLAGS) -I . -I utils -o $@ $^
|
||||
$(OUT_SIMD): $(SOURCES)
|
||||
$(CC) $(CFLAGS) -msimd128 -I . -I utils -o $@ $^
|
||||
|
||||
clean:
|
||||
rm -f $(OUT)
|
||||
rm -f $(OUT) $(OUT_SIMD)
|
||||
|
||||
all: $(OUT)
|
||||
all: $(OUT) $(OUT_SIMD)
|
||||
|
||||
@@ -9,7 +9,8 @@
|
||||
"sideEffects": false,
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
"./mtcute.wasm": "./src/mtcute.wasm"
|
||||
"./mtcute.wasm": "./src/mtcute.wasm",
|
||||
"./mtcute-simd.wasm": "./src/mtcute-simd.wasm"
|
||||
},
|
||||
"scripts": {
|
||||
"build:wasm": "docker build --output=lib --target=binaries lib"
|
||||
|
||||
@@ -2,12 +2,19 @@ import type { MtcuteWasmModule, SyncInitInput } from './types.js'
|
||||
|
||||
export * from './types.js'
|
||||
|
||||
export const SIMD_AVAILABLE: boolean = /* @__PURE__ */ WebAssembly.validate(new Uint8Array(
|
||||
[0, 97, 115, 109, 1, 0, 0, 0, 1, 5, 1, 96, 0, 1, 123, 3, 2, 1, 0, 10, 10, 1, 8, 0, 65, 0, 253, 15, 253, 98, 11],
|
||||
))
|
||||
|
||||
export function getWasmUrl(): URL {
|
||||
// would be nice if we could just use `new URL('@mtcute/wasm/mtcute.wasm', import.meta.url)`
|
||||
// wherever this is used, but vite does some funky stuff with transitive dependencies
|
||||
// making it not work. probably related to https://github.com/vitejs/vite/issues/8427,
|
||||
// but asking the user to deoptimize the entire @mtcute/web is definitely not a good idea
|
||||
// so we'll just use this hack for now
|
||||
if (SIMD_AVAILABLE) {
|
||||
return new URL(/* @vite-ignore */ './mtcute-simd.wasm', import.meta.url)
|
||||
}
|
||||
return new URL(/* @vite-ignore */ './mtcute.wasm', import.meta.url)
|
||||
}
|
||||
|
||||
|
||||
BIN
packages/wasm/src/mtcute-simd.wasm
Executable file
BIN
packages/wasm/src/mtcute-simd.wasm
Executable file
Binary file not shown.
Binary file not shown.
148
packages/wasm/tests/ctr-simd.test.ts
Normal file
148
packages/wasm/tests/ctr-simd.test.ts
Normal file
@@ -0,0 +1,148 @@
|
||||
import { hex } from '@fuman/utils'
|
||||
import { beforeAll, describe, expect, it } from 'vitest'
|
||||
|
||||
import { __getWasm, createCtr256, ctr256, freeCtr256 } from '../src/index.js'
|
||||
|
||||
import { initWasmSimd } from './init.js'
|
||||
|
||||
beforeAll(async () => {
|
||||
await initWasmSimd()
|
||||
})
|
||||
|
||||
describe('aes-ctr (simd)', () => {
|
||||
const key = hex.decode('603DEB1015CA71BE2B73AEF0857D77811F352C073B6108D72D9810A30914DFF4')
|
||||
const iv = hex.decode('F0F1F2F3F4F5F6F7F8F9FAFBFCFDFEFF')
|
||||
|
||||
describe('NIST', () => {
|
||||
// https://csrc.nist.gov/CSRC/media/Projects/Cryptographic-Standards-and-Guidelines/documents/examples/AES_CTR.pdf
|
||||
const data = hex.decode(
|
||||
`6BC1BEE2 2E409F96 E93D7E11 7393172A
|
||||
AE2D8A57 1E03AC9C 9EB76FAC 45AF8E51
|
||||
30C81C46 A35CE411 E5FBC119 1A0A52EF
|
||||
F69F2445 DF4F9B17 AD2B417B E66C3710`.replace(/\s/g, ''),
|
||||
)
|
||||
const dataEnc = hex.decode(
|
||||
`601EC313 775789A5 B7A7F504 BBF3D228
|
||||
F443E3CA 4D62B59A CA84E990 CACAF5C5
|
||||
2B0930DA A23DE94C E87017BA 2D84988D
|
||||
DFC9C58D B67AADA6 13C2DD08 457941A6`.replace(/\s/g, ''),
|
||||
)
|
||||
|
||||
it('should correctly encrypt', () => {
|
||||
const ctr = createCtr256(key, iv)
|
||||
const res = ctr256(ctr, data)
|
||||
freeCtr256(ctr)
|
||||
|
||||
expect(hex.encode(res)).toEqual(hex.encode(dataEnc))
|
||||
})
|
||||
|
||||
it('should correctly decrypt', () => {
|
||||
const ctr = createCtr256(key, iv)
|
||||
const res = ctr256(ctr, dataEnc)
|
||||
freeCtr256(ctr)
|
||||
|
||||
expect(hex.encode(res)).toEqual(hex.encode(data))
|
||||
})
|
||||
})
|
||||
|
||||
describe('stream', () => {
|
||||
const data = hex.decode('6BC1BEE22E409F96E93D7E117393172A')
|
||||
const dataEnc1 = hex.decode('601ec313775789a5b7a7f504bbf3d228')
|
||||
const dataEnc2 = hex.decode('31afd77f7d218690bd0ef82dfcf66cbe')
|
||||
const dataEnc3 = hex.decode('7000927e2f2192cbe4b6a8b2441ddd48')
|
||||
|
||||
it('should correctly encrypt', () => {
|
||||
const ctr = createCtr256(key, iv)
|
||||
const res1 = ctr256(ctr, data)
|
||||
const res2 = ctr256(ctr, data)
|
||||
const res3 = ctr256(ctr, data)
|
||||
|
||||
freeCtr256(ctr)
|
||||
|
||||
expect(hex.encode(res1)).toEqual(hex.encode(dataEnc1))
|
||||
expect(hex.encode(res2)).toEqual(hex.encode(dataEnc2))
|
||||
expect(hex.encode(res3)).toEqual(hex.encode(dataEnc3))
|
||||
})
|
||||
|
||||
it('should correctly decrypt', () => {
|
||||
const ctr = createCtr256(key, iv)
|
||||
const res1 = ctr256(ctr, dataEnc1)
|
||||
const res2 = ctr256(ctr, dataEnc2)
|
||||
const res3 = ctr256(ctr, dataEnc3)
|
||||
|
||||
freeCtr256(ctr)
|
||||
|
||||
expect(hex.encode(res1)).toEqual(hex.encode(data))
|
||||
expect(hex.encode(res2)).toEqual(hex.encode(data))
|
||||
expect(hex.encode(res3)).toEqual(hex.encode(data))
|
||||
})
|
||||
})
|
||||
|
||||
describe('stream (unaligned)', () => {
|
||||
const data = hex.decode('6BC1BEE22E40')
|
||||
const dataEnc1 = hex.decode('601ec3137757')
|
||||
const dataEnc2 = hex.decode('7df2e078a555')
|
||||
const dataEnc3 = hex.decode('a3a17be0742e')
|
||||
const dataEnc4 = hex.decode('025ced833746')
|
||||
const dataEnc5 = hex.decode('3ff238dea125')
|
||||
const dataEnc6 = hex.decode('1055a52302dc')
|
||||
|
||||
it('should correctly encrypt', () => {
|
||||
const ctr = createCtr256(key, iv)
|
||||
const res1 = ctr256(ctr, data)
|
||||
const res2 = ctr256(ctr, data)
|
||||
const res3 = ctr256(ctr, data)
|
||||
const res4 = ctr256(ctr, data)
|
||||
const res5 = ctr256(ctr, data)
|
||||
const res6 = ctr256(ctr, data)
|
||||
|
||||
freeCtr256(ctr)
|
||||
|
||||
expect(hex.encode(res1)).toEqual(hex.encode(dataEnc1))
|
||||
expect(hex.encode(res2)).toEqual(hex.encode(dataEnc2))
|
||||
expect(hex.encode(res3)).toEqual(hex.encode(dataEnc3))
|
||||
expect(hex.encode(res4)).toEqual(hex.encode(dataEnc4))
|
||||
expect(hex.encode(res5)).toEqual(hex.encode(dataEnc5))
|
||||
expect(hex.encode(res6)).toEqual(hex.encode(dataEnc6))
|
||||
})
|
||||
|
||||
it('should correctly decrypt', () => {
|
||||
const ctr = createCtr256(key, iv)
|
||||
const res1 = ctr256(ctr, dataEnc1)
|
||||
const res2 = ctr256(ctr, dataEnc2)
|
||||
const res3 = ctr256(ctr, dataEnc3)
|
||||
const res4 = ctr256(ctr, dataEnc4)
|
||||
const res5 = ctr256(ctr, dataEnc5)
|
||||
const res6 = ctr256(ctr, dataEnc6)
|
||||
|
||||
freeCtr256(ctr)
|
||||
|
||||
expect(hex.encode(res1)).toEqual(hex.encode(data))
|
||||
expect(hex.encode(res2)).toEqual(hex.encode(data))
|
||||
expect(hex.encode(res3)).toEqual(hex.encode(data))
|
||||
expect(hex.encode(res4)).toEqual(hex.encode(data))
|
||||
expect(hex.encode(res5)).toEqual(hex.encode(data))
|
||||
expect(hex.encode(res6)).toEqual(hex.encode(data))
|
||||
})
|
||||
})
|
||||
|
||||
it('should not leak memory', () => {
|
||||
const data = hex.decode('6BC1BEE22E409F96E93D7E117393172A')
|
||||
const mem = __getWasm().memory.buffer
|
||||
const memSize = mem.byteLength
|
||||
|
||||
for (let i = 0; i < 100; i++) {
|
||||
const ctrEnc = createCtr256(key, iv)
|
||||
const ctrDec = createCtr256(key, iv)
|
||||
|
||||
for (let i = 0; i < 100; i++) {
|
||||
ctr256(ctrDec, ctr256(ctrEnc, data))
|
||||
}
|
||||
|
||||
freeCtr256(ctrEnc)
|
||||
freeCtr256(ctrDec)
|
||||
}
|
||||
|
||||
expect(mem.byteLength).toEqual(memSize)
|
||||
})
|
||||
})
|
||||
41
packages/wasm/tests/ige-simd.test.ts
Normal file
41
packages/wasm/tests/ige-simd.test.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { hex } from '@fuman/utils'
|
||||
import { beforeAll, describe, expect, it } from 'vitest'
|
||||
|
||||
import { __getWasm, ige256Decrypt, ige256Encrypt } from '../src/index.js'
|
||||
|
||||
import { initWasmSimd } from './init.js'
|
||||
|
||||
beforeAll(async () => {
|
||||
await initWasmSimd()
|
||||
})
|
||||
|
||||
describe('aes-ige (simd)', () => {
|
||||
const key = hex.decode('5468697320697320616E20696D706C655468697320697320616E20696D706C65')
|
||||
const iv = hex.decode('6D656E746174696F6E206F6620494745206D6F646520666F72204F70656E5353')
|
||||
|
||||
const data = hex.decode('99706487a1cde613bc6de0b6f24b1c7aa448c8b9c3403e3467a8cad89340f53b')
|
||||
const dataEnc = hex.decode('792ea8ae577b1a66cb3bd92679b8030ca54ee631976bd3a04547fdcb4639fa69')
|
||||
|
||||
it('should correctly encrypt', () => {
|
||||
const aes = ige256Encrypt(data, key, iv)
|
||||
|
||||
expect(hex.encode(aes)).toEqual(hex.encode(dataEnc))
|
||||
})
|
||||
|
||||
it('should correctly decrypt', () => {
|
||||
const aes = ige256Decrypt(dataEnc, key, iv)
|
||||
|
||||
expect(hex.encode(aes)).toEqual(hex.encode(data))
|
||||
})
|
||||
|
||||
it('should not leak memory', () => {
|
||||
const mem = __getWasm().memory.buffer
|
||||
const memSize = mem.byteLength
|
||||
|
||||
for (let i = 0; i < 10000; i++) {
|
||||
ige256Decrypt(ige256Encrypt(data, key, iv), key, iv)
|
||||
}
|
||||
|
||||
expect(mem.byteLength).toEqual(memSize)
|
||||
})
|
||||
})
|
||||
@@ -15,3 +15,19 @@ export async function initWasm(): Promise<void> {
|
||||
const buffer = await blob.arrayBuffer()
|
||||
initSync(buffer)
|
||||
}
|
||||
|
||||
export async function initWasmSimd(): Promise<void> {
|
||||
const url = new URL('../src/mtcute-simd.wasm', import.meta.url)
|
||||
|
||||
if (import.meta.env.TEST_ENV === 'node') {
|
||||
const fs = await import('node:fs/promises')
|
||||
const blob = await fs.readFile(url)
|
||||
initSync(blob)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
const blob = await fetch(url)
|
||||
const buffer = await blob.arrayBuffer()
|
||||
initSync(buffer)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user