2025-08-18

LurkScript, Concurrent Lurk, and LurkOS: Unlocking the Next Era of ZK‑Powered Applications

\(\)

Introduction #

This document details LurkScript, Concurrent Lurk, and LurkOS, three additions to the Lurk ecosystem that enable general‑purpose, zero‑knowledge applications with private state and verifiable interaction. LurkScript provides a proof‑native programming model in an approachable language. Concurrent Lurk extends the language and runtime with a provable actor runtime for composition, message passing, and long-lived state. The LurkOS kernel provides a resource and message routing abstraction layer. Together they support fully private, composable, asynchronous application logic that can be settled on blockchain.

What do LurkScript and LurkOS enable? In short: a developer‑friendly way to write complex, stateful programs (in an approachable, functional, JavaScript‑like syntax) that LurkOS, a provable operating system, executes as actors with isolated state and typed message handlers to manage those programs and their interactions. The result is a platform for privacy‑preserving, composable applications that integrate cleanly with existing systems and blockchains.

Limitations in Prevailing ZK Stacks #

Despite major progress in ZK systems, building applications, not just one‑off proofs, remains cumbersome. Other ZK languages and VMs have several shortcomings making it hard to use them to build composable, scalable applications:

  • Missing proof‑native semantics: Application development needs language features that make private state, interaction, and composability first‑class. Without a notion of content‑addressed programs, handling I/O, and explicit state transition boundaries, developers end up reinventing these basics per‑app (or more often, not at all).
  • No built‑in, long‑lived state. Application development needs language features that support a built‑in notion of maintaining state across multiple transactions or users. There’s no equivalent of an Ethereum‑style contract state that multiple users can update with proofs in a standardized way. As a result, multi‑step, multi‑party workflows become brittle hand‑offs of state and checks across proofs.
  • No application model for composition. Cross‑app composition requires a single, comprehensible, unified model for all application and user interaction. Absent that, bespoke interfaces quickly make composition unmanageable.
  • No standardized, proof‑oriented data language: Application I/O needs a shared schema for provable interaction. In practice this means a typed, content‑addressed message format, consistent handler signatures, and a standard query/ack/reject protocol. Without this, verifiable composability of application code is difficult to impossible.

These challenges have kept ZK applications relatively narrow in scope. What’s been missing is a general‑purpose, developer‑friendly, and blockchain‑ready ZK programming model. This is exactly what LurkScript and LurkOS aim to provide.

LurkScript and Concurrent Lurk #

LurkScript is a high‑level language for writing Lurk programs, enabling developers to write zero‑knowledge logic in a approachable JavaScript‑like syntax that runs on the Lurk. Formally, LurkScript is a functional, immutable subset of JavaScript that compiles to Lurk. The subset on Lurk is side‑effect‑free, which yields deterministic evaluation suitable for proof-based applications.

LurkScript compiles to Concurrent Lurk (CL). CL extends base Lurk’s single-trace, small-step semantics with Erlang-style concurrency. Every control point explicitly carries its next continuation (with a stable hash identity), which exposes safe preemption points and enables interleaving processes without shared mutable state.

Processes and state. Execution is a finite set of lightweight, isolated processes. Each process holds a Lurk progressively-reduced expression, environment, and continuation; it also owns a private mailbox. All data passed between processes is immutable.

Operational semantics.

  • Local reduction: Any enabled process performs one standard Lurk reduction step at a time; the global configuration evolves by interleaving chunks of these local steps.
  • Process lifecycle: A process can spawn a child (evaluating an expression under a fresh initial continuation) and terminate with a value. These steps only change the set of processes; they do not mutate shared state (because it is immutable).
  • Asynchronous communication: A process sends a value to another process’s mailbox (non-blocking). A process receives by suspending until its mailbox receives a message (per-mailbox order is preserved).

Continuations and content addressing. Lurk’s continuations are explicit, and the content-addressed AST gives each continuation and expression a cryptographic identity. Preemption occurs only at continuation boundaries; swapping code or policy is an explicit change of content IDs, which is auditable in proofs.

Scheduling and proofs. CL introduces an explicit scheduler that interleaves single-step reductions of ready processes. Each process in the process table is given a chance to take a max number of reduction steps. Once one process terminates, the next process runs, until all processes have terminated. Thanks to per-process determinism and the absence of shared mutation, correctness is independent of the particular interleaving (subject to standard independence conditions).

