Replacing Javascript with Webassembly on Paste.me
How and why I did replace parts of Javascript with Webassembly for Paste.me
I did create Paste.me (formerly pastedb.io) many years ago! I was always fascinated with services that you can use, to share information in a manner, that the provider can not read the contents of the shared information. From their perspective, it is simply zeroes and ones.
How does Paste.me work?
There are popular paste services, just to mention one of them, pastebin. Pastebin is quite popular amongst them, mentioned a lot in the news, mostly due to some malicious actors using it to share otherwise private information anonymously, like leaked database data, passwords or other credentials.
I do not condone these kind of activities, this is also not why these services are created. I did create mine as an alternative, with a light-weight version using modern libraries and languages to create a fast and simple to use service which protects your right, to share private information with anyone, without the provider knowing what you are sharing.
Pastes are encrypted client side, real plaintext data does not touch the servers. Recently I did also introduced a way to attach small files to the paste which is nice to send config files or upload small binaries that you can share securely.
Read more in my previous articles:
See more links at the end of the post!
Technology stack
For the crypto part I did use the builtin SubtleCrypto Web API from javascript. This API provides low-level cryptographic functions that you can use in your products. Also as the warning on that link says, it is really recommended that you use this carefully. Any misconfiguration can lead to the result, where your product that is using these cryptographic functions and is built with a good intent, can be very much insecure in the end.
All of the implemented functions are async, which is nice since encrypting data can take a specific amount of time based on the amount and client resources. You can easily wait for Promises to complete and then react accordingly.
Webassembly (WASM)
I was always a fan of Webassembly since its introduction a few years ago. I think it has a great potential in partially replacing javascript with more efficient code. While things are looking way better now, there are still some downsides but I am positive that these will improve during in the future.
WebAssembly (abbreviated Wasm) is a binary instruction format for a stack-based virtual machine. Wasm is designed as a portable target for compilation of high-level languages like C/C++/Rust, enabling deployment on the web for client and server applications.
Source: webassembly.org
Like the quote says, you can compile high-level languages (also Go, not just the mentioned ones) to a portable format, that is, as of right now supported by all major browsers. It does not depend on any platform specific traits. The binary works on mobile browsers too, which is awesome!
A WASM binary is efficient, safe, runs in a sandboxed environment, is easily debuggable and it is part of the open web platform. They offer you to keep backward compatibility.
You can call javascript functions from your Go code while also gathering values from the javascript context. It provides you with a lot of features that you can leverage.
While being a fan, I was missing a good use-case where I would be able to leverage Webassembly at its full extent. A few days back I was reminded by a friend about Webassembly again, and I decided I will try to replace the Javascript functions for encryption and decryption with a simple WASM module, that would handle the workflow instead.
Writing the module
Writing the whole module took me actually only a few hours. Go is easy to work with. I had to study the syscall/js
module from the go core. I will use this module for communicating with Javascript stack. In Go, the functions from the binary are assigned directly on the window
object, I hope this will improve soon. I really like the way Rust has it. There you can easily export your functions and then assign them to variables in Javascript and use them wherever needed. The functions are not strictly tied to the window
object!
Since Go uses the window object, we must be careful not to overwrite any of the existing functions.
Let’s take a look at a simple example on how to sum two numbers.
package main
import (
"fmt"
"syscall/js"
)
func main() {
fmt.Printf("WASM module loaded!\n")
c := make(chan struct{}, 0)
js.Global().Set("sum_numbers", js.FuncOf(SumNumbers))
<-c
}
func SumNumbers(this js.Value, args []js.Value) interface{} {
a := args[0].Int()
b := args[1].Int()
// here we will return the sum of a+b
return a+b
}
With this example, we will define a function called sum_numbers
on the window object, that could be then called by providing two numbers. The args
parameter of the function contains the function arguments. In this case we are kind of taking the first and second parameter, lets call them a
and b
and sum them.
Since Go is a statically typed language, we should always check if the argument is a number. Of course it is good to check for your actual use-case. Sometimes the type does not matter, but in our use-case it does. If we provided a string for our current program above, the program would panic and exit.
The better example would be this one here:
package main
import (
"fmt"
"syscall/js"
)
func main() {
fmt.Printf("WASM module loaded!\n")
c := make(chan struct{}, 0)
js.Global().Set("sum_numbers", js.FuncOf(SumNumbers))
<-c
}
func SumNumbers(this js.Value, args []js.Value) interface{} {
// accept numbers only
if args[0].Type() != js.TypeNumber || args[1].Type() != js.TypeNumber {
return js.Null()
}
// for better readability, I did assign these to variables
a := args[0].Int()
b := args[1].Int()
// here we will return the sum of a+b
return a+b
}
As you may have noticed, we create a channel, this is needed so our program is still running, and we can call the same function again and again. Without this, the WASM module would load and then immediately exit, making it not useful in our use-case.
This example will only accept numbers, as expected. If we get any non-number type, we simply return null
. We can get the Type of arguments and then compare them with javascript types. Example:
func main() {
if args[0].Type() != js.TypeNumber {
fmt.Println("The argument is not a number type")
} else {
fmt.Println("The argument is a number type")
}
}
To load the module, you will need some javascript which you can find in the official Golang repository in the misc/wasm folder. There are very good example on how to load and run the binary. You need to fetch the binary and initiate the module.
To build your go app, you will need a to set the proper GOOS
and GOARCH
to target the right platform. You may need to set the OS and Architecture in your IDE’s too, since usually, you develop using your local platform. (linux/amd64, darwin/amd64 or windows/amd64)
This is a special case, where you have a different target to build for. The resulting binary is not tied to any platform.
GOOS=js GOARCH=wasm go build
Replacing the javascript functions with the WebAssembly module proved to have a positive result in the speed of the encryption process.
With shorter strings the javascript version was slightly faster than the WASM module. With more data, the average processing time gap widened and was quite significant.
Based on my very limited measurements, I’ve seen a speed increase for larger data in an amount of milliseconds and with bigger files even multiple seconds. No proper benchmarks were done, that is why I do not publish any results at this time.
I am happy with the end result. I think this will be a huge benefit in the future, if more options arrive for the various use-cases. The process of posting a paste or opening an existing one got faster too. So it is a win-win for the users too!
I will leave the links to the repos here. The source code is completely open source, I invite everyone to check it, if there are some improvements possible, please let me know, I would love to make it even faster and better than it already is. There is also a CLI tool, which you can leverage for posting pastes from your servers or from a terminal without the need to open the website. You will soon be able to attach files from the terminal client too!
Post improvements or file issues if you find any!