Skip to content
lapisproject-devPublic

About

Fully decentralized P2P social network protocol (Kotlin/JVM) — web-of-trust reputation, Bitcoin/Lightning-anchored micropayments, and machine-to-machine node reputation, with no central servers or platform tokens

Topics

Resources

Contributing

Stars

1 star

Watchers

0 watching

Forks

Latest commit

 

History

94 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Lapis Net

CI Kotlin 2.4.20 JDK 25 Apache 2.0

Lapis Net

Lapis Net is a fully decentralized peer-to-peer social network protocol on the Kotlin/JVM platform. It is part of the Lapis family of distributed systems, alongside Lapis Cloud.

The protocol combines:

  • Decentralized identity — secp256k1 keypairs, Bitcoin-compatible

  • Content-addressed storage — Nabu (IPFS-on-libp2p), DHT + Bitswap

  • Web-of-trust reputation — Veritas, a shortest-path trust graph

  • Gossip-based propagation — libp2p GossipSub

  • Bitcoin/Lightning-anchored micropayment relevance — Virtus (on-chain OP_RETURN and Lightning BOLT-11 payment proofs)

  • Sybil-resistant free "likes" — Karma, weighted by Veritas and a Bitcoin time anchor

  • Machine-to-machine infrastructure reputation — Madli, a five-metric weighted-median node-reputation layer

The protocol core is deliberately neutral about curation, sorting, and reputation models. Veritas, Karma, Virtus, and Madli (the scoring dimensions) are optional tools for browser/view implementations, not protocol requirements.

Status

Every wave through V0.9 is implemented and merged to master, followed by four post-arc maintenance waves (V0.9.8 to V0.9.14, below), a fix release and a feature release. The current release is v0.11.0 (v0.10.0 was the first with a chronological version number: up to v0.9.9 the tags are wave identifiers (V0.7 was never a standalone wave — its scope was folded into the incremental browser growth of V0.2–V0.5), from v0.10.0 on they count releases in the order they ship). See docs/roadmap.adoc for the explanation and a map of every tag to its content. Full technical detail for each module is in the Modules table below; the full wave-by-wave history — including scope cuts and hardening follow-ups — is in docs/roadmap.adoc and docs/architecture.adoc.

Wave What it added Status

V0.1

Protocol core: secp256k1/Ed25519 identity, libp2p bootstrap, Nabu (DHT + Bitswap) storage, Veritas (web-of-trust)

✅

V0.2

Virtus (LTR micropayment economy, on-chain OP_RETURN) + the first real client, the Minimal-Browser

✅

V0.3

Karma (Sybil-resistant free "likes", Bitcoin time-anchored)

✅

V0.4

Genesis-bootstrap, QR-code/deep-link identity linking, keystore encryption at rest (Argon2id + AES-256-GCM)

✅

V0.5

Madli (machine-to-machine node reputation, weighted-median aggregation)

✅

V0.6

Lightning payment-proof verification (LightningProof, a second LtrProof type alongside on-chain)

✅

V0.7

(no standalone wave — folded into V0.2–V0.5’s incremental browser growth)

—

V0.8

Direct messages (X3DH/Double Ratchet, online + offline delivery, encrypted attachments, acceptance policy, browser UI) and 1:1 voice calls over WebRTC

✅

V0.9

Asynchronous messages / mail replacement: encryption, threading, attachments, spam protection

✅

V0.9.8

Repair of cross-node DHT provider discovery at the storage layer (broken since V0.1.4)

✅

V0.9.9

NAT traversal over Circuit Relay v2: a node behind NAT publishes a relay address and can be dialled through the relay (opt-in relay operation, no hole punching)

✅

V0.9.10

Relay hardening: a stalled circuit can no longer block the other circuits and reservation renewals sharing its connection (per-circuit stall timeout plus a per-connection stall budget), and a node reuses a relayed connection the peer already opened to it (released in v0.10.0)

✅

V0.9.11

DHT layer hardening in lapis-net-storage: absolute deadlines and concurrency caps on provider lookups, bounded provider store, sanitized peer addresses, validated Kademlia requests (released in v0.10.0)

✅

v0.10.1

Fix release: a race in the relay’s circuit setup (present in v0.9.9 and v0.10.0) made relay connections hang for 10 s and fail with no warning or error in the log (the decoding exception was visible only in DEBUG output); fixed on both legs; a deterministic regression test covers the hop leg, and the destination’s leg is covered only by an assertion that the relay’s control-failure counter stays at zero. Adds the release pipeline: a tag push builds, smoke-tests and publishes the downloadable ZIPs with SHA256SUMS

