Skip to main content

citesig

CiteSig is an open protocol for embedding cryptographic verification signatures inside factual claims — a trust layer for the AI era.

citesig is the reference Python implementation of CiteSig v0.1. It provides sign(), verify(), canonical serialization, and the compact form defined in the specification.

Install

pip install citesig

Requires Python 3.9 or later. Depends on cryptography for Ed25519 primitives.

Quick example

import os
from datetime import datetime, timezone

from citesig import (
    sign,
    verify,
    ed25519,
    did_key_from_ed25519_public_key,
)

# Generate a signing key (32-byte seed).
seed = os.urandom(32)
public_key = ed25519.public_key_from_seed(seed)
signer = did_key_from_ed25519_public_key(public_key)

# Sign a claim.
attestation = sign(
    {
        "claim": "The Great Barrier Reef is approximately 2,300 km long.",
        "signer": signer,
        "sources": [
            {"url": "https://barrierreef.org/the-reef/facts"},
        ],
        "issued_at": datetime.now(timezone.utc).isoformat(timespec="seconds"),
    },
    seed,
)

# Verify it.
result = verify(attestation)
if result.ok:
    print("Signature verified.")
else:
    print(f"Rejected: {result.reason}{result.detail}")

API

sign(claim, private_key_seed) -> dict

Produces a signed CiteSig attestation.

  • claim — partial attestation mapping. claim, signer, sources, issued_at are required; v defaults to citesig/0.1. Extension fields with names containing a colon (e.g. myapp:field) are included in the signed content per spec §3.2.
  • private_key_seed — 32-byte Ed25519 seed.
  • Returns — a new dict with all input fields plus a populated sig (base64url-encoded 64-byte Ed25519 signature).

verify(attestation, *, resolve_signer=None) -> VerifyResult

Verifies a CiteSig attestation per spec §5.3.

  • attestation — a signed attestation mapping.
  • resolve_signer — optional callable (signer: str) -> Iterable[bytes]. Called for https:// signers. Not called for did:key: signers, which resolve offline.
  • Returns — a VerifyResult(ok, reason=None, detail=None). On failure, reason is one of:
    • version-unknownv is not citesig/0.1
    • field-missing — a REQUIRED field is absent
    • field-malformed — a field has the wrong type or shape
    • signature-invalid — signature does not match the signed content
    • signer-unresolvable — signer identifier could not be resolved to a public key

Constants live on RejectionReason (e.g. RejectionReason.SIGNATURE_INVALID).

Use verify_async(...) if you need an async resolve_signer.

canonicalize(value) -> bytes

Returns the JCS (RFC 8785) canonicalization of value as UTF-8 bytes. This is the signing input format per spec §4.

to_compact(attestation) -> str

Encodes a signed attestation as its compact form per spec §6:

citesig:0.1:<b64u(claim)>:<b64u(signer)>:<b64u(sourcedigest)>:<b64u(issued_at)>:<b64u(sig)>

where sourcedigest is SHA-256(JCS(sources)). Compact form is lossy for sources — recipients can verify the signature but must obtain the full attestation to enumerate sources.

parse_compact(compact) -> CompactAttestation

Parses a compact-form string into a CompactAttestation dataclass with v, claim, signer, source_digest (32 bytes), issued_at, sig. The original sources list cannot be recovered from a compact-form string.

Utility exports

  • ed25519.public_key_from_seed(seed) -> bytes — derive the public key from a 32-byte seed.
  • ed25519.sign(message, seed) -> bytes — raw Ed25519 signing (64-byte signature).
  • ed25519.verify(message, signature, public_key) -> bool — raw Ed25519 verification.
  • ed25519_public_key_from_did_key(did) -> bytes — decode a did:key:z... to its 32-byte public key.
  • did_key_from_ed25519_public_key(public_key) -> str — encode a 32-byte public key as did:key:z....
  • base64url.encode(bytes) -> str / base64url.decode(str) -> bytes — RFC 4648 §5 (no padding).

Conformance

This implementation passes all v0.1 conformance vectors from the specification:

  • 3 ACCEPT signature vectors (two sources / empty sources / extension field)
  • 3 REJECT signature vectors (claim tampering / unknown version / extension stripping)
  • 4 canonicalization vectors (key ordering / whitespace / UTF-8 / empty arrays)

Run the test suite yourself:

git clone https://github.com/citesig/spec
cd spec/impl/py
pip install -e ".[test]"
pytest

The JavaScript reference implementation (@citesig/core on npm) and this Python implementation are byte-for-byte compatible — signatures produced by one verify with the other.

Specification

License

MIT for code (this package). CC-BY-4.0 for specification prose in the main repository.

Security

A valid CiteSig signature does not mean the claim is true. It means the signer identified by signer produced the attestation, and the claim + sources + timestamp + extension fields have not been altered since. Consumers building trust decisions on top of CiteSig must layer signer reputation, source quality, and independent verification on top.

Report vulnerabilities to security@citesig.org (or open an issue at github.com/citesig/spec/issues for non-sensitive reports).

Download files

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

Source Distribution

citesig-0.1.0.tar.gz (15.6 kB view details)

Uploaded Source

Built Distribution

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

citesig-0.1.0-py3-none-any.whl (13.8 kB view details)

Uploaded Python 3

File details

Details for the file citesig-0.1.0.tar.gz.

File metadata

  • Download URL: citesig-0.1.0.tar.gz
  • Upload date:
  • Size: 15.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.12.10

File hashes

Hashes for citesig-0.1.0.tar.gz
Algorithm Hash digest
SHA256 e5f451c677a13d6bf2dbf96026b0ec91f00dc2c5a7260463b066371bbcab4a8f
MD5 e30927cc34ed200b1d5a52039e66d788
BLAKE2b-256 d23a8263227f7c5474d8f01ac35967c528b9c4c252d8639752975a525bf110cf

See more details on using hashes here.

File details

Details for the file citesig-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: citesig-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 13.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.12.10

File hashes

Hashes for citesig-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 86b5a61e7f1c7ce9b72f0c13ba3d366756c7970ff86999b983e3b483c0e88b54
MD5 d9b3b69ef441da2e807b8c2212c0b843
BLAKE2b-256 edc3ae45df567f44cc24a15726f30fe2807ec4cac2280f834e205a597325b650

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 files

0.0.1

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page