CertiSigma SDKs

Official clients for Python and JavaScript/TypeScript. Attest, verify, and encrypt with a single function call.

PyPI version npm version API Reference License: MIT
pip pip install certisigma
npm npm install @certisigma/sdk

How It Works

CertiSigma provides three-tier cryptographic attestation for any SHA-256 hash:

  1. T0 — ECDSA Signature — Immediate. The server signs your hash with a P-256 key upon receipt. POST /attest
  2. T1 — TSA Timestamp — Within minutes. Hashes are batched into a Merkle tree and submitted to an RFC 3161 Time Stamping Authority. GET /verify/{hash}
  3. T2 — Bitcoin Anchor — Within hours. The Merkle root is anchored to the Bitcoin blockchain via OpenTimestamps, providing immutable proof. GET /attestation/{id}/evidence

The SDK handles hashing, request signing, error handling, and optional client-side encryption. You send a hash (or a file), the SDK does the rest.

Installation

Sync and async clients built on httpx. Python 3.10+.

bash
pip install certisigma

For client-side encryption support (AES-256-GCM):

bash
pip install certisigma[crypto]

Authentication

Pass your API key to the client constructor. The SDK sends it as a Bearer token on every request.

python
import os
from certisigma import CertiSigmaClient

client = CertiSigmaClient(api_key=os.environ["CERTISIGMA_API_KEY"])
Key formats: cs_live_... for production, cs_demo_... for sandbox. Never hard-code keys — use environment variables or a secrets manager.

If your admin has configured an IP Allowlist on your key, requests from non-whitelisted IPs will receive 403 Forbidden. See IP Allowlist docs.

Quick Start

python
from certisigma import CertiSigmaClient, hash_file, hash_bytes

client = CertiSigmaClient(api_key="cs_live_your_key_here")

# 1. Compute the SHA-256 hash of your file
file_hash = hash_file("contract.pdf")
print(f"SHA-256: {file_hash}")  # 64-char lowercase hex

# 2. Attest — creates a timestamped, signed proof of existence
result = client.attest(file_hash, source="my-app")
print(f"Attestation: {result.id} at {result.timestamp}")
print(f"ECDSA signature: {result.signature}")

# 3. Verify — confirm the hash was attested
check = client.verify(file_hash)
print(f"Exists: {check.exists}, Level: {check.level}")

# Or hash raw bytes directly
data_hash = hash_bytes(b"any raw content")
result2 = client.attest(data_hash)

Async Support

Use AsyncCertiSigmaClient for non-blocking I/O in asyncio applications.

python
import asyncio
from certisigma import AsyncCertiSigmaClient, hash_file

async def main():
    # hash_file() is sync (CPU-bound) — call before entering async context
    file_hash = hash_file("contract.pdf")

    async with AsyncCertiSigmaClient(api_key="cs_live_xxx") as client:
        result = await client.attest(file_hash, source="async-pipeline")
        print(f"{result.id}{result.hash_hex}")

        check = await client.verify(file_hash)
        print(f"Exists: {check.exists}")

asyncio.run(main())

Recommended Attestation Workflow

A production integration typically follows this pattern:

python
import json, sqlite3
from certisigma import CertiSigmaClient, hash_file

client = CertiSigmaClient(api_key="cs_live_xxx")

# 1. Hash the file locally (SHA-256, streamed in 8 KiB chunks)
hash_hex = hash_file("contract.pdf")

# 2. Attest
result = client.attest(hash_hex, source="contract-pipeline")

# 3. Save to your local database
db = sqlite3.connect("attestations.db")
db.execute("""INSERT INTO attestations
    (id, hash_hex, timestamp, signature, source, file_path)
    VALUES (?, ?, ?, ?, ?, ?)""",
    (result.id, result.hash_hex, result.timestamp,
     result.signature, "contract-pipeline", "contract.pdf"))
db.commit()

# 4. Later — download the full cryptographic evidence
evidence = client.get_evidence(result.id)
print(f"Level: {evidence.level}")  # T0, T1, or T2

# 5. Save evidence as a local manifest (include T2 when available)
with open("contract.pdf.certisigma.json", "w") as f:
    json.dump({
        "id": result.id,
        "hash": result.hash_hex,
        "timestamp": result.timestamp,
        "signature": result.signature,
        "level": evidence.level,
        "t1": evidence.t1,
        "t2": evidence.t2,
    }, f, indent=2)

Attest Response Fields

FieldTypeDescription
idstrAttestation ID (att_...)
hash_hexstrSHA-256 hash that was attested
timestampstrISO 8601 creation time
signaturestrBase64 ECDSA P-256 signature
statusstrcreated (new) or existing (already attested)
etagstrETag for optimistic concurrency on metadata updates
claim_idintYour API key's claim ID for this attestation
sourcestrSource label you provided
extra_datadictMetadata you attached

What to Save After Attestation

Always persist the attestation result in your own database. Recommended SQLite schema:

