Skip to main content

AI evidence envelopes (AiEvidenceEnvelopeV1) — canonicalize, seal with RFC 3161 timestamps via Sigill, and verify.

Project description

sigill-sdk (Python)

PyPI License

Tamper-evident AI evidence envelopes for Python. Build an AiEvidenceEnvelopeV1 record of any AI generation, seal it with an RFC 3161 timestamp via Sigill, and verify it offline at any later point.

The cryptographic primitives — RFC 8785 canonical JSON, SHA-256 hash binding, RFC 3161 timestamp parsing — are all handled inside the SDK. You hand it your prompt, response, and metadata; you get back a signed envelope. Apps don't need to implement canonicalization, hash binding, or timestamp protocol logic themselves.

For the underlying spec — what's in an envelope, what gets hashed in what order, what "valid" means — see spec/README.md. The same spec ships in this repo's sibling: the .NET SDK at sigill-dotnet. Identical test vectors, byte-compatible output.

Install

pip install sigill-sdk

Python 3.9+. The only runtime dependencies are httpx, jcs (the reference RFC 8785 implementation), and asn1crypto.

30-second example

from sigill_sdk import SigillClient, EnvelopeBuilder

client = SigillClient(api_key="sigill_...")  # from Settings → API Keys at sigill.ai

envelope = (
    EnvelopeBuilder()
    .with_purpose(category="summarization", business_context="support-ticket-summary")
    .with_actor(type="service", id="svc-support-summarizer", tenant_id="tenant-acme")
    .with_activity(name="ticket.summarize", correlation_id="trace-abc-123")
    .with_model(provider="anthropic", name="claude-opus-4-7",
                parameters={"max_tokens": 1024, "temperature": 0.2})
    .with_prompt_inline("Summarize the following support ticket in three bullet points.")
    .with_output_inline("Customer reports login fails after password reset.")
    .build()
)

sealed = client.seal(envelope)
# sealed["integrity"]["envelopeHash"]   ← SHA-256 of canonical JSON
# sealed["proofs"][0]["tsrBase64"]      ← RFC 3161 timestamp from Sigill

# ...persist sealed somewhere durable (DB, S3, your audit log)...

# Later — re-verify cryptographically. Anyone with the sealed envelope can do this:
result = client.verify(sealed)
assert result.is_valid
print("Stamped at:", result.timestamps[0]["gen_time"], "by", result.timestamps[0]["tsa_name"])

That's the whole hot path. Everything below is detail you only reach for when you need it.

Keeping PII out of the envelope

For sensitive prompts and responses, store hash references in the envelope instead of the content itself. The SDK hashes the bytes you supply, records the hash in the envelope, and the original bytes are yours to keep, redact, or delete.

prompt_bytes = "Classify identity doc. Subject: Jane Doe, born 1985-03-14.".encode()
response_bytes = b'{"document_type":"passport","confidence":0.97}'

envelope = (
    EnvelopeBuilder()
    .with_purpose(category="classification", regulatory_basis=["EU-AI-Act:Annex-III"])
    .with_actor(type="user", id="user-9b2f1a", tenant_id="tenant-acme")
    .with_activity(name="kyc.classify")
    .with_model(provider="anthropic", name="claude-opus-4-7")
    .with_prompt_ref("prompt", content_type="text/plain")
    .with_output_ref("output", content_type="application/json")
    .with_policy_metadata(redactionApplied=True, redactionPolicy="pii-redaction-v3")
    .build()
)

sealed = client.seal(
    envelope,
    external_payloads={"prompt": prompt_bytes, "output": response_bytes},
)
# The envelope now contains SHA-256("prompt bytes") and SHA-256("response bytes")
# under prompt.hash and output.hash. The bytes themselves are NOT stored.

When you later need to audit, supply the bytes again — verify confirms they hash to the same registered values:

result = client.verify(
    sealed,
    external_payloads={"prompt": prompt_bytes, "output": response_bytes},
)
assert result.is_valid

If the bytes have been deleted or modified, verification reports exactly which ref is missing or wrong:

result = client.verify(sealed, external_payloads={"prompt": prompt_bytes})
# result.is_valid -> False
# result.issues[0].kind   -> VerificationIssueKind.HASH_MISMATCH
# result.issues[0].target -> "output"
# result.issues[0].message -> "payload_not_supplied: external bytes for ref 'output' …"

Error handling

Producer-time errors raise; verification errors are collected. This split is deliberate: when sealing, you have a single in-flight operation that either works or doesn't. When verifying, an audit UI wants every problem at once, not just the first.

When Surface Spec §7 kind
seal() — every TSA Sigill tried failed TimestampUnavailable (with failures: list) timestamp_unavailable
seal() — caller pre-declared a hash that doesn't match supplied bytes HashMismatch hash_mismatch
seal() — input contains values JCS rejects (NaN, Infinity) CanonicalizationFailed canonicalization_failed
verify() — anything wrong result.issues[], result.is_valid == False per-issue kind field

