feat(sdk): rewrite SDK on crypto-wasm, delete the legacy X25519 stack#43
Conversation
Phase 3 of the ZK-friendly stack migration. The SDK's cryptographic
surface is now a thin JS-side wire-format + PTB layer over
crates/crypto-wasm (BabyJubjub + Poseidon + Pedersen), with zero
cryptographic logic in TypeScript. Every X25519/HKDF/ChaCha20/Blake2b
file is deleted.
New SDK modules:
- wasm.ts — single import site for the crypto-wasm bindings
- suite.ts — seal/open mirroring crates/protocol::envelope exactly
(ECDH -> KDF -> Poseidon-stream cipher -> Poseidon MAC),
single-recipient, encoding-id folded into the MAC
- envelope.ts — wire codec for the new on-chain Envelope struct
- wallet-keys.ts — BJJ keypair derivation from a wallet signature,
inlined from the removed wallet-derived-keys package;
canonical-message scheme preserved, output is a BJJ
keypair (signature bytes feed keypair_from_seed directly,
no HKDF — keeps all crypto on the BJJ+Poseidon stack)
Rewritten: commitments.ts (Pedersen-only, no hash-scheme dispatch),
tx.ts (PTB builders for the 4 new Move entry points), constants.ts
(down to module names + CLOCK_ID + SDK_PROTOCOL_VERSION), errors.ts,
protocol.ts, registry.ts (BJJ pubkey coords), client.ts
(single-recipient API), feed.ts (new event shapes), index.ts.
Deleted: suite-x25519*, suites*, encrypt*, envelope-codec,
envelope-unified, hash-poseidon, the legacy tests, and the entire
packages/wallet-derived-keys package. Dropped @noble/* + poseidon-lite
from the SDK and @noble/* from apps/protocol.
crypto-wasm: switched from --target web to --target bundler so the
bundle resolves in both Node (vitest + vite-plugin-wasm) and browsers;
added a `domain_tag` binding (Blake2b->Fq protocol tag) the envelope
suite needs for role-key derivation. apps/curve updated to drop its
top-level `await init()` (bundler target auto-instantiates).
CI: ci.yml / release.yml / deploy-protocol-demo.yml now install Rust +
wasm-pack and build crypto-wasm/pkg before pnpm install (the workspace
`link:` dep needs the built pkg on disk). Dropped the wallet-derived-keys
matrix entry from release.yml.
apps/protocol rewired to the new SDK: IdentityBar/RegistryView/Feed
display BJJ pubkey + commitment coordinates; useWhisperKeys/useDevSession
derive a BJJ keypair via signPersonalMessage. Compose/Commit are
Phase-4-pending placeholders (the SDK primitives are ready; only the
React forms remain — Phase 4 / #38). The dApp compiles, boots, and
renders; send/commit actions are explicitly stubbed.
Cross-language parity: a 9-test suite reproduces
crates/protocol/tests/envelope_fixture.rs byte-for-byte (ciphertext
Vector 3, MAC tag over the augmented input, encoding-id binding,
round-trip recovery, wrong-recipient + tampered-ciphertext rejection).
Verification (all green): pnpm typecheck (4 projects), SDK tests 11/11,
SDK + dApp build, cargo check --workspace, networks.ts drift check.
Grep gate returns zero matches for the legacy crypto stack.
Closes #37
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`macTag` was used for both the MAC *role* domain tag and (as an envelope field) the actual MAC output — confusing in the crypto- critical seal/open path. Rename the locals to `cipherRoleTag` / `macRoleTag`. No behavioral change. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Review pass: APPROVE — no critical, no important findings. The reviewer did a full crypto-correctness pass: `suite.ts` mirrors `crates/protocol::envelope` exactly (ECDH → role-tagged KDF → encrypt-then-MAC with encoding-id folded into the MAC input, MAC-verify-before-decrypt on open); all four PTB builders match the Move ABI in order + type; the parity test pins the same vectors as the Rust fixture; the Ed25519 signature slice (`[1,65]`) is correct; CI wasm-pack pre-steps are correctly ordered before `pnpm install`. Addressed the one actionable nit in b60030e: renamed `macTag`/`cipherTag` domain-tag locals to `macRoleTag`/`cipherRoleTag` so they don't collide with the envelope's `macTag` field name. Pure rename, no logic change — 11/11 tests still pass. The second nit (`@deprecated MODULE` re-export) is out of this issue's scope. Merging once CI confirms. [agent comment] |
Summary
Phase 3 of the ZK-friendly stack migration: the SDK's cryptographic surface becomes a thin JS-side wire-format + PTB layer over crates/crypto-wasm (BabyJubjub + Poseidon + vector Pedersen). Zero cryptographic logic remains in TypeScript. Every X25519/HKDF/ChaCha20/Blake2b file is deleted, and the entire
packages/wallet-derived-keyspackage is removed.Linked Issue
Closes #37
Design decisions locked in before claiming
Recorded on #37 (comment 1, scope adjustment):
packages/sdk/src/wallet-keys.ts(no separate package). Canonical-message scheme preserved; output is a BJJ keypair. The signature bytes feedkeypair_from_seeddirectly (no intermediate HKDF) — keeps every primitive on the BJJ+Poseidon stack and avoids re-introducing a foreign hash.--target web→--target bundlerso one artifact resolves in both Node (vitest + vite-plugin-wasm) and browsers.apps/curve+apps/zkconsumers updated.crates/protocol/tests/envelope_fixture.rs(full envelope round-trip).release.ymlin this PR (semantic-release would crash otherwise).protocol-demotypecheck + build, this PR also rewiresapps/protocol's import sites to keep CI green. The Compose/Commit/Feed action handlers are stubbed as Phase-4-pending placeholders — the SDK primitives are all ready; only the React forms remain for Phase 4: rebuild apps/protocol on the new SDK; verify via Playwright #38.Requirements checklist
packages/wallet-derived-keysentirely + drop from release workflow@noble/*+poseidon-litefrom the SDK;@noble/*from apps/protocolwasm.ts,suite.ts,envelope.ts,wallet-keys.tscommitments.ts(Pedersen-only),tx.ts,constants.ts,index.ts_v*file/symbol names, noLegacy/Unified/Historical/Compattypes, no scheme stringspnpm --filter @whisper-protocol/sdk buildclean, tests passx25519|chacha|hkdf|blake2|@noble/|@stablelib|_unified|legacyunder packages/sdk + apps/protocol (the only_v[1-5]match isTEXT_UTF8_V1_ID, a spec encoding identifier, not legacy API naming)Cross-language parity (the load-bearing test)
packages/sdk/src/__tests__/envelope-fixture.test.tsreproducescrates/protocol/tests/envelope_fixture.rsagainst the same pinned Alice/Bob/envelope-42 fixture:specs/babyjub-cipher.mdVector 3 (byte-for-byte)[encoding_id, ...ciphertext](the Option-A binding)openrecovers[1,2,3,4]+ the encoding idAny drift between Rust and TS (domain tag, KDF order, MAC input encoding) surfaces here.
Testing
pnpm typecheck— all 4 workspace projects cleanpnpm --filter @whisper-protocol/sdk test— 11/11 (2 protocol + 9 envelope parity)pnpm -r --filter "./packages/*" run build+pnpm --filter protocol-demo build— both clean (dApp: 2.2 MB wasm + 968 KB JS)cargo check --workspace— clean (crypto-wasmdomain_tagaddition)node packages/sdk/scripts/generate-networks.mjs --check— in syncNotes for the reviewer
ci.yml,release.yml,deploy-protocol-demo.ymlnow install Rust + wasm-pack and buildcrates/crypto-wasm/pkgbeforepnpm install, because the SDK'scrypto-wasmworkspace dep is alink:to the (gitignored) wasm-pack output. Without the pre-step,pnpm install --frozen-lockfilefails on a missing link target.domain_tag) — it's a thin binding over the existingcrypto::poseidon::domain_tag, needed by the envelope suite for role-key derivation. No new cryptographic logic.[agent PR]