Internals · for collectors, galleries, conservators and anyone who wants to check without trusting us
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.
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>"
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…
| Step | Value |
|---|---|
| SHA-256 of the file | 11c2a31c38c080a2cc45291ea701106263f67a71fb46086d5611796e1cd4e736 |
| Fingerprint | EcKjHDjAgKLMRSkepwEQYg — first 16 bytes, base64url. This is what the tag carries. |
| Artist's key | 9148b582d195330f894192a34aa80a47639237894bf6366eaa0fa12271b9550a, confirmed by
alexanderjameshamilton.com/pages/artfirma |
| Tag signature | Ed25519 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.
| Signature | Signed text | Proves |
|---|---|---|
| On the tag | NFCSIGN1|chip ID|work|fingerprint | This 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 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.
| What | Where | Without us |
|---|---|---|
| Address on the tag | purl.org/artfirma/v — Internet Archive's permanent-address service |
Redirect can point to any copy of the page |
| Verification page | this site; copies on Arweave and Zenodo (DOI 10.5281/zenodo.23188764); any saved copy | Any copy verifies signatures in the browser, offline |
| Artists' public keys | built into every copy of the page; keys.json | Travel with the page |
| Records and photos | registry; every file also on Arweave, findable by fingerprint | The page finds a record on Arweave by the fingerprint on the tag |
| Specification | spec.html — next to every copy | Anyone can rebuild a verifier |
The page tries, in order, until one source returns bytes with the fingerprint from the tag:
r/<work>.json next to the page itself (this site, or any saved copy with its r/ folder);?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;…/r/<artist key>/<work>.json;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.
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.
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.