vry:st documentation

Everything you need to attest a credential, prove a claim, and settle a receipt on Robinhood Chain. Read sections 01–04 first; the rest is reference.

01Overview

vry:st is a selective-disclosure layer. It lets a wallet answer one exact question from a verifier — is this holder over 18, is this holder in a permitted jurisdiction, does this holder own at least one unit of asset X — with a zero-knowledge proof, and lets the answer be settled on Robinhood Chain as a reusable receipt.

The protocol has three moving parts:

  • Issuers sign credentials about a holder once.
  • Holders keep credentials locally and generate proofs from them.
  • Verifiers publish the claim they need and accept receipts.

Nothing about the holder is written on-chain except a boolean, the claim it answers, an expiry, and a nullifier. The raw credential never leaves the holder's device.

02Vocabulary

termmeaning
credentialA signed statement from an issuer about a holder, stored encrypted in the holder's wallet.
schemaThe typed shape of a credential (e.g. residency/v1).
claimA predicate over one or more credentials (e.g. age >= 18).
proofA zero-knowledge argument that the holder's credentials satisfy the claim.
receiptThe on-chain record of a verified proof: claim id, result, scope, expiry, nullifier.
scopeWhich verifiers may consume a receipt.
nullifierA per-scope pseudonym that prevents double-use without revealing the holder.

03Threat model

vry:st is designed so that the following parties learn nothing beyond what is listed.

partylearnsdoes not learn
verifierclaim result, expiry, scope-bound nullifieridentity, credential contents, other receipts
issuerthat it issued a credentialwhere or when the holder proved anything
chain observerthat a receipt exists for claim id Cwho the holder is, which issuer signed
vry:st contractsthe same as a chain observereverything else

Out of scope: a compromised holder device, a malicious issuer signing false credentials (mitigated by the issuer registry and revocation), and metadata leakage from the holder's own RPC provider (mitigated by relayers, see §21).

04Quickstart

Ten lines from zero to a settled receipt. This assumes a browser wallet with the vry:st module installed (§27).

import { vryst } from "@vryst/sdk";

// 1. load a credential (one-time, from an issuer)
await vryst.attest({ issuer: "0xKYC…", schema: "residency/v1" });

// 2. prove a claim the market asked for
const proof = await vryst.prove({ claim: "jurisdiction in permitted_set", reveal: [] });

// 3. settle the receipt on Robinhood Chain
const receipt = await vryst.settle(proof, { ttl: "30d", scope: "0xMarket…" });

console.log(receipt.id); // vry:st/0x9f2a…c41e

05Install the SDK

npm install @vryst/sdk
# or
pnpm add @vryst/sdk

The SDK ships two entry points: @vryst/sdk for holders (browser and mobile), and @vryst/sdk/verifier for verifiers (server or contract-side helpers). Proving runs in WebAssembly; expect a first-load download of the proving key (~6 MB, cached).

Network. Default configuration targets Robinhood Chain mainnet. Pass { network: "testnet" } to the client constructor to use the test deployment.

06Credentials

A credential is a set of typed fields committed to and signed by an issuer. It is not a token, is never transferred, and is never published. The holder stores it encrypted under a wallet-derived key.

Lifecycle

  1. Holder requests a credential from an issuer (off-chain, issuer's own flow).
  2. Issuer signs a commitment to the fields plus a holder binding.
  3. Holder imports it with vryst.attest().
  4. Holder proves claims against it as many times as they like.
  5. Issuer may revoke; the next proof will fail (§14).

07Schemas

Schemas define field names, types and constraints. They are versioned and registered on-chain so that any verifier can write a claim against a known shape.

{
  "id": "residency/v1",
  "fields": {
    "jurisdiction": { "type": "iso3166-2" },
    "verified_at":  { "type": "timestamp" },
    "level":        { "type": "enum", "values": ["basic","enhanced"] }
  }
}

Core schemas at launch: identity/v1 (age, nationality), residency/v1, accreditation/v1, sanctions/v1, holding/v1 (asset ownership snapshot). Anyone can register a new schema; verifiers choose which to trust.

08Claims

A claim is a small predicate language over schema fields. It is deliberately limited so that every claim compiles to a bounded circuit.

operatorexample
comparisonage >= 18
set membershipjurisdiction in permitted_set
set exclusionsubject not in sanctions_list
freshnessverified_at > now - 365d
conjunctionage >= 18 and jurisdiction in permitted_set

Claims are hashed into a claim id. Two verifiers asking the same question get the same claim id, which is what makes receipts reusable.

09Proofs

A proof demonstrates that (a) the holder possesses a credential signed by an issuer in the accepted issuer set, (b) the credential satisfies the claim, (c) the credential is not revoked as of a recent root, and (d) the nullifier is correctly derived — without revealing which issuer, which credential, or any field values.

Proof generation happens entirely client-side. Typical timings on a 2024 laptop: 300–700 ms; on a mid-range phone: 1–2 s.

10Receipts

A receipt is what actually lives on Robinhood Chain.

struct Receipt {
  bytes32 claimId;
  bool    result;
  bytes32 nullifier;   // scope-bound
  bytes32 scope;       // verifier address or ANY
  uint64  expiresAt;
  uint64  settledAt;
}

Receipts have no owner field. The link between a wallet and a receipt is established only inside the proof, and only to the verifier the receipt is scoped to.

11Scopes & TTL

Scope limits who can consume a receipt. scope: "0xMarket…" means only that contract's checks will pass; scope: "any" produces a receipt any verifier can read, at the cost of a shared nullifier across verifiers (weaker unlinkability, see §20).

TTL is set by the holder at settlement, bounded by a verifier-declared maximum. After expiry the receipt is inert. There is no renewal transaction; the holder simply proves again.

Recommendation. Default to a narrow scope and a short TTL. Broad scopes are convenient and slightly less private.

12Issuers

Issuers are the trust roots. An issuer registers a signing key and the schemas it will sign in the issuer registry (§19). Verifiers choose their own accepted issuer set; vry:st does not curate one.

The proof shows membership in the verifier's accepted set without revealing which member signed. A verifier accepting five KYC providers cannot tell which one a holder used.

13Verifiers

A verifier is any contract or service that publishes a requirement: a claim, an accepted issuer set, a maximum TTL, and a scope policy. Requirements are registered on-chain so wallets can discover them and prepare proofs before the user even reaches the action.

vryst.verifier.publish({
  claim: "jurisdiction in permitted_set and age >= 18",
  issuers: ["0xA…", "0xB…", "0xC…"],
  maxTtl: "90d",
  scope: "self"
});

14Revocation

Each issuer maintains a revocation accumulator. Proofs include a non-membership argument against a recent accumulator root. Verifiers set how recent "recent" must be (default: 24 h).

Revocation is forward-only: existing receipts stay valid until they expire. This is deliberate — retroactively invalidating a receipt would leak that the specific holder was revoked. Short TTLs are the correct tool if a verifier needs faster reaction.

15Architecture

holder device                  Robinhood Chain
┌──────────────────┐           ┌──────────────────────┐
│ wallet module    │  settle   │ ReceiptRegistry      │
│  · credentials   │──────────▶│ IssuerRegistry       │
│  · prover (wasm) │           │ SchemaRegistry       │
└──────────────────┘           │ Verifier contracts   │
        ▲                      └──────────────────────┘
        │ attest                          ▲ check
┌──────────────────┐                      │
│ issuer           │           ┌──────────────────────┐
│  · signs creds   │           │ market / app         │
│  · revocation    │           └──────────────────────┘
└──────────────────┘

No vry:st server sits in the proving path. A relayer service exists to submit settlement transactions on the holder's behalf (§21) but it is optional and replaceable.

16Proof system

vry:st uses a transparent-setup SNARK with universal circuits per claim operator family. Concretely, every claim compiles to one of a small set of fixed circuits parameterised by public inputs (claim id, issuer set root, revocation root, scope, nullifier). This keeps verification cost on Robinhood Chain constant regardless of how complex the underlying credential is.

Signature scheme for issuers is an in-circuit-friendly curve signature. Commitments are Poseidon-based. The proving key is public and reproducible from the circuit sources.

17Credential format

{
  "schema": "residency/v1",
  "fields": { "jurisdiction": "TR-34", "verified_at": 1757721600, "level": "enhanced" },
  "holder": "<holder commitment>",
  "issuer": "0xKYC…",
  "nonce":  "<random>",
  "sig":    "<issuer signature over commit(fields, holder, nonce)>"
}

The holder commitment binds the credential to a wallet-derived secret, so a stolen credential file cannot be proved from another wallet.

18Receipt registry contract

functiondescription
settle(proof, publicInputs)Verifies the proof, writes a Receipt, emits Settled.
check(claimId, scope, nullifier)Returns the receipt's result and expiry if live.
isLive(receiptId)Boolean.

The registry is non-upgradeable. New proof systems are deployed as new registries; verifiers opt in.

19Issuer registry contract

Stores issuer keys, supported schemas, and the current revocation root per issuer. Issuers update their own roots; nobody else can. Verifiers reference issuer addresses when publishing requirements, and the registry supplies the Merkle root of any accepted set on demand.

20Nullifiers & unlinkability

The nullifier is H(holderSecret, claimId, scope). Properties:

  • Same holder, same claim, same scope → same nullifier. A verifier cannot be double-served.
  • Same holder, different scope → different nullifier. Two verifiers cannot join their records.
  • Same holder, different claim → different nullifier. A verifier asking two questions cannot trivially link them unless it scopes both to itself and asks them together.

With scope: "any" the third property weakens: every verifier sees the same nullifier per claim. Prefer narrow scopes when unlinkability across venues matters.

21Settlement on Robinhood Chain

Settlement is an ordinary transaction to the receipt registry. Holders can submit it themselves or hand the proof to a relayer, which submits on their behalf and is paid from the settlement fee. Using a relayer prevents the holder's gas-paying address from being associated with the receipt.

Finality follows Robinhood Chain's own; verifiers may accept a receipt as soon as it is included.

22Fees

  • Attest: free. Off-chain.
  • Prove: free. Local computation.
  • Settle: gas plus a small protocol fee that funds relayers and issuer-registry maintenance.
  • Check: gas only, paid by the verifier.

Exact figures are published in the changelog and can change; the SDK exposes vryst.fees() for the live values.

23vryst.attest()

vryst.attest(options: {
  issuer: Address;
  schema: string;
  credential?: CredentialFile;  // if already obtained
}): Promise<{ credentialId: string }>

If credential is omitted, the SDK opens the issuer's registered onboarding URL and awaits a signed credential via the wallet callback.

24vryst.prove()

vryst.prove(options: {
  claim: string;              // predicate, see §08
  reveal?: string[];          // fields to disclose in plaintext (default: none)
  issuers?: Address[];        // restrict to accepted set
  scope?: Address | "any";
}): Promise<Proof>

reveal exists for the rare case a verifier legitimately needs a value (e.g. a tax residency code). Revealed fields are included as public inputs and shown to the user for explicit consent before proving.

25vryst.settle()

vryst.settle(proof: Proof, options: {
  ttl: string;                // "30d", "12h"
  scope: Address | "any";
  relayer?: "default" | "none" | URL;
}): Promise<Receipt>

26vryst.verify()

import { verifier } from "@vryst/sdk/verifier";

const ok = await verifier.check({
  claimId, scope: SELF, nullifier
}); // { result: true, expiresAt: 1760313600 }

For on-chain use, call the registry directly (§28).

27Wallet module

The wallet module is what stores credentials and runs the prover. It ships as a browser extension companion and as an embeddable library for wallet vendors. Responsibilities:

  • Derive the holder secret from the wallet key without exposing it.
  • Encrypt credentials at rest.
  • Show the user exactly what a proof will disclose before signing off.
  • Cache proving keys and revocation roots.

28Verifier contract interface

interface IVrystVerifier {
  function requirement() external view returns (Requirement memory);
  function gate(bytes32 nullifier) external view returns (bool);
}

// example use inside a market contract
modifier onlyEligible(bytes32 nullifier) {
  require(REGISTRY.check(CLAIM_ID, address(this), nullifier).result, "vry:st: not eligible");
  _;
}

29Errors

codemeaningfix
E_NO_CREDENTIALNo stored credential matches the schema.Run attest() first.
E_CLAIM_FALSECredential exists but does not satisfy the claim.None; the proof will settle as false if you choose to settle it.
E_REVOKEDIssuer revoked the credential.Re-onboard with the issuer.
E_STALE_ROOTCached revocation root older than verifier allows.SDK refreshes automatically; retry.
E_SCOPE_MISMATCHReceipt scoped to a different verifier.Settle again with the correct scope.
E_EXPIREDReceipt TTL passed.Prove and settle again.

30Guide: gating a tokenized-equity market

  1. Decide the claim. Most equity venues on Robinhood Chain need jurisdiction in permitted_set and age >= 18, some add accredited == true.
  2. Pick issuers you accept. Three or more is recommended so holders have a choice and your accepted set does not leak which provider they used.
  3. Publish the requirement (§13).
  4. Add the onlyEligible modifier to order-placing functions (§28).
  5. In your frontend, call vryst.prepare(requirementId) on page load so the proof is ready before the user hits "buy".

31Guide: becoming an issuer

  1. Generate an issuer keypair with cli issuer keygen.
  2. Register the key and your schemas in the issuer registry.
  3. Integrate the signing step at the end of your existing verification flow: cli issuer sign --schema residency/v1 --holder <commitment>.
  4. Run the revocation service, which publishes a new accumulator root on a schedule you choose.
  5. List your onboarding URL so wallets can route new holders to you.

32Privacy checklist

  • Ask for the narrowest claim that actually gates the action.
  • Never use reveal unless a regulation names the field.
  • Scope receipts to your own contract.
  • Set the shortest TTL your UX can tolerate.
  • Accept several issuers, not one.
  • Use a relayer for settlement so the gas payer is not the holder.
  • Do not log nullifiers with IP addresses.

33FAQ

Is a receipt a token?

No. It has no owner, cannot be transferred, and is only meaningful together with a proof the holder controls.

Can vry:st see my credential?

No. There is no vry:st server in the proving path. Credentials are encrypted on your device.

What if my issuer disappears?

Existing credentials keep proving until the verifier removes that issuer from its accepted set or the credential's freshness bound expires.

Can a verifier find out which issuer I used?

Not from the proof. Only that it was one of the accepted set.

Is this KYC?

It sits after KYC. An issuer still verifies you once; vry:st lets you reuse that fact without re-sharing the documents.

34Glossary

accepted issuer set — the issuers a verifier trusts. accumulator — a compact structure for proving non-revocation. claim id — hash of a claim. holder secret — a wallet-derived value binding credentials to a person. nullifier — per-scope pseudonym preventing reuse. receipt — the on-chain record. relayer — a service submitting settlements for a fee. requirement — a verifier's published claim, issuer set, TTL and scope policy. scope — the consumer a receipt is bound to. TTL — how long a receipt lives.