diff --git a/node/EncryptMessage.js b/node/EncryptMessage.js index 0d7b11cc..6c5c46a5 100644 --- a/node/EncryptMessage.js +++ b/node/EncryptMessage.js @@ -1,7 +1,25 @@ -const ModuleFactory = require('./encrypt.js'); +const path = require('path'); +const ModuleFactory = require('./encrypt_node.js'); + +let ModulePromise = null; + +function getModule() { + if (!ModulePromise) { + ModulePromise = ModuleFactory({ + locateFile: (filename) => { + // Should return absolute path to encrypt_node.wasm + if (filename.endsWith('.wasm')) { + return path.join(__dirname, 'encrypt_node.wasm'); + } + return path.join(__dirname, filename); + } + }); + } + return ModulePromise; +} async function encryptMessage(txData, publicKey, aadTE = '', aadAES = '') { - const Module = await ModuleFactory(); + const Module = await getModule(); return Module.ccall( 'encryptMessage', // Name of the exported C++ function 'string', // Return type @@ -12,7 +30,7 @@ async function encryptMessage(txData, publicKey, aadTE = '', aadAES = '') { async function encryptMessageDualKey( txData, firstPublicKey, secondPublicKey, aadTE = '', aadAES = '') { - const Module = await ModuleFactory(); + const Module = await getModule(); return Module.ccall( 'encryptMessageDualKey', // Name of the exported C++ function 'string', // Return type @@ -22,7 +40,7 @@ async function encryptMessageDualKey( } async function encryptMessageMockup(txData) { - const Module = await ModuleFactory(); + const Module = await getModule(); return Module.ccall( 'encryptMessageMockup', // Name of the exported C++ function 'string', // Return type diff --git a/node/EncryptMessage.mjs b/node/EncryptMessage.mjs index b723d087..0a4b2f11 100644 --- a/node/EncryptMessage.mjs +++ b/node/EncryptMessage.mjs @@ -1,7 +1,114 @@ -import ModuleFactory from './encrypt.js'; +import ModuleFactory from './encrypt_web.js'; +import wasmAsset from './encrypt_web.wasm'; + +let ModulePromise = null; + +function getRuntimeBaseUrl() { + if (typeof self !== 'undefined' && self.location && self.location.href) { + return self.location.href; + } + if (typeof document !== 'undefined' && document.baseURI) { + return document.baseURI; + } + return null; +} + +function toAbsoluteAssetUrl(filename) { + if (typeof filename !== 'string' || filename.length === 0) { + throw new TypeError('filename must be a non-empty string'); + } + + const baseUrl = getRuntimeBaseUrl(); + if (!baseUrl) { + return filename; + } + + try { + return new URL(filename, baseUrl).href; + } catch { + return filename; + } +} + +function getWasmModuleCandidate() { + if (typeof WebAssembly === 'undefined' || typeof WebAssembly.Module === 'undefined') { + return null; + } + + if (wasmAsset instanceof WebAssembly.Module) { + return wasmAsset; + } + + if (wasmAsset && wasmAsset.default instanceof WebAssembly.Module) { + return wasmAsset.default; + } + + try { + if (wasmAsset instanceof Uint8Array || wasmAsset instanceof ArrayBuffer) { + return new WebAssembly.Module(wasmAsset); + } + + if (wasmAsset && (wasmAsset.default instanceof Uint8Array || wasmAsset.default instanceof ArrayBuffer)) { + return new WebAssembly.Module(wasmAsset.default); + } + } catch { + // Fall through to URL-based loading below. + } + + return null; +} + +function getWasmUrlCandidate() { + if (typeof wasmAsset === 'string' && wasmAsset.length > 0) { + return wasmAsset; + } + + if (wasmAsset && typeof wasmAsset.default === 'string' && wasmAsset.default.length > 0) { + return wasmAsset.default; + } + + if (wasmAsset && typeof wasmAsset.href === 'string' && wasmAsset.href.length > 0) { + return wasmAsset.href; + } + + return null; +} + +function getModule() { + if (!ModulePromise) { + const moduleOptions = {}; + const wasmModule = getWasmModuleCandidate(); + const wasmUrl = getWasmUrlCandidate(); + + if (wasmModule) { + moduleOptions.instantiateWasm = (imports, receiveInstance) => { + const instance = new WebAssembly.Instance(wasmModule, imports); + receiveInstance(instance, wasmModule); + return instance.exports; + }; + } + + if (wasmUrl) { + moduleOptions.locateFile = (filename) => { + if (filename.endsWith('.wasm')) { + return wasmUrl; + } + + return toAbsoluteAssetUrl(filename); + }; + } else { + moduleOptions.locateFile = (filename) => { + return toAbsoluteAssetUrl(filename); + }; + } + + ModulePromise = ModuleFactory(moduleOptions); + } + return ModulePromise; +} export async function encryptMessage(txData, publicKey, aadTE = '', aadAES = '') { - const Module = await ModuleFactory(); + const Module = await getModule(); return Module.ccall( 'encryptMessage', // Name of the exported C++ function 'string', // Return type @@ -12,7 +119,7 @@ export async function encryptMessage(txData, publicKey, aadTE = '', aadAES = '') export async function encryptMessageDualKey( txData, firstPublicKey, secondPublicKey, aadTE = '', aadAES = '') { - const Module = await ModuleFactory(); + const Module = await getModule(); return Module.ccall( 'encryptMessageDualKey', // Name of the exported C++ function 'string', // Return type @@ -22,7 +129,7 @@ export async function encryptMessageDualKey( } export async function encryptMessageMockup(txData) { - const Module = await ModuleFactory(); + const Module = await getModule(); return Module.ccall( 'encryptMessageMockup', // Name of the exported C++ function 'string', // Return type diff --git a/node/EncryptMessageNode.mjs b/node/EncryptMessageNode.mjs new file mode 100644 index 00000000..ed4bad46 --- /dev/null +++ b/node/EncryptMessageNode.mjs @@ -0,0 +1,56 @@ +import { createRequire } from 'module'; +import { fileURLToPath } from 'url'; +const require = createRequire(import.meta.url); +const ModuleFactory = require('./encrypt_node.js'); +let ModulePromise = null; +function resolveModuleAssetPath(filename) { + if (typeof filename !== 'string' || filename.length === 0) { + throw new TypeError('filename must be a non-empty string'); + } + const resolvedPath = fileURLToPath(new URL(filename, import.meta.url)); + if (typeof resolvedPath !== 'string' || resolvedPath.length === 0) { + throw new Error('Failed to resolve module asset path'); + } + return resolvedPath; +} +function getModule() { + if (!ModulePromise) { + ModulePromise = ModuleFactory({ + locateFile: (filename) => { + return resolveModuleAssetPath(filename); + } + }); + } + return ModulePromise; +} + +export async function encryptMessage(txData, publicKey, aadTE = '', aadAES = '') { + const Module = await getModule(); + return Module.ccall( + 'encryptMessage', + 'string', + ['string', 'string', 'string', 'string'], + [txData, publicKey, aadTE, aadAES] + ); +} + +export async function encryptMessageDualKey( + txData, firstPublicKey, secondPublicKey, aadTE = '', aadAES = '') { + const Module = await getModule(); + return Module.ccall( + 'encryptMessageDualKey', + 'string', + ['string', 'string', 'string', 'string', 'string'], + [txData, firstPublicKey, secondPublicKey, aadTE, aadAES] + ); +} + +export async function encryptMessageMockup(txData) { + const Module = await getModule(); + return Module.ccall( + 'encryptMessageMockup', + 'string', + ['string'], + [txData] + ); +} diff --git a/node/package.json b/node/package.json index b5b258c9..528a585a 100644 --- a/node/package.json +++ b/node/package.json @@ -1,6 +1,6 @@ { "name": "@skalenetwork/t-encrypt", - "version": "0.8.0", + "version": "0.9.0", "keywords": [ "SKALE", @@ -11,13 +11,6 @@ "encryption", "wasm" ], - "browser": { - "fs": false, - "os": false, - "path": false, - "crypto": false, - "child_process": false - }, "homepage": "https://github.com/skalenetwork/libBLS", "license": "AGPL-3.0", "author": "SKALE Labs and contributors", @@ -33,14 +26,21 @@ "sideEffects": false, "exports": { ".": { - "import": "./EncryptMessage.mjs", - "require": "./EncryptMessage.js" + "browser": "./EncryptMessage.mjs", + "node": { + "import": "./EncryptMessageNode.mjs", + "require": "./EncryptMessage.js" + }, + "default": "./EncryptMessage.mjs" } }, "files": [ "EncryptMessage.js", "EncryptMessage.mjs", - "encrypt.js", - "encrypt.wasm" + "EncryptMessageNode.mjs", + "encrypt_node.js", + "encrypt_node.wasm", + "encrypt_web.js", + "encrypt_web.wasm" ] } diff --git a/scripts/run_emscripten_test.sh b/scripts/run_emscripten_test.sh index 4c78501e..2fe304e6 100644 --- a/scripts/run_emscripten_test.sh +++ b/scripts/run_emscripten_test.sh @@ -12,7 +12,7 @@ cp "$ROOT_DIR/tools/generate_bls_keys" "$ABS_BUILD_DIR/" cp "$ROOT_DIR/tools/decrypt_message" "$ABS_BUILD_DIR/" cp "$ROOT_DIR/test/test.js" "$ABS_BUILD_DIR/" cp "$ROOT_DIR/test/test2Keys.js" "$ABS_BUILD_DIR/" -cp "$ABS_BUILD_DIR/threshold_encryption/encrypt."* "$ABS_BUILD_DIR/" +cp "$ABS_BUILD_DIR/threshold_encryption/encrypt_node."* "$ABS_BUILD_DIR/" cd "$ABS_BUILD_DIR/" # Number of times to run the test block diff --git a/test/test.js b/test/test.js index d0de71d2..0b7743b6 100644 --- a/test/test.js +++ b/test/test.js @@ -1,4 +1,4 @@ -const ModuleFactory = require("./encrypt.js"); +const ModuleFactory = require("./encrypt_node.js"); const BLS_PUBLIC_KEY = process.argv[2]; const TX_DATA = process.argv[3]; diff --git a/test/test2Keys.js b/test/test2Keys.js index 9412aa1d..17e8555a 100644 --- a/test/test2Keys.js +++ b/test/test2Keys.js @@ -1,4 +1,4 @@ -const ModuleFactory = require("./encrypt.js"); +const ModuleFactory = require("./encrypt_node.js"); const FIRST_BLS_PUBLIC_KEY = process.argv[2]; const SECOND_BLS_PUBLIC_KEY = process.argv[3]; diff --git a/threshold_encryption/CMakeLists.txt b/threshold_encryption/CMakeLists.txt index 58d6684b..719640c5 100644 --- a/threshold_encryption/CMakeLists.txt +++ b/threshold_encryption/CMakeLists.txt @@ -144,11 +144,39 @@ if (NOT EMSCRIPTEN) endif() if (EMSCRIPTEN) - add_executable(encrypt ../threshold_encryption/encryptMessage.cpp ../tools/utils.cpp) - set_target_properties(encrypt PROPERTIES LINK_FLAGS "-s EXIT_RUNTIME=1 -s USE_PTHREADS=0 -s MODULARIZE -s ALLOW_MEMORY_GROWTH=1 -s NO_DISABLE_EXCEPTION_CATCHING=1 -s EXPORTED_RUNTIME_METHODS='[\"ccall\"]' -s EXPORTED_FUNCTIONS='[\"_encryptMessage\", \"_encryptMessageDualKey\",\"_encryptMessageMockup\"]' -s ENVIRONMENT='node,web' -s SINGLE_FILE=1") - target_include_directories(encrypt SYSTEM PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} ${THIRD_PARTY_DIR}) - target_compile_definitions(encrypt PRIVATE MCL=1) - target_link_libraries(encrypt PRIVATE te) + set(ENCRYPT_SOURCES ../threshold_encryption/encryptMessage.cpp ../tools/utils.cpp) + set(ENCRYPT_LINK_FLAGS "-s EXIT_RUNTIME=1 -s USE_PTHREADS=0 -s MODULARIZE -s ALLOW_MEMORY_GROWTH=1 -s NO_DISABLE_EXCEPTION_CATCHING=1 -s EXPORTED_RUNTIME_METHODS='[\"ccall\"]' -s EXPORTED_FUNCTIONS='[\"_encryptMessage\", \"_encryptMessageDualKey\",\"_encryptMessageMockup\"]'") + + # encrypt_node (for Node.js) + add_executable(encrypt_node ${ENCRYPT_SOURCES}) + target_include_directories(encrypt_node SYSTEM PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} ${THIRD_PARTY_DIR}) + target_compile_definitions(encrypt_node PRIVATE MCL=1) + target_link_libraries(encrypt_node PRIVATE te) + set_target_properties(encrypt_node PROPERTIES + OUTPUT_NAME "encrypt_node" + LINK_FLAGS "${ENCRYPT_LINK_FLAGS} -s ENVIRONMENT='node'" + ) + + # encrypt_web (for Browser / Vite / Webpack 5 / native ESM consumers) + add_executable(encrypt_web ${ENCRYPT_SOURCES}) + target_include_directories(encrypt_web SYSTEM PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} ${THIRD_PARTY_DIR}) + target_compile_definitions(encrypt_web PRIVATE MCL=1) + target_link_libraries(encrypt_web PRIVATE te) + set_target_properties(encrypt_web PROPERTIES + OUTPUT_NAME "encrypt_web" + LINK_FLAGS "${ENCRYPT_LINK_FLAGS} -s ENVIRONMENT='web' -s EXPORT_ES6=1" + ) + + add_custom_command(TARGET encrypt_node POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_BINARY_DIR}/encrypt_node.js ${CMAKE_SOURCE_DIR}/node/encrypt_node.js + COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_BINARY_DIR}/encrypt_node.wasm ${CMAKE_SOURCE_DIR}/node/encrypt_node.wasm + COMMENT "Copying encrypt_node to node package folder" + ) + add_custom_command(TARGET encrypt_web POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_BINARY_DIR}/encrypt_web.js ${CMAKE_SOURCE_DIR}/node/encrypt_web.js + COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_BINARY_DIR}/encrypt_web.wasm ${CMAKE_SOURCE_DIR}/node/encrypt_web.wasm + COMMENT "Copying encrypt_web to node package folder" + ) add_executable(encrypt_message ../test/encryptMessageJS.cpp ../tools/utils.cpp) target_include_directories(encrypt_message SYSTEM PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} ${THIRD_PARTY_DIR})