sql
CREATE TABLE IF NOT EXISTS attestations (
    id            TEXT PRIMARY KEY,
    hash_hex      TEXT NOT NULL,
    timestamp     TEXT NOT NULL,
    signature     TEXT NOT NULL,
    source        TEXT,
    original_file TEXT,
    level         TEXT DEFAULT 'T0',
    created_at    TEXT DEFAULT CURRENT_TIMESTAMP
);
Local manifest: Save a .certisigma.json file alongside each attested file. This creates an offline-verifiable record without querying the API.

Batch Operations

Attest or verify up to 100 hashes in a single API call. Efficient for bulk workloads.

python
from certisigma import hash_file
import glob

# Compute hashes for all invoices
files = glob.glob("invoices/*.pdf")
hashes = [hash_file(f) for f in files]

# Attest up to 100 hashes in one call
batch = client.batch_attest(hashes, source="monthly-invoices")
print(f"Created: {batch.created}, Existing: {batch.existing}")

# Later — verify the same files haven't been tampered with
current_hashes = [hash_file(f) for f in files]
results = client.batch_verify(current_hashes)
print(f"Found: {results.found}/{results.count}")

File Attestation

Hash any local file and attest the SHA-256 in one call.

python
result = client.attest_file("/path/to/document.pdf", source="uploads")
print(f"Attested: {result.hash_hex}")

Hashing Utilities

Standalone SHA-256 functions to compute hashes without attestation. Useful for pre-computing hashes, local verification, batch preparation, or audit workflows.

python
from certisigma import hash_file, hash_bytes

# Hash a file (streamed in 8 KiB chunks — constant memory)
file_hash = hash_file("/path/to/document.pdf")
print(f"SHA-256: {file_hash}")  # 64-char hex

# Hash raw bytes
data_hash = hash_bytes(b"raw content")

# Verify a file against a known hash
assert hash_file("/path/to/document.pdf") == file_hash

# Pre-compute hashes for batch attestation
hashes = [hash_file(p) for p in file_paths]
batch = client.batch_attest(hashes, source="bulk-ingest")
FunctionInputReturns
hash_file(path)str | Path64-char hex SHA-256
hash_bytes(data)bytes | bytearray | memoryview64-char hex SHA-256

Metadata and Claims

Each API key owns its own claim on an attestation. Claims hold source (a label) and extra_data (a JSON object). Multiple API keys can independently claim the same hash.

python
# Update claim metadata
result = client.update_metadata(
    "att_1234",
    source="pipeline-v2",
    extra_data={"project": "alpha", "version": "2.0"}
)

# Soft-delete claim
client.delete_metadata("att_1234")

# Get full cryptographic evidence
evidence = client.get_evidence("att_1234")
print(evidence.level)
Optimistic concurrency: The API returns an ETag with every attestation. When updating metadata via PATCH /attestation/{id}/metadata, send If-Match: <etag> to prevent conflicting updates. On mismatch, the API returns 412 Precondition Failed.

Client-Side Encryption (Zero Knowledge)

Encrypt metadata with AES-256-GCM before sending. The server never sees plaintext — true zero-knowledge storage. Requires pip install certisigma[crypto].

python
from certisigma.crypto import generate_key, encrypt_metadata, decrypt_metadata

# Generate a key (store securely — server never sees it)
key = generate_key()

# Encrypt before sending
encrypted = encrypt_metadata({"secret": "classified"}, key)
result = client.attest(hash_hex, extra_data=encrypted, client_encrypted=True)

# Decrypt after retrieving
check = client.verify(hash_hex)
plaintext = decrypt_metadata(check.extra_data, key)

Available crypto functions: generate_key(), encrypt_metadata(), decrypt_metadata(), is_encrypted(), rotate_key().

Key management: Store encryption keys in a dedicated secrets manager (Vault, AWS KMS, etc.). If you lose the key, the encrypted data is irrecoverable — the server cannot help.

Verification Examples

python
# Verify a single hash
check = client.verify("e3b0c44...")
if check.exists:
    print(f"Attested at {check.timestamp}, Level: {check.level}")

# Verify from a saved manifest
import json
with open("contract.pdf.certisigma.json") as f:
    manifest = json.load(f)
check = client.verify(manifest["hash"])
print(f"Level: {check.level}")

# Batch verify (up to 100)
results = client.batch_verify(["aabb...", "ccdd..."])
for item in results.results:
    print(f"{item.hash_hex}: {item.exists}")

Public Verification (No API Key)

Verification endpoints are public — anyone can verify an attestation without an API key. This enables third-party audits, compliance checks, and independent verification without sharing credentials.

python
from certisigma import CertiSigmaClient, hash_file

# No api_key needed — public verification
client = CertiSigmaClient()

file_hash = hash_file("contract.pdf")
check = client.verify(file_hash)
print(f"Exists: {check.exists}, Level: {check.level}")

