# @quantus/crypto Quantus post-quantum crypto for the browser: ML-DSA-65 and ML-DSA-87 signatures, Poseidon2-over-Goldilocks account-id hashing, and hardened BIP44 key derivation. Every function delegates to the crates the Quantus runtime itself uses — `qp-rusty-crystals-dilithium`, `qp-poseidon-core`, `qp-rusty-crystals-hdwallet` — rather than reimplementing them. A browser wallet that disagreed with the chain about a key or a signature would produce perfectly well-formed output that the chain rejects, and nothing on this side could tell. ## Why a separate package from `@polkadot/wasm-crypto` They cannot share a Cargo build. `wasm-crypto` is compiled with `nightly-2022-06-24` against a 2019-era dependency set; the ML-DSA crates use inline `const {}` blocks that require Rust >= 1.79. Modernising the older build would mean rewriting upstream's sr25519/ed25519 crypto, which is the thing most worth leaving untouched so rebases onto upstream stay boring. What *is* shared is the packaging: the WASM is zlib-compressed and base64'd into the JS at build time, so nothing is fetched at runtime. That matters because the consumer is an MV3 service worker under `script-src 'self' 'wasm-unsafe-eval'`, which can compile WASM but cannot usefully fetch it, and because callers like `pair.sign()` are synchronous and have no `await` to give. `@polkadot/wasm-bridge` is deliberately not used: its `Bridge` implements wasm-bindgen 0.2.79's JS-heap ABI, while this crate builds with 0.2.128, which uses externref tables. wasm-bindgen's own generated glue plus `initSync` is both smaller and correct. The only runtime dependency is `fflate`, for zlib inflate. Base64 decoding is fifteen lines here rather than a dependency. Both were originally taken from `@polkadot/wasm-util`, which turned out to cost more than it saved: its index re-exports `packageDetect`, dragging in a `@polkadot/util` peer dependency for a side effect we do not want, and being a workspace package it resolved through its *own* repo's node_modules when this package was consumed by symlink from another checkout — which is exactly how `quantus/common` consumes it during development. ## Scheme selector `Scheme.MlDsa87 = 0`, `Scheme.MlDsa65 = 1` — these are the chain's own `DilithiumSignatureScheme` variant indices, so the number threaded through this API is the byte that ends up on the wire. New accounts use ML-DSA-65; ML-DSA-87 is legacy and must be supported but never chosen. ## Signing context ML-DSA hashes a context into the signature. Quantus extrinsics on spec >= 148 are verified under `QUANTUS_EXTRINSIC`, earlier specs under the empty context. A signature made under the wrong one is cryptographically valid, rejected by the chain, and indistinguishable locally — so use `contextForSpec(specVersion)` rather than picking one by hand. Nothing here guesses on your behalf. ## Sizes come from the crate `sizes(scheme)` returns the public/secret/signature lengths rather than exposing constants to copy. They are consensus-critical — the runtime decodes a fixed-size array with no compact length prefix — and a JS constant that drifted would re-frame every byte after the signature while looking entirely healthy. ## Building ```sh yarn install-build-deps # downloads wasm-bindgen 0.2.128 and binaryen ./scripts/build-quantus.sh ``` The Rust toolchain is pinned in `rust-toolchain.toml` to the same channel the chain builds its runtime with. ## Tests `cargo test` runs conformance tests whose expected values come from the `quantus` CLI, not from this crate — the dev-genesis account ids, HD derivation at both schemes' default paths, and context separation. A test that pinned our own output would keep passing through exactly the drift they exist to catch.