Post-Quantum Signing
This guide shows how to run Silent Network on one server, create a wallet from a React frontend, register an ML-DSA verifier key, and submit an ML-DSA verification transaction to the ZKNOX verifier contract on Base Sepolia.
The flow uses Silent Network for authentication, key generation, and signing:
- Passkey or Auth0 authenticates the user.
- Wallet creation generates
secp256k1,ed25519, andmldsa44keys. - The ML-DSA flow signs raw message bytes and sends Base Sepolia transactions to the ZKNOX smart contract: first
setKeyregisters the converted public key, thenverifychecks the message and signature.
Prerequisites
- Access to the Silence Laboratories GitHub Container Registry packages.
- Docker and Docker Compose on the server that will run Silent Network.
- Node.js 22+ for the React frontend.
- A browser that supports Passkeys if you use Passkey authentication.
For production deployments, use Deploying on Confidential Space. The local Docker Compose setup below is intended for development environments.
1. Authenticate Docker to GHCR
Log in with a GitHub account or token that can pull the Silent Network images:
echo "$GITHUB_TOKEN" | docker login ghcr.io -u "$GITHUB_USERNAME" --password-stdin
2. Start Silent Network
This section gives the shortest path to run the nodes and backend locally. For a deeper explanation of the node setup, backend setup, request flow, and service code breakdown, see Setup nodes and Setup backend.
Clone the deployment repository on the server:
git clone https://github.com/silence-laboratories/silent-network-deploy
cd silent-network-deploy
Configure the image registry and start the auth-enabled local network:
export IMG_URI=ghcr.io/silence-laboratories/el-services/
export IMG_TAG=v4.0.3-policy-with-auth
export GH_TOKEN="$GITHUB_TOKEN"
export DOCKER_DEFAULT_PLATFORM=linux/amd64 # (Optional) required for ARM64 machine
docker compose -f ./local/docker-compose.yaml up
The local deployment exposes:
- WPBE:
localhost:8090 - MPC Node 1:
localhost:8081 - MPC Node 2:
localhost:8082 - MPC Node 3:
localhost:8083 - MPC Node 4:
localhost:8084 - MPC Node 5:
localhost:8085
Verify the services:
curl http://localhost:8090/v2/version
{"docker_image_tag":"v4.0.3-policy-with-auth","build_hash":"v3.1.0-silent-pay-demo-6-gc781050d","profile":"release","api_key":"wpbe1","public_key":"759d0c9b07d2d0bbc4979789e97fa1d5ef502fa0f253c93e38aa824e218cb719","features":"default, eoa_auth, eph_auth, gcp_verifier, passkey_auth, policy_engine, user_auth","orig_id":"not set"}
curl --insecure https://localhost:8081/v2/version
{"docker_image_tag":"v4.0.3-policy-with-auth","build_hash":"v3.1.0-silent-pay-demo-6-gc781050d","profile":"release","api_key":"wpbe2","public_key":"02d9f040ba87b0c9d85edcdfd056dcc48cc2ff078c9fc97cf97fe457a05ffabe6d","features":"default, eoa_auth, eph_auth, gcp_verifier, passkey_auth, policy_engine, user_auth","orig_id":"not set"}
3. Start the React wallet frontend
This section shows how to run the frontend locally using the Wallet Provider Client SDK. For the full SDK walkthrough, including authentication, wallet creation, transaction signing, see Setup frontend.
Clone the examples repository:
git clone https://github.com/silence-laboratories/pq-demo-react.git
cd pq-demo-react
Run the remaining frontend commands from your React wallet application directory.
Create the environment file:
cp .env.example .env
For a local Silent Network, set these values in .env:
VITE_WALLET_PROVIDER_URL=ws://localhost:8090
VITE_API_VERSION=v1
VITE_RP_ID=localhost
VITE_RP_NAME=http://localhost:5173
VITE_MPC_THRESHOLD=2
VITE_MPC_PARTIES=3
For ML-DSA, Silent Network currently supports these threshold/party combinations:
Threshold t | Parties n |
|---|---|
| 2 | 2 |
| 2 | 3 |
| 3 | 3 |
The .env values above use t=2, n=3.
Set the Base Sepolia and ZKNOX verifier values:
VITE_SEPOLIA_RPC_URL=https://sepolia.base.org
VITE_SEPOLIA_CHAIN_ID=84532
VITE_SEPOLIA_CHAIN_NAME=Base Sepolia
VITE_EXPLORER_URL=https://sepolia.basescan.org
VITE_ZKNOX_CONTRACT_ADDRESS=0x092c5d82069de997E34Ce2505CA7D5042f3721ef
Install dependencies and start the frontend:
bun install
bun run dev
The frontend is now running at http://localhost:5173.
4. Create wallet and submit an ML-DSA verification transaction
From the frontend:
- Select
Sign with passkey. - Click
Register passkey. Approve the passkey creation. - Click
New Wallet. EnterWallet Name. Continue. - Select Wallet Security
ECDSA + ML-DSA-44 (ZKNOX). Continue. - Click
Create Wallet. Authenticate with created passkey. - Wait for Base Sepolia ETH funding.
- Register the ZKNOX key with
setKey. - Click
Sendto start signing and sending a verification transaction to Base Sepolia. - Enter
Message to sign. ClickVerify, then authenticate passkey to approve the signing. - Once passkey verification is done, the signed transaction will call
verifyon Base Sepolia.
The frontend asks Silent Network to sign raw bytes with mldsa44, converts the public key and signature into the ZKNOX verifier input format, then sends transactions to the Base Sepolia smart contract:
function setKey(bytes memory pubkey) external returns (bytes memory);
function verify(bytes memory pk, bytes memory m, bytes memory signature, bytes memory ctx)
external
view
returns (bool);
The transaction to address is the ZKNOX smart contract. The setKey calldata registers the encoded public key and returns the pointer used by verify. The verify calldata includes that pointer, the message hash, the ML-DSA signature, and an empty context value.
5. ZKNOX verifier parameters
The ZKNOX NIST verifier expects ML-DSA-44 inputs in the verifier contract shape, not the packed SDK response shape.
The conversion uses:
- ML-DSA-44 public key size:
1312bytes. - ML-DSA-44 signature size:
2420bytes. - Raw SDK message:
message. - Message hash for signing and verification:
keccak256(message). - Context:
0xfor this demo. - Signature split:
cTilde(32bytes),z(2304bytes), andh(84bytes). - Public key conversion: recover
A_hatfromrho, computetr, decodet1, scale by2^13, run NTT, compact each polynomial, then encode the result as thesetKeypublic key blob.
The setKey call is:
function setKey(bytes memory pubkey) external returns (bytes memory);
setKey stores the encoded ML-DSA public key and returns a pointer. The frontend calls setKey after the ZKNOX wallet receives Base Sepolia ETH, waits for the registration transaction, and saves the returned pointer for later verification calls.
The verifier call is:
function verify(bytes memory pk, bytes memory m, bytes memory signature, bytes memory ctx)
external
view
returns (bool);
verify checks the stored public key pointer, message hash, ML-DSA signature, and context. The frontend first calls verify as a read-only check. If the result is true, it can optionally send a transaction that calls verify (note: as a view function this does not update contract state; it only records the call itself on-chain).
Use the ZKNOX conversion utility in walletprovider-sdk as the reference implementation for converting SDK ML-DSA outputs into the verifier input format.