WASM Ed25519 (RFC 8032) — Tier-2 fallback for Ed25519 when
crypto.subtlelacks it.
Module wasmEd25519 | Source packages/front/fw/src/crypto/wasm/ed25519.js | Deps wasmRuntime | Worker-safe yes
WASM-backed Ed25519 digital signatures (RFC 8032): keygen, sign, and verify. The binary is the libsodium ref10 closure framed by the @awacloud/fw-wasm-crypto package's arena allocator, SHA-512 seam, and staged-entropy RNG seam. Signatures are deterministic (RFC 8032 §5.1 — no nonce randomness).
This is the Tier-2 fallback for environments where crypto.subtle is unavailable (non-secure-context, locked-down workers). In secure contexts prefer ../webcrypto/ed25519.md, which is hardware-accelerated. The pure-JS ../pkc/ed25519.md remains the universal default.
The binary ships scalar-only (ed25519.scalar.wasm; simd: false in targets.json; ref10 has no simd128 lane). The { variant: 'scalar' } option is passed explicitly to wasmRuntime.load because the runtime's selectVariant() defaults to simd and has no automatic fallback.
Resolve
const wasmEd25519 = runtime.resolve('wasmEd25519');
// Returns: { isAvailable, keygen, sign, verify }
API
| Method | Signature | Returns |
|---|---|---|
isAvailable |
() => boolean |
true when WebAssembly is present |
keygen |
(seed?: Uint8Array) => Promise<{publicKey: Uint8Array, privateKey: Uint8Array}|false> |
Fresh Ed25519 key pair |
sign |
(privateKey: Uint8Array, message: Uint8Array) => Promise<Uint8Array|false> |
64-byte detached signature |
verify |
(publicKey: Uint8Array, signature: Uint8Array, message: Uint8Array) => Promise<boolean> |
true / false (no-throw) |
Encodings
| Item | Encoding | Length |
|---|---|---|
seed |
raw 32-byte secret (optional; drawn from crypto.getRandomValues if omitted) |
32 bytes |
publicKey |
compressed Ed25519 point (RFC 8032 encoding) | 32 bytes |
privateKey |
expanded sk = seed ‖ publicKey — identical to pkc/ed25519 format |
64 bytes |
signature |
detached Ed25519 signature R ‖ s | 64 bytes |
The privateKey encoding is the libsodium "expanded sk" (seed concatenated with the compressed public key), which matches the pure-JS pkc/ed25519 module exactly, enabling full cross-tier signature interoperability.
All methods resolve false (or verify → false) when:
- a key / seed / signature has the wrong byte length, or an argument is not a
Uint8Array WebAssemblyis unavailable, or the binary fails to load (fetch error, ABI mismatch)- the WASM entry returns a non-zero status (
verifyalso returnsfalseon a non-matching signature)
None of them ever reject.
Examples
const wasmEd25519 = runtime.resolve('wasmEd25519');
if (!wasmEd25519.isAvailable()) {
// Fall back to webcrypto/ed25519 (secure contexts) or pure-JS pkc/ed25519.
}
// Generate a key pair (random seed drawn internally).
const { publicKey, privateKey } = await wasmEd25519.keygen();
// Sign and verify.
const enc = new TextEncoder();
const msg = enc.encode('hello');
const sig = await wasmEd25519.sign(privateKey, msg); // 64-byte R||s
const ok = await wasmEd25519.verify(publicKey, sig, msg);
// ok === true
// Deterministic keygen from a fixed seed (RFC 8032 §7.1 TEST 1).
function hexToBytes(h) {
const o = new Uint8Array(h.length / 2);
for (let i = 0; i < o.length; i++) o[i] = parseInt(h.slice(i * 2, i * 2 + 2), 16);
return o;
}
const seed = hexToBytes('9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60');
const kp = await wasmEd25519.keygen(seed);
// kp.publicKey === d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a
Worker Usage
const worker = fw.createWorker(
function ({ libs }) {
// wasmRuntime fetches the colocated .wasm by name inside the worker —
// no main-thread closure is serialized.
libs.wasmEd25519.keygen().then((kp) => {
self.postMessage(kp !== false);
});
},
{ dependencies: ['wasmEd25519'] }
);
Notes
- Prefer WebCrypto in secure contexts:
webcrypto/ed25519is hardware-accelerated wherecrypto.subtlesupports Ed25519 (Chrome 113+, Firefox 130+, Safari 17+). UsewasmEd25519only whencrypto.subtleis unavailable or Ed25519 is not yet in the browser'ssubtle. - RFC 8032 compliance: signatures are standard Ed25519 (RFC 8032 §5.1 / FIPS 186-5 §7.6) — interoperable with OpenSSL, libsodium, and all compliant implementations.
- Cross-tier interoperability: the 64-byte
privateKeyformat (seed ‖ publicKey) is identical betweenwasmEd25519andpkc/ed25519. A key pair generated in one tier can be used directly in the other for both signing and verification. - Scalar-only:
ed25519.simd.wasmis not shipped (ref10 has no simd128 lane). The{ variant: 'scalar' }pin is mandatory; a default load would fail on the missing SIMD binary. - No-throw contract: all methods resolve to a value or
false; they never reject. Input validation (lengths,instanceof Uint8Array) happens before any WASM call. - Output is always a fresh copy:
readBytescopies out of WASM linear memory into a newUint8Array. The caller owns the returned buffer. - RNG seam not used for keygen: the binary exports
rng_stage/rng_reset(the ABI triple), buted25519_keypairis SEED-EXPLICIT (crypto_sign_ed25519_seed_keypair) — it does not use the rng seam. The seed is always supplied by the JS layer (from the caller or fromcrypto.getRandomValues).
See also
- wasmRuntime — shared WASM loader adapter
- webcrypto/ed25519 — HW-backed Ed25519 in secure contexts (prefer this)
- pkc/ed25519 — pure-JS Ed25519 (universal default; same key format)
- wasm/x25519 — WASM X25519 key agreement (Curve25519)