go · no cgo · content credentials
C2PA provenance, pure Go.
Read, verify and write C2PA / Content Credentials manifests in JPEG, PNG, MP4/HEIC, WebP, TIFF, GIF, MP3, SVG and PDF — a fast claim reader for triage, a full cryptographic verifier, and a signer checked against c2pa-rs — with no c2pa-rs dependency and no cgo.
MIT licensed · pure-Go crypto/CBOR/COSE · Go 1.26+
info := c2pa.Read(ctx, c2pa.JPEG, f) // or c2pa.PNG, c2pa.BMFF, c2pa.PDF … if info.Present { fmt.Println(info.ClaimGenerator) // "Adobe Firefly" fmt.Println(info.AIGenerated) // declared AI-generated? fmt.Println(info.SignedBy) // claimed signer (unverified) }
what you get
Three modes, one pure-Go library.
A fast reader that surfaces what a file claims, a full verifier that proves it, and a signer that writes it — the complete C2PA validation algorithm and a writer checked against c2pa-rs, no c2pa-rs dependency and no cgo.
Pure Go, no cgo
No c2pa-rs dependency and no cgo — pure-Go crypto, CBOR, and COSE all the way down, so it cross-compiles and vendors like any other Go package.
Read for triage
Fast, best-effort, never-errors surfacing of what a file claims: creating tool, title, format, AI-generated flag, claimed signer, and signing time.
Validate in full
The complete C2PA validation algorithm, executed in pure Go, reporting per-step C2PA status codes — success, informational, and failure — for every check.
Sign in pure Go
Write c2pa.claim.v2 manifests into all nine containers with your own key — COSE_Sign1, optional RFC 3161 timestamps, provenance chained on re-sign — every output validated before it is written, and checked against c2pa-rs's c2patool in CI.
Signatures & chains
Verifies COSE signatures (ES256/384/512, PS256/384/512, EdDSA), the certificate chain against the C2PA profile, and assertion plus hard-binding hashes.
Timestamps & revocation
RFC 3161 timestamp verification, opt-in OCSP/CRL revocation, and recursive ingredient validation with cycle detection for nested manifests.
Batteries included
Official C2PA conformance trust lists embedded in the binary, overridable anchors, and tunable options — across JPEG, PNG, BMFF, RIFF, TIFF, GIF, MP3, SVG and PDF.
usage
Triage fast, or verify in full.
Read tells you what a file claims — accurate-as-recorded, not proven. Validate runs the whole algorithm and reports C2PA status codes you can trust.
// Read — fast, unverified triage info := c2pa.Read(ctx, c2pa.JPEG, f) if info.Present { fmt.Println(info.ClaimGenerator) // tool fmt.Println(info.AIGenerated) // AI? fmt.Println(info.SignedBy) // claimed }
// Validate — full cryptographic proof res := c2pa.Validate(ctx, c2pa.JPEG, f, opts...) if res.Valid { fmt.Println("verified, signed at", res.SignedAt) } for _, s := range res.Statuses { fmt.Println(s.Code, s.Severity) // C2PA codes }
- claimSignature.validated
- signingCredential.trusted
- assertion.dataHash.match
- timeStamp.validated
- signingCredential.untrusted
- assertion.dataHash.mismatch
sign
Write Content Credentials.
A crypto.Signer and its certificate chain in, a signed asset out — the same nine containers, the COSE algorithm inferred from the key, an RFC 3161 timestamp when you ask for one. An asset that already carries a manifest keeps it as the new manifest's parentOf ingredient. Nothing is written unless the output already validates.
signer, err := c2pa.NewSigner(key, chain,
c2pa.WithClaimGenerator("my-app", "1.2.0"),
c2pa.WithTimestampAuthority("https://timestamp.digicert.com")) // optional
err = signer.Sign(ctx, c2pa.JPEG, in, out, c2pa.Manifest{
Title: "photo.jpg",
Actions: []c2pa.Action{{
Action: c2pa.ActionCreated,
DigitalSourceType: c2pa.DigitalSourceTypeDigitalCapture,
}},
})
- c2patool: Valid
- trust --trust_anchors: Trusted
- timeStamp.trusted
- ingredient parentOf
install
Add it to your module.
Pure-Go dependencies only, no cgo. Requires Go 1.26+.
go get github.com/richardwooding/c2pa
import "github.com/richardwooding/c2pa"
Requires Go 1.26+. Full API docs on pkg.go.dev.