How Lacuna works

protocol · api · faq

Lacuna 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:

aeon1…   classic: spend public key + view public key (97 characters)
aeonq1…  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.

Dead drops: sealed notes

Every output on a new transaction carries a 160-byte sealed note: up to 140 bytes of text chosen by the sender, encrypted and tagged with the same shared secret that hides the payment. On aeonq1 addresses that secret comes from ML-KEM-768, so the note stays sealed against a quantum computer. The field is always present and always the same size, so the chain cannot tell a payment that carries a message from one that does not. The note is covered by the transaction's ring signatures, so nobody can swap it or strip it after signing. A note names no sender: the receiver learns only what the sender chose to write. Write one in the wallet's send form. Notes sent to you appear in the wallet under dead drops, and the faucet attaches one to every drip so you can see it work.

Payment proofs

A payment proof lets the sender show one person that a payment happened, without opening anything else. In the wallet, every payment sent from that device is listed under sent payments with a copy proof button. The proof is a string that starts with aeonproof1. Paste it into the explorer and your browser checks four things: the transaction is on the chain, the proof was signed with that transaction's secret key (so only the sender could have made it), the output pays the named address, and the stated amount opens the output's commitment. The sender can put a short message inside the proof, such as an invoice number, and the signature covers it.

A proof opens exactly one output. It does not reveal the sender's address, balance, other payments or which ring member was spent, and it does not reveal the shared secret, so the dead drop on that output stays sealed. Proofs are made from data the sending wallet keeps on its own device, and the node never sees them unless you choose to share one.

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.

Meridian

Meridian is the quantum upgrade. ML-KEM payment detection is live today on aeonq1 addresses. Two parts are still ahead. Spends move from elliptic-curve ring signatures to hash-based proofs. The anonymity set grows from 16 outputs to the whole chain. Coins move to the new format once, and the wallet does it for you.

API

GET  /api/work?address=A            block template paying the coinbase to A
POST /api/work   {id, nonce}         submit a solved nonce (re-checked by the node)
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
POST /api/spent   {kis:[…]}         which key images are spent
GET  /api/decoys?n=15&avoid=a,b     random ring members
POST /api/tx      {tx}              submit a private transaction
POST /api/faucet  {address}         25 tLAC, once an hour

Run a node

cd lacuna && npm i && npm start      # http://localhost:8288

Environment: PORT, DATA_PATH (chain file), BLOCK_SEC (target block time), ARGON_KIB (Argon2id memory), AEON_MINT (the upstream token address the API reports; this site shows the $LACUNA address set in client/common.js).

FAQ

Is tLAC the same as $LACUNA? No. tLAC is the testnet coin and has no value. $LACUNA is the Robinhood Chain 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.

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 aeonq1 addresses use ML-KEM-768, a NIST-standard lattice scheme designed to resist that. Meridian extends the same protection to spends.

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