For Developers

Encryption API

Post-quantum encryption via simple REST endpoints. Sign your request, call the API, get encrypted data back. No SDKs, no key management, no complexity.

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

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.

GET/healthz

Health Check

Get the current block number needed for signatures. Call this before every encrypt/decrypt request.

healthCheck.js
// 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
//     }
//   }
// }
POST/encrypt-payload

Encrypt 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.

encryptPayload.js
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);
POST/decrypt-payload

Decrypt Payload

Decrypt a payload that was encrypted to your secret. Only the secret owner or delegate can decrypt.

decryptPayload.js
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!"
POST/encrypt-file

Encrypt File

Encrypt files up to 1GB. This is an async operation—you get a jobId to poll for completion.

encryptFile.js
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
  }
}
POST/decrypt-file

Decrypt File

Decrypt a .cifer file. Async operation with authenticated download. Unlike encrypt, downloading decrypt results requires authentication.

decryptFile.js
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

Encrypt PayloadchainId_secretId_signer_blockNumber_plainText
Decrypt PayloadchainId_secretId_signer_blockNumber_encryptedMessage
Encrypt/Decrypt FilechainId_secretId_signer_blockNumber
Download Decrypt ResultchainId_secretId_signer_blockNumber_jobId_download
Delete JobchainId_secretId_signer_blockNumber_jobId_delete

Frequently 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.

What You Get

Post-quantum encryption (ML-KEM-768 + AES-256-GCM)
Zero key management—keys stay in secure enclaves
Payload encryption up to 10KB (instant)
File encryption up to 1GB (async)
Wallet-based authentication (EIP-191)
Owner/delegate access control
Hex or Base64 output formats
Job status polling and cleanup

Ready to Start Building?

Try the playground to test the API, or contact us for integration support.