zkTLS Integration

End-to-end architecture, privacy model, sybil resistance, and CCA behavior for Umia's zkTLS integration

This page is the implementation-level companion to Umia Extension and zkTLS. It explains how Umia integrates Reclaim proofs into Tailored Auctions, how the privacy model works in practice, how sybil resistance is enforced, and what this changes for the Continuous Clearing Auction (CCA).

Why We Use Reclaim

Tailored Auctions need a way to make statements like "this round is for GitHub contributors" or "this bucket is for a specific community" without turning the auction into a centralized KYC flow.

Reclaim gives Umia a way to:

  • gate early auction steps by offchain credentials;
  • bind a proof to the bidder's wallet so it cannot be replayed by someone else;
  • preserve public onchain execution of the auction itself;
  • add identity-provider-scoped sybil resistance for gated rounds;
  • keep the CCA's price discovery and settlement logic unchanged.

What Umia Actually Integrates

At a high level, Umia combines six layers:

LayerRole
Umia provider registryPublished provider recipes, each with immutable versions, that say what to prove about an offchain account
Umia Extension + attestorThe extension runs the provider flow in the user's browser; Umia's attestor witnesses the TLS session and signs the claim
Umia app frontendRequests proofs, stores them locally, and attaches them to bids as hookData
Umia APISigns proof-request sessions, serves the extension's session and provider endpoints, verifies proofs off-chain, stores registration proofs, computes identity hashes, and serves eligibility
UmiaValidationHookVerifies wallet binding, provider compatibility, monotonic eligibility, and onchain identity ownership
CCACalls the hook on every bid; accepts or rejects the bid based on verification state

The important design choice is that Reclaim is the proof protocol, while Umia is the auction-specific policy layer. Proofs use Reclaim's claim format and its beacon contract, but Umia hosts the stack: the attestor, the provider registry, the session backend and the extension. Umia decides which proofs are accepted for which auction step and how that affects bid eligibility.

Architecture Diagram

configure auction policy deploy, setCCA, enableStepBatch create auction with hook validate hookData fetchWitnessesForClaim eligibility + proof APIs proof request / proof result provider verification flow session, provider config, proof TLS session proxied TLS submitBid with hookData optional pre-submit checks Founder / OpsDefines lock requirements, provider IDs,and provider hashes per step Umia CLI / Deploy FlowDeploys the hook, pairs it to the auction,and enables step-level providers UmiaValidationHookWallet binding, provider checks,monotonic registration, identity gate Continuous Clearing AuctionNormal CCA pricing, clearing, and settlementwith gated bid admission Reclaim Beacon ContractLists the attestor witnesses for each epoch Umia App UIRequests proof configs, stores proofs locally,and submits bids with hookData Umia API + DBEligibility, proof-request config, proof storage,identityKey, permit wallets, hook metadata Umia ExtensionRuns the provider verification flowin the user session Umia AttestorWitnesses the TLS sessionand signs the claim Offchain ProviderGitHub, token source, reputation system, etc.

Sequence Diagram

Open gated auction GET eligibility(auction, wallet) Requirements, current step, proof/permit state Click Verify POST /proof-request Signed session config with wallet context Start verification in extension Provider proof flow over a TLS session Proxied TLS traffic Encrypted response Signed claim Submit proof to the session Proof POST /verify with proof (registration proofs only) verifiedFromStep or error Place bid submitBid(maxPrice, amount, owner, hookData) validate(maxPrice, amount, owner, sender, hookData) fetchWitnessesForClaim(...) Expected epoch witnesses Checks the accepted session, providerHash, wallet context, identityKey Resolve step, check context, provider and identity Verify signatures. A bid proof is consumed, a registration proof stays reusable User App UI Umia API Umia Extension Umia Attestor Provider CCA Validation Hook Reclaim Beacon

Current default app flow

For ordinary early-bid steps, verify off-chain, store the proof in the backend, then send that proof inline in hookData when bidding. Once the wallet is registered onchain, later bids on ordinary proof-gated steps send empty hookData. Steps configured with verifyEveryBid use a server permit while the wallet's allowance covers the bid, otherwise a fresh, amount-bound proof generated for that bid, and ignore standing registration.

How We Integrate zkTLS Into an Auction

1. We deploy and pair the hook

Each gated auction gets a UmiaValidationHook. The hook is paired to a single CCA via setCCA(address).

That pairing matters because the hook caches the CCA's step ranges and resolves the current step from block number during validate().

2. We configure allowed providers per step

