How to compile gmssl library with wasm and use it in javascript
Opened this issue · 0 comments
GMSSL is a popular library for china national shangmi(SM)
algorithms, include sm2
, sm3
, sm4
and related. But do not have a good implements of Javascript language. also there was another robust library for Shangmi, but it was not used in finance yet. after few days working, today i gonna show you how to use EMScripten
to compile it and use it in javascript within browser.
requirements
- ubuntu 22.04 LTS
- gmssl
Install EMScripten and Compile GMSSL
sudo apt install emscripten llvm clang
git clone https://github.com/guanzhi/GmSSL
cd GmSSL && mkdir build
cd build
emcmake cmake ..
emmake make -j8
when you finished the compile process. you will get a lot of wasm file and js file. but it can not work correctly yet.
and you have a static library was named libgmssl.a
now.
write c code and compile to WASM with gmssl lib(libgmssl.a)
you should write your own code and compile it with libgmssl.a
#ifdef __EMSCRIPTEN__
#include <emscripten.h>
EMSCRIPTEN_KEEPALIVE
char* run_test_sm3(const char *test_data) {
return test_sm3(test_data);
}
EMSCRIPTEN_KEEPALIVE
SM2Keys* run_test_sm2_keys(void) {
return generate_sm2_keys();
}
EMSCRIPTEN_KEEPALIVE
char* run_test_sm2_encrypt(const char* public_key_hex, const char* plaintext) {
return test_sm2_encrypt(public_key_hex, plaintext);
}
EMSCRIPTEN_KEEPALIVE
char* run_test_sm2_decrypt(const char* private_key_hex, const char* ciphertext) {
return test_sm2_decrypt(private_key_hex, ciphertext);
}
EMSCRIPTEN_KEEPALIVE
char* run_test_sm2_sign(const char* private_key_hex, const char* message){
return test_sm2_sign(private_key_hex, message);
}
EMSCRIPTEN_KEEPALIVE
int run_test_sm2_verify(const char* public_key_hex, const char* message, const char* signature_hex){
return test_sm2_verify(public_key_hex, message, signature_hex);
}
#endif
those code was demonstrated how to keep your c code can be calling in the Javascript. Once you compiled those c code, you will get a wasm
file and js
file.
use below shell script to compile it
echo "【Clearing】Remove old complied files....."
rm sm.html sm.js sm.wasm
echo "【Start】Start to building....."
static_gmssllib_path=$PWD/libgmssl.a
source_code=smtest.c
emcc -I/usr/local/include $static_gmssllib_path $source_code \
-s EXPORTED_RUNTIME_METHODS='["ccall", "cwrap","UTF8ToString", "stringToUTF8", "getValue"]' \
-o sm.html
echo "【Done】Build complete"
if you need to return value from c function, you must export runtime function : UTF8ToString
call it from javascript
<html>
<head>
<script src="sm.js"></script>
<script>
function stringToHex(str) {
let hex = '';
for (let i = 0; i < str.length; i++) {
hex += str.charCodeAt(i).toString(16).padStart(2, '0');
}
return hex;
}
function hexToString(hex) {
// Ensure the hex string length is even
if (hex.length % 2 !== 0) {
throw new Error("Invalid hex string");
}
let str = '';
for (let i = 0; i < hex.length; i += 2) {
// Parse each pair of hex digits as an integer, then convert to a character
const byte = parseInt(hex.substr(i, 2), 16);
str += String.fromCharCode(byte);
}
return str;
}
setTimeout(() => {
const run_test_sm3 = Module.cwrap('run_test_sm3', 'string', ['string']);
// Call the wrapped function
// const hexString = '74657374'; // 'test' in hex
const result = run_test_sm3(stringToHex('test'));
// Output the result
console.log("SM3 Hash:", result);
const run_generate_sm2_keys = Module.cwrap('run_test_sm2_keys', 'number', []);
const keysPtr = run_generate_sm2_keys();
const privateKeyPtr = Module.getValue(keysPtr, 'i32');
const publicKeyPtr = Module.getValue(keysPtr + 4, 'i32');
const privateKey = Module.UTF8ToString(privateKeyPtr);
const publicKey = Module.UTF8ToString(publicKeyPtr);
console.log("Private Key:", privateKey);
console.log("Public Key:", publicKey);
const run_sm2_encrypt = Module.cwrap('run_test_sm2_encrypt', 'string', ['string', 'string']);
const run_sm2_decrypt = Module.cwrap('run_test_sm2_decrypt', 'string', ['string', 'string']);
const run_sm2_sign = Module.cwrap('run_test_sm2_sign', 'string', ['string', 'string']);
const run_sm2_verify = Module.cwrap('run_test_sm2_verify', 'int', ['string', 'string', 'string']);
var plaintext = "This is funny";
try {
var ciphertextHex = run_sm2_encrypt(publicKey, plaintext);
console.log("Ciphertext (hex):", ciphertextHex);
var dec_cipher = run_sm2_decrypt(privateKey, ciphertextHex);
console.log("Plaintext:", dec_cipher);
// pass param to function to c with stringToHex
var signature = run_sm2_sign(privateKey, stringToHex(plaintext));
console.log("Plaintext signature:", signature);
var verify_result = run_sm2_verify(publicKey, stringToHex(plaintext), signature);
if (verify_result == 1) {
console.log("Signature Verify: passed",);
} else {
console.log("Signature Verify: failed",);
}
} catch (e) {
console.error("An error occurred:", e);
}
}, 1000);
</script>
</head>
</html>
and run python -m http.server
to host the html, then you will see:
all exported function can be called by wasm with Module.cwrap('run_test_sm2_keys', 'number', []);
and
Troubleshooting
- RuntimeError: Aborted(Assertion failed: native function
run_test_sm3
called before runtime initialization)
setTimeout(()=>{
Module._run_test_sm3('test');
}, 1000);
- xxx function was not exported
change your function in the below function list
-s EXPORTED_FUNCTIONS='["_run_test_sm3", "_hex_to_bytes", "_sm3_init", "_sm3_update", "_sm3_finish"]'
- if you need return a value in c but can not accept it in js
just export runtime function: UTF8ToString
EXPORTED_RUNTIME_METHODS='["ccall", "cwrap","UTF8ToString", "stringToUTF8", "getValue"]'
-
if memroy error with
Module._xxx
Just replace it withModule.cwrap('run_test_sm2_keys', 'number', []);
-
compare two hash was equal in sign function and verify function
gmssl was accept hex value by default.