✓ 53/53 tests NIST FIPS 203 0 dependencies TON Web3

Post-quantum E2EE
for TON Web3

The first end-to-end encryption library for TON wallets that is resistant to quantum computers. ML-KEM-768 + P-256 X3DH + Double Ratchet. Rust WASM core. Zero npm dependencies.

$ npm install @stvor/web3
270KB
WASM bundle size
0
npm dependencies
53
NIST test vectors
768
ML-KEM security bits

Why post-quantum?

Store Now, Decrypt Later. Adversaries are recording encrypted traffic today to decrypt it once quantum computers become available. Your users' messages sent in 2025 could be read in 2030.

Elliptic curve cryptography (Ed25519, secp256k1, P-256) used by every Web3 wallet today is vulnerable to Shor's algorithm running on a sufficiently large quantum computer.

@stvor/web3 uses ML-KEM-768 — standardised by NIST in FIPS 203 (August 2024) — combined with classical P-256 in a hybrid scheme. Breaking it requires breaking both simultaneously, which is impossible with any known algorithm.

Features

🔐
Hybrid post-quantum X3DH
ML-KEM-768 + P-256 ECDH combined via HKDF. Secure against classical and quantum attacks simultaneously.
🔄
Double Ratchet
Signal Protocol session management. Forward secrecy and break-in recovery. Every message uses a fresh key.
👛
TON wallet identity
No accounts, no passwords. Your userId is your TON wallet address. Sign once with TonConnect.
⛓️
On-chain key registry
Public keys stored in a FunC smart contract. Trustless, censorship-resistant, verifiable by anyone.
📦
Zero dependencies
0 npm runtime dependencies. Crypto core is Rust compiled to WASM (270 KB). Ships in a single file.
NIST ACVTS verified
53 official test vectors. ECDH P-256, ECDSA, AES-256-GCM, and HKDF-SHA256 match NIST reference outputs exactly.

Crypto stack

Identity → TON wallet address (Ed25519 signature)
Key exchange → Hybrid X3DH
├── P-256 ECDH (classical, NIST)
└── ML-KEM-768 (post-quantum, NIST FIPS 203)
└── HKDF-SHA256(ecdh_sk ‖ mlkem_ss, "STVOR-HYBRID-v1")
Sessions → Double Ratchet (Signal Protocol)
Encryption → AES-256-GCM
Signing → ECDSA P-256 / SHA-256
Runtime → Rust → WebAssembly (270 KB, zero C deps)

Installation

npm install @stvor/web3

Works in any environment with Web Crypto API and WebAssembly — all modern browsers and Node.js ≥ 18.

Quick start

1

Initialize the WASM crypto engine

import initWasm from '@stvor/web3/wasm';

const wasm = await initWasm();
2

Connect with a TON wallet

import { StvorWeb3 } from '@stvor/web3';

const client = await StvorWeb3.connect({
  provider: tonConnectProvider,     // TonConnect wallet
  contractAddress: 'EQD...',        // stvor_registry address
  tonApiUrl: 'https://testnet.toncenter.com/api/v2',
  wasm,
});
3

Listen for messages

client.onMessage(msg => {
  console.log(`From ${msg.from}:`, msg.data);
  // msg.timestamp, msg.id also available
});
4

Send a post-quantum encrypted message

await client.send('0:bob_address...', {
  text: 'Hello, quantum-safe Web3!',
  // any JSON-serializable payload
});

TON Connect integration

@stvor/web3 uses a minimal TonConnectProvider interface — no external SDK dependency. If you already use @tonconnect/sdk, wrap it with the adapter below.

import { TonConnect } from '@tonconnect/sdk';

const connector = new TonConnect();
await connector.connect({ universalLink: 'https://app.tonkeeper.com/ton-connect' });

// Adapter — maps TonConnect to TonConnectProvider interface
const provider = {
  account: {
    address: connector.account.address,
    chain: connector.account.chain,
  },
  async signData(payload) {
    return connector.signData(payload);
  },
  async signTransaction(to, bodyHex, value) {
    const result = await connector.sendTransaction({
      messages: [{ address: to, amount: value, payload: bodyHex }],
    });
    return result.boc;
  },
};

const client = await StvorWeb3.connect({ provider, wasm, /* ... */ });

API reference

StvorWeb3.connect(config)

Creates and connects a client. Registers public keys on-chain via the registry contract.

ParameterTypeDescription
providerTonConnectProviderTON wallet provider
contractAddressstringstvor_registry address
tonApiUrlstringTonCenter API endpoint
wasmWasmModuleInitialized WASM module
tonApiKey?stringOptional TonCenter API key
pollIntervalMs?numberPoll interval in ms (default: 5000)
pqc?booleanEnable ML-KEM-768 hybrid (default: true)

