For Web3 Developers

On-Chain Confidential Storage

Store encrypted data on public blockchains with post-quantum security. Zero-key encryption, threshold custody, and on-chain authorization—all without managing cryptographic keys.

Live on Ternoa zkEVM+ mainnet. Ethereum & Arbitrum coming soon.

Why Build with CIFER

Enterprise-grade encryption for blockchain applications without the complexity.

Zero-Key Encryption

No key management required. Keys are generated, split, and secured inside TEE enclaves. You never handle cryptographic material.

Post-Quantum Security

ML-KEM-768 key encapsulation combined with AES-256-GCM. Your data stays secure against future quantum attacks.

Threshold Custody

Private keys are split into 5 shards across secure nodes. Any 3 can reconstruct—no single point of failure.

On-Chain Authorization

Transparent owner/delegate model recorded on-chain. Decryption rights are auditable and revocable.

Smart Contracts

Live on Ternoa zkEVM+ Mainnet (Chain ID: 752025)

Secrets Controller

Manages secret lifecycle and ownership

0x4e31230737847C0895Df4F11876056960537E9Df
Secrets Controller ABI
// ABI
function secretCreationFee() view returns (uint256)
function createSecret() payable
function createSecret(uint8 secretType) payable
function getSecretState(uint256 secretId) view returns (bool isSyncing, bytes publicKeyCid)
function getSecretsByWallet(address wallet) view returns (uint256[])

On-Chain Storage

Stores encrypted data commitments

0x6A8b01CA9AB653510F579cfB59502880DCD0F174
Vault ABI
// ABI
function userSecretId(address user) view returns (uint256)
function setSecretId(uint256 secretId)
function store(bytes32 key, bytes encryptedMessage, bytes cifer)
function getUserMetadata(address user, bytes32 key) view returns (uint256 secretId, uint256 storedAtBlock)
function computeDataId(address user, bytes32 key) view returns (bytes32)

How It Works

Six steps from plaintext to encrypted on-chain storage.

1

Create a Secret

Call createSecret() on the Secrets Controller contract. Pay a small fee (1 CAPS) and receive your secretId.

javascript
const fee = await controller.secretCreationFee();
const tx = await controller.createSecret(1, { value: fee });
// secretId from SecretCreated event
2

Wait for Sync

New secrets need time to sync with TEE enclaves. Poll getSecretState() until isSyncing = false.

javascript
const state = await controller.getSecretState(secretId);
if (!state.isSyncing && state.publicKeyCid) {
  console.log("Secret is ready!");
}
3

Link to Vault

Call setSecretId() once to associate your wallet with your secret in the vault contract.

javascript
await vault.setSecretId(yourSecretId);
// Now your wallet is linked to this secret
4

Encrypt Data

Sign your data string and call the Blackbox API. Receive cifer (encapsulated key) and encryptedMessage.

javascript
const dataString = `${chainId}_${secretId}_${wallet}_${blockNumber}_${plainText}`;
const signature = await signer.signMessage(dataString);
const { cifer, encryptedMessage } = await encryptPayload(dataString, signature);
5

Store On-Chain

Call vault.store() with your encrypted data. The ciphertext is emitted in events for low-cost storage.

javascript
const key = stringToBytes32("my-record-key");
await vault.store(key, encryptedMessage, cifer);
// Data is now stored on-chain, encrypted
6

Retrieve & Decrypt

Read metadata from the vault, fetch ciphertext from event logs, and decrypt via the Blackbox API.

javascript
const metadata = await vault.getUserMetadata(address, key);
// Fetch cifer + encryptedMessage from logs
const { decryptedMessage } = await decryptPayload(cifer, data, signature);

Try It Without Writing Code

Test the On-Chain Confidential Storage in our interactive playground

Use Cases

Insurance Records

Encrypt claims, medical records, and sensitive documents with long-term archival security. Only authorized parties can decrypt.

Audit-Friendly Records

Store integrity proofs on-chain while keeping data encrypted. Prove existence and timestamp without exposing content.

Sealed-Bid Auctions

Run fair auctions where bids are encrypted until reveal. Prevent front-running and ensure bid confidentiality.

Cross-Entity Collaboration

Share encrypted artifacts with partners where decryption is controlled by on-chain authority, not informal key sharing.

Complete Code Example

Encrypt and Store On-Chain

encryptAndStore.js
import { ethers } from "ethers";

const BASE_URL = "https://cifer-blackbox.ternoa.dev:3010";
const CHAIN_ID = 752025;
const VAULT_ADDRESS = "0x6A8b01CA9AB653510F579cfB59502880DCD0F174";

async function encryptAndStore(plainText, keyLabel) {
  // 1. Setup
  const provider = new ethers.BrowserProvider(window.ethereum);
  const signer = await provider.getSigner();
  const walletAddress = await signer.getAddress();
  
  // 2. Get current block number
  const health = await fetch(`${BASE_URL}/healthz`).then(r => r.json());
  const blockNumber = health.chainStatus[CHAIN_ID].lastKnownBlockNumber;
  
  // 3. Get user's secretId from vault
  const vault = new ethers.Contract(VAULT_ADDRESS, [
    "function userSecretId(address) view returns (uint256)",
    "function store(bytes32, bytes, bytes)"
  ], signer);
  
  const secretId = await vault.userSecretId(walletAddress);
  
  // 4. Build and sign encrypt payload
  const dataString = `${CHAIN_ID}_${secretId}_${walletAddress}_${blockNumber}_${plainText}`;
  const signature = await signer.signMessage(dataString);
  
  // 5. Call encrypt API
  const encryptResponse = await fetch(`${BASE_URL}/encrypt-payload`, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      data: dataString,
      signature: signature,
      outputFormat: "hex"
    })
  });
  
  const { cifer, encryptedMessage } = await encryptResponse.json();
  
  // 6. Store on-chain
  const keyBytes = ethers.toUtf8Bytes(keyLabel);
  const key = ethers.zeroPadValue(ethers.hexlify(keyBytes), 32);
  
  const tx = await vault.store(key, encryptedMessage, cifer);
  const receipt = await tx.wait();
  
  return { key, blockNumber: receipt.blockNumber, txHash: receipt.hash };
}

