Skip to main content
// DOCUMENTATIONVERSION: 1.0.0文書

NixProtocol Documentation

LOADING_DOCS_MODULE

Add zero-knowledge proofs and encryption to your application. NixProtocol provides privacy infrastructure for Ethereum and L2s. Reference implementation for quick integration, or enterprise engagements for custom solutions.

Mainnet LiveEthereumBaseArbitrumOptimism

Architecture Overview

ARCH_001

NixProtocol provides privacy through a layered architecture combining encryption, zero-knowledge proofs, and smart contracts.

System Architecture

┌─────────────────────────────────────────────────────────────────┐
│                        Your Application                          │
└─────────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────────┐
│                      @nixprotocol/sdk                            │
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────────────────┐  │
│  │  Key Gen    │  │  ElGamal    │  │   ZK Proof Generator    │  │
│  │  (BJJ)      │  │  Encrypt    │  │   (Groth16/WASM)        │  │
│  └─────────────┘  └─────────────┘  └─────────────────────────┘  │
└─────────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────────┐
│                     Smart Contracts                              │
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────────────────┐  │
│  │  NixERC20   │  │  NixPool    │  │   Verifier Contracts    │  │
│  │  (Balances) │  │  (Dark Pool)│  │   (Groth16 Verifier)    │  │
│  └─────────────┘  └─────────────┘  └─────────────────────────┘  │
└─────────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────────┐
│         Ethereum  │  Base  │  Arbitrum  │  Optimism             │
└─────────────────────────────────────────────────────────────────┘

Data Flow

01
Client Side
  • Generate BabyJubJub keypair
  • Encrypt values with ElGamal
  • Generate ZK proofs locally
  • Sign transactions
02
On-Chain
  • Verify ZK proofs
  • Store encrypted balances
  • Homomorphic updates
  • Emit privacy-preserving events
03
Auditor Access
  • Selective disclosure keys
  • View-only access grants
  • Compliance reporting
  • Audit trail generation

Key Components

BJJ
BabyJubJub Curve

Twisted Edwards curve optimized for ZK circuits. Used for keypairs and ElGamal encryption. Parameters: a=-1, d=168696/168700, base field = BN254 scalar field.

G16
Groth16 Proofs

Succinct non-interactive proofs with constant 3-element proof size (~200 bytes). Verification cost: ~200k gas. Trusted setup required per circuit.

PSN
Poseidon Hash

ZK-optimized hash function with ~8x fewer constraints than SHA256/Keccak in circuits. Used for commitments, nullifiers, and Merkle trees.

Quickstart

QUICK_START

Get up and running in 5 minutes. Generate keys, encrypt data, and create proofs.

1

Install the SDK

bash
npm install @nixprotocol/sdk ethers
2

Generate Keypair

typescript
import { generateKeyPair } from '@nixprotocol/sdk';

// Deterministic from wallet signature (recommended)
const signature = await signer.signMessage("NixProtocol Key Derivation");
const keyPair = generateKeyPair(signature);

console.log('Public Key:', keyPair.publicKey);  // [x, y] curve point
console.log('Private Key:', keyPair.privateKey); // scalar
3

Encrypt & Decrypt

typescript
import * as elgamal from '@nixprotocol/sdk/elgamal';

// Encrypt a value
const ciphertext = elgamal.encrypt(1000n, recipientPublicKey);

// Decrypt (only recipient can do this)
const decrypted = elgamal.decrypt(ciphertext, recipientPrivateKey);

// Homomorphic addition
const sum = elgamal.add(ciphertext1, ciphertext2);

Installation

PKG_INSTALL

NixProtocol is available as a set of NPM packages for different integration needs.

PKG.01
@nixprotocol/sdkMIT

Core SDK - encryption, decryption, ZK proof generation

PKG.02
@nixprotocol/stealth-walletBUSL-1.1

Stealth address generation and Nix ID encoding

PKG.03
@nixprotocol/relayerBUSL-1.1

Gasless meta-transactions via EIP-712

PKG.04
@nixprotocol/primitivesMIT

Low-level cryptographic primitives

Encryption

NixProtocol uses ElGamal encryption on the BabyJubJub curve for homomorphic operations on encrypted values.

Key Properties

  • Additively Homomorphic: Add encrypted values without decrypting
  • Probabilistic: Same plaintext encrypts differently each time
  • ZK-Friendly: Efficient to prove statements about encrypted values

Mathematical Foundation

BabyJubJub Curve

Twisted Edwards curve defined over the BN254 scalar field:

ax² + y² = 1 + dx²y² where a = -1, d = 168696/168700
ElGamal Encryption

