How Lurk works

protocol · api · faq

Lurk is a private proof-of-work chain. Senders, receivers and amounts are hidden by default on every transaction. This page explains each piece and the API the wallet uses.

Addresses

A wallet is one 32-byte seed. From it come a spend key and a view key (scalars on ristretto255) and an ML-KEM-768 key pair. Each wallet has two addresses:

lk1…   classic: spend public key + view public key (96 characters)
lkq1…  quantum: the same keys + an ML-KEM-768 public key (about 1,700 characters)

Both receive into the same wallet. The quantum address is long because post-quantum public keys are large.

Stealth outputs

Nobody pays an address directly. For every payment the sender derives a fresh one-time address P = Hs(secret, i)·G + A. The shared secret comes from ECDH with the view key on classic addresses, or from ML-KEM-768 encapsulation on quantum addresses, in which case the 1,088-byte ciphertext rides with the output. The receiver checks every output with their view key or ML-KEM secret key. Nobody else can tell two payments to the same wallet are related.

Hidden amounts

Amounts are Pedersen commitments C = x·G + v·H. The blinding factor x and the amount travel encrypted to the receiver. Every output carries a 48-bit range proof: 48 bit commitments, each with a 2-member ring signature proving it opens to 0 or to 2^j. This stops anyone creating coins from negative amounts. Inputs and outputs balance as points: Σ pseudo-inputs = Σ outputs + fee·H.

Ring signatures and key images

Each input names 16 outputs from the chain: the real one and 15 decoys. An MLSAG signature over two rows proves that the signer owns one of the 16 one-time keys, and that its commitment matches the pseudo-input commitment, without saying which one. The signature publishes a key image I = x·Hp(P). One output always gives the same key image, so a second spend is rejected, yet nothing links the key image to the output.

Verify any transaction yourself

Every transaction page in the explorer has a verify in your browser button. Your browser fetches the transaction and its ring members from /api/rawtx/:id and runs the same verifyTx the node runs: every MLSAG ring signature, every 48-bit range proof and the balance of commitments. Nothing is taken on trust from the node. The code is open source, with a test suite that runs on every push.

Proof of work

Blocks are mined with Argon2id, a memory-hard hash. The header holds height, previous hash, timestamp, difficulty, transaction root and coinbase id. A block is valid when argon2id(header | nonce) × difficulty < 2^256. Difficulty retargets every block toward a 30-second block time. The reward is (100M − emitted) / 2^19 with a 2.0 tail emission that never ends. Coinbase outputs need 3 confirmations before they can be spent.

Blackout

Blackout is the quantum upgrade. ML-KEM payment detection already runs today on lkq1 addresses. Two parts are still to come: spends move from elliptic-curve ring signatures to hash-based proofs, and the anonymity set grows from 16 outputs to every output on the chain. Coins migrate to the new format once, and the wallet handles it for you.

API

GET  /api/stats                     chain height, difficulty, outputs, key images, emission
GET  /api/blocks?limit=15&before=N  recent blocks
GET  /api/block/:height|:hash       one block with its transactions
GET  /api/txs                       mempool + recent private transactions
GET  /api/tx/:id                    rings, key images, commitments, ML-KEM ciphertexts
GET  /api/rawtx/:id                 full transaction + ring members, for independent verification
GET  /api/search?q=                 height, block hash, tx id or key image
GET  /api/outputs?from=G&limit=2000 outputs for wallet scanning
GET  /api/keyimages?from=N&to=M     spent key images (wallets look for theirs locally)
GET  /api/ring?g=a,b,…              ring members by position
POST /api/tx      {tx}              submit a private transaction
POST /api/faucet  {address}         25 tLURK, once an hour

Run a node

npm i && npm start      # http://localhost:8266

Environment: PORT, DATA_PATH (chain file), BLOCK_SEC (target block time), ARGON_KIB (Argon2id memory), LURK_MINT (the $LURK token address shown on the site).

FAQ

Is tLURK the same as $LURK? No. tLURK is the free faucet coin and has no value. $LURK is the Solana token that funds and carries the project to mainnet.

Where are my keys? In your browser's local storage, created on your device. The node never sees your seed, view key or ML-KEM secret key. Back up the seed. Clearing site data without it loses the wallet.

Why does sending take a few seconds? Your browser builds the ring signature, 48-bit range proofs for every output and, for quantum addresses, an ML-KEM encapsulation. That work happens on your device so nothing private is sent to the node.

Does the node know which outputs are mine? No. The wallet downloads every output and the full list of spent key images and does its own matching. It picks its 15 decoys itself, keeps the same ring if you retry, and asks the node for the whole ring at once, so the node never learns which member is the real input.

Can the explorer see my balance? No. It shows rings, key images, commitments and ciphertexts. Only the wallet holding the view key or ML-KEM secret key can open an output.

What does a quantum computer break today? Elliptic-curve keys, so it could in theory unmask ECDH payment detection on classic addresses. Payments to lkq1 addresses use ML-KEM-768, a NIST-standard lattice scheme designed to resist that. Blackout extends the same protection to spends.

Why 16? A ring of 16 is a practical size for proof time and transaction size. Blackout removes the limit by hiding each spend among every output.