ArtFirma

Internals · for collectors, galleries, conservators and anyone who wants to check without trusting us

How ArtFirma tags work

A tag on an artwork proves three things: who signed it (the artist's key), what it is (one exact catalogue record), and which chip it was signed for. None of this depends on our servers: the proof is mathematics that any copy of the verification page — or ten lines of code — can redo.

The short version

artist's 12 words ──> signing key (Ed25519, never leaves the artist's phone)
                                │ signs
                                ▼
tag link:  …/v?u=<chip ID>&c=<counter>&m=<chip code>#1.<work>.<fingerprint>.<signature>
                │ written by the chip on every tap      └──── written once by the artist ─────┘
                ▼
fingerprint = first 16 bytes of SHA-256 of the record file <work>.json (title, size, photo hash…)
signature   = Ed25519 over  "NFCSIGN1|<chip ID>|<work>|<fingerprint>"
  • Change one letter of the record → its fingerprint changes → “record differs”.
  • Copy the signature to another chip → that chip reports another ID → NOT VERIFIED.
  • Forge a signature → impossible without the artist's 12 words. Not even we can sign in an artist's name.

A real example

Alexander James Hamilton, Contact States I — Film 137-151, work number contact-states-ii-0137-0151. Its record is a small JSON file, stored permanently on Arweave: arweave.net/1ddInjhV…

StepValue
SHA-256 of the file11c2a31c38c080a2cc45291ea701106263f67a71fb46086d5611796e1cd4e736
FingerprintEcKjHDjAgKLMRSkepwEQYg — first 16 bytes, base64url. This is what the tag carries.
Artist's key9148b582d195330f894192a34aa80a47639237894bf6366eaa0fa12271b9550a, confirmed by alexanderjameshamilton.com/pages/artfirma
Tag signatureEd25519 by that key over NFCSIGN1|<chip ID>|contact-states-ii-0137-0151|EcKjHDjAgKLMRSkepwEQYg

The record itself does not point to the tag; the tag points to the record by its fingerprint. That is why the record can live anywhere — our site, the registry, Arweave, a USB stick — and still be checked: only bytes with the same fingerprint are accepted.

Two signatures

SignatureSigned textProves
On the tagNFCSIGN1|chip ID|work|fingerprintThis chip is this work, with this record.
On the record (<work>.json.sig)NFCSIGN-RECORD1|work|sha256 of the record The artist published this record — so a copy of the registry can be checked without any tag.

The chip code: why a copied tag shows

The chip is an NXP NTAG 424 DNA. On every tap it raises its read counter and writes a fresh code m into the link: an AES-CMAC with a key that never leaves the chip (each tag has its own key). Copying the link to an ordinary sticker freezes the counter and the code. The page notices a counter that does not go up; the optional online check (our service) recomputes the code and remembers the last counter it saw, so an old or copied link is reported as such. If the online check is gone, the signature check still works — only this extra hint is lost.

Where everything lives — and what if we disappear

WhatWhereWithout us
Address on the tagpurl.org/artfirma/v — Internet Archive's permanent-address service Redirect can point to any copy of the page
Verification pagethis site; copies on Arweave and Zenodo (DOI 10.5281/zenodo.23188764); any saved copy Any copy verifies signatures in the browser, offline
Artists' public keysbuilt into every copy of the page; keys.jsonTravel with the page
Records and photosregistry; every file also on Arweave, findable by fingerprint The page finds a record on Arweave by the fingerprint on the tag
Specificationspec.html — next to every copyAnyone can rebuild a verifier

How the page finds a record

The page tries, in order, until one source returns bytes with the fingerprint from the tag:

  1. r/<work>.json next to the page itself (this site, or any saved copy with its r/ folder);
  2. other copies of the registry: ?r=<address> in the page address (any number of times), and mirrors built into the page — laid out as <address>/<artist key>/<work>.json or <address>/<work>.json;
  3. the online registry: …/r/<artist key>/<work>.json;
  4. Arweave, through any gateway's GraphQL: the file tagged App-Name: ArtFirma, Type: record, Fingerprint: <fingerprint from the tag>. Photos are found the same way by Type: photo, Sha256: <hash from the record>.

No source has to be trusted, because a record is accepted only if its bytes hash to the fingerprint signed on the tag. The whole registry is also on Arweave under one address, the newest one being listed as arweave_manifest in the registry index — so ?r=https://arweave.net/<manifest>/ works, as does a folder of a backup served from anywhere.

What we do not store

No collector data: no names, no IP addresses, no locations, no cookies, no analytics. The optional online check keeps only the chip ID, the last counter and two timestamps. Artists' 12 words and signing keys never reach us; the service holds only each artist's chip-code key (encrypted), which can neither sign a work nor rewrite a tag.

Check it yourself

With Node.js and no libraries — the record from Arweave, its signature and the artist's key:

// node check.mjs
import { createHash, createPublicKey, verify } from 'node:crypto';
const work = 'contact-states-ii-0137-0151';
const record = Buffer.from(await (await fetch('https://arweave.net/1ddInjhV575xyx6MLIAUv15djtOlhCtfKj7hZBROPyI')).arrayBuffer());
const [pub, sig] = (await (await fetch('https://arweave.net/qlasZMX9gYlQYJXwQKvdh3VHkCk3KAl-i_AbQt60xu4')).text()).trim().split('.');
const sha = createHash('sha256').update(record).digest();
console.log('fingerprint', sha.subarray(0, 16).toString('base64url'));   // = the one on the tag
const key = createPublicKey({ key: Buffer.concat([Buffer.from('302a300506032b6570032100', 'hex'), Buffer.from(pub, 'hex')]), format: 'der', type: 'spki' });
console.log('signed by', pub, verify(null, Buffer.from(`NFCSIGN-RECORD1|${work}|${sha.toString('hex')}`), key, Buffer.from(sig, 'base64url')));

The tag signature is checked the same way, over NFCSIGN1|chip ID|work|fingerprint, with the part after # in the tag's link.