✅

v0.11.0 (V0.9.12)

Madli relay-reliability metric (daily vector wire format v2, v1 stays readable; the publisher is off by default), usage-based relay priority (reputation is never a hard gate), automatic relay selection (two relays per node, only for a node declared PRIVATE), a reachability heuristic that only warns, a hardened IP classifier. Wired into the browser node and lapisnet node in V0.9.15: automatic selection only with reachability=private, the publisher only with publish-relay-reliability=true

✅

v0.11.0 (V0.9.13, V0.9.14)

DHT fallback next to GossipSub for peer records, prekey bundles, mail and direct-message pointers (IPNS value per identity, own IPNS responder and validator, bounded record store, outbox, rendezvous provider records, consumers). Plaintext mail leaves no DHT pointer. Wired into the browser node and lapisnet node in V0.9.15 (on by default, off with --dht-fallback=false; lapisnet node has no direct messages and no mail)

✅

v0.11.0 (V0.8.7b)

/call.html: the browser page as a remote control for voice calls (Server-Sent Events, session-token cookie, Host and Origin checks, in-memory history)

✅

Known, deliberate gaps — not oversights, each documented at the point it was cut:

  • No embedded Lightning node — proof verification only, no payment sending; no BOLT-12 offer publication.

  • NAT traversal is relay-only — no hole punching. A node behind NAT can now be reached: it reserves a slot on a Circuit-Relay-v2 relay and publishes the resulting /p2p-circuit address, and a peer that holds only that address dials it through the relay with an end-to-end Noise session the relay cannot read. Acting as a relay is opt-in and off by default. What is still missing is dcutr hole punching, which would let two NAT’d peers upgrade a relayed connection to a direct one: it is absent from jvm-libp2p entirely and would have to be implemented from scratch. Every relayed connection therefore stays relayed for its whole life, and relay operators are not compensated: reputation (Madli) is the intended route: a relay-reliability metric exists since v0.11.0, but the publisher that writes it is off by default and no application switches it on, and the usage-based priority ledger is local and memory-only (a new identity starts at 0). Using a relay is an explicit, programmatic setting (LapisNode.relayClient.reserve(relay)). Since v0.11.0 the library also has an automatic relay selection (LapisNode.startAutoRelay, only for a node declared NodeReachability.PRIVATE) and a reachability heuristic that merely warns; there is still no AutoNAT, and the browser and lapisnet node --reachability=private start it (the publisher stays off unless --publish-relay-reliability=true), and lapis-net-call refuses relayed media on purpose, so voice calls still need a direct connection. Rollout constraint: a record advertising the new RELAY capability bit is rejected outright by older nodes, so readers must be upgraded everywhere before any node advertises it.

  • Browser call remote control (V0.8.7b) — /call.html remote-controls a call whose audio runs in the JVM node process. Since V0.9.15 the acceptance policy is wired in the browser: blocked callers are shown locally and nothing is sent to them. No mute, no persisted call history (the history is held in memory).

  • No group calls, video, multi-device messaging, or MLS — cut from the V0.8 arc’s scope entirely on 2026-08-04, not a partial or stubbed implementation.

  • No delivery receipts or typing indicators for direct messages — deliberately deferred, not built even as stubs.

  • The DHT fallback is wired since V0.9.15 (V0.9.13, V0.9.14). The browser publishes and looks up peer records, prekey bundles and mail/DM pointers over the DHT (GossipSub stays the primary path); it is on by default and --dht-fallback=false turns it off. The CLI’s node subcommand uses it for the peer directory only. DHT lookups tell the peers that are asked who is looking for whom, until traffic anonymisation exists (docs/architecture.adoc, section 11); the rendezvous key of a victim can be eclipsed by throwaway identities; plaintext mail is deliberately never recoverable from the DHT; a lookup needs the owner’s Ed25519 peer ID.

Comparison

How Lapis Net stands next to the five closest decentralized social protocols — full methodology and sources at net.lapisproject.dev/comparison.

Feature Lapis Net Nostr Secure Scuttlebutt Farcaster Bluesky / AT Proto Mastodon / Fediverse

Genuine peer-to-peer (no required relay, hub, or homeserver)

●

◐

●

○

○

○

Cryptographic identity instead of an account

●

●

●

◐

◐

○

Weighted web of trust (transitive trust paths)

●

◐

◐

○

○

○

Bitcoin/Lightning-native value layer

●

●

○

○

○

○

Market-based visibility (real value at stake, not a black-box algorithm)

●

○

○

◐

○

○

End-to-end encrypted voice calls

●

○

○

○

○

○

Asynchronous long-lived messaging (e-mail replacement)

●

◐

◐

○

○

○

Machine-to-machine node reputation (infrastructure, not opinions)

●

○

○

○

○

○

● yes · ◐ partial · ○ no

Honest caveat: Nostr, Mastodon, Bluesky and Farcaster each already have millions of users and mature client ecosystems. Lapis Net is, as of this comparison, a working multi-wave prototype (V0.1–V0.9 implemented) without a public network yet — a real trade-off against the table above, not a detail to gloss over. One clarification on the first row: Lapis Net can use volunteer relays to reach nodes behind home routers; they forward only end-to-end-encrypted traffic and are never required for a node that is directly reachable.

Modules

Module Purpose

lapis-net-core

Base protocol abstractions shared by all other modules.

lapis-net-identity

secp256k1 identity keypairs, Ed25519 dual-key binding for the libp2p peer ID, keystore encryption at rest (Argon2id + AES-256-GCM), and multi-device/pseudonym self-trust linking.

lapis-net-storage

DHT + Bitswap content storage via Nabu. Cross-node provider discovery works since V0.9.8 (it had been broken since V0.1.4); the DHT layer is bounded and validates untrusted input since V0.9.11.

lapis-net-trust

Veritas (web-of-trust): trust-edge data model, shortest-path scoring algorithm, GossipSub propagation, Nabu persistence.

lapis-net-networking

libp2p node bootstrap, peer identity derivation, mDNS discovery, GossipSub, the Genesis-bootstrap/QR-code connection flow, and relay-based NAT traversal over Circuit Relay v2 (V0.9.9: reservation client, opt-in relay server, /p2p-circuit transport with backpressure and admission limits).

lapis-net-virtus

Virtus (the LTR micropayment economy): on-chain (OP_RETURN) and Lightning (BOLT-11) payment proofs, 10%/24h decay, view-specific accumulation.

lapis-net-karma

Karma (free likes): Bitcoin time-anchored, Veritas-weighted Sybil-resistant "like" scoring.

lapis-net-madli

Madli (machine-to-machine node reputation): five-metric observation vector (plus, since V0.9.12, an optional relay-reliability block in wire format v2, written only if the off-by-default publisher is enabled) with Veritas-weighted, manipulation-resistant weighted-median aggregation.

lapis-net-mail

Asynchronous messages (the e-mail replacement), fully implemented (V0.9.1–V0.9.4): a signed, addressed MessageEnvelope bound by CID to its own MessageBody blob, gossiped on one GossipSub topic per recipient identity, Nabu-persisted at both ends (V0.9.1); hybrid-ecies encryption (HKDF + AES-256-GCM over a secp256k1 ECDH exchange, V0.9.2); cycle-safe, receive-order-independent threading, encrypted attachments, a local sent-folder view, and a browser inbox UI with verified XSS protection (V0.9.3); and a Veritas/Karma scoring filter plus a Lightning first-contact deposit for spam resistance (V0.9.4). A follow-up security-hardening pass closed a critical unauthenticated remote memory-exhaustion bug found in this module and, on audit, in three other already-shipped modules (lapis-net-trust, lapis-net-virtus, lapis-net-karma) plus a fourth instance in lapis-net-browser.

lapis-net-policy

Acceptance-gate machinery extracted from lapis-net-mail in V0.8.6 so lapis-net-dm (direct messages) could share it rather than reimplement it: AcceptanceGate/AcceptanceGateEvaluator (the gate vocabulary and evaluation logic — Veritas distance, Karma threshold, Lightning first-contact deposit, explicit accept/deny lists) and VeritasPathCache (a bounded, LRU-evicting cache so a flood of never-repeating throwaway identities can’t force an unbounded-heap shortest-path search on the GossipSub validator hot path).

lapis-net-directory

Signed peer/presence directory records (PeerRecord): identity bound to its current network addresses and libp2p peer identity, with a monotonic sequence number and a heartbeat TTL, gossiped and Nabu-persisted — the foundational discovery layer V0.8’s later sub-waves (online DM addressing) resolve recipients through (V0.8.1). Also carries PrekeyBundle gossip/indexing (PrekeyBundleGossip/PrekeyBundleIndex) for `lapis-net-ratchet’s prekey bundles (V0.8.2).

lapis-net-ratchet

X25519 encryption sub-key and the X3DH handshake (Signal’s Extended Triple Diffie-Hellman, reimplemented from the public specification): PrekeyBundle/PrekeyBundleCodec, PrekeyStore (durable one-time-prekey consumption), and the X3dh handshake itself. Deliberately network-free (V0.8.2). Plus the Double Ratchet itself: DoubleRatchetSession (forward-secret, post-compromise-secure 1:1 sessions over an HKDF root chain and HMAC symmetric chains), RatchetMessage/RatchetMessageCodec, a bounded evicting SkippedMessageKeyStore, and DoubleRatchetSessionCodec for encrypted-at-rest session persistence, including the encodeWithKey/decodeWithKey Argon2id-bypass pair lapis-net-dm uses for per-message persistence (V0.8.3).

lapis-net-dm

The custom /lapis/dm/1.1.0 libp2p stream protocol wiring lapis-net-ratchet’s X3DH/Double-Ratchet primitives into a live network path: `DmProtocol/DmProtocolHandler (stream-protocol binding, the first raw-libp2p-stream parser in this codebase — length-validated-before-allocation frame decoding, slowloris/absolute-lifetime timeouts, per-peer stream cap), DmEnvelope/DmEnvelopeCodec (the outer wire frame), DmSessionManager (first-contact/resumed session state machine, durable session persistence, the identity-authority-safe inbound handler), and DmStore (in-memory-only conversation history) — online 1:1 direct messages between directly-dialable peers (V0.8.4), plus V0.8.5’s offline Nabu mailbox (MailboxPointer/MailboxGossip/MailboxPoller/MailboxRedeliveryScheduler, DmSessionManager.sendOffline) for offline delivery via a Bitswap-fetched, periodically re-announced signed pointer.

lapis-net-call

1:1 audio calls over WebRTC (V0.8.7): CallManager (the call-control state machine — INVITE/ACCEPT/REJECT/HANGUP, a three-executor concurrency model, invite-flood rate limiting), CallSignal/CallSignalCodec (the wire frame carried as a DmMessageType.CALL_SIGNAL envelope’s ratchet plaintext), CallSdpPolicy (SSRF-class defenses against a peer-supplied SDP), and WebRtcCallMediaEngine (the sole dev.onvoid.webrtc:webrtc-java consumer — host-ICE-candidates only, no STUN/TURN). Depends on lapis-net-dm for signaling transport and DTLS-fingerprint identity binding; lapis-net-dm itself stays entirely WebRTC-free.

lapis-net-browser

Minimal-Browser — the first real Lapis Net client, exposing Veritas/Virtus/Karma data, the LTR record-authoring endpoints, a mail inbox UI (V0.9.3), and a direct-message UI (V0.8.6b), and the call remote control /call.html (V0.8.7b).

lapis-net-cli

Entry point / multi-node demo harness proving trust propagation end to end.

Running a node with the V0.9.x features

# headless node behind a NAT: reserves relays, uses the DHT directory (no DM, no mail)
lapisnet node --reachability=private
# browser node: same switches as arguments, LAPISNET_* variables or $LAPISNET_HOME/browser.properties
lapisnet-browser --reachability=private --dm-veritas-gate=true
# mail gates (unreleased, V0.9.17): off by default; mail from a sender who passes none of them is discarded
lapisnet-browser --mail-veritas-gate=true --mail-karma-min-score=2

Mail acceptance is wired in the browser node (unreleased, after v0.11.0). Keys: --mail-veritas-gate (LAPISNET_MAIL_VERITAS_GATE, file key mailAcceptance.veritasGate), --mail-karma-min-score (LAPISNET_MAIL_KARMA_MIN_SCORE, mailAcceptance.karmaMinScore), --mail-min-deposit-msat (LAPISNET_MAIL_MIN_DEPOSIT_MSAT, mailAcceptance.minDepositMsat, no effect yet). All gates are off by default. Accepted contacts are always let through; blocked mail is discarded without reply and only counted in the status panel.

An invalid option aborts the start. See docs/release-notes/v0.11.0.adoc for all options, defaults and the metadata warning about the DHT fallback.

Building

./gradlew clean check   # compiles, runs tests, runs ktlint
./gradlew :lapis-net-cli:run

Requires JDK 25.

To run the reference node together with its minimal browser UI (main view, mail inbox, direct messages):

./gradlew :lapis-net-browser:run

