Skip to content

Nbk7822/provenancestamp

Repository files navigation

ProvenanceStamp

Verifiable provenance badges for AI-generated content.

Stamp AI-generated text with cryptographic metadata and embed a clickable badge anywhere that content appears. Anyone can click the badge to verify the model, timestamp, and content integrity.

This post was written with AI assistance.
<a href="https://provenancestamp.dev/verify/01JQXYZ...">🛡 AI Generated</a>

C2PA solves provenance for images, video, and audio. ProvenanceStamp does the same for text.


Quick Start

npm install @provenancestamp/sdk
import { ProvenanceStamp } from '@provenancestamp/sdk';

const ps = new ProvenanceStamp({ apiKey: process.env.PROVENANCE_API_KEY });

const stamp = await ps.stamp({
  content: aiGeneratedText,
  model: 'claude-sonnet-4-20250514',
  provider: 'anthropic',
  generatedAt: new Date().toISOString(),
});

console.log(stamp.verifyUrl);    // https://provenancestamp.dev/verify/01JQXYZ...
console.log(stamp.badges.html);  // <a href="...">🛡 AI Generated</a>

That's it. Paste the badge HTML wherever the content appears.


How It Works

When you call ps.stamp(), the SDK:

  1. Normalizes the content (NFC, trim, collapse whitespace) and computes SHA-256(content)
  2. Builds a deterministic metadata bundle (sorted keys) and computes SHA-256(metadata)
  3. Generates a ULID stamp ID (sortable, unique, no external deps)
  4. Computes HMAC-SHA256(apiKey, stampId + contentHash + metadataHash) as a signature
  5. POSTs the record to the verification service
  6. Returns badge snippets in HTML, Markdown, JSON-LD, and React formats

When someone clicks the badge, they land on the verification page showing the full provenance chain.

Content integrity: Call ps.verifyContent(text, stampId) to confirm the text matches the stored hash — detecting any modifications made after stamping.


API Reference

new ProvenanceStamp(config?)

Option Type Description
apiKey string Your API key. Falls back to PROVENANCE_API_KEY env var.
serviceUrl string Verification service URL. Defaults to https://provenancestamp.dev.

ps.stamp(input): Promise<StampResult>

interface StampInput {
  content: string;              // required — the AI-generated text
  model: string;                // required — e.g. "claude-sonnet-4-20250514"
  provider?: string;            // e.g. "anthropic", "openai"
  promptFingerprint?: string;   // SHA-256 of the prompt (use ps.hashPrompt())
  temperature?: number;
  generatedAt: string;          // required — ISO 8601 timestamp
  confidence?: number;          // 0–1
  author?: string;              // person or org that requested generation
  customMetadata?: Record<string, string>;
}

interface StampResult {
  id: string;           // ULID stamp ID
  contentHash: string;  // SHA-256 of normalized content
  metadataHash: string; // SHA-256 of metadata bundle
  signature: string;    // HMAC-SHA256 signature
  stampedAt: string;    // ISO 8601 timestamp from the service
  verifyUrl: string;    // https://provenancestamp.dev/verify/{id}
  badges: {
    html: string;       // inline HTML badge
    markdown: string;   // Markdown image link
    jsonLd: string;     // <script type="application/ld+json"> block
    react: string;      // copy-pasteable React component
  };
}

ps.hashPrompt(prompt): Promise<string>

Hash a prompt string to produce a promptFingerprint. The original prompt is never stored — only its SHA-256 hash.

ps.verify(stampId): Promise<VerifyResult>

Fetch and verify a stamp record. Returns { valid, stamp, error? }.

ps.verifyContent(content, stampId): Promise<VerifyResult>

Verify a stamp and check that the provided content matches the stored hash. Returns { valid, contentMatch, stamp, error? }.


Badge Formats

HTML (three styles)

<!-- compact (default) -->
<a href="https://provenancestamp.dev/verify/01JQ..." ...>🛡 AI Generated</a>

<!-- minimal -->
<a href="...">🛡 AI Verified</a>

<!-- full -->
<a href="...">🛡 Claude Sonnet 4 · Mar 2026</a>

Generate with options.style and options.theme ('light' | 'dark'):

