# Stvor SDK > End-to-end encryption for any app. No cryptography expertise required. Stvor SDK implements Signal Protocol (X3DH + Double Ratchet + one-time prekeys) and optionally ML-KEM-768 post-quantum KEM (NIST FIPS 203) — using only Node.js built-in APIs. Zero external dependencies. ## Install ```bash npm install @stvor/sdk ``` Node.js >= 18. Zero npm dependencies. ## Quickstart ```ts import { Stvor } from '@stvor/sdk'; const alice = await Stvor.connect({ userId: 'alice', appToken: 'stvor_live_xxx', relayUrl: 'https://relay.stvor.xyz', }); const bob = await Stvor.connect({ userId: 'bob', appToken: 'stvor_live_xxx', relayUrl: 'https://relay.stvor.xyz', }); bob.onMessage(msg => console.log(msg.from, msg.data)); await alice.send('bob', { text: 'Hello!' }); await alice.disconnect(); await bob.disconnect(); ``` ## Stvor.connect(config) ```ts const client = await Stvor.connect({ userId: string, // any unique identifier appToken: string, // must start with 'stvor_' relayUrl: string, // 'https://relay.stvor.xyz' or self-hosted timeout?: number, // ms, default: 10000 pollIntervalMs?: number, // default: 1000 sealedSender?: boolean, // hide sender from relay, default: false pqc?: boolean, // ML-KEM-768 post-quantum hybrid, default: false }); ``` ## client.send(recipientId, data, options?) ```ts await client.send('bob', 'Hello'); await client.send('bob', { amount: 100, currency: 'USD' }); await client.send('bob', Buffer.from([1, 2, 3])); await client.send('bob', new Date()); await client.send('bob', 'Hey', { timeout: 30_000 }); await client.send('bob', 'Hey', { waitForRecipient: false }); ``` ## client.onMessage(handler) ```ts const unsubscribe = client.onMessage(msg => { // msg.from — sender userId // msg.data — decrypted value (original type preserved) // msg.timestamp — Date // msg.id — string }); unsubscribe(); ``` ## Group Chats ```ts // Create group — sender keys distributed to all members via 1-to-1 sessions await alice.createGroup('team-chat', ['bob', 'charlie']); // One encryption, all members receive await alice.sendToGroup('team-chat', { text: 'Hello team!' }); // Receive bob.onGroupMessage(msg => { // msg.groupId, msg.from, msg.data, msg.timestamp, msg.id }); // Manage members (auto-ratchets on removal — removed member cannot decrypt future messages) await alice.addGroupMember('team-chat', 'dave'); await alice.removeGroupMember('team-chat', 'charlie'); ``` ## Post-Quantum (ML-KEM-768) ```ts const alice = await Stvor.connect({ userId: 'alice', appToken: 'stvor_live_xxx', relayUrl: 'https://relay.stvor.xyz', pqc: true, }); ``` When `pqc: true`: - ML-KEM-768 key pair generated on connect (encapsulation key: 1184 bytes) - On first message: sender mlkemEncaps(peerEk) → ciphertext embedded in message - Recipient: mlkemDecaps(ct, dk) → same shared secret - Root key = HKDF(X3DH_secret ‖ ML-KEM_secret, "stvor-hybrid-root-v1") - Verified against NIST ACVTS official test vectors (KeyGen + Encap + Decap) - Falls back to classical X3DH if peer doesn't support PQC ## Sealed Sender ```ts // Relay sees `to` but never `from` const alice = await Stvor.connect({ ..., sealedSender: true }); ``` Each message: ephemeral ECDH keypair → ECDH(epk, recipientIK) → AES-256-GCM envelope. Fresh ephemeral key per message — no linkability. ## GDPR Compliance ```ts // Art. 17 — right to erasure await alice.deleteMyData(); // { deletedAt, messagesDeleted } // Art. 20 — data portability const data = await alice.exportMyData(); // { publicKeys, pendingMessages, registeredAt, lastActivity } // Note: message content is E2EE — relay cannot export it ``` ## Supported Data Types | Type | Example | |------|---------| | string | 'Hello' | | number | 42, 3.14 | | boolean | true, false | | null | null | | Uint8Array / Buffer | Binary files, images | | object / array | { key: 'val' }, [1,2,3] | | Date | new Date() | | Set | new Set([1,2,3]) | | Map | new Map([['a',1]]) | ## Security Properties - **Forward Secrecy** — Double Ratchet, new key per message - **Post-Compromise Security** — DH ratchet rotation - **Post-Quantum** — ML-KEM-768 hybrid (optional, NIST FIPS 203 verified) - **One-time prekeys** — full X3DH with OPK pool, forward secrecy before first ratchet - **Replay protection** — nonce + timestamp validation - **TOFU** — SHA-256 fingerprint binding, throws on key change - **Zero-knowledge relay** — relay stores only ciphertext - **Sealed sender** — ephemeral ECDH hides sender from relay (optional) - **Group E2EE** — Sender Keys, O(1) encryption per message - **GDPR built-in** — right to erasure + data portability ## Cryptography (zero external dependencies) | Primitive | Use | |---|---| | ECDH P-256 | X3DH key agreement | | ECDSA P-256 | Signed prekey verification | | AES-256-GCM | AEAD encryption | | HKDF-SHA-256 | Key derivation | | HMAC-SHA-256 | Chain key ratcheting | | SHA3-512 | X3DH G function | | SHAKE-128 | ML-KEM SampleNTT | | SHAKE-256 | ML-KEM PRF/noise | | SHA-256 | TOFU fingerprinting | | ML-KEM-768 | Post-quantum KEM (FIPS 203, from scratch) | ## Relay Options ### Hosted (recommended) ```ts relayUrl: 'https://relay.stvor.xyz' ``` No setup. Accepts any `stvor_*` token. ### Local dev ```bash npx @stvor/sdk mock-relay # port 4444 ``` ### Self-hosted ```bash git clone https://github.com/sapogeth/sdk-relay && node server.js ``` ## Browser SDK ```ts import { StvorWebSDK } from '@stvor/sdk/web'; const sdk = await StvorWebSDK.create({ userId, appToken, relayUrl }); sdk.onMessage((from, data) => { ... }); await sdk.send(recipientId, data); sdk.disconnect(); ``` Keys persisted in IndexedDB — identity survives page refreshes. ## Links - npm: https://www.npmjs.com/package/@stvor/sdk - Docs: https://sdk.stvor.xyz/docs - GitHub (relay): https://github.com/sapogeth/sdk-relay - Hosted relay: https://relay.stvor.xyz - llms.txt: https://sdk.stvor.xyz/llms.txt