Unified Lux Wallet shared core as a pnpm workspace. Umbrella @luxwallet/sdk re-exports independent modules; connect/SIWx stays in luxwallet/connect (depended on, not duplicated). Packages (packages/): - @luxwallet/chains REAL chain registry, one source of truth, emits chains.json for native. Seeds Lux C (96369/96368/96370), Zoo (200200/200201), Hanzo (36963/36964), Sparkle Pony (36911/36910), Pars (494949/7071) as EVM; X/P/Q/Z Lux families as non-EVM entries (builderStatus todo). - @luxwallet/rpc REAL gateway client: https://<gateway>/v1/rpc/<chainId>, default api.hanzo.ai + brand/per-chain override (getBootnodeRpcUrl semantics). - @luxwallet/crypto facade over luxfi/crypto C core (WASM/UniFFI). Exact CryptoBackend interface + FIPS sizes; impl stubbed (throws 'not yet bound'). NOT @noble/post-quantum. LLM.md documents WASM bind + the on-chain ML-DSA precompile wire format + shared KAT vectors. - @luxwallet/keyring account model + keystore. Keyring store + BIP-44 paths + AccountID framing REAL; crypto calls stubbed via @luxwallet/crypto. - @luxwallet/tx EVM unsigned EIP-1559 builder REAL (viem); P/X/UTXO/SVM typed stubs with builderStatus + LLM.md citing Lux tx types. - @luxwallet/sdk thin umbrella barrel (+ subpath exports). TS project references (tsc --build), vitest per package, react-native + browser/default export conditions. verify-clean gate: no @noble PQ, no GPL, crypto facade dependency-free. pnpm -r typecheck: 6/6 pass. pnpm -r test: 44 pass. pnpm verify: OK.
2.4 KiB
@luxwallet/keyring — implementation notes
Reference impl (Go, working): ~/work/lux/sdk/wallet
pq_wallet.go— the in-memory PQ account store (modeled byKeyring).account/pq_account.go— ML-DSA-65 account derivation + Sign/Verify.account/recovery.go— SLH-DSA-192s recovery accounts (branch 2').account/cshake.go— AccountID + seed-expansion cSHAKE framing.account/scheme.go—WalletSchemeID/RecoverySchemeIDbyte values.
What's real here
Keyring— add/get/remove/list, refusing duplicate AccountIDs. Matches PQWallet semantics. No crypto needed; fully tested.derivationPathFor— BIP-44 paths: EVM (coinType 60), ML-DSA branch (m/44'/9000'/<net>'/0'/<role>'/<idx>'), SLH-DSA recovery branch (2').accountIdMessage— the exactu32be(networkId) ‖ u8(scheme) ‖ pubkeyframing fed to cSHAKE.
What's stubbed (throws "not yet bound" via @luxwallet/crypto)
-
Seed expansion + keygen (
deriveAccount). The Go path is:child_seed = BIP-32 walk→ξ = cSHAKE-256(N="LUX_PQ_KEYGEN_V1", S="LUX/WALLET/<ROLE>/V1", child_seed, 32)→ML-DSA-65.KeyGen(ξ). TodayderiveAccountcallssigner.fromSeed(masterSeed)as a binding placeholder. TODO: add BIP-32 + role-bound cSHAKE to the crypto backend and do the full walk so derivations match the Go vectors. -
AccountID cSHAKE (
deriveAccountId). Must be cSHAKE-256 withN="LUX_ACCOUNT_V1",S="LUX/WALLET/ACCOUNT_ID/V1", outLen 48 — NOT bare SHAKE256. The crypto backend currently exposes onlyshake256; TODO: addcshake256(n, s, msg, outLen)toCryptoBackend(the luxfi/crypto core already has cSHAKE) and call it here. Lock against the Go output via the sharedvectors.jsonKAT (see crypto LLM.md). -
Keystore seal/unseal (
sealAccount/unsealAccount). TODO: argon2id KDF + XChaCha20-Poly1305 AEAD over the secret key, via @luxwallet/crypto primitives only. No second crypto library.
Hard rules
- All crypto goes through
@luxwallet/crypto(→ luxfi/crypto). Never import@noble/*or any other crypto lib here. - ML-DSA-65 is the production scheme (0x42). SLH-DSA-192s (0x62) is recovery only and never on the hot signing path — keep the type boundary (separate role + branch) so a recovery key cannot be used as a hot key.
- AccountID is bound to
networkId, so the same key on two networks yields two AccountIDs. Do not drop the networkId from the framing.