For message m, public key H = sG (where s is private key, G is generator):

Encrypt: Pick random r, compute C1 = rG, C2 = rH + mG
Decrypt: Compute mG = C2 - sC1, solve DLOG for m
Add: (C1, C2) + (C1', C2') = (C1+C1', C2+C2')
DLOG Precomputation

Decryption requires solving discrete log. We use baby-step giant-step with precomputed tables for values up to 2⁴⁰ (~1 trillion). Larger values use windowed approach.

Security Parameters

Curve Security

~126-bit security level (subgroup order ~2²⁵¹)

Randomness

256-bit random r per encryption, CSPRNG required

Ciphertext Size

4 field elements = 128 bytes per encrypted value

Max Value

2⁴⁰ with precomputation, larger with performance tradeoff

Zero-Knowledge Proofs

NixProtocol uses Groth16 proofs for efficient on-chain verification. Proofs are ~200 bytes and cost ~200k gas to verify.

Registration Proof

Proves knowledge of private key corresponding to public key

Transfer Proof

Proves balance ≥ amount and correct re-encryption

Withdraw Proof

Proves encrypted balance covers withdrawal amount

Spend Proof

Proves Merkle membership without revealing which leaf

Stealth Addresses

Generate one-time addresses for receiving payments that cannot be linked to your main address.

typescript
import { generateStealthWallet, packNixId } from '@nixprotocol/stealth-wallet';

// Generate a complete stealth wallet
const wallet = generateStealthWallet();
console.log('Nix ID:', wallet.nixId);
// NIX_ABC123... (~75 chars, shareable payment address)

// Unpack to get components
const { evmAddress, bjjPublicKeyX, signBit } = unpackNixId(nixId);

Compliance & Auditor Access

Privacy with accountability. Grant selective view access to auditors, regulators, or internal compliance teams.

How Auditor Access Works

1
Generate View Key

Derive a separate view-only key from your master key. This key can decrypt balances but cannot sign transactions.

2
Grant Access

Share the view key with authorized auditors. Can be time-limited or revoked.

3
Audit Trail

Auditors can decrypt historical balances and transaction amounts for compliance reporting.

Implementation

typescript
import { deriveViewKey, grantAuditorAccess } from '@nixprotocol/sdk';

// Generate view-only key (cannot sign, only decrypt)
const viewKey = deriveViewKey(masterKeyPair);

// Create time-limited access grant
const accessGrant = await grantAuditorAccess({
  viewKey: viewKey,
  auditorId: '[email protected]',
  expiresAt: Date.now() + 90 * 24 * 60 * 60 * 1000, // 90 days
  scope: ['balances', 'transfers'], // What they can see
});

// Auditor can now decrypt with their access grant
const auditorClient = new NixAuditorClient(accessGrant);
const report = await auditorClient.generateComplianceReport({
  address: userAddress,
  fromDate: '2024-01-01',
  toDate: '2024-12-31',
});

Access Levels

View Balances

Decrypt current and historical encrypted balances

View Transfers

Decrypt transfer amounts between addresses

Full Access

Complete visibility including linked addresses

Regulatory Compatible: Designed for institutions requiring audit trails, tax reporting, and regulatory compliance while maintaining user privacy from public view.

SDK Overview

The @nixprotocol/sdk package is the main entry point for most integrations.

Exports

generateKeyPair(seed) - Deterministic BabyJubJub keypair
generateRandomKeyPair() - Random keypair
elgamal.encrypt(value, pubKey) - Encrypt a bigint
elgamal.decrypt(ciphertext, privKey) - Decrypt ciphertext
elgamal.add(c1, c2) - Homomorphic addition
poseidon.hash(inputs) - Poseidon hash function

Key Generation

Generate BabyJubJub keypairs for encryption and proof generation.

Important: Use deterministic key derivation from wallet signatures in production. Random keypairs require secure private key storage.

ElGamal Encryption

ElGamal encryption on BabyJubJub curve for additively homomorphic operations.

typescript
import * as elgamal from '@nixprotocol/sdk/elgamal';

// Ciphertext structure
interface Ciphertext {
  C1: [bigint, bigint];  // g^r - randomness component
  C2: [bigint, bigint];  // h^r * g^m - message component
}

// Encrypt
const ct = elgamal.encrypt(1000n, publicKey);

// Decrypt
const value = elgamal.decrypt(ct, privateKey); // 1000n

// Homomorphic addition
const sum = elgamal.add(ct1, ct2);
// decrypt(sum) === decrypt(ct1) + decrypt(ct2)

Nix ID

