Bug
When building any program that transitively depends on crypto/internal/fips140/drbg
(e.g. crypto/ecdsa, crypto/rsa) for the wasm target with TinyGo + Go 1.26,
the produced app.wasm imports gojs.runtime.getRandomData, but
targets/wasm_exec.js does not provide an implementation. The module fails
to instantiate at runtime with:
LinkError: WebAssembly.instantiate(): Import #13 "gojs" "runtime.getRandomData": function import requires a callable
Background
Reproducer
go.mod
main.go
package main
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
)
func main() {
_, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
println("err:", err.Error())
return
}
println("ok")
}
$ tinygo version
tinygo version 0.41.1 darwin/arm64 (using go version go1.26.2 and LLVM version 20.1.1)
$ tinygo build -target wasm -no-debug -o app.wasm .
$ wasm-objdump -j Import -x app.wasm | grep getRandomData
- func[13] sig=7 <crypto/internal/sysrand.getRandomValues> <- gojs.runtime.getRandomData
$ cp $(tinygo env TINYGOROOT)/targets/wasm_exec.js .
$ node wasm_exec.js app.wasm
[LinkError: WebAssembly.instantiate(): Import #13 "gojs" "runtime.getRandomData": function import requires a callable]
Expected
node wasm_exec.js app.wasm should print ok and exit 0.
Suggested fix
Add the missing import to the gojs block of targets/wasm_exec.js. The
upstream Go signature is func getRandomValues(r []byte), which TinyGo's
WASM C ABI passes as (ptr i32, len i32, cap i32) -> nil. The
Crypto.getRandomValues Web API has a 64 KiB per-call limit, so we chunk:
gojs: {
// func getRandomData(r []byte)
"runtime.getRandomData": (slice_ptr, slice_len, slice_cap) => {
const buf = loadSlice(slice_ptr, slice_len, slice_cap);
// crypto.getRandomValues has a 65536-byte cap per call
for (let offset = 0; offset < buf.length; offset += 65536) {
crypto.getRandomValues(
buf.subarray(offset, Math.min(offset + 65536, buf.length))
);
}
},
// ...existing imports below...
}
Verification
After applying the patch above to targets/wasm_exec.js:
$ node wasm_exec.js app.wasm
ok
Environment
|
|
| TinyGo |
0.41.1 darwin/arm64 |
| Go |
1.26.2 |
| LLVM |
20.1.1 |
| Repro target |
-target=wasm |
| Confirmed broken on |
Node 23.11.0 and Chrome 130 |
I'm happy to send a PR if this analysis looks correct.
Bug
When building any program that transitively depends on
crypto/internal/fips140/drbg(e.g.
crypto/ecdsa,crypto/rsa) for thewasmtarget with TinyGo + Go 1.26,the produced
app.wasmimportsgojs.runtime.getRandomData, buttargets/wasm_exec.jsdoes not provide an implementation. The module failsto instantiate at runtime with:
Background
crypto/rand.Readroutes throughcrypto/internal/fips140/drbg.Read,which seeds itself from
crypto/internal/sysrand.Read.js/wasm,crypto/internal/sysranddeclarescrypto/internal/sysrand/rand_js.go)[]byteimport compile, but thecorresponding JS-side implementation was never added to
targets/wasm_exec.js.Reproducer
go.modmain.goExpected
node wasm_exec.js app.wasmshould printokand exit 0.Suggested fix
Add the missing import to the
gojsblock oftargets/wasm_exec.js. Theupstream Go signature is
func getRandomValues(r []byte), which TinyGo'sWASM C ABI passes as
(ptr i32, len i32, cap i32) -> nil. TheCrypto.getRandomValuesWeb API has a 64 KiB per-call limit, so we chunk:Verification
After applying the patch above to
targets/wasm_exec.js:Environment
-target=wasmI'm happy to send a PR if this analysis looks correct.