Skip to content

Repository files navigation

Open Print

Open Print is the portable record and verification layer for digital art editions. It defines signed JSON documents for artworks, editions, issuers, certificates, issuance events, registries, and checkpoints.

The project is intentionally narrower than an application. It does not provide accounts, payments, a marketplace, object storage, or a hosting service. The implemented reference application is Open Press, which uses this package to run institutional issuance, claims, collection, and public verification workflows.

Current implementation

The v1 implementation includes:

  • JSON Schemas and runtime validation for issuer, certificate, registry-event, checkpoint, and archive documents;
  • deterministic JSON canonicalization and SHA-256 digests;
  • Ed25519 issuer keys, signed certificates, and signed registry events;
  • unique certificate IDs and edition sequences;
  • append-only issuance, cancellation, and replacement semantics;
  • signed registry checkpoints for anchoring registry history;
  • an atomic Node file-store for single-machine issuance;
  • portable archive and restore commands;
  • content-addressed static artwork publishing;
  • verification reports that separate integrity, issuer, signature, registry, and current-status findings;
  • a v1 compatibility consumer, frozen public-surface baseline, and independent Python conformance fixture;
  • deterministic security, tamper, malformed-input, and release checks.

Open Print 1.0.0 is published to npm under the latest dist-tag. The older 0.1 API remains available so applications can continue reading earlier certificates while adopting the signed v1 format.

Open Press integration

Open Press demonstrates how the core package fits inside a hosted application:

flowchart TD
    media[Institution-hosted media]
    version[Open Press artwork version]
    edition[Fixed edition]
    certificate[Open Print certificate]
    event[Signed registry event]

    media -->|SHA-256 identity| version
    version --> edition
    edition --> certificate
    certificate --> event
Loading

The integration boundary is split deliberately:

  1. Open Press hashes institution-hosted media and stores the media identity with an immutable artwork version. Open Press does not retain the original file.
  2. A server-side managed signer provisions the Open Print issuer document and key, then uses signV1Certificate and appendV1Issuance to create the certificate and issuance event.
  3. Supabase stores the public issuer, certificate, registry-event, and checkpoint documents. Database constraints enforce the application’s fixed edition supply and unique edition_id + sequence allocation.
  4. Collector claims, transfers, display permissions, generative output records, and current-holder projections are application records around the signed certificate. They do not mutate the certificate or registry event.

The Open Press signer uses the core API directly:

import {
  OPEN_PRINT_V1_ISSUER_SCHEMA,
  OPEN_PRINT_V1_VERSION,
  appendV1Issuance,
  canonicalize,
  exportV1IssuerPublicKey,
  generateV1IssuerKeyPair,
  sha256Hex,
  signV1Certificate,
} from "@open-print/core";

The private key stays in signer infrastructure. It is never sent to the React client, collector browser, or public registry. Open Press currently uses a file-backed signer for local development and a pilot cPanel deployment; a production deployment should replace that key store with an HSM/KMS-backed provider.

Install

npm install @open-print/core

The current 1.0.0 release is available on npm under the latest dist-tag. For local development, clone this repository and install it from a local path when you need to work against unreleased changes. Open Press currently uses the local dependency path ../smartcontract/open-print; its standalone signer deployment vendors the package as a tarball.

The package uses Ajv to apply its published JSON Schemas at runtime. It supports modern browsers and Node.js 20.10 or newer.

A v1 certificate workflow

An issuer first creates or obtains a v1 issuer record and a protected Ed25519 private key. The application then creates claims, signs the certificate, and appends the corresponding issuance event:

import {
  appendV1Issuance,
  signV1Certificate,
  verifyV1Certificate,
} from "@open-print/core";

const certificate = await signV1Certificate(claims, privateKey, {
  keyId: "issuer:gallery-example#2026-01",
});

const registry = await appendV1Issuance(certificate, events, {
  id: "event:gallery-example:certificate-001",
  signing: { issuer, privateKey, keyId: "issuer:gallery-example#2026-01" },
});

const report = await verifyV1Certificate(certificate, {
  issuer,
  registry,
});

The verification report distinguishes record integrity, issuer identity, signature validity, registry presence, and current status. A missing issuer record is not silently collapsed into a binary “authentic” or “inauthentic” answer.

Media identity and immutable records

Open Print hashes bytes; it does not host them. An application can identify an institution-hosted file before publishing an artwork version:

import { sha256Hex } from "@open-print/core";

const digest = await sha256Hex(await file.arrayBuffer());

Open Press stores that digest, source URL, media type, and byte size as part of an immutable artwork version. If the source bytes change, the digest changes and the application must publish a new version. The core package does not assume that a URL, a filename, or a database row is an identity.

An issued certificate is frozen by the library and identified by the SHA-256 digest of its complete claims. Editing a downloaded JSON file is possible, but the edit produces a different digest and breaks the issuer signature.

appendV1Issuance returns a new registry with one event and leaves the prior registry untouched. Cancellation and replacement are later events that point back to the earlier certificate.

Collector privacy