client.send(to, data)

Encrypts data with a hybrid post-quantum session and delivers it via TON Storage.

await client.send('0:abc...', { text: 'Hello!' });

client.onMessage(handler)

Registers a message handler. Returns an unsubscribe function.

const unsub = client.onMessage(msg => {
  // msg.from      — sender TON address
  // msg.data      — decrypted payload
  // msg.timestamp — Date
  // msg.id        — message ID
});
unsub(); // stop listening

client.disconnect()

Stops message polling and clears all session state from memory.

Low-level WASM API

Direct access to the Rust crypto primitives for advanced use cases.

ML-KEM-768

import { wasm_mlkem_keygen, wasm_mlkem_encaps, wasm_mlkem_decaps }
  from '@stvor/web3/wasm';

// Generate keypair — { ek: string, dk: string } (base64url)
const { ek, dk } = JSON.parse(wasm_mlkem_keygen());
// ek = 1184-byte encapsulation key (public)
// dk = 64-byte decapsulation key seed (secret)

// Encapsulate: generates a shared secret and a ciphertext
const { ct, ss } = JSON.parse(wasm_mlkem_encaps(ek));

// Decapsulate: recover the shared secret from ciphertext
const ss2 = wasm_mlkem_decaps(dk, ct);
// ss === ss2  ✓

Hybrid X3DH

import { WasmKeyPair, wasm_mlkem_keygen,
         wasm_hybrid_session_initiate, wasm_hybrid_session_respond }
  from '@stvor/web3/wasm';

const aliceIK  = new WasmKeyPair();
const aliceSPK = new WasmKeyPair();
const bobIK    = new WasmKeyPair();
const bobSPK   = new WasmKeyPair();
const bobPqc   = JSON.parse(wasm_mlkem_keygen());

// Alice: initiate hybrid session → get session + ML-KEM ciphertext
const { session_json, mlkem_ct } = JSON.parse(
  wasm_hybrid_session_initiate(
    aliceIK, aliceSPK,
    bobIK.public_key, bobSPK.public_key,
    bobPqc.ek,
  )
);
const alice = WasmSession.from_json(session_json);

// Bob: respond with ML-KEM ciphertext from Alice
const bob = wasm_hybrid_session_respond(
  bobIK, bobSPK,
  aliceIK.public_key, aliceSPK.public_key,
  bobPqc.dk, mlkem_ct,
);

Session encrypt / decrypt

// Encrypt
const blob = alice.encrypt(new TextEncoder().encode('gm'));

// Decrypt
const pt = bob.decrypt(blob);
new TextDecoder().decode(pt); // → 'gm'

// Serialize / restore session state
const json = alice.to_json();
const restored = WasmSession.from_json(json);

Security

Hybrid PQC key derivation

shared_key = HKDF-SHA256(
  ikm  = ecdh_sk ‖ mlkem_ss,   // P-256 ‖ ML-KEM-768
  salt = 0x00...00  (32 bytes),
  info = "STVOR-HYBRID-v1"
)
Security guarantee: breaking shared_key requires breaking both P-256 ECDH and ML-KEM-768 simultaneously. No known classical or quantum algorithm can do this.

NIST ACVTS verification

All primitives tested against official NIST Cryptographic Algorithm Validation Program vectors:

25
P-256 ECDH
NIST KAS ECC CDH
15
ECDSA P-256/SHA-256
NIST FIPS 186-3 SigVer
21
AES-256-GCM
NIST SP 800-38D
3
HKDF-SHA256
RFC 5869 / SP 800-56C

Forward secrecy

The Double Ratchet protocol generates a fresh symmetric key for every message. Compromising a session key exposes only that single message — not past or future ones.

Key zeroization

All private key material is zeroed from memory after use via the Rust zeroize crate. Keys do not linger in memory after a session ends.

Comparison

@stvor/web3 XMTP Waku Signal (libsignal)
ML-KEM-768 (PQC)
Account Abstraction (ERC-4337 + TON v5)
TON wallet identity
Double Ratchet
On-chain key registry
Zero npm dependencies
NIST ACVTS verified
Rust WASM crypto core

Account Abstraction

@stvor/web3 supports any smart wallet — ERC-4337 on EVM and TON Wallet v5 extensions. Identity is derived from the wallet address, not from a private key. The same API works for EOA wallets, Safe, Coinbase Smart Wallet, Biconomy, ZeroDev, and TON v5.