A typical seal-with-fallback:

from sigill_sdk import SigillClient, TimestampUnavailable

try:
    sealed = client.seal(envelope, external_payloads=payloads)
    persist(sealed)
except TimestampUnavailable as e:
    # All TSAs in our rotation failed. Persist the envelope unsealed and seal it later.
    log.warning("TSA outage: %d attempts, failures=%r", e.attempts, e.failures)
    persist_for_async_sealing(envelope, payloads)

Cross-language interop

This SDK and the .NET SDK at sigill-dotnet share the same spec, JSON Schema, and test vectors. An envelope sealed by either SDK verifies with either SDK — the canonical bytes are byte-identical.

The interop guarantee is enforced by tests: both test suites read the same files under spec/test-vectors/ and assert that their canonical output matches the committed reference bytes. The spec/ directory in this repo is a vendored copy; the canonical source lives under spec/ in sigill-dotnet too, and the bytes are byte-identical between the two.

Pinning a specific TSA

By default, seal() uses Sigill's auto mode — round-robin across the TSAs you have enabled, with automatic failover. That's the recommended setting for production: you get redundancy at no cost.

If you need to record that a specific TSA produced the timestamp (compliance reason, specific policy OID), pass it explicitly:

sealed = client.seal(envelope, tsa_slug="digicert")           # SHA-256, US TSA
sealed = client.seal(envelope, tsa_slug="sectigo")            # SHA-512
sealed = client.seal(envelope, tsa_slug="skid-ecc",           # eIDAS Qualified
                     qualified=True)

Available slugs and their properties: see Sigill's TSA documentation.

Async / context manager

SigillClient is a sync client built on httpx.Client. Wrap it in with to ensure the underlying HTTP connection pool is closed when you're done:

with SigillClient(api_key="...") as client:
    sealed = client.seal(envelope)

If you need an async API, open an issue — it's a thin wrapper away.

Lower-level surface

The SDK exposes its primitives in case you need them outside the seal/verify flow:

from sigill_sdk import canonicalize, compute_envelope_hash

canonical_bytes = canonicalize({"b": 2, "a": 1})  # → b'{"a":1,"b":2}'
digest_hex, canonical_bytes = compute_envelope_hash(envelope)

This is what every test vector is built from, and it's what the cross-language interop guarantee comes down to.

What this SDK is not

It is not a substitute for TSA chain validation. The SDK confirms the TSR's embedded message-imprint matches your envelope, but it does not — by design in v1 — validate the TSA's certificate chain back to a trust anchor. Sigill's POST /tsa/verify endpoint does that server-side; for offline trust-anchor validation, use a dedicated library like sigstore-python or shell out to openssl ts -verify. v2 of this SDK will provide a pluggable trust policy.

Development

git clone https://github.com/sigill-ai/sigill-python.git
cd sigill-python
pip install -e ".[dev]"
pytest

The 39-test suite runs offline in <1s. CI runs against Python 3.9 through 3.13.

License

Apache 2.0 — see LICENSE.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

sigill_sdk-0.1.10.tar.gz (30.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

sigill_sdk-0.1.10-py3-none-any.whl (24.2 kB view details)

Uploaded Python 3

File details

Details for the file sigill_sdk-0.1.10.tar.gz.

File metadata

  • Download URL: sigill_sdk-0.1.10.tar.gz
  • Upload date:
  • Size: 30.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for sigill_sdk-0.1.10.tar.gz
Algorithm Hash digest
SHA256 b77ce2fc73d7c5fda9237c26c0808262fe5fd344436424d3c41fcf25dca8de95
MD5 e14154e7c58a4ee8dee570668b2f81c2
BLAKE2b-256 806b39c5d41defb019000f3e3fd51c71fcc8b101db4025831bf666efcc52f607

See more details on using hashes here.

Provenance

The following attestation bundles were made for sigill_sdk-0.1.10.tar.gz:

Publisher: release.yml on sigill-ai/sigill-python

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file sigill_sdk-0.1.10-py3-none-any.whl.

File metadata

  • Download URL: sigill_sdk-0.1.10-py3-none-any.whl
  • Upload date:
  • Size: 24.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for sigill_sdk-0.1.10-py3-none-any.whl
Algorithm Hash digest
SHA256 19c864f8f2389423c44ba38e79b58f5131125b655fd7f3e13270d33394c46cae
MD5 578f6da18f71336b40d23debb81edd4a
BLAKE2b-256 6e5d7516777555fdf28de4aab9472812da13c9ebdd58f839df49def28fa247e3

See more details on using hashes here.

Provenance

The following attestation bundles were made for sigill_sdk-0.1.10-py3-none-any.whl:

Publisher: release.yml on sigill-ai/sigill-python

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page