Files
zeekay 2afe7557a4 feat(desktop): native omnichain post-quantum Lux Wallet (node-independent)
Mirror of the live fix in hanzoai/gui pkgs/ai (@hanzo/ai) applied to this
repo's pre-convergence frontend tree. Replaces the hanzo-node-backed crypto
wallet — which couldn't create/show a wallet without a connected node — with a
self-contained stack under apps/hanzo-desktop/src/lib/lux-wallet:

- luxfi/crypto WASM (real FIPS-204 ML-DSA-65 + secp256k1 + keccak); EVM keys via
  BIP-44 keys.deriveSecp256k1 (bit-identical to ethers/MetaMask), PQ identity
  via keys.serviceIdentity. Secrets sealed in OS keychain, never plaintext.
- create / import / list / reveal / balance / send across the 11 ready EVM
  chains (@luxwallet/chains + /rpc); non-EVM X/P/Q/Z marked "coming soon".
- Sign-In-With-Wallet: /v1/iam/web3/nonce -> CAIP-122 -> EIP-191 sign ->
  /v1/iam/web3/verify (@luxwallet/connect + @luxfi/crypto).
- local-HD-PQ default; MPC / Safe selectable (coming soon).

Wiring: rewrite pages/crypto-wallet.tsx onto the store; node-independent /wallet
route + "Sign in with Wallet" on quick-connection; drop dead wallets-context.
MIT/BSD deps vendored under apps/hanzo-desktop/vendor.

NOTE: this app now renders @hanzo/ai (main.tsx), so the shipping wallet fix
lands via hanzoai/gui; this mirror keeps the repo's own tree consistent.
2026-07-07 21:32:08 -07:00
..

@luxfi/crypto

The literal luxfi/crypto Go code compiled to WebAssembly — not a TypeScript reimplementation.

The .wasm shipped in this package is GOOS=js GOARCH=wasm go build of github.com/luxfi/crypto + cloudflare/circl. Every byte it produces is wire-identical to the Lux on-chain precompiles and verifiers — the same ML-DSA-65, secp256k1, keccak/sha3/shake, and X-Wing code that runs on the chain, now callable from Node and the browser.

Install

npm install @luxfi/crypto

Usage

import { init } from "@luxfi/crypto";

const c = await init();          // loads + instantiates the wasm (idempotent)

// ML-DSA-65 (FIPS 204, NIST Level 3)
const { publicKey, secretKey } = c.mldsa65.keygen();
const sig = c.mldsa65.sign(secretKey, new TextEncoder().encode("hello"));
c.mldsa65.verify(publicKey, new TextEncoder().encode("hello"), sig); // true

// secp256k1 (Ethereum-compatible recoverable ECDSA)
const pub = c.secp256k1.getPublicKey(sk);            // 65-byte uncompressed
const s = c.secp256k1.sign(sk, digest32);            // 65-byte [R||S||V]
const rec = c.secp256k1.recover(digest32, s);        // == pub

// Hashes
c.keccak256(bytes);        // 32 bytes
c.sha3_256(bytes);         // 32 bytes (FIPS 202)
c.shake256(bytes, 64);     // arbitrary length

// X-Wing KEM (ML-KEM-768 + X25519), draft-connolly-cfrg-xwing-kem
const kp = c.xwing.keygen();                          // pk 1216 / sk 32
const { sharedSecret, ciphertext } = c.xwing.encapsulate(kp.publicKey);
c.xwing.decapsulate(kp.secretKey, ciphertext);        // == sharedSecret

// luxfi/keys derivation (BIP-39/BIP-32 over the same luxfi primitives)
c.keys.serviceIdentity(mnemonic, "hanzo/kms-operator"); // ML-DSA-65 NodeID
c.keys.deriveSecp256k1(mnemonic, 0, 0);                 // m/44'/60'/0'/0/0

Browser

The wasm is auto-resolved next to the module in Node. In the browser, pass it explicitly to avoid bundler resolution quirks:

await init(fetch(new URL("@luxfi/crypto/crypto.wasm", import.meta.url)));

init() also accepts a BufferSource, a Response/Promise<Response>, a URL, or a filesystem path string.

API surface

Group Methods
mldsa65 keygen(), keygenFromSeed(seed32), sign(sk, msg, ctx?, {deterministic?}), verify(pk, msg, sig, ctx?)
secp256k1 sign(sk, digest32), recover(digest32, sig65), getPublicKey(sk, compressed?), verify(pk, digest32, sig64)
hashes keccak256(data), sha3_256(data), shake256(data, outLen?)
xwing keygen(), keygenFromSeed(seed32), encapsulate(pk, seed?), decapsulate(sk, ct)
keys serviceIdentity(mnemonic, servicePath), deriveSecp256k1(mnemonic, account?, index?)

All byte arguments and results are Uint8Array. Sizes: ML-DSA-65 pk 1952 / sk 4032 / sig 3309; X-Wing pk 1216 / sk 32 / ct 1120 / shared 32; secp256k1 sig 65.

Why standard Go and not TinyGo

The binary is built with the standard Go toolchain (GOOS=js GOARCH=wasm), not TinyGo. TinyGo cannot build the dependency graph: luxfi/crypto/mldsa pulls in luxfi/accel (GPU dispatch) whose cgo #cgo build-constraint lines are "not implemented" by TinyGo, and circl's ML-DSA/ML-KEM use stdlib paths TinyGo's runtime does not fully support. Standard Go excludes the cgo paths under CGO_ENABLED=0 and falls back to the pure-Go circl implementation — the same code the chain runs. The result is larger but wire-identical:

Toolchain Result
TinyGo -target=wasm ✗ fails (luxfi/accel cgo #cgo lines unsupported)
Go GOOS=js GOARCH=wasm ✓ crypto.wasm ≈ 7.2 MB (2.33 MB gzipped)

License

SEE LICENSE IN LICENSE — Lux Ecosystem License. Pure-Go cryptography from github.com/luxfi/crypto, cloudflare/circl, and decred/dcrd/secp256k1.