# Batch verify also works without a key
results = client.batch_verify([file_hash])
print(f"Found: {results.found}/{results.count}")

Only verify, batch_verify, and health work without a key. Calling attest, batch_attest, update_metadata, or delete_metadata without an api_key raises AuthenticationError immediately — the request is never sent.

Rate Limits

The API enforces per-key rate limits using a sliding window:

LimitDefaultNotes
Requests / minute1,000Configurable per key by admin
Monthly quotaUnlimitedOptional, per plan
Batch max size100Hashes per batch call

When rate-limited, the API returns 429 Too Many Requests with these headers:

  • Retry-After: 60
  • X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset

The SDK automatically raises RateLimitError with a retry_after property.

python
import time
from certisigma import RateLimitError

def attest_with_backoff(client, hash_hex, max_retries=3):
    for attempt in range(max_retries):
        try:
            return client.attest(hash_hex)
        except RateLimitError as e:
            wait = e.retry_after * (2 ** attempt)
            time.sleep(wait)
    raise RuntimeError("Max retries exceeded")

Polling vs Webhooks

Attestations progress through levels asynchronously. Two patterns to track upgrades:

Polling (simple, low-volume)

python
import time

def wait_for_t1(client, att_id, timeout=600):
    start = time.time()
    while time.time() - start < timeout:
        status = client.status(att_id)
        if status.level in ("T1", "T2"):
            return status
        time.sleep(30)
    raise TimeoutError("T1 not reached")

Webhooks (production, real-time)

Register a webhook via the API (POST /webhook/register) to get notified when an attestation reaches T1 or T2.

EventWhenPayload includes
t1_completeTSA timestamp obtainedattestation_id, hash_hex, tsa_timestamp
t2_completeBitcoin anchor confirmedattestation_id, hash_hex, bitcoin_block, confirmed_at

Deliveries are signed with HMAC-SHA256. Verify the signature to ensure authenticity:

python
import hmac, hashlib