Nix ID is a shareable payment identifier that encodes both EVM address and BabyJubJub public key.

Example Nix ID:

NIX_5J2K7M9P3Q6R8T1V4W7X0Z3B6C9D2F5G8H1J4K7L0M3N6P9Q2R5S8T1U4V7W0X3Y6Z9

Smart Contracts Overview

NixProtocol smart contracts handle on-chain privacy operations including encrypted balances and ZK proof verification.

NixERC20

ERC-20 wrapper with encrypted balances and private transfers

NixPool

Dark pool for completely unlinkable withdrawals using Merkle trees

NixTools

Utility contracts for cryptographic operations

NixERC20

Main contract for encrypted token operations.

solidity
// Register with ZK proof (~320k gas)
function register(
    uint256[2] calldata publicKey,
    bytes calldata proof
) external;

// Deposit ERC20 (~570k gas)
function deposit(address token, uint256 amount) external;

// Private transfer (~950k gas)
function privateTransfer(
    address token,
    address to,
    bytes calldata proof
) external;

// Withdraw (~800k gas)
function withdraw(address token, bytes calldata proof) external;

NixPool

Dark pool pattern for completely unlinkable transactions.

solidity
// Deposit creates commitment in Merkle tree
function deposit(uint256 commitment) external payable;

// Withdraw with ZK proof - UNLINKABLE to deposit
function withdraw(
    uint256 nullifierHash,
    uint256 recipient,
    uint256 amount,
    uint256[2] calldata pA,
    uint256[2][2] calldata pB,
    uint256[2] calldata pC
) external;

Deployed Addresses

Reference implementation live on mainnet.

Ethereum MainnetLive
NixERC200x...
StealthRegistry0x...
NixTools0x...
BaseLive
NixERC200x...
StealthRegistry0x...
NixTools0x...
Arbitrum OneLive
NixERC200x...
StealthRegistry0x...
NixTools0x...
OptimismLive
NixERC200x...
StealthRegistry0x...
NixTools0x...

Reference implementation. Security audit in progress. Do not deploy with significant value until audit complete.

Private ERC-20 Guide

Add encrypted balances to any ERC-20 token in 5 steps.

  1. 1Deploy NixERC20 contract pointing to your token
  2. 2Users register with BabyJubJub public key
  3. 3Deposit converts public tokens to encrypted balance
  4. 4Transfer moves encrypted amounts between users
  5. 5Withdraw converts back to public tokens

Complete Integration Example

typescript
import { ethers } from 'ethers';
import { 
  generateKeyPair, 
  generateRegistrationProof,
  generateTransferProof,
  elgamal 
} from '@nixprotocol/sdk';
import { NixERC20__factory } from '@nixprotocol/contracts';

// Setup
const provider = new ethers.BrowserProvider(window.ethereum);
const signer = await provider.getSigner();
const nixERC20 = NixERC20__factory.connect(NIX_ERC20_ADDRESS, signer);

// Step 1: Generate keypair from wallet signature (deterministic)
const signature = await signer.signMessage("NixProtocol Key Derivation v1");
const keyPair = generateKeyPair(signature);

// Step 2: Register (one-time per address)
const isRegistered = await nixERC20.isRegistered(signer.address);
if (!isRegistered) {
  const regProof = await generateRegistrationProof(keyPair);
  const regTx = await nixERC20.register(keyPair.publicKey, regProof);
  await regTx.wait();
}

// Step 3: Deposit - convert public tokens to encrypted balance
const USDC_ADDRESS = '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48';
const amount = ethers.parseUnits('100', 6); // 100 USDC

// First approve
const usdc = new ethers.Contract(USDC_ADDRESS, ['function approve(address,uint256)'], signer);
await (await usdc.approve(NIX_ERC20_ADDRESS, amount)).wait();

// Then deposit
const depositTx = await nixERC20.deposit(USDC_ADDRESS, amount);
await depositTx.wait();

// Step 4: Check encrypted balance
const encryptedBalance = await nixERC20.getEncryptedBalance(USDC_ADDRESS, signer.address);
const balance = elgamal.decrypt({
  C1: [encryptedBalance[0], encryptedBalance[1]],
  C2: [encryptedBalance[2], encryptedBalance[3]]
}, keyPair.privateKey);
console.log('Decrypted balance:', balance.toString()); // 100000000 (6 decimals)

// Step 5: Private transfer
const recipientPubKey = await nixERC20.getPublicKey(recipientAddress);
const transferAmount = ethers.parseUnits('25', 6);
const transferProof = await generateTransferProof({
  senderKeyPair: keyPair,
  recipientPublicKey: recipientPubKey,
  amount: transferAmount,
  currentBalance: balance,
  currentCiphertext: encryptedBalance
});

