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_atare required;vdefaults tocitesig/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 forhttps://signers. Not called fordid:key:signers, which resolve offline.- Returns — a
VerifyResult(ok, reason=None, detail=None). On failure,reasonis one of:version-unknown—vis notcitesig/0.1field-missing— a REQUIRED field is absentfield-malformed— a field has the wrong type or shapesignature-invalid— signature does not match the signed contentsigner-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 adid:key:z...to its 32-byte public key.did_key_from_ed25519_public_key(public_key) -> str— encode a 32-byte public key asdid:key:z....base64url.encode(bytes) -> str/base64url.decode(str) -> bytes— RFC 4648 §5 (no padding).
Conformance
This implementation passes every v0.1 conformance vector, which ship in the source distribution:
- 20 canonicalization vectors (
tests/vectors/v0.1/canonical.json) - 17 signing vectors, 6 accept and 11 reject — one for every rejection reason in spec §5.3 (
tests/vectors/v0.1/signatures.json)
The signing vectors use a published, deliberately trivial test seed (the bytes 0x00 through 0x1f). It is not a key and must never be used as one.
Run the test suite from the source distribution:
pip download citesig==0.1.1 --no-binary :all: --no-deps
tar xzf citesig-0.1.1.tar.gz && cd citesig-0.1.1
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
Changes in 0.1.1
- Number canonicalization now follows RFC 8785 exactly (ECMA-262
Number::toString): whole-valued floats, exponent thresholds, exponent padding and negative zero now serialize byte-identically to the JavaScript implementation. Integers outside the exactly representable double range are rejected rather than rounded. sign()now fills in the default version whenvis passed explicitly asNone, matching the JavaScript implementation. Previously this produced an attestation nothing could verify.- The conformance vectors now ship with the source distribution.
- Package metadata points to https://citesig.org/spec/.
License
MIT for code and test vectors (this package). CiteSig is maintained by Machine Accountability Corporation.
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 and other issues to security@deepinquiry.ai.
Metadata
Release files for citesig 0.1.2
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| citesig-0.1.2.tar.gz | 20.4 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| citesig-0.1.2-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 35.6 kB
Release files / citesig-0.1.2.tar.gz
| Download URL | citesig-0.1.2.tar.gz |
|---|---|
| Size | 20.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
4569f7992bb015d69521d9b625e9819d8643c79e6d00a683afb73ad1c0df4d87
|
|
BLAKE2b-256 checksum How to use checksums |
6749ce35a2f26b5661d17d3cd67b9308e1778fca106001e71f59d18fd9d717fd
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.10
|
Release files / citesig-0.1.2-py3-none-any.whl
| Download URL | citesig-0.1.2-py3-none-any.whl |
|---|---|
| Size | 15.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
106cbf3cc0dc1886f3cbbe4cf2322e5693b4ca67f1c2c869af503d2499aa659a
|
|
BLAKE2b-256 checksum How to use checksums |
76b11e8a89efd135eaff2510f9ed63aeb89ffd71bd1de9a8924439e3e6c73b5c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.10
|
Security for citesig 0.1.2
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.