def verify_webhook(body_bytes: bytes, secret: str, signature_header: str) -> bool:
    """Verify CertiSigma webhook HMAC-SHA256 signature."""
    expected = "sha256=" + hmac.new(
        secret.encode(), body_bytes, hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(expected, signature_header)
Retry policy: Failed deliveries are retried 3 times with exponential backoff (1 min, 5 min, 15 min). Each delivery has a 10-second timeout. Use GET /webhook/{id}/deliveries to inspect delivery history.

Best practice: Use webhooks as the primary notification channel and polling as a fallback for missed events.

Evidence & OTS Verification

Once an attestation reaches T2, the Merkle root has been anchored to the Bitcoin blockchain via OpenTimestamps. Use get_evidence() to retrieve the full cryptographic bundle, then use the SDK helpers to inspect the Bitcoin block or save the raw .ots proof file.

Recommended Workflow

  1. Register a t2_complete webhook (or poll with status())
  2. When notified, call get_evidence(att_id)
  3. Save the updated manifest including T2 data
  4. Optionally save the .ots proof file for independent verification
python
from certisigma import CertiSigmaClient, get_blockchain_url, save_ots_proof
import json

client = CertiSigmaClient(api_key="cs_live_xxx")

# Retrieve the full evidence bundle
evidence = client.get_evidence("att_1234")
print(f"Level: {evidence.level}")  # "T0", "T1", or "T2"

if evidence.level == "T2":
    # Get the Bitcoin block explorer link
    block_url = get_blockchain_url(evidence)              # mempool.space/block/...
    tx_url    = get_blockchain_url(evidence, type="tx")  # mempool.space/tx/...
    print(f"Block: {block_url}")
    print(f"Tx:    {tx_url}")
    print(f"Height: {evidence.t2['bitcoinBlockHeight']}")

    # Save the raw .ots proof (for independent OTS verification)
    save_ots_proof(evidence, "contract.pdf.ots")

    # Update your local manifest
    with open("contract.pdf.certisigma.json") as f:
        manifest = json.load(f)
    manifest["level"] = evidence.level
    manifest["t2"] = evidence.t2
    with open("contract.pdf.certisigma.json", "w") as f:
        json.dump(manifest, f, indent=2)

Evidence Response Fields

FieldTypeDescription
hash_hexstrSHA-256 hash of the attested data
levelstrHighest confirmed level: T0, T1, or T2
t0 — ECDSA Signature (immediate)
t0.signaturestrBase64-encoded ECDSA P-256 signature
t0.algorithmstrECDSA-P256-SHA256
t0.publicKeyIdstrSigning key identifier for key pinning
t0.registeredAtstrISO 8601 timestamp of registration
t1 — TSA Timestamp + Merkle Proof (minutes)
t1.batchIdintT1 batch identifier
t1.merkleRootstrMerkle root hash of the batch
t1.merkleProoflistMerkle inclusion proof path (leaf → root)
t1.tsaProviderstrTSA provider (e.g. sectigo_qualified)
t1.tsaTimestampstrRFC 3161 timestamp (ISO 8601)
t1.tsaTokenstrBase64 TSA token for independent verification
t2 — Bitcoin Anchor via OpenTimestamps (hours)
t2.dailyRootstrDaily Merkle root anchored to Bitcoin
t2.t1ToT2ProoflistMerkle proof linking T1 batch root to T2 daily root
t2.bitcoinBlockHeightintBitcoin block number containing the anchor
t2.bitcoinBlockHashstrBitcoin block hash
t2.bitcoinTxidstrBitcoin transaction ID
t2.confirmedAtstrISO 8601 confirmation timestamp
t2.otsProofstrRaw OpenTimestamps proof (Base64-encoded binary)

SDK Helper Functions

FunctionInputReturns
get_blockchain_url(evidence)EvidenceResultmempool.space block URL, or None
get_blockchain_url(evidence, type="tx")EvidenceResultmempool.space transaction URL, or None
save_ots_proof(evidence, path)EvidenceResult, strTrue if saved, False if T2 not ready

Independent OTS Verification

The saved .ots file is a standard OpenTimestamps proof. Verify it independently — no CertiSigma involvement needed:

bash
# Install the OpenTimestamps client
pip install opentimestamps-client

# Verify the proof against the Bitcoin blockchain
ots verify contract.pdf.ots

# Or verify online at https://opentimestamps.org
Full chain of trust: Your hash → T0 ECDSA signature → T1 Merkle tree + TSA timestamp → T2 Bitcoin blockchain anchor. Each tier is independently verifiable. The .ots proof ties directly to a Bitcoin block — immutable, decentralized, permanent.
Blockchain explorer: Use get_blockchain_url(evidence) to get a direct link to mempool.space, where anyone can independently verify the Bitcoin block and transaction containing your anchor.

Error Handling

python
from certisigma import (
    CertiSigmaError,
    AuthenticationError,
    RateLimitError,
    QuotaExceededError,
)

try:
    client.attest(hash_hex)
except AuthenticationError:
    print("Invalid API key")
except RateLimitError as e:
    print(f"Rate limited, retry after {e.retry_after}s")
except QuotaExceededError:
    print("Monthly quota reached")
except CertiSigmaError as e:
    print(f"API error {e.status_code}: {e}")

Security Best Practices

  • Environment variables: Store API keys in CERTISIGMA_API_KEY, never in code.
  • Client-side encryption: Use encrypt_metadata() for sensitive data in extra_data.
  • Key rotation: Use rotate_key(envelope, old_key, new_key) to re-encrypt without decrypting on the server.
  • Signature verification: Verify ECDSA signatures independently for the highest assurance.
  • IP Allowlist: Restrict API key usage to known IP ranges.
  • Webhook secrets: Store the signing_secret in a secrets manager. It is shown only once.
  • Audit trail: Log all attestation IDs and hashes locally for independent auditing.

Configuration Reference

ParameterDefaultDescription
api_keyNoneBearer token (cs_live_... or cs_demo_...). Optional for verify/health.
base_urlhttps://api.certisigma.chAPI endpoint
timeout30.0Request timeout in seconds

Requirements

  • Python 3.10+
  • httpx >= 0.25.0
  • cryptography >= 44.0.0 (optional, for certisigma.crypto)

Package

PyPI   certisigma  —  MIT License — Ten Sigma Sagl

Changelog

v1.4.0

  • New: get_blockchain_url(evidence) — returns a mempool.space URL for the Bitcoin block or transaction
  • New: save_ots_proof(evidence, path) — saves the raw OpenTimestamps .ots proof to a local file

v1.3.0

  • New: api_key is now optional — public endpoints (verify, batch_verify, health) work without authentication
  • New: AuthenticationError raised immediately if auth-required method called without key (no request sent)

v1.2.0

  • New: standalone hash_file() and hash_bytes() utility functions
  • Fix: PyPI project URL now links to SDK documentation

v1.1.1 — Initial Public Release

  • Full API coverage: attest, verify, batch, file attestation, metadata, evidence, status, health
  • Sync (CertiSigmaClient) and async (AsyncCertiSigmaClient) clients
  • Client-side AES-256-GCM encryption with key rotation support
  • Typed error hierarchy: AuthenticationError, RateLimitError, QuotaExceededError
  • Full type annotations (py.typed marker)

How It Works

CertiSigma provides three-tier cryptographic attestation for any SHA-256 hash:

  1. T0 — ECDSA Signature — Immediate. The server signs your hash with a P-256 key upon receipt. POST /attest
  2. T1 — TSA Timestamp — Within minutes. Hashes are batched into a Merkle tree and submitted to an RFC 3161 Time Stamping Authority. GET /verify/{hash}
  3. T2 — Bitcoin Anchor — Within hours. The Merkle root is anchored to the Bitcoin blockchain via OpenTimestamps, providing immutable proof. GET /attestation/{id}/evidence

The SDK handles hashing, request signing, error handling, and optional client-side encryption. You send a hash (or a file), the SDK does the rest.

Installation

Zero dependencies. Works in Node.js 18+ and modern browsers (native fetch).

bash
npm install @certisigma/sdk

Full TypeScript definitions included (src/index.d.ts, src/crypto.d.ts).

Authentication

Pass your API key to the client constructor. The SDK sends it as a Bearer token on every request.

javascript
const { CertiSigmaClient } = require('@certisigma/sdk');

const client = new CertiSigmaClient({
  apiKey: process.env.CERTISIGMA_API_KEY,
});
Key formats: cs_live_... for production, cs_demo_... for sandbox. Never hard-code keys — use environment variables or a secrets manager.

If your admin has configured an IP Allowlist on your key, requests from non-whitelisted IPs will receive 403 Forbidden. See IP Allowlist docs.

Quick Start

javascript
const { CertiSigmaClient, hashFile, hashBytes } = require('@certisigma/sdk');
const fs = require('fs');

const client = new CertiSigmaClient({ apiKey: 'cs_live_your_key_here' });

// 1. Compute the SHA-256 hash of your file
const fileHash = await hashFile(fs.readFileSync('contract.pdf'));
console.log(`SHA-256: ${fileHash}`);  // 64-char lowercase hex

// 2. Attest — creates a timestamped, signed proof of existence
const result = await client.attest(fileHash, { source: 'my-app' });
console.log(`Attestation: ${result.id} at ${result.timestamp}`);

// 3. Verify — confirm the hash was attested
const check = await client.verify(fileHash);
console.log(`Exists: ${check.exists}, Level: ${check.level}`);

// Or hash raw bytes directly
const dataHash = await hashBytes(new TextEncoder().encode('any raw content'));
const result2 = await client.attest(dataHash);

Recommended Attestation Workflow

A production integration typically follows this pattern:

javascript
const { CertiSigmaClient, hashFile } = require('@certisigma/sdk');
const fs = require('fs');

const client = new CertiSigmaClient({ apiKey: process.env.CERTISIGMA_API_KEY });

// 1. Hash the file locally (SHA-256 via Web Crypto API)
const hashHex = await hashFile(fs.readFileSync('contract.pdf'));

// 2. Attest
const result = await client.attest(hashHex, { source: 'contract-pipeline' });

// 3. Save to a local JSON manifest
const manifest = {
  id: result.id,
  hash: result.hash_hex,
  timestamp: result.timestamp,
  signature: result.signature,
  source: 'contract-pipeline',
};
fs.writeFileSync('contract.pdf.certisigma.json', JSON.stringify(manifest, null, 2));

// 4. Later — download evidence (includes T2 when available)
const evidence = await client.getEvidence(result.id);
console.log(`Level: ${evidence.level}`);

// 5. Update manifest with evidence
manifest.level = evidence.level;
manifest.t1 = evidence.t1;
manifest.t2 = evidence.t2;
fs.writeFileSync('contract.pdf.certisigma.json', JSON.stringify(manifest, null, 2));

Attest Response Fields

FieldTypeDescription
idstringAttestation ID (att_...)
hash_hexstringSHA-256 hash that was attested
timestampstringISO 8601 creation time
signaturestringBase64 ECDSA P-256 signature
statusstringcreated (new) or existing (already attested)
etagstringETag for optimistic concurrency on metadata updates
claim_idnumberYour API key's claim ID for this attestation
sourcestringSource label you provided
extra_dataobjectMetadata you attached

What to Save After Attestation

Always persist the attestation result locally. Example JSON manifest pattern:

json
{
  "id": "att_a1b2c3d4e5f6",
  "hash": "e3b0c44298fc1c149afbf4c8996fb924...",
  "timestamp": "2025-06-15T10:30:00Z",
  "signature": "MEUCIQDx...",
  "source": "contract-pipeline",
  "original_file": "contract.pdf",
  "level": "T2",
  "t1": { "batchId": 42, "merkleRoot": "c4f3b2...", "tsaTimestamp": "2025-06-15T10:35:00Z" },
  "t2": {
    "bitcoinBlockHeight": 850123,
    "bitcoinBlockHash": "00000000000000000002a7c4...",
    "bitcoinTxid": "d4e5f6a7b8c9...",
    "confirmedAt": "2025-06-15T14:20:00Z"
  }
}
Local manifest: Save a .certisigma.json file alongside each attested file. This creates an offline-verifiable record without querying the API.

Batch Operations

Attest or verify up to 100 hashes in a single API call.

javascript
const { hashFile } = require('@certisigma/sdk');
const fs = require('fs');

// Compute hashes for all invoices
const files = fs.readdirSync('invoices/').filter(f => f.endsWith('.pdf'));
const hashes = await Promise.all(
  files.map(f => hashFile(fs.readFileSync(`invoices/${f}`)))
);

// Attest up to 100 hashes in one call
const batch = await client.batchAttest(hashes, { source: 'invoice-processor' });
console.log(`Created: ${batch.created}, Existing: ${batch.existing}`);

// Later — verify the same files haven't been tampered with
const currentHashes = await Promise.all(
  files.map(f => hashFile(fs.readFileSync(`invoices/${f}`)))
);
const results = await client.batchVerify(currentHashes);
console.log(`Found: ${results.found}/${results.count}`);

File Attestation

Hash a file client-side (via Web Crypto) and attest the SHA-256 in one call. Works with File, Blob, ArrayBuffer, or Uint8Array.

javascript
// Browser — from a file input
const file = document.getElementById('fileInput').files[0];
const result = await client.attestFile(file, { source: 'upload-form' });

// Node.js — from a buffer
const buf = fs.readFileSync('document.pdf');
const result2 = await client.attestFile(buf, { source: 'cli-tool' });

Hashing Utilities

Standalone SHA-256 functions to compute hashes without attestation. Available from the main package or @certisigma/sdk/hash.

javascript
const { hashFile, hashBytes } = require('@certisigma/sdk');
// or: const { hashFile, hashBytes } = require('@certisigma/sdk/hash');

// Hash a File, Blob, ArrayBuffer, or Uint8Array
const fileHash = await hashFile(fileInput.files[0]);
console.log(`SHA-256: ${fileHash}`);  // 64-char hex

// Hash raw bytes
const dataHash = await hashBytes(new TextEncoder().encode('raw content'));

// Verify a file against a known hash
const currentHash = await hashFile(fileInput.files[0]);
console.assert(currentHash === fileHash);

// Node.js — hash a local file for batch preparation
const fs = require('fs');
const hashes = await Promise.all(
  filePaths.map(p => hashBytes(fs.readFileSync(p)))
);
const batch = await client.batchAttest(hashes, { source: 'bulk-ingest' });
FunctionInputReturns
hashFile(input)File | Blob | ArrayBuffer | Uint8ArrayPromise<string> — 64-char hex
hashBytes(data)Uint8Array | ArrayBufferPromise<string> — 64-char hex

Metadata and Claims

Each API key owns its own claim on an attestation. Claims hold source (a label) and extraData (a JSON object). Multiple API keys can independently claim the same hash.

javascript
// Update claim metadata
await client.updateMetadata('att_1234', {
  source: 'pipeline-v2',
  extraData: { project: 'alpha', version: '2.0' },
});

// Soft-delete claim
await client.deleteMetadata('att_1234');

// Get full cryptographic evidence
const evidence = await client.getEvidence('att_1234');
Optimistic concurrency: The API returns an ETag with every attestation. When updating metadata via PATCH /attestation/{id}/metadata, send If-Match: <etag> to prevent conflicting updates. On mismatch, the API returns 412 Precondition Failed.

Client-Side Encryption (Zero Knowledge)

Encrypt metadata with AES-256-GCM before sending. The server never sees plaintext — true zero-knowledge storage. Uses the Web Crypto API.

javascript
const { generateKey, encryptMetadata, decryptMetadata } = require('@certisigma/sdk/crypto');

// Generate a key (store securely — server never sees it)
const key = await generateKey();

// Encrypt before sending
const encrypted = await encryptMetadata({ secret: 'classified' }, key);
await client.attest(hash, { extraData: encrypted, clientEncrypted: true });

// Decrypt after retrieving
const result = await client.verify(hash);
const plaintext = await decryptMetadata(result.extra_data, key);

Available crypto functions: generateKey(), encryptMetadata(), decryptMetadata(), isEncrypted(), rotateKey().

Key management: Store encryption keys in a dedicated secrets manager (Vault, AWS KMS, etc.). If you lose the key, the encrypted data is irrecoverable — the server cannot help.

Verification Examples

javascript
// Verify a single hash
const check = await client.verify('e3b0c44...');
if (check.exists) {
  console.log(`Attested at ${check.timestamp}, Level: ${check.level}`);
}

// Verify from a saved manifest
const manifest = JSON.parse(fs.readFileSync('contract.pdf.certisigma.json', 'utf8'));
const check2 = await client.verify(manifest.hash);
console.log(`Level: ${check2.level}`);

// Batch verify (up to 100)
const results = await client.batchVerify(['aabb...', 'ccdd...']);
results.results.forEach(r => console.log(`${r.hash_hex}: ${r.exists}`));

Public Verification (No API Key)

Verification endpoints are public — anyone can verify an attestation without an API key. This enables third-party audits, compliance checks, and independent verification without sharing credentials.

javascript
const { CertiSigmaClient, hashFile } = require('@certisigma/sdk');
const fs = require('fs');

// No apiKey needed — public verification
const client = new CertiSigmaClient();

const fileHash = await hashFile(fs.readFileSync('contract.pdf'));
const check = await client.verify(fileHash);
console.log(`Exists: ${check.exists}, Level: ${check.level}`);

// Batch verify also works without a key
const results = await client.batchVerify([fileHash]);
console.log(`Found: ${results.found}/${results.count}`);

Only verify, batchVerify, and health work without a key. Calling attest, batchAttest, updateMetadata, or deleteMetadata without an apiKey throws AuthenticationError immediately — the request is never sent.

Rate Limits

The API enforces per-key rate limits using a sliding window:

LimitDefaultNotes
Requests / minute1,000Configurable per key by admin
Monthly quotaUnlimitedOptional, per plan
Batch max size100Hashes per batch call

When rate-limited, the API returns 429 Too Many Requests with these headers:

  • Retry-After: 60
  • X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset

The SDK automatically throws RateLimitError with a retryAfter property.

javascript
const { RateLimitError } = require('@certisigma/sdk');

async function attestWithBackoff(client, hashHex, maxRetries = 3) {
  for (let i = 0; i < maxRetries; i++) {
    try {
      return await client.attest(hashHex);
    } catch (err) {
      if (err instanceof RateLimitError) {
        const wait = err.retryAfter * 1000 * (2 ** i);
        await new Promise(r => setTimeout(r, wait));
      } else throw err;
    }
  }
  throw new Error('Max retries exceeded');
}

Polling vs Webhooks

Attestations progress through levels asynchronously. Two patterns to track upgrades:

Polling (simple, low-volume)

javascript
async function waitForT1(client, attId, timeoutMs = 600000) {
  const start = Date.now();
  while (Date.now() - start < timeoutMs) {
    const s = await client.status(attId);
    if (s.level === 'T1' || s.level === 'T2') return s;
    await new Promise(r => setTimeout(r, 30000));
  }
  throw new Error('T1 not reached within timeout');
}

Webhooks (production, real-time)

Register a webhook via the API (POST /webhook/register) to get notified when an attestation reaches T1 or T2.

EventWhenPayload includes
t1_completeTSA timestamp obtainedattestation_id, hash_hex, tsa_timestamp
t2_completeBitcoin anchor confirmedattestation_id, hash_hex, bitcoin_block, confirmed_at

Deliveries are signed with HMAC-SHA256. Verify the signature to ensure authenticity:

javascript
const crypto = require('crypto');

function verifyWebhook(bodyBuffer, secret, signatureHeader) {
  const expected = 'sha256=' + crypto
    .createHmac('sha256', secret)
    .update(bodyBuffer)
    .digest('hex');
  return crypto.timingSafeEqual(
    Buffer.from(expected),
    Buffer.from(signatureHeader)
  );
}
Retry policy: Failed deliveries are retried 3 times with exponential backoff (1 min, 5 min, 15 min). Each delivery has a 10-second timeout. Use GET /webhook/{id}/deliveries to inspect delivery history.

Best practice: Use webhooks as the primary notification channel and polling as a fallback for missed events.

Evidence & OTS Verification

Once an attestation reaches T2, the Merkle root has been anchored to the Bitcoin blockchain via OpenTimestamps. Use getEvidence() to retrieve the full cryptographic bundle, then use the SDK helpers to inspect the Bitcoin block or save the raw .ots proof file.

Recommended Workflow

  1. Register a t2_complete webhook (or poll with status())
  2. When notified, call getEvidence(attId)
  3. Save the updated manifest including T2 data
  4. Optionally save the .ots proof file for independent verification
javascript
const { CertiSigmaClient, getBlockchainUrl, saveOtsProof } = require('@certisigma/sdk');
const fs = require('fs');

const client = new CertiSigmaClient({ apiKey: process.env.CERTISIGMA_API_KEY });

// Retrieve the full evidence bundle
const evidence = await client.getEvidence('att_1234');
console.log(`Level: ${evidence.level}`);  // "T0", "T1", or "T2"

if (evidence.level === 'T2') {
  // Get the Bitcoin block explorer link
  const blockUrl = getBlockchainUrl(evidence);              // mempool.space/block/...
  const txUrl    = getBlockchainUrl(evidence, 'tx');      // mempool.space/tx/...
  console.log(`Block: ${blockUrl}`);
  console.log(`Tx:    ${txUrl}`);
  console.log(`Height: ${evidence.t2.bitcoinBlockHeight}`);

  // Save the raw .ots proof (for independent OTS verification)
  saveOtsProof(evidence, 'contract.pdf.ots');

  // Update your local manifest
  const manifest = JSON.parse(fs.readFileSync('contract.pdf.certisigma.json', 'utf8'));
  manifest.level = evidence.level;
  manifest.t2 = evidence.t2;
  fs.writeFileSync('contract.pdf.certisigma.json', JSON.stringify(manifest, null, 2));
}

Evidence Response Fields

FieldTypeDescription
hash_hexstringSHA-256 hash of the attested data
levelstringHighest confirmed level: T0, T1, or T2
t0 — ECDSA Signature (immediate)
t0.signaturestringBase64-encoded ECDSA P-256 signature
t0.algorithmstringECDSA-P256-SHA256
t0.publicKeyIdstringSigning key identifier for key pinning
t0.registeredAtstringISO 8601 timestamp of registration
t1 — TSA Timestamp + Merkle Proof (minutes)
t1.batchIdnumberT1 batch identifier
t1.merkleRootstringMerkle root hash of the batch
t1.merkleProofArrayMerkle inclusion proof path (leaf → root)
t1.tsaProviderstringTSA provider (e.g. sectigo_qualified)
t1.tsaTimestampstringRFC 3161 timestamp (ISO 8601)
t1.tsaTokenstringBase64 TSA token for independent verification
t2 — Bitcoin Anchor via OpenTimestamps (hours)
t2.dailyRootstringDaily Merkle root anchored to Bitcoin
t2.t1ToT2ProofArrayMerkle proof linking T1 batch root to T2 daily root
t2.bitcoinBlockHeightnumberBitcoin block number containing the anchor
t2.bitcoinBlockHashstringBitcoin block hash
t2.bitcoinTxidstringBitcoin transaction ID
t2.confirmedAtstringISO 8601 confirmation timestamp
t2.otsProofstringRaw OpenTimestamps proof (Base64-encoded binary)

SDK Helper Functions

FunctionInputReturns
getBlockchainUrl(evidence)EvidenceResultmempool.space block URL, or null
getBlockchainUrl(evidence, 'tx')EvidenceResultmempool.space transaction URL, or null
saveOtsProof(evidence, path)EvidenceResult, stringtrue if saved, false if T2 not ready

Independent OTS Verification

The saved .ots file is a standard OpenTimestamps proof. Verify it independently — no CertiSigma involvement needed:

bash
# Install the OpenTimestamps client
pip install opentimestamps-client

# Verify the proof against the Bitcoin blockchain
ots verify contract.pdf.ots

# Or verify online at https://opentimestamps.org
Full chain of trust: Your hash → T0 ECDSA signature → T1 Merkle tree + TSA timestamp → T2 Bitcoin blockchain anchor. Each tier is independently verifiable. The .ots proof ties directly to a Bitcoin block — immutable, decentralized, permanent.
Blockchain explorer: Use getBlockchainUrl(evidence) to get a direct link to mempool.space, where anyone can independently verify the Bitcoin block and transaction containing your anchor.

Error Handling

javascript
const { AuthenticationError, RateLimitError, QuotaExceededError } = require('@certisigma/sdk');

try {
  await client.attest(hash);
} catch (err) {
  if (err instanceof AuthenticationError) {
    console.error('Invalid API key');
  } else if (err instanceof RateLimitError) {
    console.error(`Rate limited, retry after ${err.retryAfter}s`);
  } else if (err instanceof QuotaExceededError) {
    console.error('Monthly quota reached');
  }
}

Security Best Practices

  • Environment variables: Store API keys in CERTISIGMA_API_KEY, never in source code.
  • Client-side encryption: Use encryptMetadata() for sensitive data in extraData.
  • Key rotation: Use rotateKey(envelope, oldKey, newKey) to re-encrypt without decrypting on the server.
  • Signature verification: Verify ECDSA signatures independently for the highest assurance.
  • IP Allowlist: Restrict API key usage to known IP ranges.
  • Webhook secrets: Store the signing_secret in a secrets manager. It is shown only once.
  • Audit trail: Log all attestation IDs and hashes locally for independent auditing.

Configuration Reference

ParameterDefaultDescription
apiKeyundefinedBearer token (cs_live_... or cs_demo_...). Optional for verify/health.
baseUrlhttps://api.certisigma.chAPI endpoint
timeout30000Request timeout in ms

Requirements

  • Node.js 18+ or any modern browser with fetch support
  • Zero external dependencies

Package

npm   @certisigma/sdk  —  MIT License — Ten Sigma Sagl

Changelog

v1.4.0

  • New: getBlockchainUrl(evidence) — returns a mempool.space URL for the Bitcoin block or transaction
  • New: saveOtsProof(evidence, path) — saves the raw OpenTimestamps .ots proof to a local file

v1.3.0

  • New: apiKey is now optional — public endpoints (verify, batchVerify, health) work without authentication
  • New: AuthenticationError thrown immediately if auth-required method called without key (no request sent)

v1.2.0

  • New: standalone hashFile() and hashBytes() utility functions
  • New: @certisigma/sdk/hash subpath export

v1.1.1 — Initial Public Release

  • Full API coverage: attest, verify, batch, file attestation, metadata, evidence, status, health
  • Client-side AES-256-GCM encryption via Web Crypto API with key rotation
  • Typed error hierarchy: AuthenticationError, RateLimitError, QuotaExceededError
  • Full TypeScript definitions (index.d.ts, crypto.d.ts)
  • Works in Node.js 18+ and modern browsers — zero dependencies