Auction steps are configured with providerHash values, plus human-readable providerId labels for UI/indexing. Each accepted hash is also assigned a permanent identity namespace with setIdentityProvider before proofs for it can pass.

In practice, the CLI computes or accepts provider hashes and then calls:

  • enableStepBatch(stepIndices, providerHashesPerStep, providerIdsPerStep) during deployment;
  • addStepProviders(...) or setStepProviders(...) for later updates.

This is how a founder says, for example, "step 0 accepts GitHub provider X" while later steps may accept a broader or different set.

3. We expose readable requirements to the app

The API/indexer layer stores a human-readable description per provider and per step. That is what powers the lock-requirements UI in the app.

4. We mint proof requests server-side

The Umia app never sees the app signing key.

Instead, the API signs a session request with Umia's own app key, pins the exact published provider version, and points the request at Umia's backend. The wallet must be one of the caller's linked wallets. The API records the session for that user and returns a serialized config that carries the session, a short-lived attestor credential, and the chain and beacon epoch the proof is for. During that step, Umia binds the bidder wallet into the proof context:

ReclaimProofRequest.init(appId, appSecret, providerId, { providerVersion, envUrl: umiaBackend })
proofRequest.setJsonContext({ contextAddress: wallet, contextMessage, identityProvider })

contextMessage names the auction: register:<chain>:<auction>:<nonce> for a registration proof, or bid:<chain>:<auction>:<step>:<amount>:<nonce> for a bid proof. The API rejects any other form for an auction.

That wallet binding is critical. It means the proof is not just "someone has this credential". It is "this credential was issued for this wallet context".

5. We verify off-chain before the user bids

The extension submits the proof to the user's session, and the API accepts it only if the attestor's signature, chain, beacon epoch and pinned provider match the session. When the app then stores a registration proof, the API re-checks it and applies Umia-specific policy checks:

  • the auction must actually use a zkTLS hook;
  • the requested step must be enabled and must not verify every bid;
  • the proof must be a registration proof (bid proofs are never stored);
  • the proof must come from the caller's accepted session for this auction and hook;
  • the proof's providerHash and pinned provider must match one of the step's accepted providers;
  • the proof's contextAddress is the wallet bound at request time;
  • the proof is normalized to the earliest step that accepts that provider and whose registration carries to the requested step.

That last point is important for monotonic access. If a proof qualifies a user for an earlier locked step, Umia records the earliest valid step so later bids can reuse that stronger eligibility.

6. We store the proof and reuse it at bid time

After off-chain verification, the proof is stored in the API database and cached locally in the browser. On an ordinary proof-gated step, the frontend sends, in order:

  • empty hookData if the wallet is already registered onchain;
  • an in-memory freshly verified proof;
  • a locally cached stored proof;
  • a backend-stored proof fetched for the wallet;
  • a server permit, only when the wallet holds no proof and the step accepts permits.

A permit-only step always takes a server permit. A step that verifies every bid takes a permit while the wallet's allowance covers the bid, and otherwise a fresh bid proof.

What Goes Onchain

The bid path still ends at the CCA's normal submitBid(...) call, but now hookData may carry verification material.

For Reclaim proofs, Umia encodes:

hookData = abi.encodePacked(uint256 proofStep) ++ abi.encode(Reclaim.Proof)

The validation hook then:

  1. resolves the current auction step;
  2. checks whether that step is gated;
  3. on an ordinary proof-gated step, checks whether the wallet is already registered from this or an earlier step;
  4. if not, decodes hookData and verifies the proof for proofStep;
  5. on an ordinary step, registers the wallet from proofStep for monotonic access; on a step that verifies every bid, consumes the bid proof instead.

This means verification is part of bid admission, not part of price formation.

How This Affects CCA

Reclaim changes who can submit a valid bid in a given step, but it does not change the core economic mechanics of the auction.

CCA surfaceEffect of Reclaim integration
Bid entryA bid may require valid hookData before it is accepted
Clearing priceUnchanged
Fill logicUnchanged
Settlement and liquidity seedingUnchanged
Auction step timingSame CCA steps, but some steps are gated
User stateOnce registered from step N, the user needs no credential at N and later ordinary proof-gated steps

In other words, Reclaim adds an admission-control layer around the auction. It does not fork or replace the CCA itself.

The Privacy Model in Practice

What is private

Umia does not require public KYC and does not need a real-world legal identity to gate an auction. The bidder proves eligibility through a signed Reclaim proof that is bound to their wallet.

What is intentionally bound