It prints the local address to open in a browser and the identity fingerprint. For calls it also prints a link of the form http://127.0.0.1:7878/call.html#t=<token>; (same device only: the token is regenerated on every start and must not be shared or logged; open the link from the console, a bare /call.html is refused). The HTTP port is currently fixed at 7878 (there is no environment variable for it yet), so only one browser node can run per machine; a second instance on the same machine fails with "address already in use" and would also share the same identity and storage directory unless LAPISNET_HOME is changed. State lives under $LAPISNET_HOME (default ~/.lapisnet). Environment variables: LAPISNET_KEYSTORE_PASSPHRASE encrypts the identity at rest (without it, and without an interactive console, the identity is stored unencrypted with POSIX file permissions only and a warning is logged); LAPISNET_LISTEN_ADDR sets the libp2p listen address (the default is loopback-only, so no other machine can reach the node until this is set); LAPISNET_BOOTSTRAP_PEERS is a comma-separated list of peer multiaddrs, each including /p2p/<peerId>, and entries in $LAPISNET_HOME/bootstrap.peers (one per line) are added to it.

There is no public network yet, and connecting two real nodes is still a manual step. Set LAPISNET_LISTEN_ADDR to the machine’s concrete LAN address, for example /ip4/192.168.1.20/tcp/4001. The main page shows a connect link (lapisnet://connect?maddr=…​&pk=…​) and the same link as a QR code; on the other node, paste that link into the "Paste a connect link" field of its main page, or enter the plain multiaddr, including /p2p/<peerId>, in the connect field in the page header. The peer ID is also logged at startup (BrowserServer listening on …​ (peer <peerId>)). A wildcard such as /ip4/0.0.0.0/tcp/4001 makes the node listen on every interface, but the QR code and connect link would then carry the wildcard, which another machine cannot dial; in that case type /ip4/<LAN-IP>/tcp/4001/p2p/<peerId> into the header connect field by hand, taking the peer ID from the log. Peers discovered through mDNS are only logged and cached, never dialled automatically and not shown in the UI. If you only have one machine, ./gradlew :lapis-net-cli:run runs a multi-node trust-propagation demo inside a single process.

Download

Releases are listed at https://github.com/lapisproject-dev/Lapis-Net/releases. v0.10.1 is the first release built by the release pipeline, whose publish step was tested only against local stand-ins before that run, not against GitHub itself. Starting with v0.10.1 (the current release is v0.11.0), a release is published together with lapis-net-browser-<version>.zip, lapis-net-cli-<version>.zip and SHA256SUMS. Releases before that, including v0.10.0, have no downloadable files: check the release’s asset list, and build from source (see Building) if none are attached.

Prerequisite: JDK 25 on the PATH or in JAVA_HOME.

unzip lapis-net-browser-<version>.zip
lapis-net-browser-<version>/bin/lapis-net-browser   # node plus minimal browser UI, see Building

unzip lapis-net-cli-<version>.zip
lapis-net-cli-<version>/bin/lapis-net-cli           # multi-node demo, ends by itself after a few seconds

The ZIPs also contain .bat start scripts for Windows; those are not tested. The environment variables described under Building, in particular LAPISNET_HOME and LAPISNET_KEYSTORE_PASSPHRASE, apply to the unpacked distributions as well.

Verify a download against SHA256SUMS, which lists both ZIPs, so the check has to be limited to the file you actually downloaded (<file> is its name, for example lapis-net-cli-<version>.zip):

# Linux
sha256sum -c --ignore-missing SHA256SUMS
# macOS (shasum has no --ignore-missing, so select the line of your file)
awk '$2 == "<file>"' SHA256SUMS | shasum -a 256 -c -

The artifacts are not signed; the checksums only detect corrupted downloads. How releases are built and published: docs/releasing.adoc.

Development conventions

  • Code style is enforced by ktlint and checked in CI.

  • Logging uses kotlin-logging: private val logger = KotlinLogging.logger {} in every file that logs.

  • Documentation is written in AsciiDoc; diagrams (once introduced) use kUML via the kuml Asciidoctor macro — no PlantUML, Mermaid, or hand-drawn diagrams.

  • All identifiers (classes, interfaces, packages) are named in English. Domain terms (Veritas, Karma, Virtus, Madli) are the one exception, as they are proper nouns from other languages used directly as identifiers: Veritas and Virtus are Latin, Karma is Sanskrit, and Madli is Georgian.

License

Apache License 2.0 — see LICENSE.

Contributing

About

Fully decentralized P2P social network protocol (Kotlin/JVM) — web-of-trust reputation, Bitcoin/Lightning-anchored micropayments, and machine-to-machine node reputation, with no central servers or platform tokens

Topics

Resources

Contributing

Stars

1 star

Watchers

0 watching

Forks

Releases

Sponsor this project

Packages

Contributors

Languages