Skip to content

wasm: gojs.runtime.getRandomData is missing from wasm_exec.js, breaking crypto/ecdsa, crypto/rsa, and other DRBG users #5357

@cxjava

Description

@cxjava

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

module repro

go 1.26

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions