01 · IntroductionWhat SELEN is
SELEN is a proof-of-work Layer 1 where privacy is not a setting. Every transfer hides three things: which output was spent, who received the new outputs, and how much moved. The chain still lets anyone check that no coins were created from nothing and that nothing was spent twice.
What sets it apart is the receiver. Most private chains find a payment's receiver with elliptic-curve Diffie–Hellman. That works today, but a large quantum computer running Shor's algorithm would let anyone who knows an address go back through an archived chain and pick out every payment to it. SELEN quantum addresses use ML-KEM-768, the lattice key-encapsulation scheme standardised by NIST as FIPS 203, so recorded payments stay closed after that day.
The node, block explorer, browser wallet, faucet and miner are one open-source Node.js program. The public testnet runs on exactly the code described here.
02 · Quick startFrom nothing to a private payment
- Open the wallet and create a wallet. Write down the 24 words and pick a password for this browser.
- Open the Faucet tab and request tSELEN. The coins arrive after the next block.
- Wait about five minutes. New outputs can sit in a ring once they have 10 confirmations.
- Send some to a friend's
selenq1…address. Open the transfer in the explorer and press Verify in your browser. - Want more? Point the miner at your address.
03 · AddressesTwo kinds of address
A wallet is a 32-byte seed. Everything else is derived from it with domain-separated hashes:
a = H("selen/spend", seed) spend key, A = a·G
b = H("selen/view", seed) view key, B = b·G
kem = ML-KEM-768.KeyGen(SHA-512("selen/kem" ‖ seed))
| Prefix | Contains | Finds payments with |
|---|---|---|
| selenq1… | A, B and the 1,184-byte ML-KEM public key | ML-KEM-768 decapsulation. Sealed against Q-day. |
| selen1… | A and B | Elliptic-curve Diffie–Hellman (r·B). Private today, not sealed against Q-day. |
Addresses use bech32m encoding. A quantum address is long, around 2,000 characters, because it carries a full lattice public key. Wallets copy it for you. Addresses never appear on chain.
04 · OutputsHow a payment is addressed
For every output the sender derives a fresh shared secret with the receiver:
quantum: (capsule, ss) = ML-KEM-768.Encaps(kem_pk) capsule = 1,088 bytes
classic: r random, R = r·G, ss = r·B
s = H("selen/key", ss)
P = s·G + A one-time key, unlinkable to the address
y = H("selen/mask", ss) commitment blinding
C = y·G + v·H Pedersen commitment to the amount v
ek = v ⊕ SHA-256("selen/amount" ‖ ss)[0..8]
tag = SHA-256("selen/tag" ‖ ss)[0] one-byte view tag for fast scanning
The receiver's wallet tries every new output. For quantum outputs it decapsulates the capsule with its ML-KEM secret key; for classic ones it computes b·R. If the view tag matches and s·G + A equals P, the output is theirs, and the amount and blinding factor open the commitment. The one-time secret key is x = s + a.
The group is ristretto255, a prime-order group, so there are no cofactor edge cases in ring signatures or key images. H is a second generator obtained by hashing to the curve, so nobody knows its discrete log with respect to G.
05 · TransfersRings, key images, balance
Rings of 16
Each input names 16 outputs from the chain by global index: the real one plus 15 decoys picked by the wallet, sorted ascending so the position says nothing. The wallet signs with MLSAG over two rows: the one-time keys Pj, and the commitment differences Cj − C′, where C′ is the input's pseudo-output commitment.
Key images
Signing reveals I = x·Hp(P). The same output always gives the same I, so nodes refuse any transfer whose key image is already on chain or already waiting in the mempool.
Balance
Pseudo-output masks are chosen so that ΣC′ = ΣCout + fee·H. Nodes check that equation without learning any amount.
Range proofs
Each output commitment is split into 48 bit commitments Ci = ri·G + bi·2i·H, with Σri = y. A two-member ring signature over {Ci, Ci − 2iH} proves each bi is 0 or 1. The amount is therefore between 0 and 248 − 1 and cannot wrap around the group order to mint coins.
Fees
0.0001 tSELEN per started 1,000 bytes. A typical two-output transfer to quantum addresses is about 17 kB.
06 · ConsensusArgon2id proof of work
A block header holds version, height, previous block id, time, Merkle root of the transfers, difficulty and nonce. Its proof of work is Argon2id over the header bytes (2 MiB of memory, one pass, salt SELEN-POW-v1-tst). A header is valid when the 256-bit hash, read big-endian, is at most (2256 − 1) / difficulty.
Difficulty retargets every block with a linearly weighted moving average over the last 45 solve times, aiming at 30 seconds. Timestamps must exceed the median of the last 11 blocks and may run at most 120 seconds ahead of a node's clock. Nodes follow the chain with the most cumulative work.
Nobody signs a block. There is no validator set, no stake and no block-signing key to steal.
Emission
The first transaction of each block is the coinbase. It pays 32 tSELEN plus fees to the miner's address as a one-time output. The reward halves every 1,051,200 blocks (about a year) down to a tail of 0.5 tSELEN. Coinbase amounts are public by design so anyone can audit supply; the receiver is still hidden.
07 · ParametersTestnet constants
| Constant | Value |
|---|---|
| network | selen-testnet-1 |
| block time | 30 s target, LWMA over 45 blocks |
| ring size | 16 |
| maturity | 10 blocks before an output can sit in a ring |
| range proof | 48 bits |
| KEM | ML-KEM-768 (FIPS 203), 1,184-byte keys, 1,088-byte capsules |
| group | ristretto255 |
| proof of work | Argon2id, 2 MiB, t = 1, p = 1 |
| units | 1 tSELEN = 108 atomic units |
| limits | 16 inputs, 16 outputs, 250 kB per transfer, 200 transfers or 4 MB per block |
| trusted setup | none |
08 · Running a nodeHold the chain yourself
npm install npm start # own chain, built-in miner and faucet PEER=https://selen.network npm start # follow the public testnet, verify every block
With PEER set, the node downloads blocks in batches and re-checks proof of work, timestamps, difficulty, every ring signature, every range proof, every balance equation and every key image before it stores anything. If the peer shows a branch with more work, the node rolls back to the fork point and follows it. Transfers and blocks you submit locally are forwarded to the peer.
| Variable | Meaning |
|---|---|
| PORT | HTTP port, default 9310 |
| DATA_DIR | where chain.db lives |
| MINER | 0 to disable the built-in miner |
| MINER_THREADS / MINER_DUTY | threads, and the fraction of time they work |
| MINER_ADDRESS | reward address for the built-in miner |
| FAUCET | 0 to disable the faucet |
09 · MiningBrowser and command line
/mine runs Argon2id (WebAssembly) in one Web Worker per thread. It asks the node for a job tied to your address, searches nonces, and submits a block when the hash meets the target.
node tools/miner.mjs --node https://selen.network --address selenq1… --threads 4
The command-line miner uses Node's native Argon2id and the same job API. Each found block pays its full reward to your address.
10 · Node APIEverything the site reads
All responses are JSON. Amounts are strings in atomic units.
| Endpoint | Returns |
|---|---|
| GET /api/status | height, tip, difficulty, hashrate, output and key-image counts, mempool size |
| GET /api/events | server-sent events: block, mempool, rollback |
| GET /api/blocks?before=&limit= | block summaries, newest first |
| GET /api/block/:height|:id | header, Argon2id hash, transfers |
| GET /api/tx/:id | full transfer plus resolved ring members, for in-browser verification |
| GET /api/outputs?from=&limit= | outputs by global index, for wallet scanning |
| GET /api/ring?ids= | keys and commitments of the given outputs |
| GET /api/keyimages?from= | spent key images since a height, plus pending ones |
| POST /api/tx | submit a signed transfer |
| GET /api/mining/job?address= | header template, target, Argon2id parameters |
| POST /api/mining/submit | { job, nonce } → accepted block |
| POST /api/faucet | { address } → testnet coins (one per address per 12 h) |
| GET /api/sync/blocks?from= | raw blocks for peers |
11 · Security modelWhat holds, and against whom
| Property | Today | Against a quantum adversary |
|---|---|---|
| receiver, quantum address | hidden | hidden: ML-KEM-768 |
| receiver, classic address | hidden | exposed to anyone who knows the address |
| amount | hidden | hidden: Pedersen commitments hide perfectly, and the encrypted amount is keyed by the KEM secret |
| which ring member was spent | hidden among 16 | can be revealed: key images rest on discrete log |
| no inflation | sound | needs Perigee: binding of commitments rests on discrete log |
Put plainly: a future quantum computer could tell which of the sixteen ring members a recorded input really spent, and in principle forge new spends. It could not tell who received a payment sent to a selenq1 address, or how much. Perigee moves spend proofs to hash-based constructions to close the first two gaps.
12 · $SELENThe token
$SELEN is a token on Robinhood Chain that funds development and carries the project to mainnet. It is separate from tSELEN, the testnet coin.
Contract address: published at launch.
13 · Current statusWhere things stand
- Live: private testnet, quantum addresses, explorer with in-browser verification, browser wallet, faucet, browser and CLI mining, peer sync.
- Now: $SELEN on Robinhood Chain.
- Planned: Perigee: hash-based spend proofs and full-chain anonymity, specified and reviewed in public.
- After review: mainnet from a fresh genesis with Perigee active at block one.