Skip to content

How Stamping Works

A document stamp is a self-contained proof of a document's existence, authorship, and integrity at a specific point in time. This page explains the pipeline that creates one.

The Stamping Pipeline

Document Content
       |
       v
  [1. HASH] -----> DocumentHash (SHA3-256 / BLAKE3 / SHA-256)
       |
       v
  [2. BUILD] ----> DocumentStamp (models assembled)
       |
       v
  [3. SIGN] -----> Ed25519 signature over canonical stamp bytes
       |
       v
  [4. ANCHOR] ---> Blockchain transactions for immutable timestamps
       |
       v
  [5. STORE] ----> Stamp saved to JSON file

Step 1: Hashing

The first step converts arbitrary document content into a fixed-length cryptographic hash. PrimeStamp supports three algorithms:

Algorithm Output Notes
SHA3-256 256-bit (32 bytes) Default. NIST standard, quantum-resistant family.
BLAKE3 256-bit (32 bytes) Fastest option. Requires optional blake3 package.
SHA-256 256-bit (32 bytes) Widely compatible. Same family as Bitcoin.

The engine accepts three input types, all normalized to bytes before hashing:

# String content -- encoded to UTF-8
engine.hash_document("Hello, world")

# Raw bytes
engine.hash_document(b"\x89PNG\r\n...")

# File path -- read from disk
from pathlib import Path
engine.hash_document(Path("document.pdf"))

The result is a DocumentHash object containing the algorithm name and the hex-encoded hash value:

doc_hash = engine.hash_document("Hello, world")
print(doc_hash.algorithm)  # HashAlgorithm.SHA3_256
print(doc_hash.value)      # "a1b2c3d4..."  (64 hex characters)

Step 2: Building the Stamp

The StampEngine.create_stamp() method assembles a DocumentStamp from several component models:

  • DocumentCore -- Contains the plaintext hash and storage configuration
  • AuthorIdentity -- Identity mode (keypair, anonymous) and public key
  • AnchorConfig -- Requested anchors, trust threshold, and trust weights
  • StampLineage -- Semantic version, parent stamps, and changelog
  • EncryptionConfig -- Encryption algorithm, IV, key hash (if encryption is enabled)

Each stamp gets a unique UUID (stamp_id) and a UTC creation timestamp.

If a preset is specified, its values override storage, encryption, anchor, and trust threshold settings:

stamp = engine.create_stamp(content, preset="archival")
# Equivalent to:
stamp = engine.create_stamp(
    content,
    storage_mode=StorageMode.ARWEAVE,
    encryption=EncryptionAlgorithm.AES_256_GCM,
    anchors=[AnchorType.BITCOIN, AnchorType.ETHEREUM, AnchorType.RFC3161],
    trust_threshold=10,
)

Step 3: Signing

If the engine has a key pair, the stamp is automatically signed after creation. Signing works as follows:

  1. The stamp is serialized to canonical bytes -- a JSON representation with sorted keys, excluding the signature field itself.
  2. The canonical bytes are signed with the Ed25519 private key, producing a 64-byte signature.
  3. The signature (hex-encoded) and public key are stored in the author field of the stamp.
# Canonical bytes for signing
message = stamp.to_signable_bytes()

# The signature covers the entire stamp structure except itself
signature = sign_hex(message, key_pair.private_key)

Anonymous stamps

If no key pair is provided, the stamp is created with mode="anonymous" and no signature. It can still be verified for content integrity (hash match) and anchor proofs, but authorship cannot be confirmed.


Step 4: Encryption (Optional)

When encryption is enabled (via preset or explicit parameter), the document content is encrypted before storage:

  1. A 256-bit encryption key is generated (or provided by the caller).
  2. The content is encrypted with AES-256-GCM or ChaCha20-Poly1305.
  3. The ciphertext hash, initialization vector, and key hash are recorded in the stamp's EncryptionConfig.

The plaintext hash is always computed from the original content and stored in the stamp. This allows hash verification without decryption. The ciphertext hash provides a separate integrity check on the encrypted data.

Key management modes:

Mode Description
user_held User manages the encryption key (default)
deterministic Key is derived from a secret + document hash + salt
threshold_escrow Key is split via Shamir's Secret Sharing (k-of-n)
timelock_puzzle Key is released after a time-locked puzzle is solved

Step 5: Anchoring

Anchoring submits the stamp's hash to external systems that provide immutable timestamps. Each anchor type has a different trust weight (see Trust Scoring):

  • Bitcoin -- Via OpenTimestamps. Batches hashes into Merkle trees aggregated into Bitcoin transactions.
  • Ethereum -- Direct on-chain transaction with the stamp hash as calldata.
  • Solana -- Sub-second finality for time-sensitive stamps.
  • RFC 3161 -- Legally recognized timestamp authority. Returns a signed TSA token.
  • Arweave -- Permanent on-chain storage with content addressing.

Anchor proofs are stored as AnchorProof objects with status tracking (pending, submitted, confirmed, failed) and chain-specific data (transaction hash, block height, Merkle path).


Step 6: Serialization

The completed stamp is a Pydantic model that serializes cleanly to JSON:

# Save to file
engine.save_stamp(stamp, "document.stamp.json")

# Serialize to JSON string
json_str = engine.stamp_to_json(stamp)

# Load from file
stamp = engine.load_stamp("document.stamp.json")

# Deserialize from JSON string
stamp = engine.stamp_from_json(json_str)

Optional: Prime-Space Fingerprinting

For documents that need partial proof capabilities, a PrimeSpaceFingerprint can be attached to the stamp. This is a separate computation that:

  1. Decomposes the document into chunks indexed by prime numbers
  2. Builds a Merkle tree over the chunk hashes
  3. Computes multi-scale shadow transforms at prime scales (2, 3, 5, 7, 11, 13)
  4. Records frequency signatures (character distribution, entropy)

The fingerprint enables several proof types without full document disclosure:

  • Existence proof -- Prove a section exists using a Merkle path
  • Structure proof -- Reveal shadow transforms to prove structural similarity
  • Similarity proof -- Compare shadow fingerprints between documents
  • Authorship proof -- Match frequency signatures against an author profile
  • Priority proof -- Prove the document was stamped before a specific time
from primestamp import generate_fingerprint

fp = generate_fingerprint(document_bytes)
stamp.prime_fingerprint = fp

See Quickstart for usage examples.