stamp.badges.html  // default: compact, light
generateHtmlBadge(id, url, input, { style: 'full', theme: 'dark' })

Markdown

[![AI Verified](https://img.shields.io/badge/AI%20Verified-Claude%20Sonnet%204-brightgreen...)](https://provenancestamp.dev/verify/01JQ...)

JSON-LD

<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "CreativeWork",
  "identifier": "01JQ...",
  "generator": { "@type": "SoftwareApplication", "name": "claude-sonnet-4-20250514" },
  ...
}
</script>

React

stamp.badges.react contains a copy-pasteable React component with no dependencies (all styles inline).


Integrations

Anthropic

import Anthropic from '@anthropic-ai/sdk';
import { ProvenanceStamp } from '@provenancestamp/sdk';

const client = new Anthropic();
const ps = new ProvenanceStamp({ apiKey: process.env.PROVENANCE_API_KEY });

const response = await client.messages.create({ model: 'claude-sonnet-4-20250514', ... });
const content = response.content[0].text;

const stamp = await ps.stamp({
  content,
  model: 'claude-sonnet-4-20250514',
  provider: 'anthropic',
  promptFingerprint: await ps.hashPrompt(myPrompt),
  generatedAt: new Date().toISOString(),
});

OpenAI

import OpenAI from 'openai';
import { ProvenanceStamp } from '@provenancestamp/sdk';

const openai = new OpenAI();
const ps = new ProvenanceStamp({ apiKey: process.env.PROVENANCE_API_KEY });

const completion = await openai.chat.completions.create({ model: 'gpt-4o', ... });
const content = completion.choices[0].message.content ?? '';

const stamp = await ps.stamp({
  content, model: 'gpt-4o', provider: 'openai',
  generatedAt: new Date().toISOString(),
});

See examples/ for more: blog post injection, Express middleware.


CLI

npm install -g provenancestamp

# Set up API key
provenancestamp init

# Stamp a file
provenancestamp stamp post.md --model claude-sonnet-4-20250514 --author "My Blog"

# Stamp from stdin
cat output.txt | provenancestamp stamp --stdin --model gpt-4o --output markdown

# Verify a stamp
provenancestamp verify 01JQXYZ...

# Verify a stamp and check file integrity
provenancestamp verify 01JQXYZ... post.md

Cryptographic Design

Content Hash    = SHA-256(NFC(trim(collapse_whitespace(content))))
Metadata Bundle = JSON.stringify(sort_keys({ content_hash, model, provider,
                    prompt_fingerprint, temperature, confidence,
                    generated_at, author, custom_metadata }))
Metadata Hash   = SHA-256(Metadata Bundle)
Stamp Signature = HMAC-SHA256(key=api_key, msg=stamp_id + content_hash + metadata_hash)

The signature is stored with the stamp. It proves to the key holder that the record wasn't modified — but it's symmetric, so public trustless verification requires hitting the service.

Roadmap: Asymmetric Ed25519 signatures for trustless verification without needing the service online.


Self-Hosting

git clone https://github.com/provenancestamp/provenancestamp
cd provenancestamp
pnpm install

# Set environment variables
cp .env.example .env
# Edit .env: set MASTER_API_KEY

# Run the server
pnpm --filter @provenancestamp/server dev

The server uses SQLite (no separate database to set up). To use Postgres later, swap better-sqlite3 for a Postgres client in packages/server/src/db/schema.ts.

Create your first API key:

curl -X POST http://localhost:3000/api/v1/keys \
  -H "X-API-Key: $MASTER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "My App"}'

The key field in the response is your API key. It's shown only once.


Project Structure

provenancestamp/
├── packages/
│   ├── sdk/        @provenancestamp/sdk — stamp creation, badge generation, verification
│   ├── server/     Hono API + SQLite + SSR verification page
│   └── cli/        provenancestamp CLI (Commander.js)
└── examples/       Working integration examples

Roadmap

  • Ed25519 asymmetric signatures for trustless public verification
  • C2PA interoperability (embed stamp as C2PA assertion)
  • Browser extension for auto-verification of badges on any page
  • WordPress / Ghost plugins
  • Streaming content support (stamp as chunks arrive)

License

MIT

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors