Skip to main content
This page is for the engineer running KYC in-house who needs verified customers on-chain. After reading it you will know which artifacts leave your infrastructure, which never do, and which claims a proof can and cannot back. You run the trusset-kyc-zk-proofs tool on your own machines. It reads your KYC records and writes, per subject, a Merkle root to publish on chain, one zk-STARK per proved field, and a manifest signed with your operator key.

What leaves your infrastructure

Trusset never sees the underlying KYC data. Names, birth dates, nationalities and document numbers stay on your machine, and no endpoint accepts them. What does travel is the evidence that a predicate held:
A production instance requires the STARK bytes and verifies each one before filing a claim. Sending only the manifest is refused with PROOF_MISSING.A commitment carries no value and a STARK reveals nothing beyond the predicate, so uploading them discloses no personal data. secrets.enc is the file that would, and it is never accepted. Keep it and your .trusset/keys.env on your own machines.

Enforcement depends on the instance

Two settings decide how strict a submission is judged, and they are not the same field. A PRODUCTION instance requires proofs and rejects stub manifests, whatever the instance itself is set to. A DEVELOPMENT instance does neither by default, which is what makes a structural dry run possible without generating STARKs. An instance can also opt in on top of that. Read both from Get Operator Key: proofVerificationEnabled is what you asked for, proofVerificationEnforced is what a submission is actually judged against. Gate on the enforced flag.

Run the proof tool

init asks four questions about your institution, generates both keys, pins the batch date, and prints your operator public key. Load the keys, drop records in input/, and run it:
The root hash appears in three places and they are the same value: kycHash.hex, the kycHash field of manifest.json, and the kycHash column of the batch CSV.
init pins the batch date into config.json. Leave it pinned. Every per-subject key and commitment derives from that number, so a bundle proved against a moving clock cannot be reproduced tomorrow, which is exactly what an auditor asks for.

From bundle to verified customer

1

Register your operator key, once

Every manifest signature is checked against this key. Take the value npm run show prints.
Rotating it later does not re-verify anything already filed. Claims added under the old key stay on chain, and their manifests no longer verify against the new one. Keep the old key with the bundles it signed.
2

Verify on-chain with the root hash

Load the bundle’s manifest.json and call verifyFromManifest. The wallet address, root hash, country, investor type, and expiry days are read straight from the manifest; pass overrides for anything you want to change.
result.kycHash equals manifest.kycHash once the root hash is on-chain. result.operation is verify for a new identity and update for an existing one.If you only stored the root hash (for example from the batch CSV), call verify directly with kycHash:
3

Add proof-backed claims

With the root on chain, the bundle can back claims. Send the signature and the STARKs alongside the manifest.
claims.added counts what landed, and claims.claims holds one entry per claim as { claimType, dataHash, status, txHash }. One transaction is sent per claim. Use selected: ['KYC'] to add a subset.
4

Check the result

status.onChain.isVerified is now true and status.onChain.kycHash matches the manifest.

Which claims a proof can back

The KYC claim always derives from the root hash. Field-level claims derive from the manifest’s proof leaves: A canonical field proved with a different circuit is skipped rather than filed elsewhere. A country leaf proved with preimage_knowledge backs nothing, because filing it under another slot would let it read as an unrelated attestation. AML, TAX_RESIDENCY, SANCTIONS_CHECK, and PEP_CHECK are not proof-backed. Add them with addClaim. Those four are also the only slots a leaf with no canonical meaning may be filed under.

Re-proving a subject

Re-running the tool always produces a new root, because the batch date is bound into every leaf hash. The bundle is legitimate and supersedes the registered one, but its root no longer matches what the registry holds. addClaimsFromProof refuses that with HASH_MISMATCH. Rotating the registered root to the new bundle is a separate, opt-in operation: see Attest Proofs, which takes the rotation transaction alongside the claims.

Onboard in batch

The tool’s batch CSV maps row-for-row onto batchVerify entries - up to 500 subjects in one on-chain transaction:
Addresses that are already actively verified are skipped on-chain (their existing root hash is kept); revoked identities are re-verified. Claims still go one bundle at a time, because each one carries its own proofs.

Troubleshooting

A verdict is stored against the bundle, so re-submitting the same bundle answers from the recorded report rather than re-running the STARK verification. verification.cached tells you which happened. A transient VERIFY_BUSY or VERIFY_TIMEOUT is never cached.