Result. CL adds concurrency to Lurk with a small set of higher-level constructs. It provides: isolated processes with asynchronous mailboxes, a CPS-based preemption model over content-addressed programs, and a proof discipline that composes concurrent executions.

LurkOS #

While LurkScript makes writing ZK programs easier, and Concurrent Lurk provides lightweight processes, CPS-based preemption, per-process mailboxes, and scheduling; LurkOS adds the application model: content-addressed resources, functional handlers, a capability-scoped kernel API, and typed message routing. LurkOS defines how applications structure state, communicate, and compose safely.

Core concepts #

  • Resource (capability unit). A resource is code + private state packaged behind a stable domain ID derived from its content address (commit(fun)). It exposes:
    • a handler table mapping message types(event, state, control) → (newState, ack|reject)
    • an optional query handler: (state, query) → answer for pure predicates over private state
  • Domain (content-addressed identity). A resource’s domain is the capability boundary. Messages are accepted only when the event’s resourceDomain matches a domain the receiver expects.
  • Typed message I/O. Messages carry a type tag (e.g., transfer) and a known payload schema. Dispatch is by type; shape/constraints are enforced by the handler. Outcomes are explicit: ack(value) or reject(reason).

Declarative execution model #

A LurkOS handler is a pure state transition:

(step) : (state, event, control) → (state′, ack | reject)
(query): (state, q) → answer
  • No implicit mutation; all effects are via kernel calls listed below.
  • Because transitions are pure and typed, they’re easy to prove and to aggregate across steps.

Kernel control surface (capabilities) #

The kernel mediates all effects; typical surface:

  • query(domain, q) — obtain a predicate/answer from another resource without revealing its state
  • sendResource(domain, msg, aux?) — send a typed message
  • sendResourcePending(domain, msg, aux?, s′, onAck, onReject) — send a message and stage the local state as s′ until an outcome arrives; on ack, finalize; on reject, revert/compensate via the provided continuations
  • ack(value) / reject(reason) — conclude the current handler with an explicit outcome
  • isResourceDomain(x) — validate the sender/ack’s domain to enforce capability boundaries

These calls are the only way a handler can interact with anything outside its own state; they define LurkOS’s effect system over CL.

Message routing semantics #

  1. Addressing. A sender targets a domain (the content address of a resource).
  2. Delivery. The receiver’s kernel validates event.resourceDomain and routes by message type to the matching handler.
  3. Validation. If domain/type/schema checks fail, the handler (or kernel) rejects.
  4. Outcomes. Handlers must end in ack or reject. With sendResourcePending, the sender’s staged state either commits on ack or reverts/compensates on reject , a local two-phase pattern that keeps cross-resource protocols robust and provable.
  5. Composition. Complex flows (e.g., KYC-gated pay, escrow, rate-limited transfers) are expressed by chaining typed messages across domains, with intermediate checks implemented as queries to other resources.

Upgrade & versioning #

  • Code identity. Because domains are derived from commit(fun), a resource’s identity is immutable.
  • Upgrade. Installing a new version yields a new domain; callers opt-in by switching the domain they address. This makes upgrades explicit and auditable in proofs; old flows keep working against the old domain.

Security & correctness invariants #

  • Capability isolation. Only messages from expected domains are honored (isResourceDomain), preventing confused-deputy/cross-resource spoofing.
  • Type-driven dispatch. Handlers see well-formed payloads per type; malformed messages are rejectable and thus unprovable as valid transitions.
  • Private state by default. State is never exported; external checks happen via query, which returns only the potentially-filtered result needed for policy.
  • Deterministic transitions. Given (state, event), the handler’s result is deterministic; concurrency/interleaving is managed by CL, not LurkOS.

Pulling it all together through examples #

Minimal token “pay” flow: Rather than a global map of balances, each actor owns its balance. A pay handler deducts locally and sends a typed message to the recipient’s token resource. Here’s a distilled snippet illustrating the style (handlers elided for brevity elsewhere in this post):

