Home Apps My Licenses FAQ Pricing Register App Get License
Trust anchor by zkLicensing

Trust anchor

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.

Next steps: check trust in four steps

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:

  1. Read the wallet popup carefully. Your wallet shows the destination address — this is the zkApp that will hold your MINA — and the exact amount. Copy the destination address.
  2. Open a public Mina explorer in a new tab — e.g. minascan.io — paste the destination address, and read the account state.
  3. Check the verification-key hash against the Published VK hashes table below. It must equal the "Current" row (for a new purchase or renewal) — or, for a refund of an older license, the row matching that address's original deployment generation. This proves the contract logic is genuinely zkLicensing.
  4. Check the 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.

Published VK hashes

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.

Platform manifest pubkeys

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.

What "trust anchor" means

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:

  • The vendor's branded page claims to be wired to zkLicensing. You don't have to believe the page. You verify by checking whether the destination zkApp's on-chain VK hash matches a row above.
  • The zkApp's on-chain 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.
  • The zk proof the SDK produces after your purchase claims to prove a valid, non-refunded license. Same idea. Any Mina verifier — including one you run yourself — can independently check the proof against the on-chain root and the VK hash on this page.

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 this does and doesn't prove

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.

Why this matters on mainnet specifically

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.

Generation rotation

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:

  • The new row goes to the top with status Current.
  • The previous row's status flips to Retired, but the row stays. Licenses purchased under the retired generation continue to verify against their original VK hash.
  • New purchases route to the current generation automatically.

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.

Building on zkLicensing? The same VK hash table is exported as 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.