const transferTx = await nixERC20.privateTransfer(
  USDC_ADDRESS, 
  recipientAddress, 
  transferProof
);
await transferTx.wait();
console.log('Private transfer complete!');

Gasless Transactions

Let users transact without holding native tokens. They sign, you pay gas via EIP-712 meta-transactions.

How It Works

1
User creates intent
2
Signs EIP-712 message
3
Relayer submits tx
4
User pays fee in token

Basic Usage

typescript
import { NixRelayer } from '@nixprotocol/relayer';

const relayer = new NixRelayer({
  url: 'https://relayer.nixprotocol.com',
  chainId: 1, // Ethereum mainnet
});

// User signs, relayer pays gas
const result = await relayer.sendPrivateTransfer({
  signer: userSigner,           // User's wallet (MetaMask, etc.)
  token: USDC_ADDRESS,
  recipient: recipientAddress,
  proof: transferProof,
  feeToken: USDC_ADDRESS,       // Pay fee in USDC
  maxFee: ethers.parseUnits('1', 6), // Max 1 USDC fee
});

console.log('Tx hash:', result.txHash);

Self-Hosted Relayer

typescript
// server.ts - Your own relayer backend
import { NixRelayerServer } from '@nixprotocol/relayer/server';

const relayer = new NixRelayerServer({
  privateKey: process.env.RELAYER_PRIVATE_KEY,
  rpcUrl: process.env.RPC_URL,
  supportedTokens: [USDC, USDT, DAI],
  feePercentage: 0.1, // 0.1% fee
});

// Validate and execute meta-transactions
app.post('/relay', async (req, res) => {
  const { signedRequest } = req.body;
  
  // Verify signature and fee payment
  const isValid = await relayer.verifyRequest(signedRequest);
  if (!isValid) return res.status(400).json({ error: 'Invalid request' });
  
  // Execute on-chain
  const result = await relayer.execute(signedRequest);
  res.json({ txHash: result.hash });
});

Fee Structure

OperationGas Cost~Fee (at 30 gwei)
Private Transfer~950k gas~$50-80
Withdraw~800k gas~$40-65
Register~320k gas~$15-25

* Fees are significantly lower on L2s (Base, Arbitrum, Optimism)

Integration Paths

Choose the right approach based on your privacy requirements.

Basic1-2 hours

Add Privacy to ERC-20

Wrap any ERC-20 with encrypted balances. Amounts hidden but transfers linkable.

Intermediate2-4 hours

Gasless UX

Users sign, relayer pays gas. Better UX with no native token requirement.

Advanced1-2 days

Full Privacy Payments

Complete anonymity with unlinkable withdrawals using NixPool dark pool pattern.

Error Handling

Common errors and how to handle them in your integration.

Error CodeCauseSolution
INVALID_PROOFZK proof verification failedRegenerate proof with correct inputs
INSUFFICIENT_BALANCEEncrypted balance less than transfer amountCheck decrypted balance before transfer
NOT_REGISTEREDUser hasn't registered public keyCall register() with valid proof first
NULLIFIER_USEDAttempting to spend already-spent noteUse fresh commitment for new transaction
INVALID_MERKLE_ROOTMerkle root doesn't match on-chain stateFetch latest root before proof generation
DECRYPTION_FAILEDWrong private key or corrupted ciphertextVerify keypair matches registered public key

Handling Errors

typescript
import { NixError, NixErrorCode } from '@nixprotocol/sdk';

try {
  const tx = await nixERC20.privateTransfer(recipient, proof);
  await tx.wait();
} catch (error) {
  if (error instanceof NixError) {
    switch (error.code) {
      case NixErrorCode.INVALID_PROOF:
        // Regenerate proof with fresh randomness
        const newProof = await generateTransferProof(/*...*/);
        break;
      case NixErrorCode.INSUFFICIENT_BALANCE:
        // Show user their actual balance
        const balance = await getDecryptedBalance();
        throw new Error(`Insufficient balance: ${balance}`);
      default:
        console.error('Nix error:', error.message);
    }
  }
  throw error;
}

Debugging Tips

Enable Debug Mode

Set NIX_DEBUG=true to log proof inputs and circuit constraints.

Verify Keys Match

Most errors stem from keypair mismatch. Always verify public key matches on-chain registration.

Check Gas Limits

ZK proof verification requires ~200k gas. Ensure transactions have sufficient gas limit.

Security Best Practices

Critical security considerations for production deployments.

