CertiSigma Python SDK
Official Python client for the CertiSigma cryptographic attestation API.
Installation
pip install certisigma
Or from source:
cd sdk/python
pip install -e .
Quick Start
import os
from certisigma import CertiSigmaClient, hash_file, hash_bytes, hash_string
client = CertiSigmaClient(api_key=os.environ["CERTISIGMA_API_KEY"])
# 1. Compute the SHA-256 hash of your file
file_hash = hash_file("contract.pdf")
# 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
data_hash = hash_bytes(b"any raw content")
result = client.attest(data_hash)
Public Verification (No API Key)
Verification endpoints are public. You can verify attestations without any API key:
from certisigma import CertiSigmaClient, hash_file
# No api_key needed — works out of the box
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}")
Hashing Utilities
Standalone SHA-256 hash functions -- compute hashes without attestation:
from certisigma import hash_file, hash_bytes, hash_string
# Hash a file (streamed, constant memory)
file_hash = hash_file("/path/to/document.pdf")
# Hash raw bytes
data_hash = hash_bytes(b"raw content")
# Hash a string (UTF-8, no file needed)
str_hash = hash_string("SN-2026-001234")
Or hash + attest in one step:
result = client.attest_file("/path/to/document.pdf")
print(f"Attested: {result.hash_hex}")
String Attestation
Attest identifiers (serial numbers, license keys, batch codes) or any non-file content without temporary files. The string is hashed client-side (UTF-8); only the hash reaches the API:
# One-step: hash + attest
result = client.attest_string("SN-2026-001234", source="serial-registry")
# Verify later
check = client.verify_string("SN-2026-001234")
assert check.exists
# Also works for files
check = client.verify_file("/path/to/document.pdf")
Canonical form: UTF-8, no BOM, no trailing newline, no normalization.
hash_string("SN-001")andhash_string("SN-001\n")produce different hashes.Cross-SDK determinism:
hash_string()in Python andhashString()in JavaScript produce identical hashes for the same input string.Note:
sourceis never auto-populated from the string content. Pass it explicitly if needed — otherwise the string would leak into server-visible metadata.
Async Support
import asyncio, os
from certisigma import AsyncCertiSigmaClient
async def main():
async with AsyncCertiSigmaClient(api_key=os.environ["CERTISIGMA_API_KEY"]) as client:
result = await client.attest("abcdef..." * 4 + "0" * 16)
print(result.id)
asyncio.run(main())
Batch Operations
# Attest up to 100 hashes in one call — returns full claim metadata per item
batch = client.batch_attest(
["aabb..." * 4, "ccdd..." * 4],
source="monthly-invoices"
)
print(f"Created: {batch.created}, Existing: {batch.existing}")
for att in batch.attestations:
print(f" {att['id']} claim={att['claim_id']} src={att['source']}")
# Verify batch (public, no key needed)
results = client.batch_verify(["aabb..." * 4, "ccdd..." * 4])
print(f"Found: {results.found}/{results.count}")
# Detailed mode — certification level + claim metadata (requires api_key)
results = client.batch_verify(["aabb..." * 4], detailed=True)
for r in results.results:
if r["exists"]:
print(f" {r['id']} level={r['level']} src={r['source']}")
Attestation Status
Check the current trust tier of an attestation (public, no API key needed):
status = client.status("att_X7aQ9vL2mN5pR8sT1uW4yZ0K")
print(f"Level: {status.level}") # "T0", "T1", or "T2"
print(f"Signature: {status.signature_available}")
print(f"Merkle: {status.merkle_proof_available}")
print(f"OTS: {status.ots_available}")
| Method | Input | Auth | Best for |
|---|---|---|---|
verify(hash) |
SHA-256 hash | Optional | Compliance checks, "does this hash exist?" |
status(att_id) |
Attestation ID | No | Dashboards, progress tracking, polling |
get_evidence(att_id) |
Attestation ID | No | Independent verification, long-term archival |
Attestation IDs are opaque public strings. New IDs use att_ plus 24 Base64URL
characters; keep and pass the full string returned by the API. Legacy numeric
IDs remain accepted only for historical attestations.
Metadata Management
# Update claim metadata
result = client.update_metadata("att_X7aQ9vL2mN5pR8sT1uW4yZ0K", source="pipeline-v2", extra_data={"project": "alpha"})
# Soft-delete claim
client.delete_metadata("att_X7aQ9vL2mN5pR8sT1uW4yZ0K")
# Get evidence
evidence = client.get_evidence("att_X7aQ9vL2mN5pR8sT1uW4yZ0K")
print(evidence.level, evidence.t0)
Evidence & OTS Verification
import os
from certisigma import CertiSigmaClient, get_blockchain_url, save_ots_proof
client = CertiSigmaClient(api_key=os.environ["CERTISIGMA_API_KEY"])
# Get full cryptographic evidence (T0 + T1 + T2)
evidence = client.get_evidence("att_X7aQ9vL2mN5pR8sT1uW4yZ0K")
if evidence.level == "T2":
# Bitcoin block explorer link
print(get_blockchain_url(evidence)) # mempool.space/block/...
print(get_blockchain_url(evidence, "tx")) # mempool.space/tx/...
# Save the raw .ots proof for independent verification
save_ots_proof(evidence, "contract.pdf.ots")
# Then verify with: ots verify contract.pdf.ots
Client-Side Encryption (Zero Knowledge)
Requires: pip install certisigma[crypto]
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
plaintext = decrypt_metadata(result.extra_data, key)
Error Handling
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}")
Configuration
| Parameter | Default | Description |
|---|---|---|
api_key |
None |
Bearer token (cs_live_...). Optional for verify/health. |
base_url |
https://api.certisigma.ch |
API endpoint |
timeout |
30.0 |
Request timeout in seconds |
Proxy: httpx respects HTTP_PROXY / HTTPS_PROXY / NO_PROXY environment variables.
Custom TLS CA: Configure httpx with verify="/path/to/ca-bundle.pem" for corporate proxies.
Retry: Not built-in. Implement at caller level — retry only on 429 and 5xx, fail fast on other 4xx.
Share Tokens (Forensic Metadata Sharing)
Create time-limited, auditable tokens for sharing attestation metadata with forensic analysts:
token = client.create_share_token(
attestation_ids=["att_X7aQ9vL2mN5pR8sT1uW4yZ0K"],
expires_in=86400,
recipient_label="forensic-analyst",
max_uses=10,
)
print(f"Share token: {token.share_token}") # shown once, save it
tokens = client.list_share_tokens()
info = client.get_share_token_info(token.id)
client.revoke_share_token(token.id)
Structured Tagging
Multi-dimensional classification of attestations with server-side querying:
# Upsert tags (max 50 per attestation per key)
client.put_tags("att_X7aQ9vL2mN5pR8sT1uW4yZ0K", tags=[
{"key": "department", "value": "hr"},
{"key": "classification", "value": "confidential"},
])
tags = client.get_tags("att_X7aQ9vL2mN5pR8sT1uW4yZ0K")
# Query by tags — AND semantics, max 10 conditions per query
results = client.query_tags(
filter={"and": [
{"key": "department", "value": "hr"},
{"key": "classification", "value": "confidential"},
]},
limit=100,
)
print(f"Found {results.count} matching attestations")
client.delete_tag("att_X7aQ9vL2mN5pR8sT1uW4yZ0K", "classification")
Tag key format: ^[a-z][a-z0-9_-]{0,62}$ (lowercase, no _ prefix — reserved). Client-encrypted tags (value_enc + value_nonce hex) are supported but excluded from server-side query.
Census — Derived Lists
Create opaque HMAC-SHA256 derived lists for third-party hash verification without revealing your inventory. Requires census scope and org_id.
import hashlib, hmac
# Create a derived list
dl = client.create_derived_list(
hashes=["a665a459...", "b4c9a289..."],
label="Partner audit Q1",
expires_in_hours=720,
)
print(f"List key (save now!): {dl.list_key}")
# Third party: match files (no API key needed)
public = CertiSigmaClient()
file_hash = "a665a459..."
derived = hmac.new(
bytes.fromhex(dl.list_key), file_hash.encode(), hashlib.sha256,
).hexdigest()
result = public.match_derived_list(dl.id, dl.list_key, [derived])
print(f"Matched: {result.matched}/{result.total}")
# Owner operations
lists = client.list_derived_lists()
detail = client.get_derived_list(dl.id)
sig = client.get_derived_list_signature(dl.id)
log = client.get_derived_list_access_log(dl.id)
client.revoke_derived_list(dl.id)
Webhooks (T1 / T2 lifecycle)
Requires an API key with the webhook scope. Events: t1_complete, t2_complete. The backend validates URLs (HTTPS in production; SSRF protections apply server-side).
import os
from certisigma import CertiSigmaClient, verify_webhook_signature
client = CertiSigmaClient(api_key=os.environ["CERTISIGMA_API_KEY"])
# Register — signing_secret is shown once; store it securely (env/secret manager)
wh = client.register_webhook(
url="https://your-server.example.com/certisigma-hook",
events=["t1_complete", "t2_complete"],
label="census-prod", # optional
)
print(wh.id, wh.signing_secret) # never log signing_secret
# List / delete / delivery history
all_hooks = client.list_webhooks()
client.delete_webhook(wh.id)
history = client.list_webhook_deliveries(wh.id)
# In your HTTP handler: verify raw body bytes against X-CertiSigma-Signature
# is_ok = verify_webhook_signature(request.body(), signature_header, signing_secret)
verify_webhook_signature uses hmac.compare_digest (constant-time). Pass raw request body as bytes, not a parsed JSON string, or verification will fail.
The public API does not accept a custom signing secret on register_webhook (omit secret or use null in raw JSON). The server always returns a random signing_secret once per registration.
AsyncCertiSigmaClient exposes the same four methods (register_webhook, list_webhooks, delete_webhook, list_webhook_deliveries).
Read Metadata
Explicit metadata read without re-verifying:
meta = client.get_metadata("att_X7aQ9vL2mN5pR8sT1uW4yZ0K")
print(f"Source: {meta.source}, Extra: {meta.extra_data}")
Security & Privacy
CertiSigma is a public attestation platform. Cryptographic proofs (hash, signature, Merkle, OTS) are intentionally public and verifiable by any party. Organizational metadata (source, extra_data, tags) is never exposed on public endpoints — only the authenticated API key owner can read their own claim data. Tags are scoped per API key with full tenant isolation. Share tokens provide time-limited, auditable, revocable read-only access to specific attestations — all accesses are logged. For sensitive metadata, use client-side encryption (encrypt_metadata()); for sensitive tag values, use client-side encrypted tags (value_enc + value_nonce).
See the Security & Privacy Model in the full documentation for the complete threat model and data boundary.
Test Vectors
Canonical test vectors with real production data are available for independent T0/T1/T2 verification:
Compatibility
- Follows Semantic Versioning 2.0.0.
- SDK v1.x targets API v1 (
/v1/prefix). Breaking API changes get a new prefix with 12-month deprecation window. - See full SDK documentation for error codes, webhook semantics, T0 signature format, and compatibility policy.
Requirements
- Python 3.10+
httpx>= 0.25.0cryptography>= 44.0.0 (optional, forcertisigma.crypto)
License
MIT — Ten Sigma Sagl, Lugano, Switzerland
Metadata
Release files for certisigma 1.11.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| certisigma-1.11.0.tar.gz | 28.8 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| certisigma-1.11.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 48.4 kB
Release files / certisigma-1.11.0.tar.gz
| Download URL | certisigma-1.11.0.tar.gz |
|---|---|
| Size | 28.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
1db672c8c348b90f74b3a1ce59f3f6883b4e070a070967ed867767cfa16d32da
|
|
BLAKE2b-256 checksum How to use checksums |
c71664c186eec81715244996d1c2be5b544df9d4aa30d8af4349e3749ae771aa
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.12.13
|
Release files / certisigma-1.11.0-py3-none-any.whl
| Download URL | certisigma-1.11.0-py3-none-any.whl |
|---|---|
| Size | 19.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
bec675c56cc928660f84ba5424015656af1ab6cb9d77808f6a196bafa87ca222
|
|
BLAKE2b-256 checksum How to use checksums |
f84cc0d8a79c78eaf7d34b2253d32c0ccbbf7625612a8fd9d583bad8295f6240
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.12.13
|