Token as a Verifiable Service

This guide walks through transforming a simple token service into a decentralized and verifiable application using the Zellular Sequencer.

We follow a progressive enhancement model, starting from a basic session-based FastAPI service, and incrementally evolving it into a replicated and cryptographically verifiable system.

Each step corresponds to a real code implementation in the Zellular token directory.

Step 1: Centralized Token Service

In the first stage, we build a basic centralized token service using FastAPI. User sessions are tracked with server-side cookies, and balances are stored in-memory.

πŸ“„ File: token/01_centralized_token_service.py

Key Concepts

  • Framework: FastAPI

  • Session Management: Via SessionMiddleware

  • Authentication: Username/password stored in memory

  • Token State: Python dict holding balances

  • Transfer Logic: Requires an active session

Endpoints

  • POST /login: Authenticate with username/password

  • POST /transfer: Send tokens from the logged-in user

  • GET /balance?username=<username>: Query balance

Example Usage

  1. Login

curl -X POST http://localhost:5001/login \
     -H "Content-Type: application/json" \
     -d '{"username": "user1", "password": "pass1"}' \
     -c cookies.txt
  1. Transfer

curl -X POST http://localhost:5001/transfer \
     -H "Content-Type: application/json" \
     -d '{"receiver": "user2", "amount": 50}' \
     -b cookies.txt
  1. Check Balance

curl http://localhost:5001/balance?username=user1

Limitations

  • Single-node, centralized architecture

  • No cryptographic guarantees

  • Relies on server-side session for authentication

In the next step, we replace the session system with cryptographic signatures for stateless and verifiable authentication.

Step 2: Signature-Based Token Service

This version removes session-based auth and introduces Ethereum-style digital signatures. Users sign transfer messages off-chain using their private key. The backend verifies these signatures and recovers the sender address directly from the signed message.

πŸ“„ File: token/02_signature_based_token_service.py

Key Concepts

  • Stateless authentication using ECDSA signatures

  • Compatible with wallets like MetaMask or eth_account

  • Server no longer stores user credentials or sessions

Endpoints

  • POST /transfer: Send a signed transfer request

  • GET /balance?address=0x…: Query token balance

Signing Format

Users must sign a message in this format:

Transfer {amount} to {receiver}

For example:

Transfer 10 to 0xAbc123...

Client-Side Signing (Python Example)

message = f"Transfer {AMOUNT} to {RECEIVER_ADDRESS}"
message_hash = encode_defunct(text=message)
signed_message = Account.sign_message(message_hash, private_key=SENDER_PRIVATE_KEY)
signature = signed_message.signature.hex()

Backend Verification

On the server:

def verify_signature(sender: str, message: str, signature: str) -> bool:
    """Verifies if the provided signature is valid for the given sender address."""
    try:
        message_hash = encode_defunct(text=message)
        recovered_address = Account.recover_message(message_hash, signature=signature)
        return recovered_address.lower() == sender.lower()
    except Exception:
        return False  # Any error in signature recovery means invalid signature


class TransferRequest(BaseModel):
    sender: str
    receiver: str
    amount: int
    signature: str


@app.post("/transfer")
async def transfer(data: TransferRequest) -> JSONResponse:
    """Handles token transfers using signature-based authentication."""
    message = f"Transfer {data.amount} to {data.receiver}"

    if not verify_signature(data.sender, message, data.signature):
        raise HTTPException(status_code=401, detail="Invalid signature")

    if balances.get(data.sender, 0) < data.amount:
        raise HTTPException(status_code=400, detail="Insufficient balance")

    balances[data.sender] -= data.amount
    balances[data.receiver] = balances.get(data.receiver, 0) + data.amount
    return JSONResponse({"message": "Transfer successful"})


Test Script

To simplify development, a helper script is included:

πŸ“„ File: token/transfer.py

This script:

  • Loads a private key

  • Signs a message

  • Sends it to the /transfer endpoint

Run it with:

python examples/token/transfer.py

Example Usage

  1. Transfer tokens

curl -X POST http://localhost:5001/transfer \
     -H "Content-Type: application/json" \
     -d '{
           "sender": "0x...",
           "receiver": "0x...",
           "amount": 10,
           "signature": "0x..."
         }'
  1. Check balance

curl http://localhost:5001/balance?address=0xYourAddress

Why This Matters

  • Cryptographic authentication without storing secrets

  • Stateless backend logic

  • Ready for replication in decentralized networks

In Step 3, we integrate the Zellular Sequencer to distribute and replicate transfer updates across nodes.

