Skip to content

feat(sdk): rewrite SDK on crypto-wasm, delete the legacy X25519 stack#43

Merged
0xErgod merged 2 commits into
mainfrom
feat/37-sdk-rewrite-on-crypto-wasm
May 28, 2026
Merged

feat(sdk): rewrite SDK on crypto-wasm, delete the legacy X25519 stack#43
0xErgod merged 2 commits into
mainfrom
feat/37-sdk-rewrite-on-crypto-wasm

Conversation

@0xErgod

@0xErgod 0xErgod commented May 28, 2026

Copy link
Copy Markdown
Owner

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-keys package is removed.

Linked Issue

Closes #37

Design decisions locked in before claiming

Recorded on #37 (comment 1, scope adjustment):

  • BJJ wallet-key derivation inlined into packages/sdk/src/wallet-keys.ts (no separate package). Canonical-message scheme preserved; output is a BJJ keypair. The signature bytes feed keypair_from_seed directly (no intermediate HKDF) — keeps every primitive on the BJJ+Poseidon stack and avoids re-introducing a foreign hash.
  • crypto-wasm loading: switched --target web--target bundler so one artifact resolves in both Node (vitest + vite-plugin-wasm) and browsers. apps/curve + apps/zk consumers updated.
  • Cross-language fixture anchored on crates/protocol/tests/envelope_fixture.rs (full envelope round-trip).
  • wallet-derived-keys workflow dropped from release.yml in this PR (semantic-release would crash otherwise).
  • Scope adjustment (CI-driven): because CI gates protocol-demo typecheck + build, this PR also rewires apps/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

  • Delete every X25519/HKDF/ChaCha20/Blake2b SDK file (suite-x25519*, suites*, encrypt*, envelope-codec, envelope-unified, hash-poseidon)
  • Delete legacy tests (envelope-codec, transport, commitments)
  • Remove packages/wallet-derived-keys entirely + drop from release workflow
  • Drop @noble/* + poseidon-lite from the SDK; @noble/* from apps/protocol
  • Add wasm.ts, suite.ts, envelope.ts, wallet-keys.ts
  • Rewrite commitments.ts (Pedersen-only), tx.ts, constants.ts, index.ts
  • No _v* file/symbol names, no Legacy/Unified/Historical/Compat types, no scheme strings
  • One TS test exercises the full encrypt→decrypt round trip via crypto-wasm against the Rust fixture — 9 parity tests, byte-for-byte
  • pnpm --filter @whisper-protocol/sdk build clean, tests pass
  • Grep gate: zero matches for x25519|chacha|hkdf|blake2|@noble/|@stablelib|_unified|legacy under packages/sdk + apps/protocol (the only _v[1-5] match is TEXT_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.ts reproduces crates/protocol/tests/envelope_fixture.rs against the same pinned Alice/Bob/envelope-42 fixture:

  • ciphertext == specs/babyjub-cipher.md Vector 3 (byte-for-byte)
  • MAC tag over [encoding_id, ...ciphertext] (the Option-A binding)
  • encoding_id carried as a public field == text-utf8-v1 id
  • envelope_id preserved; sender/recipient pubkeys match the seed-derived keypairs
  • round-trip: open recovers [1,2,3,4] + the encoding id
  • wrong-recipient, wrong-seed, and tampered-ciphertext all reject

Any drift between Rust and TS (domain tag, KDF order, MAC input encoding) surfaces here.

Testing

  • pnpm typecheck — all 4 workspace projects clean
  • pnpm --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-wasm domain_tag addition)
  • node packages/sdk/scripts/generate-networks.mjs --check — in sync

Notes for the reviewer

  • CI changes: ci.yml, release.yml, deploy-protocol-demo.yml now install Rust + wasm-pack and build crates/crypto-wasm/pkg before pnpm install, because the SDK's crypto-wasm workspace dep is a link: to the (gitignored) wasm-pack output. Without the pre-step, pnpm install --frozen-lockfile fails on a missing link target.
  • crypto-wasm got one new export (domain_tag) — it's a thin binding over the existing crypto::poseidon::domain_tag, needed by the envelope suite for role-key derivation. No new cryptographic logic.
  • Compose.tsx / Commit.tsx are placeholders rendering a "Phase-4 wiring pending" banner. Feed.tsx renders event metadata but doesn't decrypt. This is the agreed scope split — the SDK side is complete and parity-tested; the dApp UI forms are Phase 4 (Phase 4: rebuild apps/protocol on the new SDK; verify via Playwright #38). I'll update Phase 4: rebuild apps/protocol on the new SDK; verify via Playwright #38's scope to note the import rewires already landed here.
  • The dApp won't function end-to-end yet (can't compose/send through the UI), but it compiles, boots, and renders — which is what keeps CI green and unblocks Phase 4 to be pure UI work.

[agent PR]

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>
@0xErgod 0xErgod added this to the ZK-friendly stack migration milestone May 28, 2026
@0xErgod 0xErgod added zk-migration Atomic ZK-friendly stack migration (kills X25519/Blake2b/HKDF) sdk Touches packages/sdk in-progress Work is actively being done on this issue labels May 28, 2026
@0xErgod 0xErgod self-assigned this May 28, 2026
`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>
@0xErgod

0xErgod commented May 28, 2026

Copy link
Copy Markdown
Owner Author

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]

@0xErgod 0xErgod merged commit 9d1a7cb into main May 28, 2026
3 checks passed
@0xErgod 0xErgod deleted the feat/37-sdk-rewrite-on-crypto-wasm branch May 28, 2026 11:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

in-progress Work is actively being done on this issue sdk Touches packages/sdk zk-migration Atomic ZK-friendly stack migration (kills X25519/Blake2b/HKDF)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Phase 3: rewrite the SDK on crypto-wasm, delete legacy crypto files

1 participant