🔷
ERC-4337 (EVM)
Works with Safe, Coinbase Smart Wallet, Biconomy, ZeroDev, Kernel. Uses personal_sign via EIP-1193. No dependency on any AA SDK.
💎
TON Wallet v5
Signs via signData TON Connect v2. Supports wallet v5 extensions — binds E2EE session to an extension body.
🔗
UserOperation binding
Cryptographically binds an E2EE session to a specific ERC-4337 UserOperation. Proves the session belongs to the AA wallet submitting the op.
🛡️
PQC identity from any wallet
HKDF(personal_sign) → P-256 IK + SPK + ML-KEM-768 keypair. Same signature always produces same keys — deterministic and reproducible.

EVM — ERC-4337 (Safe, Coinbase, Biconomy, ZeroDev…)

import { StvorAA } from '@stvor/web3';
import initWasm from '@stvor/web3/wasm';

const wasm = await initWasm();

// Works with ANY EIP-1193 provider — EOA or smart wallet
const client = await StvorAA.connectEVM({
  provider: window.ethereum,  // MetaMask, Safe, Coinbase, Biconomy...
  chainId: 1,
  wasm,
});

// Send PQC-encrypted message to any EVM address
await client.send('0xrecipient...', { text: 'gm from AA wallet!' });

console.log(client.address);   // AA wallet address
console.log(client.chainType);  // 'evm'

TON — Wallet v5 extension

const client = await StvorAA.connectTON({
  provider: tonConnectProvider,  // TON Connect v2
  wasm,
});

// Sign a TON v5 extension body — proves ownership
const ext = client.signTonExtension('deadbeef0102');
// ext.bodyHex, ext.identitySig, ext.walletAddress

// Verify a peer's extension signature
const ok = client.verifyTonExtension(ext, peerIdentityKey);

UserOperation binding (ERC-4337)

// Bind an E2EE session to a specific UserOperation
const binding = client.bindUserOp(
  userOpHash,    // keccak256(UserOperation) — hex, 32 bytes
  sessionKey,    // Double Ratchet root key — base64url, 32 bytes
);
// binding.userOpHash, binding.identitySig, binding.sessionCommitment

// Bundler / relayer verifies the binding on-chain
const valid = client.verifyUserOp(binding, peerIdentityKey, sessionKey);
// → true: this E2EE session belongs to the AA wallet that sent the UserOp

Identity derivation

// Signature → deterministic PQC identity keys (pure Rust WASM)
// STVOR-AA-EVM-v1:{chainId}:{address}  →  personal_sign  →  HKDF
//   IK  = HKDF(sig, msg, "IK",  32)  →  P-256 keypair
//   SPK = HKDF(sig, msg, "SPK", 32)  →  P-256 keypair
//   ML-KEM-768 keypair  →  hybrid post-quantum session key exchange

const id = JSON.parse(wasm_aa_derive(
  '0xwallet', '1', 'evm', signatureB64
));
// id.ik, id.spk, id.spk_sig, id.mlkem_ek, id.address, id.chain_type

Smart contract

The stvor_registry FunC contract stores public keys and message bag IDs on-chain. No central server. No trust required.

Contract address (testnet)

EQD... # deploy with: node contracts/deploy.mjs

Internal messages (write)

OpCodeBody
register_keys0x1001op + query_id + ref(IK) + ref(SPK) + ref(SIG) + ref(ML-KEM EK)
store_message0x1002op + query_id + to_addr(267) + bag_id(256)
delete_message0x1003op + query_id + bag_id(256)

Get methods (read)

MethodInputReturns
get_keysaddress_int(ik_cell, spk_cell, sig_cell, ts)
get_messagesaddress_intdict(256 → MsgMeta)
get_message_countaddress_intint

Deploy to testnet

# Get testnet TON from @testgiver_ton_bot
cd contracts
export STVOR_MNEMONIC="word1 word2 ... word24"
node deploy.mjs

# Output:
# Contract address: EQD...
# ✓ Deployed successfully!

Live demo

Try the cryptographic primitives directly in your browser. No installation needed — the WASM engine runs locally.

Generate a post-quantum ML-KEM-768 keypair, encapsulate a shared secret, and verify both sides agree.
Click "Run" to execute in your browser...

Contributing

# Run all 53 tests
cd crypto-core && cargo test                           # 28 Rust (incl. NIST vectors)
cd sdk && node --experimental-wasm-modules \
  --import tsx/esm src/__tests__/wasm.test.ts          # 17 WASM
cd contracts && node --import tsx/esm \
  tests/registry.test.ts                              # 8 contract

# Rebuild WASM after Rust changes
cd crypto-core && wasm-pack build --target web --out-dir ../sdk/wasm