Step 3: Replicated Token Service

In this step, we integrate the Zellular Sequencer to replicate the token state across multiple nodes. Transfer requests are no longer applied directly when submitted β€” instead, they are sent to the Zellular Sequencer, which sequences them and broadcasts them to all participating replicas.

Each replica node independently fetches the same ordered batch of transfers and applies them locally. This ensures all nodes remain consistent, even in the presence of faults or restarts.

πŸ“„ File: token/03_replicated_token_service.py

Key Concepts

  • Uses the Zellular Python SDK (Zellular(…))

  • Transfers are submitted via zellular.send(…)

  • Replica nodes pull and apply batches using zellular.batches()

  • Transfers are still signed and verified using the same logic from Step 2

Transfer Submission

Transfers are submitted via the /transfer route, verified as before, and then sent to the Zellular Sequencer:

    txs = [
        {
            "sender": data.sender,
            "receiver": data.receiver,
            "amount": data.amount,
            "signature": data.signature,
        }
    ]

    zellular.send(txs, blocking=False)

This appends the transfer to the global sequence shared by all replicas.

Processing Batches from Zellular

Each replica runs a background loop using the SDK to process batches:

def process_loop() -> None:
    """Continuously processes incoming batches from Zellular."""
    for batch, index in zellular.batches():
        txs = json.loads(batch)
        for tx in txs:
            apply_transfer(tx)


The apply_transfer(tx) function:

  1. Reconstructs the signed message

  2. Verifies the signature

  3. Checks sender balance

  4. Applies the transfer if valid

This ensures all replicas apply transfers in the same order and reach the same balances.

Full Transfer Verification Logic

def apply_transfer(data: dict[str, Any]) -> None:
    """Executes a transfer after batch processing."""
    sender, receiver, amount, signature = (
        data["sender"],
        data["receiver"],
        data["amount"],
        data["signature"],
    )

    message = f"Transfer {amount} to {receiver}"
    if not verify_signature(sender, message, signature):
        logger.error(f"Invalid signature: {data}")
        return

    if balances.get(sender, 0) < amount:
        logger.error(f"Insufficient balance: {data}")
        return

    balances[sender] -= amount
    balances[receiver] = balances.get(receiver, 0) + amount
    logger.info(f"Transfer successful: {data}")


Why This Matters

  • Ensures all nodes apply transfers in the same global order

  • Enables fault-tolerant, deterministic replication

  • Balances remain consistent even if nodes crash or restart

In Step 4, we’ll introduce verifiable reads: users can query balances and verify the response using aggregated BLS signatures from the token replicas.

Step 4: Verifiable Token Service

In this step, we make balance queries verifiable by cryptographically signing every /balance response using BLS signatures. Each node signs the message with its own private key, allowing external services to confirm the authenticity of the returned value.

πŸ“„ File: token/04_verifiable_token_service.py

Key Concepts

  • /balance responses are now BLS-signed

  • Clients can collect signed values from multiple nodes

  • These signatures can later be aggregated and verified (see future section)

Why Verifiable Reads?

In a decentralized setting, it’s not enough to replicate state β€” the correctness of the state must also be verifiable.

When other services (such as wallets, exchanges, or cross-chain systems) rely on the token service, they must be able to trust the values returned from balance queries. Verifiable reads enable these external systems to independently confirm that a node is reporting accurate, untampered state, without relying on that node’s honesty.

By signing each balance response with a BLS key:

  • The node attests to the specific value it returned

  • The signature can be later verified or aggregated with others

  • Clients can detect misreporting or inconsistency across nodes

This forms the foundation for trustless interoperability between services that read from each other β€” essential for building tamper-proof decentralized infrastructure.

Balance Endpoint

The /balance endpoint signs the message before returning it:

@app.get("/balance")
async def balance(address: str) -> dict[str, Any]:
    """Retrieves the balance of a given address and returns a BLS-signed message."""
    balance = balances.get(address, 0)
    message = f"Address: {address}, Balance: {balance}".encode("utf-8")
    signature = PopSchemeMPL.sign(sk, message)
    return {"address": address, "balance": balance, "signature": str(signature)}


The message is signed using the BLS POP (Proof of Possession) scheme from the blspy library and the resulting signature is included in the API response.

For now, this step ensures that every balance query is individually signed and verifiable. In the Signature Aggregation and Verification section, we’ll explore how an aggregator can collect signed responses from multiple nodes, combine them into a single BLS signature, and how clients or external services can verify that a quorum of replicas attested to the same value.