CORSA is a decentralized messenger and DEX-oriented communication project that is currently being rebuilt around the Gazeta protocol.
Current repository status:
- current Corsa version: see
internal/core/config.CorsaVersion - Go-based desktop and node foundation
- public Go SDK in
corsa/sdkfor embedded nodes and bots - desktop chat-style UI with identity list and direct messaging
- mesh-style peer sync between local nodes
- node role model:
fullorclient - identity based on public-key fingerprint addresses
- signed and encrypted direct addressed messages over the mesh
- signed box-key discovery with local TOFU trust pinning
- every message carries a UUID, deletion flag, timestamp, and optional TTL
- messages outside the allowed clock drift are rejected and not forwarded
Gazetaanonymous encrypted notices with TTL- primary wire protocol is JSON frames over TCP
Main docs:
- roadmap: docs/roadmap.md — protocol evolution plan: mesh relay, gossip, routing, reputation, onion DM, BLE transport, Meshtastic bridge, and more
- architecture: docs/architecture.md
- protocol: docs/protocol.md
- encryption: docs/encryption.md
- donate: docs/donate.md
Desktop localization:
- built-in UI languages: English, Arabic, Spanish, Chinese, Russian, French
- startup default can be set with
CORSA_LANGUAGE=en|ar|es|zh|ru|fr - language can also be switched directly in the desktop UI
- desktop remembers the last selected language per identity
Repository layout:
cmd/corsa-desktop— desktop application with embedded local nodecmd/corsa-node— standalone node processsdk— public Go SDK for embedded nodes, command execution, and botsexamples/sic-parvis-magna-bot— minimal bot replying withSic Parvis Magnainternal/core— protocol, identity, node, servicesdocs— architecture and protocol notes
SDK and bots:
- you can import this repository as a Go module and build your own bot on the live CORSA network
- the SDK starts a node from Go code, without requiring environment-variable configuration
- commands can be executed in-process through the same
CommandTableused by the desktop console - add the dependency with
go get github.com/piratecash/corsa@latest, then importgithub.com/piratecash/corsa/sdk - SDK docs: docs/sdk.md
- mini-bot example: examples/sic-parvis-magna-bot/main.go
SDK example:
cfg := sdk.DefaultConfig()
cfg.Node.ListenAddress = ":64648"
// AdvertisePort must be set explicitly when binding to a non-default port —
// the SDK never auto-derives the advertised port from ListenAddress.
advertisePort := uint16(64648)
cfg.Node.AdvertisePort = &advertisePort
runtime, err := sdk.New(cfg)
if err != nil {
log.Fatal(err)
}
if err := runtime.Start(context.Background()); err != nil {
log.Fatal(err)
}Git hooks:
- hook installer:
scripts/install-hooks.sh - current pre-commit hook runs
make lint - install with:
make install-hooks - verify with:
make hooks-status - expected value:
./scripts/hooks/
Run examples:
# Bind explicitly on 127.0.0.1 — the loopback-peer gate accepts dials
# from 127.0.0.1 only when ListenAddress is itself a loopback host, so
# a wildcard bind (":64646") would silently reject the second instance
# below as a non-routable peer.
CORSA_LISTEN_ADDRESS=127.0.0.1:64646 CORSA_BOOTSTRAP_PEER=127.0.0.1:64647 GOCACHE=$(pwd)/.gocache GOMODCACHE=$(pwd)/.gomodcache go run ./cmd/corsa-desktop# Bind port is non-default (64647), so CORSA_ADVERTISE_PORT must be set
# explicitly — the runtime never derives advertise_port from the bind
# port. Without this the node would announce 64646 to its neighbours
# and they would dial the wrong port.
CORSA_LISTEN_ADDRESS=127.0.0.1:64647 CORSA_ADVERTISE_PORT=64647 CORSA_BOOTSTRAP_PEER=127.0.0.1:64646 GOCACHE=$(pwd)/.gocache GOMODCACHE=$(pwd)/.gomodcache go run ./cmd/corsa-desktopDefault identity files are created under .corsa/ and are separated by listen port.
Default bootstrap seed:
65.108.204.190:64646
For public or VPS nodes, the practical network settings are:
CORSA_LISTEN_ADDRESS— local bind addressCORSA_LISTENER— explicit inbound listener override:1enables listen,0disables itCORSA_ADVERTISE_PORT— self-reported listening port placed intohello.advertise_port/welcome.advertise_portand used on the receive side as the authoritative listening-port source when building an announce candidate from the observed TCP IP. Accepts an integer in the inclusive1..65535range; empty, non-numeric, or out-of-range values fall back to64646. Set this explicitly whenever the externally dialable port is not64646— the runtime never derivesadvertise_portfromCORSA_LISTEN_ADDRESS, so a node bound to:64647(with no NAT in the loop) still advertises64646and is undialable untilCORSA_ADVERTISE_PORT=64647is set. The same rule covers the NAT / port-forward / reverse-proxy case where the public dial port differs from the local bind port. The inbound TCP source port is NEVER reused as a listening port. There is noCORSA_ADVERTISE_ADDRESSenv knob in this build: the host component of the local node's external endpoint is no longer wire-published. Each peer announces other peers using the observed TCP source host plus that peer'sadvertise_port, so the local node never has to declare its own external IP — neighbours learn it from the packets that actually arrive (seedocs/protocol/handshake.md→ "Advertise Convergence")CORSA_BOOTSTRAP_PEERS— comma-separated seed listCORSA_TRUST_STORE_PATH— local pinned-contact trust databaseCORSA_NODE_TYPE—fullorclientCORSA_MAX_OUTGOING_PEERS— max outbound peer sessions, default8CORSA_CONNECT_ONLY— pin all outbound dialing to a single peer. Accepts an IP, an overlay address (.onion/.b32.i2p), or a DNS hostname, with or without a port (a bare host gets the default peer port); a DNS hostname is resolved once to an IP at pin time. The node drops every other outbound connection and (re)dials only this address; incoming connections are unaffected. Empty (default) means no restriction. This is a startup seed only — the runtimeconnectOnlyRPC command can change the target or clear it. A self-pin is rejected, and the handshake self-identity guard still prevents connecting to our own identity.CORSA_MAX_INCOMING_PEERS— optional inbound peer cap;0means no app-level capCORSA_PENDING_RING_SIZE— per-peer in-memory pending ring capacity (frames queued for a momentarily-offline peer), default200. Hard memory bound: at capacity the oldest frame for that peer is evicted (ring), never spilled to disk — the pending queue is in-memory only and does not survive a restart. Raising it lets a reconnecting peer receive more missed frames, at the cost of RAM.CORSA_MAX_CLOCK_DRIFT_SECONDS— allowed past/future clock drift for relayed messages, default600GOMEMLIMIT— standard Go runtime soft memory limit (honored natively, no app code). On a busy mesh the message/relay paths produce heavy short-lived garbage; between GC cyclesHeapAlloccan briefly balloon well past the live heap (which stays ~100–150 MB). This is GC churn, not a leak —heap_releasedshows the memory is returned. Setting e.g.GOMEMLIMIT=640MiBmakes the GC run sooner and caps those peaks (RSS/cgroup usage stays bounded) at the cost of slightly more frequent GCs. Pick a value comfortably above the live heap and the container limit; pair with a container memory limit so the soft limit and the OOM ceiling agree.CORSA_PPROF_ADDR— diagnostics only, off by default. When set to a loopback address (e.g.127.0.0.1:6060) the node starts Go'snet/http/pprofserver for memory/CPU profiling. A non-loopback host is refused at startup — the surface exposes process internals and must never face the network. Use during an active investigation, then unset:CORSA_PPROF_ADDR=127.0.0.1:6060 ./corsa-node go tool pprof http://127.0.0.1:6060/debug/pprof/heap # what is using memory now go tool pprof http://127.0.0.1:6060/debug/pprof/profile?seconds=30 # CPU
Message metadata and relay rules:
- each
SEND_MESSAGEframe carriesmessage-id,flag,timestamp, andttl-seconds FETCH_MESSAGE_IDS <topic>returns only UUIDs for lightweight syncFETCH_MESSAGE <topic> <uuid>loads one message by UUID- the preferred protocol form is one JSON object per line
- supported flags:
immutablesender-deleteany-deleteauto-delete-ttl
auto-delete-ttlmessages are removed automatically afterttl-seconds- nodes reject and do not forward messages that are too far in the past or future
- for direct messages,
ttl-secondsis optional and acts as delivery lifetime counted fromcreated_at - pending direct-message delivery and delivery-receipt retry state are held in memory only (a bounded per-peer ring) and do not survive a node restart; recovery relies on sender-side end-to-end retry, not on-disk queue state
- desktop delivery lifecycle for outgoing direct messages is:
queued -> retrying -> failed/expired -> delivered/seen
Node roles:
full— forwards mesh traffic, relays direct messages andGazetanoticesclient— syncs peers and contacts, stores local traffic, but does not forward mesh trafficclientdefaults toCORSA_LISTENER=0, so it keeps outbound sessions and identity sync without advertising a dialable peer endpointfulldefaults toCORSA_LISTENER=1listeneronly controls whether a node accepts inbound connections; it does not change relay behavior- even if
clientis started withCORSA_LISTENER=1, it must not be used as a relay for foreign traffic - a
clientstill sends its own direct messages and delivery receipts upstream; the restriction only applies to third-party traffic - default for
corsa-node:full - default for
corsa-desktop:full - recommended future default for mobile/light client:
client
Example:
CORSA_LISTEN_ADDRESS=:64646 \
CORSA_BOOTSTRAP_PEERS=65.108.204.190:64646 \
GOCACHE=$(pwd)/.gocache \
GOMODCACHE=$(pwd)/.gomodcache \
go run ./cmd/corsa-nodeDocker example:
docker compose up -d --buildThe container runs as non-root user corsa (uid=10001).
The included docker-compose.yaml already uses:
restart: unless-stopped- named volume
corsa-data - non-root runtime user
Manual docker run example:
docker volume create corsa-data
docker run -d -p 64646:64646 \
--name corsa-node \
--restart unless-stopped \
-e CORSA_LISTEN_ADDRESS=:64646 \
-e CORSA_BOOTSTRAP_PEERS=65.108.204.190:64646 \
-v corsa-data:/home/corsa/.corsa \
corsa-nodeFor host bind mount usage, prepare the directory for uid=10001 first:
mkdir -p .corsa
sudo chown -R 10001:10001 .corsa
docker run -d -p 64646:64646 \
--name corsa-node \
--restart unless-stopped \
-e CORSA_LISTEN_ADDRESS=:64646 \
-e CORSA_BOOTSTRAP_PEERS=65.108.204.190:64646 \
-v $(pwd)/.corsa:/home/corsa/.corsa \
corsa-nodeDonate:
- PirateCash:
PB2vfGqfagNb12DyYTZBYWGnreyt7E4Pug - Cosanta:
Cbbp3meofT1ESU5p4d9ucXpXw9pxKCMEyi - PIRATE / COSANTA (BEP-20):
0x52be29951B0D10d5eFa48D58363a25fE5Cc097e9 - Bitcoin:
bc1q2ph64sryt6skegze6726fp98u44kjsc5exktap - DASH:
Xv7U37XKp5d4fjvbeuganwhqXN7Sm4JJkt - Zcash (t-address):
t1bDVifcVuXWW8QctVdhuEghm8WqZjvU1US - Zcash (shielded):
zs1hwyqs4mfrynq0ysjmhv8wuau5zam0gwpx8ujfv8epgyufkmmsp6t7cfk9y0th7qyx7fsc5azm08 - Monero:
4AzdEoZxeGMFkdtAxaNLAZakqEVsWpVb2at4u6966WGDiXkS7ZPyi7haeThTGUAWXVKDTmQ9DYTWRHMjGVSBW82xRQqPxkg
Source: pirate.cash/en/donate
CORSA — это децентрализованный мессенджер и коммуникационный слой для DEX, который сейчас пересобирается вокруг протокола Gazeta.
Текущее состояние репозитория:
- текущая версия Corsa: см.
internal/core/config.CorsaVersion - Go-основа для desktop-приложения и ноды
- публичный Go SDK в
corsa/sdkдля встраиваемых нод и ботов - desktop UI в формате чата: список identity слева и direct messaging справа
- mesh-синхронизация между локальными узлами
- модель ролей узла:
fullилиclient - identity через адрес как fingerprint публичного ключа
- подписанные и зашифрованные адресные direct-сообщения поверх mesh
- подписанное распространение box key и локальный TOFU trust pinning
- каждое сообщение несет UUID, флаг удаления, timestamp и опциональный TTL
- сообщения вне допустимого дрейфа времени отклоняются и не форвардятся
- анонимные зашифрованные объявления
Gazetaс временем жизни
Основные документы:
- дорожная карта: docs/roadmap.ru.md — план развития протокола: mesh relay, gossip, маршрутизация, репутация, onion DM, BLE транспорт, Meshtastic bridge и другое
- архитектура: docs/architecture.md
- протокол: docs/protocol.md
- шифрование: docs/encryption.md
- донаты: docs/donate.md
Локализация desktop-клиента:
- встроенные языки UI: английский, арабский, испанский, китайский, русский и французский
- стартовый язык можно задать через
CORSA_LANGUAGE=en|ar|es|zh|ru|fr - язык также можно переключать прямо в desktop UI
- desktop-клиент запоминает последний выбранный язык отдельно для каждой identity
Структура репозитория:
cmd/corsa-desktop— desktop-приложение со встроенной локальной нодойcmd/corsa-node— отдельный процесс нодыsdk— публичный Go SDK для встраиваемых нод, выполнения команд и ботовexamples/sic-parvis-magna-bot— минимальный бот с ответомSic Parvis Magnainternal/core— протокол, identity, нода, сервисыdocs— описание архитектуры и протокола
SDK и боты:
- этот репозиторий можно подключать как Go-модуль и строить поверх него собственного бота в сети CORSA
- SDK поднимает ноду из Go-кода без обязательной конфигурации через
env - команды можно выполнять in-process через тот же
CommandTable, который использует desktop-консоль - зависимость добавляется через
go get github.com/piratecash/corsa@latest, затем импортируетсяgithub.com/piratecash/corsa/sdk - документация по SDK: docs/sdk.md
- пример минибота: examples/sic-parvis-magna-bot/main.go
Пример SDK:
cfg := sdk.DefaultConfig()
cfg.Node.ListenAddress = ":64648"
// AdvertisePort обязательно указывать явно при не-дефолтном bind-порту —
// SDK не выводит advertise-порт из ListenAddress автоматически.
advertisePort := uint16(64648)
cfg.Node.AdvertisePort = &advertisePort
runtime, err := sdk.New(cfg)
if err != nil {
log.Fatal(err)
}
if err := runtime.Start(context.Background()); err != nil {
log.Fatal(err)
}Git хуки:
- установщик хука:
scripts/install-hooks.sh - текущий
pre-commitзапускаетmake lint - установить:
make install-hooks - проверить:
make hooks-status - ожидаемое значение:
./scripts/hooks/
Примеры запуска:
# Bind-имся явно на 127.0.0.1 — loopback-peer гейт принимает дайлы
# с 127.0.0.1 только если ListenAddress сам на loopback-хосте. При
# wildcard-bind (":64646") вторая инстанция ниже будет молча
# отклоняться как non-routable пир.
CORSA_LISTEN_ADDRESS=127.0.0.1:64646 CORSA_BOOTSTRAP_PEER=127.0.0.1:64647 GOCACHE=$(pwd)/.gocache GOMODCACHE=$(pwd)/.gomodcache go run ./cmd/corsa-desktop# Bind-порт не дефолтный (64647), поэтому CORSA_ADVERTISE_PORT нужно
# задавать явно — runtime никогда не выводит advertise_port из bind-
# порта. Без этой переменной нода будет анонсировать соседям 64646,
# и они будут дозваниваться не туда.
CORSA_LISTEN_ADDRESS=127.0.0.1:64647 CORSA_ADVERTISE_PORT=64647 CORSA_BOOTSTRAP_PEER=127.0.0.1:64646 GOCACHE=$(pwd)/.gocache GOMODCACHE=$(pwd)/.gomodcache go run ./cmd/corsa-desktopПо умолчанию identity-файлы создаются в .corsa/ и разделяются по порту прослушивания.
Для публичных или VPS-нод практические сетевые настройки такие:
CORSA_LISTEN_ADDRESS— локальный bind-адресCORSA_LISTENER— явное переопределение входящего listener:1включает прослушивание,0выключаетCORSA_ADVERTISE_PORT— self-reported слушающий порт, помещаемый вhello.advertise_port/welcome.advertise_portи используемый приёмной стороной как авторитетный источник listening-порта при построении announce-кандидата из observed TCP IP. Принимает целое число в диапазоне1..65535включительно; пустые, нечисловые или вне диапазона значения фолбэчат к64646. Задавайте явно всегда, когда внешне-дайлабельный порт не равен64646— runtime никогда не выводитadvertise_portизCORSA_LISTEN_ADDRESS, поэтому нода с bind-ом на:64647(без NAT в цепочке) всё равно анонсирует64646и остаётся недостижимой, пока не выставленоCORSA_ADVERTISE_PORT=64647. То же правило покрывает NAT / port-forward / reverse-proxy случаи, когда публичный dial-порт отличается от локального bind-порта. Входящий TCP source port НИКОГДА не переиспользуется как listening-порт. В этом билде нетCORSA_ADVERTISE_ADDRESS: host-часть внешнего endpoint'а локальной ноды больше не публикуется на проводе. Каждый пир анонсирует других пиров парой наблюдаемый TCP source host +advertise_portэтого пира, так что локальной ноде вообще не приходится сообщать свой внешний IP — соседи учат его из пакетов, которые реально приходят (см.docs/protocol/handshake.md→ «Advertise Convergence»)CORSA_BOOTSTRAP_PEERS— список seed-нод через запятуюCORSA_TRUST_STORE_PATH— локальная база pinned-контактовCORSA_NODE_TYPE—fullилиclientCORSA_MAX_OUTGOING_PEERS— максимум исходящих peer-session, по умолчанию8CORSA_MAX_INCOMING_PEERS— опциональный лимит на входящие peer-соединения;0означает без app-level лимитаCORSA_PENDING_RING_SIZE— ёмкость in-memory пер-пирового кольца pending (фреймы в очереди для временно-офлайн пира), по умолчанию200. Жёсткий лимит памяти: при переполнении вытесняется самый старый фрейм этого пира (кольцо), на диск ничего не сбрасывается — pending-очередь живёт только в памяти и не переживает рестарт. Увеличение позволяет переподключившемуся пиру получить больше пропущенных фреймов ценой RAM.CORSA_MAX_CLOCK_DRIFT_SECONDS— допустимый дрейф времени для ретранслируемых сообщений, по умолчанию600GOMEMLIMIT— стандартный soft memory limit Go-рантайма (честно учитывается без кода приложения). На нагруженной сети message/relay пути создают много короткоживущего мусора; между GC-цикламиHeapAllocможет ненадолго раздуваться сильно выше живого heap (который держится ~100–150 МБ). Это GC churn, не утечка —heap_releasedпоказывает, что память возвращена. Установка, например,GOMEMLIMIT=640MiBзаставляет GC срабатывать раньше и ограничивает пики (RSS/cgroup usage остаётся в рамках) ценой чуть более частых GC. Берите значение с запасом над живым heap и над лимитом контейнера; задавайте вместе с memory-лимитом контейнера, чтобы soft-лимит и OOM-потолок согласовывались.
Метаданные сообщений и правила relay:
- каждый
SEND_MESSAGEкадр несетmessage-id,flag,timestampиttl-seconds FETCH_MESSAGE_IDS <topic>возвращает только UUID для легкой синхронизацииFETCH_MESSAGE <topic> <uuid>загружает одно сообщение по UUID- поддерживаемые флаги:
immutablesender-deleteany-deleteauto-delete-ttl
- сообщения с
auto-delete-ttlавтоматически удаляются послеttl-seconds - ноды отклоняют и не форвардят сообщения, которые слишком далеко в прошлом или будущем
- для direct message поле
ttl-secondsопционально и задает срок доставки, считающийся отcreated_at - состояние очереди доставки direct message и retry delivery receipt хранится только в памяти (ограниченное пер-пировое кольцо) и не переживает рестарт ноды; восстановление — через end-to-end retry на стороне отправителя, а не через on-disk queue state
- desktop показывает жизненный цикл исходящего direct message как:
queued -> retrying -> failed/expired -> delivered/seen - основной wire-format теперь: один JSON-объект на строку
Роли узла:
full— форвардит mesh-трафик, ретранслирует direct messages иGazetanoticesclient— синкает peers и contacts, хранит локальный трафик, но не делает mesh-forwardingclientпо умолчанию работает сCORSA_LISTENER=0, то есть держит исходящие сессии и синхронизацию identity без объявления dialable peer endpointfullпо умолчанию работает сCORSA_LISTENER=1listenerуправляет только входящими соединениями и не меняет relay-роль узла- даже если
clientзапущен сCORSA_LISTENER=1, его нельзя использовать как relay для чужого трафика - при этом
clientвсе равно отправляет свои собственные direct messages и delivery receipts вверх по upstream; ограничение касается только чужого трафика - значение по умолчанию для
corsa-node:full - значение по умолчанию для
corsa-desktop:full - рекомендуемое будущее значение для mobile/light client:
client
Bootstrap seed по умолчанию:
65.108.204.190:64646
Пример:
CORSA_LISTEN_ADDRESS=:64646 \
CORSA_BOOTSTRAP_PEERS=65.108.204.190:64646 \
GOCACHE=$(pwd)/.gocache \
GOMODCACHE=$(pwd)/.gomodcache \
go run ./cmd/corsa-nodeПример через Docker:
docker compose up -d --buildКонтейнер запускается не от root, а от пользователя corsa (uid=10001).
docker-compose.yaml уже использует:
restart: unless-stopped- named volume
corsa-data - запуск не от
root
Ручной пример через docker run:
docker volume create corsa-data
docker run -d -p 64646:64646 \
--name corsa-node \
--restart unless-stopped \
-e CORSA_LISTEN_ADDRESS=:64646 \
-e CORSA_BOOTSTRAP_PEERS=65.108.204.190:64646 \
-v corsa-data:/home/corsa/.corsa \
corsa-nodeДля использования bind mount с хоста сначала подготовь каталог под uid=10001:
mkdir -p .corsa
sudo chown -R 10001:10001 .corsa
docker run -d -p 64646:64646 \
--name corsa-node \
--restart unless-stopped \
-e CORSA_LISTEN_ADDRESS=:64646 \
-e CORSA_BOOTSTRAP_PEERS=65.108.204.190:64646 \
-v $(pwd)/.corsa:/home/corsa/.corsa \
corsa-nodeDonate:
- PirateCash:
PB2vfGqfagNb12DyYTZBYWGnreyt7E4Pug - Cosanta:
Cbbp3meofT1ESU5p4d9ucXpXw9pxKCMEyi - PIRATE / COSANTA (BEP-20):
0x52be29951B0D10d5eFa48D58363a25fE5Cc097e9 - Bitcoin:
bc1q2ph64sryt6skegze6726fp98u44kjsc5exktap - DASH:
Xv7U37XKp5d4fjvbeuganwhqXN7Sm4JJkt - Zcash (t-address):
t1bDVifcVuXWW8QctVdhuEghm8WqZjvU1US - Zcash (shielded):
zs1hwyqs4mfrynq0ysjmhv8wuau5zam0gwpx8ujfv8epgyufkmmsp6t7cfk9y0th7qyx7fsc5azm08 - Monero:
4AzdEoZxeGMFkdtAxaNLAZakqEVsWpVb2at4u6966WGDiXkS7ZPyi7haeThTGUAWXVKDTmQ9DYTWRHMjGVSBW82xRQqPxkg
Источник: pirate.cash/en/donate