const tokenContract = function(resourceOwnerAddress, userPubKey) {
  const handlers = {
    pay: (envelope, state, control) => {
      const msg = envelope.msg;
      const innerSig = msg.sig;
      const inner = msg.inner;
      const amount = inner.amount;
      const metadata = inner.metadata;
      const to = inner.to;

      c.log(list("verifying", userPubKey, inner, innerSig));

      if (!verify(userPubKey, inner, innerSig)) control.reject("invalid signature on payment request");
      else if ((state < amount)) control.reject("insufficient token");
      else {
        control.log(list("signature verified", innerSig, "paying", amount, "to", to));

        control.sendResourcePending(to, { type: transfer.q, amount: amount, metadata: metadata }, {}, state - amount,
          (ackEnv, state, control2) => {
            if (control.isResourceDomain(ackEnv.resourceDomain)) {
              control.log(list("got ack of transfer to", to));
              control.ack(nil);
            } else {
              control.reject("rejecting ack from invalid domain");
              control2.return(state + amount);
            }
          },
          (rejectEnv, state, control2) => {
            // No need for a domain check, since will reject either way.
            control.reject(nil);
            control2.return(state + amount);
          }
        );
      }
    }, // Then comes the other handlers for incoming transfers and any other message types

Why this feels simpler than Solidity/EVM/global shared state for private flows:

With a global shared state, you must maintain a centralized ledger/registry, and every transfer involves sending messages to that registry. This is both a bottleneck and an obstacle to privacy.

In the LurkOS model, resources are first class and can be programmed to behave according to conservation laws so that global properties are preserved through enforcing local invariants. That means a token transaction has a topology that exactly mirrors a physical cash transaction. This is more intuitive, because it matches real-world expectations and also allows for implementing systems where real-world intuition about communication patterns and privacy can be modeled directly.

Use Case: A Private Token Payment Application #

Imagine a token system where balances and transfers remain private, yet each transfer is proven correct and enforces regulatory restrictions. We model each user as an actor that holds a private balance and a KYC resource. A payment succeeds only if both sender and receiver pass KYC and the sender’s signature/authority is valid.

KYC as a resource. A dedicated KYC resource stores a certificate (that might be a commitment hash or extended to contain several data fields) and exposes a minimal interface: accept a receiveKYC message from the KYC Issuer and answer queries about whether KYC is present.

const makeKyc = function(kycIssuerAddress) {
  const kycHandlers = {
    receiveKYC: (envelope, state, control) => {
      if (envelope.sender === kycIssuerAddress) {
        control.log("got kyc");
        control.return(envelope.msg.kycCert || true);
      } else control.log("unauthorized receiveKYC request");
    }
  };
  () => ({ state: false, handlers: kycHandlers, queryHandler: (state, query) => state });
};

Token with embedded KYC checks. Just as the first example, the token resource checks the owner’s signature, then queries the KYC resource as a conditional requirement before sending a transfer to the recipient. The receiver’s token resource also checks its own KYC before accepting.

const tokenContract = function(resourceOwnerAddress, kycDomain, userPubKey) {
  const handlers = {
    pay: (envelope, state, control) => {
      const msg = envelope.msg;
      const innerSig = msg.sig;
      const inner = msg.inner;
      const amount = inner.amount;
      const metadata = inner.metadata;
      const to = inner.to;
      const amount = envelope.msg.amount;
      
      if (!verify(userPubKey, inner, innerSig)) control.reject("invalid signature on payment request");
      else if (!control.query(kycDomain, nil)) control.reject("cannot pay without kyc");
      else if ((state < amount)) control.reject("insufficient token");
      else {
        control.log(list("signature verified", innerSig, "paying", amount, "to", to));

        control.sendResourcePending(to, { type: transfer.q, amount: amount, metadata: metadata }, {}, state - amount,
          (ackEnv, state, control2) => {
            if (control.isResourceDomain(ackEnv.resourceDomain)) {
              control.log(list("got ack of transfer to", to));
              control.ack(nil);
            } else {
              control.reject("rejecting ack from invalid domain");
              control2.return(state + amount);
            }
          },
          (rejectEnv, state, control2) => {
            // No need for a domain check, since will reject either way.
            control.reject(nil);
            control2.return(state + amount);
          }
        );
      }
    }, // Then comes the other handlers for incoming transfers and any other message types

That simple one line query to the KYC resource, (!control.query(kycDomain, nil)) , was all it took to add a conditional predicate of NFT ownership to the transaction.

The power and simplicity of the resource model:

This example shows how this resource model greatly expands permissionless, composable programmability in private execution. Encapsulating logic behind a queryable interface ensures isolated interaction between applications of siloed state, with minimal application logic and unlimited composability.

This model can be used to implement complex authorization logic, selective disclosure, permissioned subnetworks, automated workflows, and all of the composability enjoyed by public smart contract platforms.

Integrating With Legacy Systems, AI Agents, and Private Shared State Applications #

Because resources are content‑addressed and messages are typed, LurkOS makes it straightforward to plug in offchain systems while keeping privacy boundaries intact:

  • Signed offchain intents: External services, enterprise systems or AI agents can sign a payment intent; the intent being verified against authorization and policy resources in a private context before action is taken onchain. This gives way to LurkOS being a private context interaction layer for offchain systems to create blockchain actions. Existing approval systems can be encoded into LurkOS resources to enable existing workflows to execute private transactions. AI agents can be given automated control of assets and applications inside LurkOS but only through resource defined interfaces, and without exposing the user’s private state to the agent.
  • Composable predicates: KYC is just one resource. You can add rate‑limiters, allow‑lists, oracles, or reputation resources that are queried as predicates as preconditions without expanding the token’s trusted computing base. This creates a world where AI agents can act on behalf of a user’s identity alongside their own identity and reputation resources, yielding an automated trust network of agents and users.
  • Private multi‑party workflows: Multi‑party workflows can be orchestrated as actors exchanging messages (e.g., escrow/sale flows where an NFT and tokens are settled conditionally). Because each step is proven, orchestration doesn’t compromise privacy of individual state. More complex application topologies (e.g., central limit order books, games, and other stateful applications) require a coordinator actor to merely consume user messages and trigger the next step. Since every message is provably consumed in the state transitions of the coordination actor, message ordering enforced at the blockchain layer, and with the coordinator never observing a user’s raw state, coordination is trust-gapped: you rely on it only for liveness, while safety and privacy are enforced by each actor’s handlers.

For illustration, consider a central limit order book (CLOB) implementation in LurkOS, where the matching engine is run as a coordination actor which users interact with via messages. The only information a message must expose to the application is the asset to trade (currently owned and provably locked by the user) and the asset to receive, along with the chosen parameters for the trade to be executed. The application, therefore, sees nothing of the user’s private state, and is ensured of the message’s validity by the network verifying the user’s proof that originated the message. Since messages are encrypted for the recipient and application, no information is leaked publicly. As the matching engine itself runs in LurkOS, it can only produce state transitions (trades) which follow the protocol logic it is provably running. As message ordering is enforced by the blockchain, with message consumption order proven by execution, users are ensured of fair execution.

Bringing Private Computation and Collaboration to Blockchain #

Ultimately the architecture and application topology that LurkOS provides with private per‑actor state, proof‑based transitions, and asynchronous message passing, benefits from a settlement layer that mirrors these patterns. Proofbase (the blockchain layer for Lurk) serves as the bridge between offchain ZK computation and onchain settlement.

By reducing the settlement layer to verifying proofs and passing messages between sharded state, Proofbase becomes a lightweight, scalable, low‑latency private application layer. Applications can remain where their assets are. Proofbase verifies proofs and issues stateful proof certificates that other chains can verify. For example, a game can be run privately with outcomes attested by a proof certificate that an Ethereum contract verifies to finalize payouts.

Conclusion and What’s Next #

LurkScript and LurkOS together bring us closer to making highly scalable, complex ZK application development practical and accessible. LurkScript offers a functional subset with content‑addressed programs and declarative transitions; LurkOS supplies actors, resources, and typed message I/O with ack/reject semantics. The approach scales to complex, multi‑party workflows, integrates with existing systems, and supports settlement on existing chains via proof verification.

The examples above illustrate the minimal surface area required to implement private payments with KYC while preserving modularity and upgradability. We also demonstrated how resources like a KYC check can be cleanly integrated, enforcing compliance without sacrificing privacy. Beyond tokens, the possibilities are broad: private DeFi, AI‑agent automation with private context, games with hidden state, queryable identity and reputation, legacy system interfaces; all implemented as LurkOS actors communicating via proofs.

Perhaps most importantly, LurkScript and LurkOS are future‑proof and adaptable. With LurkScript we’ve shown the simplicity of extending language support to other functional languages, and the portability of Lurk to other proving backends. And because LurkOS is the one “meta‑program” that Proofbase verifies, updating the proving stack or the OS itself can be a client‑side upgrade for the network. The apps you build with LurkScript today can remain reliable and efficient as the ZK technology underneath evolves.

In summary: LurkScript and LurkOS enable developers to build the kind of private, scalable dApps that the crypto community has long envisioned but could not realize with earlier tools. The era of trustless, privacy‑preserving smart contracts is here and LurkScript/LurkOS are how you build them.