Simple REST API
Just sign your request and call the endpoint. No SDK required, works with any language.
Post-Quantum Security
ML-KEM-768 key encapsulation + AES-256-GCM. Future-proof against quantum attacks.
Zero Key Management
Keys are generated and stored in secure enclaves. You never handle cryptographic material.
Payloads & Files
Encrypt strings up to 10KB instantly, or files up to 1GB with async job processing.
/healthzHealth Check
Get the current block number needed for signatures. Call this before every encrypt/decrypt request.
// Get current block number for signatures
const response = await fetch("https://cifer-blackbox.ternoa.dev:3010/healthz");
const health = await response.json();
const blockNumber = health.chainStatus["752025"].lastKnownBlockNumber;
console.log("Current block:", blockNumber);
// Response:
// {
// "status": "ok",
// "supportedChains": [752025],
// "chainStatus": {
// "752025": {
// "lastKnownBlockNumber": 5562008,
// "isWsLive": true
// }
// }
// }/encrypt-payloadEncrypt Payload
Encrypt a string payload (up to 10KB). Returns the encrypted message and the encapsulated key (cifer). Save both values—you need them to decrypt.
import { ethers } from "ethers";
const BASE_URL = "https://cifer-blackbox.ternoa.dev:3010";
const CHAIN_ID = 752025;
async function encryptPayload(secretId, plainText) {
// 1. Get current block number
const health = await fetch(`${BASE_URL}/healthz`).then(r => r.json());
const blockNumber = health.chainStatus[CHAIN_ID].lastKnownBlockNumber;
// 2. Connect wallet
const provider = new ethers.BrowserProvider(window.ethereum);
const signer = await provider.getSigner();
const walletAddress = await signer.getAddress();
// 3. Build data string: chainId_secretId_signer_blockNumber_plainText
const dataString = `${CHAIN_ID}_${secretId}_${walletAddress}_${blockNumber}_${plainText}`;
// 4. Sign with EIP-191
const signature = await signer.signMessage(dataString);
// 5. Call encrypt API
const response = await fetch(`${BASE_URL}/encrypt-payload`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
data: dataString,
signature: signature,
outputFormat: "hex" // or "base64"
})
});
const result = await response.json();
// IMPORTANT: Save BOTH values - you need them to decrypt!
return {
cifer: result.cifer, // Encapsulated key
encryptedMessage: result.encryptedMessage // Encrypted data
};
}
// Usage
const { cifer, encryptedMessage } = await encryptPayload(1, "Hello, World!");
console.log("Encrypted:", encryptedMessage);/decrypt-payloadDecrypt Payload
Decrypt a payload that was encrypted to your secret. Only the secret owner or delegate can decrypt.
async function decryptPayload(secretId, cifer, encryptedMessage) {
// 1. Get current block number
const health = await fetch(`${BASE_URL}/healthz`).then(r => r.json());
const blockNumber = health.chainStatus[CHAIN_ID].lastKnownBlockNumber;
// 2. Connect wallet
const provider = new ethers.BrowserProvider(window.ethereum);
const signer = await provider.getSigner();
const walletAddress = await signer.getAddress();
// 3. Build data string (encryptedMessage as payload)
const dataString = `${CHAIN_ID}_${secretId}_${walletAddress}_${blockNumber}_${encryptedMessage}`;
// 4. Sign with EIP-191
const signature = await signer.signMessage(dataString);
// 5. Call decrypt API
const response = 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 result = await response.json();
return result.decryptedMessage;
}
// Usage
const plainText = await decryptPayload(1, cifer, encryptedMessage);
console.log("Decrypted:", plainText); // "Hello, World!"/encrypt-fileEncrypt File
Encrypt files up to 1GB. This is an async operation—you get a jobId to poll for completion.
async function encryptFile(file, recipientSecretId) {
// 1. Get block number and connect wallet
const health = await fetch(`${BASE_URL}/healthz`).then(r => r.json());
const blockNumber = health.chainStatus[CHAIN_ID].lastKnownBlockNumber;
const provider = new ethers.BrowserProvider(window.ethereum);
const signer = await provider.getSigner();
const walletAddress = await signer.getAddress();
// 2. Build data string (no plainText for files)
const dataString = `${CHAIN_ID}_${recipientSecretId}_${walletAddress}_${blockNumber}`;
const signature = await signer.signMessage(dataString);
// 3. Upload file
const formData = new FormData();
formData.append("file", file);
formData.append("secretId", recipientSecretId.toString());
formData.append("data", dataString);
formData.append("signature", signature);
const response = await fetch(`${BASE_URL}/encrypt-file`, {
method: "POST",
body: formData
});
const { jobId } = await response.json();
// 4. Poll for completion
while (true) {
const status = await fetch(`${BASE_URL}/jobs/${jobId}/status`).then(r => r.json());
if (status.status === "completed") {
// 5. Download encrypted file (no auth needed for encrypt results)
const downloadResponse = await fetch(`${BASE_URL}/jobs/${jobId}/download`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({})
});
return await downloadResponse.blob();
}
if (status.status === "failed") throw new Error(status.error);
await new Promise(r => setTimeout(r, 2000)); // Wait 2s
}
}/decrypt-fileDecrypt File
Decrypt a .cifer file. Async operation with authenticated download. Unlike encrypt, downloading decrypt results requires authentication.
async function decryptFile(ciferFile, secretId) {
// 1. Get block number and connect wallet
const health = await fetch(`${BASE_URL}/healthz`).then(r => r.json());
const blockNumber = health.chainStatus[CHAIN_ID].lastKnownBlockNumber;
const provider = new ethers.BrowserProvider(window.ethereum);
const signer = await provider.getSigner();
const walletAddress = await signer.getAddress();
// 2. Build data string
const dataString = `${CHAIN_ID}_${secretId}_${walletAddress}_${blockNumber}`;
const signature = await signer.signMessage(dataString);
// 3. Upload .cifer file
const formData = new FormData();
formData.append("file", ciferFile);
formData.append("data", dataString);
formData.append("signature", signature);
const response = await fetch(`${BASE_URL}/decrypt-file`, {
method: "POST",
body: formData
});
const { jobId } = await response.json();
// 4. Poll for completion
while (true) {
const status = await fetch(`${BASE_URL}/jobs/${jobId}/status`).then(r => r.json());
if (status.status === "completed") {
// 5. Download decrypted file (auth required!)
const downloadDataString = `${CHAIN_ID}_${secretId}_${walletAddress}_${blockNumber}_${jobId}_download`;
const downloadSignature = await signer.signMessage(downloadDataString);
const downloadResponse = await fetch(`${BASE_URL}/jobs/${jobId}/download`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
data: downloadDataString,
signature: downloadSignature
})
});
return await downloadResponse.blob();
}
if (status.status === "failed") throw new Error(status.error);
await new Promise(r => setTimeout(r, 2000));
}
}Data String Formats
chainId_secretId_signer_blockNumber_plainTextchainId_secretId_signer_blockNumber_encryptedMessagechainId_secretId_signer_blockNumberchainId_secretId_signer_blockNumber_jobId_downloadchainId_secretId_signer_blockNumber_jobId_deleteFrequently Asked Questions
How do I authenticate requests?
Sign a data string with your wallet using EIP-191 (personal_sign). The signature proves you own the wallet and authorizes the operation. No API keys needed.
What's the data string format?
For encryption: chainId_secretId_signer_blockNumber_plainText. For decryption: chainId_secretId_signer_blockNumber_encryptedMessage. The block number prevents replay attacks.
Who can decrypt my data?
Only the secret owner or an authorized delegate can decrypt. Authorization is controlled on-chain via the Secrets Controller contract.
What's the difference between payload and file encryption?
Payload encryption is synchronous and instant (up to 10KB). File encryption is async—you upload the file, get a jobId, poll for completion, then download the result.