ZK-Based Ticket System for Store Service Incentivization MVP

Abstract

This document proposes an MVP architecture for incentivizing Waku Store service provision
through a ZK-based ticket payment protocol.

The MVP focuses on core ticket mechanics with a single Service Provider operating six Service Nodes. The architecture implements a ticket-based payment system that prevents trivial fraud,
ensures unlinkability between payment and service provision, and supports batch ticket operations.

This proposal follows a recent Service Incentivisation MVP discussion and builds upon earlier work linked there.

Background

Waku uses request–response protocols like Store to extend its Relay backbone. This enables edge nodes (such as smartphones and browsers) to retrieve missed messages.

Currently, Status runs its own Store Service Nodes. The goal of the MVP is to integrate existing Service Nodes operated by Status as the Service Provider into a new ticket-based service protocol providing unlinkability.

The long-term goal is to extend the protocol to a decentralized marketplace of independent Service Providers. Required developments might include reputation systems, progressive trust mechanisms for Service Provider selection, dynamic pricing, and alternative payment mechanisms.

MVP Requirements and Assumptions

Requirements for the MVP:

  • Security: Prevent trivial fraud; no catastrophic loss from a single session.
  • Unlinkability: Break the link between payment and service provision.
  • Bearer instruments: Possession of a ticket grants service access rights.
  • Efficiency: Support for batch operations; no per-request on-chain transactions.
  • Simplicity: Minimal protocol and contract surface for rapid iteration.
  • Upgradeability: Clear path to improvements.

The MVP makes the following simplifying assumptions:

  • Single Service Provider managing six known Service Nodes, therefore:
    • no Provider deposits or slashing;
    • no Provider discovery and selection mechanism.
  • The Service Provider provides state sync with TicketRegistry for its Service Nodes.
  • Sponsor determines the expiry period for ticket commitments it funds.
    • When Sponsor and User are separate entities, User must trust Sponsor to include correct expiry in funding transaction.
  • Unit-of-service (UoS) defined as a single request-response interaction.
  • Fixed pricing per UoS with no negotiation.
  • Single gasless L2 chain deployment.
  • Single ERC-20 token support.

The MVP focuses on proving the core ticket issuance, spending, and redemption mechanics
without reputation systems. It emphasizes simplicity and feasibility, while establishing an upgrade path to a decentralised marketplace.

Future development will involve a multi-Service Provider marketplace with reputation systems
and progressive trust mechanisms for Service Provider selection.

MVP Architecture

In this section, we describe a ZK-based ticket payment system, which is the central part of the MVP version of the Store incentivization protocol.

Terminology

This section establishes the vocabulary of the protocol actors and their interactions.

The terminology follows privacy literature conventions, avoids direct association with financial instruments, and clearly distinguishes human-managed entities from contracts (using Registry suffix).

Objects

Ticket:
A random secret value representing one unit of service (UoS). A ticket is a bearer instrument:
it can be transferred off-chain between Users. A ticket is used to generate ticket commitments (for funding) and nullifiers (for spending).

Ticket commitment:
A cryptographic commitment to a ticket: commitment = hash(ticket_secret, blinding_factor). A ticket commitment may represent one ticket or a Merkle root of multiple tickets. The commitment is recorded on-chain in the TicketRegistry contract.

Nullifier:
A unique provider-specific ticket spending identifier: nullifier = hash(ticket_secret, provider_id). A nullifier is revealed as part of an eligibility proof. A nullifier can be redeemed for cash in the TicketRegistry contract exactly once. Redeemed nullifiers are tracked in the TicketRegistry contract.

Eligibility proof:
A proof package submitted by a User to a Service Node alongside a request. An eligibility proof consists of a nullifier and a ZK proof demonstrating possession of a funded ticket. An eligibility proof is verified by a Service Node before serving a request.

ZK Proof Statement:

Public inputs: nullifier, ticket_commitment, provider_id, registry_merkle_root, block_number
Private inputs: ticket_secret, blinding_factor, merkle_path

Proof: 
I possess ticket such that ticket_commitment = hash(ticket_secret, blinding_factor)
nullifier = hash(ticket_secret, provider_id)
ticket_commitment is included in registry_merkle_root via merkle_path
ticket_commitment was unspent as of block_number