A certificate may contain a public display name or no collector name at all. Private labels belong in application data and never in signed claims. prepareV1Collector keeps those destinations separate:

import { prepareV1Collector } from "@open-print/core";

const collector = prepareV1Collector({
  publicDisplayName: null,
  privateLabel: "local conservation note",
});

claims.collector = collector.claims;

Removing collector.privateLabel later does not change the certificate. The full policy and verification limits are documented in docs/PRIVACY_AND_CLAIMS.md.

Registry checkpoints

A checkpoint is a signed note containing a registry length and the digest of the last event at that point. A verifier that remembers one checkpoint can detect a shorter registry or a different checkpoint branch later.

const checkpoint = await createV1RegistryCheckpoint(registry, {
  id: "checkpoint:gallery-example:2026-07-29",
  signing,
  previousCheckpoint,
});

Check a saved checkpoint and registry from the command line:

npx open-print-verify-checkpoint \
  checkpoint.json issuer.json registry.json previous-checkpoint.json

Checkpoints are ordinary signed JSON. They can live in Git, on removable media, or in an application archive.

Local registry and archives

The Node-only file store coordinates concurrent writers with a lock and replaces its archive atomically. Requests arriving together are committed as one batch, while each certificate receives its own sequence and signed issuance event.

import { V1FileRegistryStore } from "@open-print/core/node/file-store";

const store = new V1FileRegistryStore("./data/open-print-archive.json");
const result = await store.issue({
  certificateId,
  editionId,
  eventId,
  signing,
  createClaims: ({ sequence, issuedAt }) => ({
    ...claims,
    id: certificateId,
    issuedAt,
    sequence,
  }),
});

This reference store is intended for one machine and an ordinary local filesystem. A multi-server application such as Open Press must provide the same transaction boundary with its database; the library does not provide a distributed database lock.

An archive contains public issuer records, certificates, registry events, and checkpoints in one integrity-checked JSON document. Private keys are always excluded:

open-print-archive verify ./open-print-archive.json
open-print-archive restore ./open-print-archive.json ./restored-archive

Restore refuses to overwrite an existing directory.

Static artwork publishing

The open-print-publish command copies a file into a path containing the complete SHA-256 digest:

/artworks/{work-id}/{sha256}/{filename}
npx open-print-publish \
  ./artworks/residual-field-07/index.html \
  residual-field-07 \
  ./public

If one byte changes, the output path changes. The generated directory can be committed to Git or deployed with an ordinary static host.

What belongs here

Open Print owns:

  • artwork, edition, issuer, certificate, event, checkpoint, and archive document formats;
  • published JSON Schemas and runtime validation;
  • deterministic serialization, SHA-256 hashing, and integrity checks;
  • Ed25519 signature construction and verification;
  • certificate IDs, edition sequences, and append-only registry rules;
  • content-addressed release paths;
  • compatibility and conformance rules that other implementations can test.

An application built with Open Print is responsible for:

  • user, institution, and collector accounts;
  • storing and delivering artwork files;
  • payment and identity-provider integrations;
  • claim links, website embeds, generative collection, and exhibition views;
  • database transactions and current-holder projections;
  • protecting signing keys and operating a signer service;
  • publishing issuer, correction, cancellation, and lifecycle records.

What “non-fungible” means here

Every certificate has its own ID, sequence number within an edition, and content fingerprint. The v1 registry rejects a reused certificate ID and rejects two certificates claiming the same edition sequence.

That makes two certificates non-interchangeable as issuance records. It does not create a token, prevent copying the artwork, establish legal ownership, or create a financial asset.

Firm boundary

Open Print does not use blockchains, cryptocurrency, wallets, tokens, NFTs, or distributed ledgers. SHA-256 is used only to check whether two sets of bytes or records match. A hash is not a token, proof of legal ownership, or proof of authorship.

Working on the project

npm install
npm test
npm run compatibility:check
npm run test:security
npm run audit:production
npm run check

npm run check runs the tests, compiles an external TypeScript consumer, checks the frozen public contract, and inspects the package that would be published. Development checks require Python 3.9 or newer for the independent conformance implementation; the published library itself does not depend on Python.

npm run test:security runs the deterministic adversarial suite for signature metadata rewriting, digest recomputation, registry and checkpoint tampering, canonical encodings, malformed JSON, and CLI input limits.

npm run release:verify performs two isolated builds and checks that their shipped files and package tarballs are identical. npm run release:artifacts creates an ignored local directory containing the tarball, CycloneDX SBOM, release manifest, and checksums. Neither command publishes anything.

See CONTRIBUTING.md before changing a record shape. The stability contract is in docs/V1.md, the compatibility procedure is in docs/COMPATIBILITY_POLICY.md, and the release gates are in docs/RC1_CHECKLIST.md. Validation, key management, checkpoints, archives, privacy, threat modeling, and releasing are documented in the corresponding files under docs/. The independent implementation is documented in conformance/python/README.md. Report vulnerabilities according to SECURITY.md.

About

Open Print is an open framework for provenance tracking of art pieces that doesn't use the blockchain.

Topics

Resources

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Contributors

Languages