Key Management

Never Store Private Keys in LocalStorage

Use deterministic key derivation from wallet signatures. Keys should be derived on-demand, not persisted.

Do: Derive Keys from Signatures

Request user signature on a known message, hash it, and derive BabyJubJub keypair deterministically.

Proof Generation

Use Fresh Randomness

Every proof must use cryptographically random values. Reusing randomness leaks private information.

Client-Side Only

Generate proofs in browser/client. Never send private keys or amounts to backend servers.

Verify Before Submit

Always verify proofs locally before submitting to chain to save gas on invalid proofs.

Timing Attacks

Add random delays between operations to prevent transaction graph analysis.

Smart Contract Security

  • Verify Circuit Hash: Ensure deployed verifier matches trusted setup ceremony output
  • Check Nullifier Storage: Nullifiers must be stored permanently to prevent double-spend
  • Merkle Tree Depth: Use sufficient depth (20+) to support expected user base
  • Reentrancy Guards: All state-changing functions must be protected
Security Audit Status

Reference implementation is undergoing security audit. Do not deploy with significant value until audit is complete. Contact us for enterprise deployment security review.

Full API Reference

Complete function signatures and type definitions.

@nixprotocol/sdk

typescript
// Key Generation
function generateKeyPair(seed: string | Uint8Array): KeyPair;
function generateRandomKeyPair(): KeyPair;
function deriveViewKey(keyPair: KeyPair): ViewKey;

interface KeyPair {
  publicKey: [bigint, bigint];  // BJJ curve point
  privateKey: bigint;           // scalar
}

// ElGamal Encryption
namespace elgamal {
  function encrypt(value: bigint, publicKey: [bigint, bigint]): Ciphertext;
  function decrypt(ciphertext: Ciphertext, privateKey: bigint): bigint;
  function add(c1: Ciphertext, c2: Ciphertext): Ciphertext;
  function scalarMul(c: Ciphertext, scalar: bigint): Ciphertext;
  function rerandomize(c: Ciphertext, publicKey: [bigint, bigint]): Ciphertext;
}

interface Ciphertext {
  C1: [bigint, bigint];
  C2: [bigint, bigint];
}

// Poseidon Hash
namespace poseidon {
  function hash(inputs: bigint[]): bigint;
  function hashTwo(a: bigint, b: bigint): bigint;
}

// Proof Generation
function generateRegistrationProof(keyPair: KeyPair): Promise<Proof>;
function generateTransferProof(params: TransferParams): Promise<Proof>;
function generateWithdrawProof(params: WithdrawParams): Promise<Proof>;
function verifyProof(proof: Proof, circuitType: CircuitType): boolean;

interface Proof {
  pA: [bigint, bigint];
  pB: [[bigint, bigint], [bigint, bigint]];
  pC: [bigint, bigint];
  publicSignals: bigint[];
}

@nixprotocol/stealth-wallet

typescript
function generateStealthWallet(): StealthWallet;
function packNixId(components: NixIdComponents): string;
function unpackNixId(nixId: string): NixIdComponents;
function computeStealthAddress(spendingKey: KeyPair, viewingKey: KeyPair): string;
function scanForPayments(viewKey: ViewKey, fromBlock: number): Promise<Payment[]>;

interface StealthWallet {
  nixId: string;              // NIX_... shareable address
  evmAddress: string;         // 0x... stealth address
  spendingKeyPair: KeyPair;
  viewingKeyPair: KeyPair;
}

interface NixIdComponents {
  evmAddress: string;
  bjjPublicKeyX: bigint;
  signBit: boolean;
}

Contract ABIs

solidity
interface INixERC20 {
  function register(uint256[2] calldata publicKey, bytes calldata proof) external;
  function deposit(address token, uint256 amount) external;
  function privateTransfer(address token, address to, bytes calldata proof) external;
  function withdraw(address token, bytes calldata proof) external;
  
  function getEncryptedBalance(address token, address user) 
    external view returns (uint256[4] memory ciphertext);
  function isRegistered(address user) external view returns (bool);
  function getPublicKey(address user) external view returns (uint256[2] memory);
}

interface INixPool {
  function deposit(uint256 commitment) external payable;
  function withdraw(
    uint256 nullifierHash,
    address recipient,
    uint256 amount,
    bytes calldata proof
  ) external;
  
  function getMerkleRoot() external view returns (uint256);
  function isSpent(uint256 nullifierHash) external view returns (bool);
}
支援

Need Help Integrating?

We offer custom development for complex privacy requirements.

暗号化|ZK_PROOFS|PRIVACY|プライバシー