// Usage
const result = await encryptAndStore("My secret data", "note-001");
console.log("Stored at block:", result.blockNumber);

Retrieve and Decrypt

retrieveAndDecrypt.js
async function retrieveAndDecrypt(keyLabel) {
  const provider = new ethers.BrowserProvider(window.ethereum);
  const signer = await provider.getSigner();
  const walletAddress = await signer.getAddress();
  
  // Convert key label to bytes32
  const keyBytes = ethers.toUtf8Bytes(keyLabel);
  const key = ethers.zeroPadValue(ethers.hexlify(keyBytes), 32);
  
  // Get metadata from vault
  const vault = new ethers.Contract(VAULT_ADDRESS, [
    "function userSecretId(address) view returns (uint256)",
    "function getUserMetadata(address, bytes32) view returns (uint256, uint256)",
    "function computeDataId(address, bytes32) view returns (bytes32)"
  ], provider);
  
  const secretId = await vault.userSecretId(walletAddress);
  const metadata = await vault.getUserMetadata(walletAddress, key);
  const dataId = await vault.computeDataId(walletAddress, key);
  
  // Fetch event logs to get cifer + encryptedMessage
  const storedAtBlock = Number(metadata[1]);
  const logs = await provider.getLogs({
    address: VAULT_ADDRESS,
    fromBlock: storedAtBlock - 50,
    toBlock: storedAtBlock + 50
  });
  
  // Parse CIFERDataStored event
  const vaultInterface = new ethers.Interface([
    "event CIFERDataStored(bytes32 indexed dataId, address indexed user, bytes32 indexed key, bytes cifer, bytes encryptedMessage)"
  ]);
  
  let cifer, encryptedMessage;
  for (const log of logs) {
    try {
      const parsed = vaultInterface.parseLog(log);
      if (parsed.args.dataId === dataId) {
        cifer = parsed.args.cifer;
        encryptedMessage = parsed.args.encryptedMessage;
        break;
      }
    } catch { /* not our event */ }
  }
  
  // Get fresh block number and decrypt
  const health = await fetch(`${BASE_URL}/healthz`).then(r => r.json());
  const blockNumber = health.chainStatus[CHAIN_ID].lastKnownBlockNumber;
  
  const dataString = `${CHAIN_ID}_${secretId}_${walletAddress}_${blockNumber}_${encryptedMessage}`;
  const signature = await signer.signMessage(dataString);
  
  const decryptResponse = await fetch(`${BASE_URL}/decrypt-payload`, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      cifer: cifer,
      data: dataString,
      signature: signature,
      inputFormat: "hex"
    })
  });
  
  const { decryptedMessage } = await decryptResponse.json();
  return decryptedMessage;
}

// Usage
const plainText = await retrieveAndDecrypt("note-001");
console.log("Decrypted:", plainText);

Technical Architecture

Key security and reliability properties of the CIFER confidential storage system.

KF-1

Threshold Custody

Private decryption keys are split into 5 shards. Any 3 of 5 can reconstruct the key. No single node holds the full key at rest.

KF-2

Hybrid Encryption

Post-quantum key encapsulation (ML-KEM-768) combined with AES-256-GCM for bulk data. Best of both worlds.

KF-3

On-Chain Access Control

Decryption is gated by an owner/delegate model recorded on-chain. Authorization is auditable and transparent.

KF-4

Secret Lifecycle

Secrets have explicit states: created → syncing → synced/ready. Only synced secrets can be used for encryption.

Blackbox API

Secure enclave endpoints for encryption and decryption.

Base URL: https://cifer-blackbox.ternoa.dev:3010

MethodEndpointDescription
GET/healthzGet service health and current block number for signatures
POST/encrypt-payloadEncrypt a string payload with post-quantum encryption
POST/decrypt-payloadDecrypt a payload (owner/delegate only)
POST/encrypt-fileEncrypt files up to 1GB (async job)
POST/decrypt-fileDecrypt .cifer files (async job)

Full API documentation available in the playground.

Frequently Asked Questions

How is this different from regular encryption?

With CIFER, you never manage keys. Keys are generated inside TEE enclaves, split across multiple nodes, and never exposed. Authorization is enforced on-chain, not by trusting a central server.

What blockchain networks are supported?

Currently live on Ternoa zkEVM+ mainnet (Chain ID: 752025). Support for Ethereum, Arbitrum, and other EVM chains is coming soon.

What happens if some nodes go offline?

The 3-of-5 threshold model means decryption works even if 2 nodes are unavailable. The system is designed for high availability.

Can I revoke someone's access to decrypt?

Yes. As the secret owner, you can clear or change the delegate address on-chain at any time. Revocation is immediate and auditable.

How much does it cost?

Creating a secret costs 1 CAPS on Ternoa. Storage costs are standard EVM transaction fees. The vault uses events for ciphertext to minimize on-chain storage costs.

What You Get

Zero-key encryption—no HSMs, no key rotation
Post-quantum security (ML-KEM-768 + AES-256-GCM)
3-of-5 threshold key custody for fault tolerance
On-chain owner/delegate authorization model
Event-based storage for low gas costs
Full audit trail of all operations
Compatible with any EVM chain
File encryption up to 1GB

Ready to Build with On-Chain Privacy?

Try the playground to see it in action, or contact us for integration support.