Service request:
A request sent by a User to a Service Node that includes an eligibility proof.

Unit of service (UoS):
A response to a service request.

Actors

User:
An entity that consumes Waku Store services. The User generates ticket secrets, creates ticket commitments for funding, and attaches eligibility proofs to requests. Users may interact with any Service Nodes managed by a single Service Provider (MVP version). In future versions, Users select optimal Service Providers based on progressive trust assessment.

Sponsor:
An entity that funds ticket commitments received from Users by sending a funding transaction to TicketRegistry. A Sponsor determines an expiry period for ticket commitments it funds. Sponsors may or may not be the same entity as the User.

Service Provider:
An entity that manages one or multiple Service Nodes. The Service Provider maintains blockchain connectivity, syncs TicketRegistry state, and distributes state updates to all its Service Nodes. A Service Provider maintains a unified nullifier set across all its Service Nodes and provides real-time nullifier validation for them. A Service Provider handles batch redemption
of collected nullifiers through TicketRegistry.

The following actors are out of scope:
network observers (ISPs); blockchain entry points (RPC Service Providers); and blockchain operating entities (block producers, miners, validators).

Infrastructure

Service Node:
A Waku node running the Store service and accepting requests from Users. Service Nodes are operated by a Service Provider that may manage multiple Service Nodes. Service Nodes delegate blockchain communication to their Service Provider. A Service Node verifies an eligibility proof as follows:

  • verify ZK proofs against locally cached blockchain state;
  • forward nullifiers to their Service Provider for double-spend prevention.

TicketRegistry:
An on-chain contract that tracks funded ticket commitments, maintains spent nullifier sets to prevent double-spending, and pays Service Providers for valid nullifier redemptions. It also allows Sponsor reclamation of unused tickets after expiry.

ProviderRegistry (post-MVP):
An on-chain contract that maintains a registry of Service Providers. Service Providers must lock up a deposit to join the protocol. The registry handles provider deposits and withdrawals, enforces withdrawal delays to prevent “scam-and-exit” behavior, implements slashing for systematic service failures, and presents observable on-chain metrics that enable Users to discover and compare Providers.

Protocol Diagrams

Flow Diagram

flowchart LR
    User[User]
    Sponsor[Sponsor]
    TicketRegistry[TicketRegistry]
    ServiceProvider[Service Provider]
    ServiceNode[Service Node]

    User -->|Funding request| Sponsor
    Sponsor -->|Fund commitments| TicketRegistry

    ServiceProvider <-.->|Sync state| TicketRegistry
    ServiceProvider -.->|Distribute state| ServiceNode

    User -->|Service request| ServiceNode
    ServiceNode <-->|Double-spend nullifier check| ServiceProvider
    ServiceNode -->|Service response| User

    ServiceProvider <-.->|Redeem batch| TicketRegistry

    Sponsor <-.->|Reclaim expired| TicketRegistry

Sequence Diagram

sequenceDiagram
    autonumber
    participant User
    participant Sponsor
    participant ServiceProvider
    participant ServiceNode
    participant TicketRegistry

    Note over User: 1. Ticket Creation
    User->>User: Generate ticket_secret(s)
    User->>User: Compute ticket_commitment(s) (optionally Merkle root for batch funding)

    Note over User,Sponsor: 2. Ticket Funding
    User-->>Sponsor: Send ticket commitment
    Sponsor->>TicketRegistry: fund(commitments, expiry)
    TicketRegistry-->>Sponsor: Record commitments funded with expiry

    Note over ServiceProvider,ServiceNode: 3. State Synchronization
    ServiceProvider->>TicketRegistry: Sync funded commitments
    TicketRegistry-->>ServiceProvider: registry_merkle_root
    ServiceProvider->>ServiceNode: Distribute registry_merkle_root to all Service Nodes

    Note over User,ServiceNode: 4. Service Request
    User->>User: Construct eligibility proof (nullifier, ZK proof, registry_merkle_root)
    User->>ServiceNode: Service request + eligibility proof

    Note over ServiceNode,ServiceProvider: 5. Service Provision
    ServiceNode->>ServiceNode: Verify ZK proof including Merkle inclusion and staleness window
    ServiceNode->>ServiceProvider: Forward nullifier for double-spend check
    ServiceProvider->>ServiceProvider: Check nullifier against unified set
    ServiceProvider-->>ServiceNode: Accept or reject
    alt Valid proof and nullifier accepted
        ServiceNode-->>User: Serve requested data (UoS)
    else Rejected
        ServiceNode-->>User: Reject request
    end

    Note over ServiceProvider,TicketRegistry: 6. Redemption
    ServiceProvider->>ServiceProvider: Collect accepted nullifiers and proofs
    ServiceProvider->>TicketRegistry: redeemBatch(nullifiers, eligibility proofs)
    TicketRegistry->>TicketRegistry: Verify proofs, check unspent, mark spent
    TicketRegistry-->>ServiceProvider: Transfer payment
    Note over Sponsor,TicketRegistry: After expiry, Sponsor may reclaim unused commitments
    Sponsor->>TicketRegistry: reclaimExpired(commitments)

