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// 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// 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.
Create a Secret
Call createSecret() on the Secrets Controller contract. Pay a small fee (1 CAPS) and receive your secretId.
const fee = await controller.secretCreationFee();
const tx = await controller.createSecret(1, { value: fee });
// secretId from SecretCreated eventWait for Sync
New secrets need time to sync with TEE enclaves. Poll getSecretState() until isSyncing = false.
const state = await controller.getSecretState(secretId);
if (!state.isSyncing && state.publicKeyCid) {
console.log("Secret is ready!");
}Link to Vault
Call setSecretId() once to associate your wallet with your secret in the vault contract.
await vault.setSecretId(yourSecretId); // Now your wallet is linked to this secret
Encrypt Data
Sign your data string and call the Blackbox API. Receive cifer (encapsulated key) and encryptedMessage.
const dataString = `${chainId}_${secretId}_${wallet}_${blockNumber}_${plainText}`;
const signature = await signer.signMessage(dataString);
const { cifer, encryptedMessage } = await encryptPayload(dataString, signature);Store On-Chain
Call vault.store() with your encrypted data. The ciphertext is emitted in events for low-cost storage.
const key = stringToBytes32("my-record-key");
await vault.store(key, encryptedMessage, cifer);
// Data is now stored on-chain, encryptedRetrieve & Decrypt
Read metadata from the vault, fetch ciphertext from event logs, and decrypt via the Blackbox API.
const metadata = await vault.getUserMetadata(address, key);
// Fetch cifer + encryptedMessage from logs
const { decryptedMessage } = await decryptPayload(cifer, data, signature);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
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
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.
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.
Hybrid Encryption
Post-quantum key encapsulation (ML-KEM-768) combined with AES-256-GCM for bulk data. Best of both worlds.
On-Chain Access Control
Decryption is gated by an owner/delegate model recorded on-chain. Authorization is auditable and transparent.
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
| Method | Endpoint | Description |
|---|---|---|
| GET | /healthz | Get service health and current block number for signatures |
| POST | /encrypt-payload | Encrypt a string payload with post-quantum encryption |
| POST | /decrypt-payload | Decrypt a payload (owner/delegate only) |
| POST | /encrypt-file | Encrypt files up to 1GB (async job) |
| POST | /decrypt-file | Decrypt .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.