mylamour/blog

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

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.
image
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

you will get three files :
image

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:

image

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 with Module.cwrap('run_test_sm2_keys', 'number', []);

  • compare two hash was equal in sign function and verify function
    gmssl was accept hex value by default.

Resources