Protocol Flow

1. Ticket Creation

  • User generates random ticket secrets;
  • User computes a ticket commitment from ticket secrets (optionally as a Merkle root for batch funding).

2. Ticket Funding

  • User sends a ticket commitment to Sponsor (off-chain);
  • Sponsor determines the expiry period for the ticket commitment;
  • Sponsor submits a funding transaction with ticket commitments and expiry period to TicketRegistry (on-chain);
  • TicketRegistry records ticket commitments as funded with specified expiry (supports batch operations).

3. State Synchronization

  • Service Provider periodically synchronizes with TicketRegistry to obtain the latest Merkle root representing funded ticket commitments;
  • Service Provider distributes the latest Merkle root to all its Service Nodes;
  • Service Nodes verify proofs against locally cached state with configurable staleness tolerance, while delegating nullifier double-spend checks to Service Provider for real-time coordination.

4. Service Request

  • User generates an eligibility proof consisting of:
    • nullifier (derived from ticket secret and Service Provider ID);
    • ZK proof showing knowledge of ticket secret
      and inclusion of ticket commitment in current Merkle root;
    • Reference to Merkle root used.
  • User sends a service request to any Service Node
    operated by trusted Service Provider,
    attaching an eligibility proof.

5. Service Provision

  • Service Node receives request and eligibility proof;
  • Service Node verifies ZK proof, including Merkle inclusion and that referenced Merkle root is within acceptable staleness window;
  • Service Node forwards nullifier to Service Provider for double-spend checking;
  • Service Provider checks nullifier against unified set of previously seen nullifiers;
  • Service Provider responds to Service Node with accept or reject decision;
  • If both ZK proof is valid and Service Provider accepts nullifier, Service Node serves requested data to User.

6. Redemption

  • Service Provider collects all nullifiers that have been accepted and served by its Service Nodes;
  • At intervals, Service Provider submits a batch redemption transaction to TicketRegistry, including collected nullifiers and corresponding eligibility proofs;
  • TicketRegistry verifies eligibility proofs, checks nullifiers are unspent, marks them as spent,
    and transfers payment to Service Provider for valid redemptions;
  • If any ticket commitments remain unused after Sponsor-determined expiry, Sponsor may reclaim them from TicketRegistry.

State Synchronization Analysis

State synchronization is required because eligibility proofs reference the current set of funded ticket commitments, which evolves as tickets are funded and redeemed in TicketRegistry. Without synchronized state, Service Nodes cannot verify ticket validity and Users cannot generate valid proofs.

In the MVP, Service Nodes delegate nullifier management to their Service Provider while independently verifying cryptographic proofs. When Users make requests, they prove their ticket’s inclusion in the current state. Service Nodes accept proofs that reference recent Merkle roots, allowing some staleness while maintaining security.

The Service Provider syncs with the on-chain TicketRegistry and distributes the latest Merkle root of funded tickets to all its Service Nodes. The Service Provider maintains unified nullifier tracking and handles real-time nullifier checks and batch on-chain settlement.

This approach provides:

  • Service Node simplicity and operational efficiency;
  • Unified double-spend prevention across all Service Nodes under the same Service Provider in a single-Provider model.

