On mainnet, every zkLicensing buy, renew, and refund flow is served from the vendor's own branded page — not from this domain. That means the buyer is trusting the vendor's UI, on the vendor's domain, with the vendor's TLS certificate. The question this page answers is the one that doesn't depend on any of that: is the on-chain contract that will hold my MINA actually the zkLicensing circuit? You can prove it yes-or-no by comparing two on-chain fields against the table below.
You'll typically arrive here from a vendor's own site — something like
store.vendor.com/buy — where the buy button opens your Mina wallet
with a pre-filled transaction. Before signing, do these four things:
zkappUri field. Every zkLicensing zkApp has its
vendor's website URL written into zkappUri at deploy time — and the on-chain
permission setZkappUri is set to impossible, so no one, including
the vendor, can change it after the fact. The URI you see on the explorer must equal the
domain shown in your browser's URL bar. This proves the contract belongs to the vendor
whose page you're actually on — not a phisher who registered a genuine zkLicensing app
under a different domain and pointed customers at it.
Both must match. Together they mean the smart contract that will actually hold, release, or refund your MINA is the zkLicensing circuit and belongs to the vendor whose page you're on — regardless of what the branded page claims or looks like. Any mismatch is a stop signal: cancel the transaction and report the vendor.
Each row is a generation of the zkLicensing circuit — the smart contract that holds refund-window escrow, enforces expiry, and verifies your license proof. Every zkApp deployed by the zkLicensing platform, on behalf of any vendor, has a verification-key hash equal to its generation's row here. New generations are appended at hardfork migrations, or in the rare case that a security issue is found in the circuit. Historical rows are never removed, so licenses bought under a retired generation continue to verify against the row they were deployed under.
| Generation | Status | VK hash (Field, decimal) |
|---|---|---|
| 1 | Current | 8380713395198105004709629742590481719045223404635446983829642411637960196723 |
Source: /trustanchor/vk-hashes.json
The Field is a decimal integer — the same format Mina explorers display. Copy it verbatim for comparison; a single-character mismatch means it's not the same circuit.
The Ed25519 keys the platform uses to sign the /apps/:appId generation
list and each vendor-hosted .well-known/zklicensing/<zkAppAddress>.json.
Pin the current key when calling fetchGenerationInfo or
zklicensing-verify-trust; the rotation window lists more than one row so
old signatures continue to verify while the new key propagates.
| Status | Pubkey (Ed25519, base64) |
|---|---|
| Current | O5BBGskSzE+BOxgf3rukMiV2e4t0I1oTwi0ERtvbUfA= |
Source: /trustanchor/manifest-pubkeys.json
zklicensing-verify-trust — automating the four checks
The four checks above are the ones that matter, whether you do them
by hand in an explorer tab or with a tool. The
zklicensing-verify-trust CLI is a convenience for the
latter: same pipeline — fetch the vendor manifest, verify its
platform signature, read the on-chain zkApp — in one command. Either
approach reaches the same answer; use whichever fits your workflow.
Install it once:
npm install --global zklicensing o1js
Run it against the vendor origin from your browser bar and the zkApp address your wallet is showing you:
zklicensing-verify-trust \
--origin https://store.example.com \
--address B62q... \
--pinned-key <copy from the Platform manifest pubkeys table above> \
--cross-check
--cross-check is optional — it adds a comparison against
the zkLicensing backend's own signed record, catching a vendor serving
a stale or rolled-back manifest even when its signature still
verifies. Skip it for a purely local check.
The first line of the output is the one-glance answer:
TRUST ANCHOR VERIFIED
origin: https://store.example.com
address: B62q...
appId: my-app-v1
generation: 1
...
TRUST ANCHOR VERIFIED means every check passed;
TRUST ANCHOR FAILED means one didn't, and the
[FAIL] line below the header names which. Exit code is
0 or 1 respectively, so the same command
can slot into a purchase script that only hands the transaction to
a wallet once the anchor has verified.
A trust anchor is the root of a verification chain — the one thing you have to
trust up front, and from which every other check follows. In web TLS, the trust anchors are
the root certificate authorities baked into your browser: you trust
letsencrypt.org's certificate because your browser trusts the CA that signed
it, not because the site itself vouches for its own certificate. The chain has to bottom
out somewhere; the trust anchor is where.
For a zkLicensing purchase, the trust anchor is this page — the published verification-key hashes above. Everything else in the flow chains its trustworthiness back to that root:
zkappUri claims to belong to the vendor
whose page you're on. You don't have to believe that either. You verify by
checking that the URI matches the domain in your browser bar.
When a page carries the "Trust anchor by zkLicensing" badge, it's inviting you to run the four-step check above. The badge itself is not the guarantee — it's a signpost to the guarantee. A malicious page can trivially display the badge; only the on-chain data, pulled by you from a public Mina explorer, can tell you whether the badge is telling the truth. That asymmetry is the point: the trust anchor doesn't depend on the page you're on, which is exactly why it works when the page you're on is a vendor's, a mirror's, or a phisher's.
What it proves: the smart contract at the destination address is running the published zkLicensing logic — correct escrow of the 14-day refund window, correct expiry enforcement, correct verification of your zk proof, and no back-door path for the vendor to drain funds outside those rules. This is the strongest cryptographic guarantee the Mina protocol makes available: the on-chain code cannot lie about which circuit it is.
What it doesn't prove: that the vendor's branded page itself hasn't been tampered with, phished, or served over a lookalike domain. Those are the vendor's responsibility (their domain, their TLS certificate, their build pipeline) — check the URL bar and the padlock. The two checks are complementary: the URL bar tells you which vendor's page you're on; the VK-hash check tells you whether that page is genuinely wired to zkLicensing. A malicious page could show you the zkLicensing logo and still send your MINA to an unrelated contract — the wallet popup + explorer check is what catches that.
On mainnet, real money moves. Vendor-branded buy / renew / refund pages are the only supported customer-facing flow — zkLicensing does not operate a mainnet consumer storefront. That means the buyer has no "official" site to fall back on for visual trust; instead the trust root is this page plus the public Mina chain. Any vendor page can be independently audited by any buyer, from any browser, at any time, with two clicks: wallet popup → Mina explorer.
We append a new row above whenever the circuit changes in a way that produces a different verification key — for example, a security fix or a new contract capability. When that happens:
If you're verifying a license older than the current generation, the zkApp address for that license may map to a retired row — check the row that matches the address's original deployment history, not just the topmost row.
CANONICAL_VK_HASHES from the SDK
(zklicensing/contractInterface). The keeper asserts the on-chain hash matches
CANONICAL_VK_HASH at boot, and the verify service resolves an address's
generation before checking the proof — the drift test in the repo catches accidental
changes at PR time. See the Docs for details.