WASM in Rust

Let’s make something more than the basic WASM example that adds 2 numbers together!

https://doc.rust-lang.org/rustc/platform-support/wasm32-wasip1.html

We’ll do a toy Bitcoin address validator. (Note, it’s not a real one, we’ll just use SHA256 twice).

https://github.com/RGGH/tool_sigcheck

Bitcoin address validator in WASM - toy version
 toy Bitcoin address validator.
cargo new --lib tool_sigcheck
[package]
name = "tool_sigcheck"
version = "0.1.0"
edition = "2021"

[dependencies]
base58 = "0.2"  # For decoding Base58Check encoding
hex = "0.4"
anyhow = "1"
sha2 = "0.10"  # For double SHA-256 checksum
ed25519-compact = "2"

[lib]
crate-type = ["cdylib"]
Rust 2024 error (2021 compiles without the error)
Rust 2024 error (2021 compiles without the error)
use base58::FromBase58;
use sha2::{Digest, Sha256};
use std::env;

/// Computes double SHA-256 hash.
fn double_sha256(data: &[u8]) -> [u8; 32] {
    let hash1 = Sha256::digest(data);
    let hash2 = Sha256::digest(hash1);
    hash2.into()
}

/// Validates a Bitcoin address.
fn validate_bitcoin_address(address: &str) -> bool {
    let decoded = match address.from_base58() {
        Ok(bytes) => bytes,
        Err(_) => return false, // Invalid Base58 encoding
    };

    if decoded.len() < 4 {
        return false;
    }

    let (payload, checksum) = decoded.split_at(decoded.len() - 4);
    let expected_checksum = &double_sha256(payload)[..4];

    checksum == expected_checksum
}

/// WASI entry point: `_start`
#[no_mangle]
pub extern "C" fn _start() {
    let args: Vec<String> = env::args().collect();
    if args.len() < 2 {
        println!("โš ๏ธ  Usage: wasmer run tool_sigcheck.wasm -- <bitcoin_address>");
        return;
    }

    let address = &args[1];
    if validate_bitcoin_address(address) {
        println!("โœ… Valid Bitcoin address.");
    } else {
        println!("โŒ Invalid Bitcoin address.");
    }
}

๐Ÿš€ Rebuild & Run (WASI Preview 1)

1๏ธโƒฃ Build for wasip1

cargo build --release --target wasm32-wasip1

2๏ธโƒฃ Run with Wasmer

wasmer run target/wasm32-wasip1/release/tool_sigcheck.wasm -- 1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa

๐Ÿ”ง Fix: Add _start for WASI Execution

error: Loading exports failed
โ•ฐโ”€โ–ถ 1: Missing export _start

This error happens because WASI expects a _start function as the entry point, but Rust’s standard binary entry (main) is not automatically mapped to _start when compiled for WASI.


โœ… Solution: Add a _start Function

We need to explicitly define _start for WASI execution.

๐Ÿ”น Fix in src/lib.rs

use base58::FromBase58;
use sha2::{Digest, Sha256};
use std::env;

/// Computes double SHA-256 hash.
fn double_sha256(data: &[u8]) -> [u8; 32] {
    let hash1 = Sha256::digest(data);
    let hash2 = Sha256::digest(hash1);
    hash2.into()
}

/// Validates a Bitcoin address.
fn validate_bitcoin_address(address: &str) -> bool {
    let decoded = match address.from_base58() {
        Ok(bytes) => bytes,
        Err(_) => return false, // Invalid Base58 encoding
    };

    if decoded.len() < 4 {
        return false;
    }

    let (payload, checksum) = decoded.split_at(decoded.len() - 4);
    let expected_checksum = &double_sha256(payload)[..4];

    checksum == expected_checksum
}

/// WASI entry point: `_start`
#[no_mangle]
pub extern "C" fn _start() {
    let args: Vec<String> = env::args().collect();
    if args.len() < 2 {
        println!("โš ๏ธ  Usage: wasmer run tool_sigcheck.wasm -- <bitcoin_address>");
        return;
    }

    let address = &args[1];
    if validate_bitcoin_address(address) {
        println!("โœ… Valid Bitcoin address.");
    } else {
        println!("โŒ Invalid Bitcoin address.");
    }
}

๐Ÿš€ Rebuild & Run (WASI Preview 1)

1๏ธโƒฃ Build for wasip1

cargo build --release --target wasm32-wasip1

2๏ธโƒฃ Run with Wasmer

wasmer run target/wasm32-wasip1/release/tool_sigcheck.wasm -- 1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa

โœ… Expected Output:

โœ… Valid Bitcoin address.

๐Ÿ’ก Why This Fix Works

  • Rust does not automatically provide _start in WASI.
  • WASI runtimes (e.g., wasmer, wasmtime) look for _start as the entry point.
  • Manually defining _start makes the Wasm executable valid.

๐Ÿ’ฏ Now it works across all WASI runtimes! ๐Ÿš€

wasip1