Alternatives Considered:

  • Full blockchain sync at every Service Node would increase complexity and resource usage.
  • Pure optimistic verification without cryptographic state proofs would provide weaker security.
  • Decentralized state distribution would add coordination overhead.

Double Spending Trade-off

The architecture accepts double spending before nullifiers are redeemed on-chain. Strict prevention would require real-time blockchain synchronization, but Users are lightweight edge devices that should not track blockchain state or commit to specific Service Providers ahead of time.

The current design allows ticket commitments to be funded without reference to specific Service Providers.This provides flexibility for Sponsors to fund tickets in batches and for Users to choose Service Providers based on reputation and availability. Ticket nullifier commitment occurs at usage time, not funding time, enabling adaptation to changing Service Provider landscapes.

In a multi-Service Provider environment, a malicious User could use the same funded ticket to generate eligibility proofs for different Service Providers.

Service Providers can balance redemption timisng versus double spending exposure:

  • Wait longer to accumulate nullifiers for batch redemption;
  • Redeem immediately to minimize double-spend risk.

An alternative architecture would link ticket commitments to specific Service Providers at funding time, eliminating double spending risk but reducing flexibility.

Post-MVP Development

Multi-Provider Support with Reputation and Progressive Trust

The ProviderRegistry contract maintains an on-chain registry of independent Service Providers.
Each Provider deposits funds demonstrating commitment to honest service provision. The ProviderRegistry enforces withdrawal delays to prevent “scam-and-exit” behavior.

Providers operate within separate nullifier spaces. The TicketRegistry provides global double-spend prevention via a global set of redeemed nullifiers. Double-spend attacks across different Providers are possible before the first nullifier redemption since nullifiers are tracked locally until redeemed on-chain.

Provider reputation is based on aggregate Service Node performance metrics. Slashing penalizes Providers for systematic failures.

Service Node Discovery and Progressive Trust

A Progressive Trust protocol allows Users to incrementally evaluate Service Providers based on on-chain reputation and local interaction history. Service Providers advertise their Service Nodes
with cryptographic ownership proofs. Providers sign and publish Service Node endpoints along with performance metrics for User discovery. Users select Service Providers based on their evaluation criteria, verify Service Provider signatures, and connect to the Service Nodes operated by their chosen Service Provider. Users gradually increase exposure based on successful interactions and rotate Service Providers when systematic failures occur.

Stronger Economic Incentives

Future economic improvements might also include:

  • Price negotiation between Users and Service Providers;
  • Demand-based dynamic pricing;
  • Alternative payment mechanisms such as probabilistic micropayments or payment channels;
  • Third-party monitoring with challenge-response mechanics.

Call for Feedback

We welcome community feedback on critical design decisions, for instance:

  • Double-spend trade-offs between user flexibility and security;
  • MVP scope balancing implementation feasibility with decentralization goals;
  • Cryptographic model optimizing privacy, proof complexity, and verification efficiency;
  • Economic assumptions and their impact on adoption.

We also welcome feedback on any other aspects of the architecture.

1 Like

I don’t think this double spend protection scheme is enough.

If the smart contract is not gate-kept and user are anonymous and they can double spend (even a little) then the potential utility gained is unbounded no?

A rational actor would cheat they can’t be identified or punished anyway.

Maybe we could state that each service provider has their own contract created from a factory?

Can you clarify the expectations here? Are the nullifier committed on chain at any time, or only when redeeming?

Why not letting service nodes get root directly from chain? Sounds like we are looking at adding an extra communication protocol here which could be avoided.

Similar to above, we are adding back and forth communications here before a request by using the Service Provider as an intermediate.

Have we considered just sending the nullifier on a content topic over Waku so that all service nodes can monitor it and cache it, so that nullifier verification can be done locally?

I disagree here, complexity is being increased by getting the Service Provider to be a proxy to the blockchain for the service nodes.

I would suggest to review this point and have service nodes check the blockchain themselves for new secrets, and use Waku as a mempool for nullifiers.


Scope looks good, main feedback is that complexity is being increased by stopping service nodes to read state from the blockchain, which seems unnecessary.