The wallet address is intentionally inserted into the proof context. That is how the system prevents replay across wallets.

What is not guaranteed by Umia alone

Privacy depends on what the provider recipe includes in the signed context and extractedParameters fields.

If the provider emits a privacy-preserving pseudonymous identifier, Umia can enforce one-wallet-per-identity without learning a raw username. If the provider emits a raw handle, then that handle may be present in the proof payload and privacy is correspondingly weaker.

Important privacy nuance

Umia's integration is privacy-preserving by architecture, but not magically data-free. The actual privacy level depends on provider design. The safest provider design is to expose only the minimum fields needed for wallet binding, provider selection, and sybil resistance.

The Privacy-Preserving OPRF Piece

The sybil-resistance design is built around a stable identity shared across approved recipes for one account provider:

identityKey = keccak256(abi.encode(identityProvider, canonicalExtractedParameters))

Two details matter here:

  • identityProvider is the permanent namespace assigned to the approved claim hash. Recipes for the same account provider share it;
  • canonicalExtractedParameters is taken verbatim from the signed Reclaim context both off-chain and onchain, so both layers compute the same key byte-for-byte.

Why this is useful:

  • if the provider recipe captures an OPRF-derived pseudonymous identifier, the same real-world account maps to the same stable identity across recipes without exposing the raw account identifier;
  • Umia only needs the stable pseudonym to enforce one-wallet-per-identity;
  • the contract and API never need a traditional user database keyed by a Web2 username.

The OPRF output is computed during proof generation by the attestor Umia operates, and comes back signed inside the proof context. Umia derives identityKey from that signed output and never needs the raw account value.

Sybil Resistance

Umia enforces sybil resistance in two places.

Off-chain gate

When the API stores a proof, it computes identityKey and tries to claim that identity for the current hook.

It uses:

  • a transaction-level advisory lock on (hookId, identityKey);
  • a unique database index on (hook_id, identity_hash);
  • same-wallet re-verification logic that replaces older proof rows.

If a different wallet already claimed that identity, the API rejects the proof with a conflict instead of letting the user proceed to bid.

Onchain gate

The validation hook independently enforces the same identity claim onchain:

  • _identityToUser[identityKey] stores the wallet that owns that identity across approved recipes;
  • first claim wins;
  • re-verification by the same wallet is idempotent;
  • a different wallet reverts with IdentityAlreadyClaimed(...).

This means the backend improves UX and reduces wasted bid attempts, while the contract remains the final enforcement layer.

Missing or empty extractedParameters fail verification on both paths. Recipes grouped under one namespace must emit the same stable account value and canonical JSON shape.

Submission Modes

Umia supports two verification modes in the same hook:

ModeHow it worksTypical use
Inline Reclaim proofBid carries proofStep + Reclaim.Proof in hookDataCurrent default app UX
Server permitBid carries an EIP-712 permit in hookData (inline-only; no pre-submission path)Admin-curated or fallback allowlists

The same auction can mix Reclaim steps and permit-enabled steps.

Why the Monotonic Model Matters

Registration is monotonic: if a wallet registers at step 0, it is also valid for later ordinary proof-gated steps 1, 2, 3, and so on. Permit-only steps and steps that verify every bid still ask for a credential on each bid.

That matters because Tailored Auctions often want a funnel like:

  • early blocks reserved for a narrow cohort;
  • later blocks widened to a broader cohort;
  • final blocks fully open.

Without monotonic registration, users would need to re-prove eligibility at every step transition. With monotonic registration, one strong proof unlocks the rest of the auction path that should logically include that user.

Tradeoffs and Design Intent

This integration intentionally chooses:

  • onchain enforcement over trust in the frontend;
  • wallet-bound proofs over reusable anonymous bearer credentials;
  • identity-provider-scoped sybil resistance over fully open one-wallet-many-wallet behavior;
  • optional backend prechecks for UX, while keeping the contract as the source of truth.

The tradeoff is that privacy quality depends on provider design, and the first gated bid carries more verification data than a fully open bid.

Summary

Reclaim gives Umia a way to add offchain audience targeting to an otherwise fully onchain auction.

  • Reclaim proves eligibility.
  • Umia decides which providers are allowed for which auction steps.
  • The API binds proofs to wallets, stores proofs, and blocks obvious sybil reuse early.
  • The validation hook enforces the same rules onchain during validate().
  • The CCA keeps its native auction mechanics; only bid admission changes.

For the user-facing overview, see Umia Extension and zkTLS. For the contract-level interface, see Validation Hook.