Skip to main content

Tech

This document describes how DePIN Messaging is implemented in Neurai Core, based on the DePIN pool, network gateway, RPCs, and ECIES encryption stack.

System overview

DePIN Messaging is a token-gated, end-to-end encrypted message pool with a TCP/JSON-RPC gateway. The design assumes:

  • Messages are encrypted client-side and stored as ciphertext.
  • Pool nodes validate token ownership and signatures before storing.
  • Recipients decrypt locally using their private keys.

Runtime components

  • CDepinMsgPool: in-memory pool for encrypted messages, limits, and expiry.
  • CDepinMsgPoolServer: TCP server on the DePIN port (default 19002) with JSON-RPC support.
  • RPC layer: depin* RPCs that submit, fetch, inspect, and manage pool contents.
  • ECIES layer: hybrid encryption using secp256k1 ECDH + AES-256-GCM.
  • MCP worker (optional): background bot that polls messages and replies via an MCP AI server.

Message structure

CDepinMessage (outer envelope)

Fields:

  • token: asset name controlling access
  • senderAddress: Neurai address
  • timestamp: UNIX time
  • signature: ECDSA over secp256k1
  • encryptedPayload: serialized ECIES envelope

Signature hash (CHashWriter, double-SHA256 over serialized fields):

hash = SHA256(SHA256(token || senderAddress || timestamp || encryptedPayload))

CECIESEncryptedMessage (inner payload)

Fields:

  • ephemeralPubKey: 33 bytes (compressed secp256k1)
  • encryptedPayload: [12B nonce || ciphertext || 16B GCM tag]
  • recipientKeys: map of hash160(address) -> [12B nonce || encrypted_aes_key || 16B tag]

Because recipients are encoded by hash160(address) and stored inside the payload, the pool cannot filter per-recipient. Clients decrypt locally and discard failures.

Encryption and decryption (ECIES hybrid)

Encryption:

  1. Generate a per-message ephemeral keypair.
  2. Derive an AES-256 key from the ephemeral private key (KDF-SHA256).
  3. Encrypt plaintext once with AES-256-GCM (12B nonce, 16B tag).
  4. For each recipient:
    • ECDH with ephemeral privkey and recipient pubkey.
    • Derive wrap key (KDF-SHA256).
    • Encrypt the AES key with AES-256-GCM.
    • Store [nonce || encrypted_aes_key || tag] keyed by hash160(address).

Decryption:

  1. Locate recipient package by hash160(address).
  2. ECDH with recipient private key and message ephemeral pubkey.
  3. Derive wrap key, decrypt AES key with GCM.
  4. Decrypt message payload with AES-256-GCM.

Diagram: ECIES encryption/decryption

Client (Sender)                                              Client (Recipient)
----------------- -------------------
ephemeral keypair
(priv_e, pub_e)
|
| KDF-SHA256(priv_e)
v
AES key (K_msg)
|
| AES-256-GCM(plaintext, K_msg, nonce_msg)
v
encryptedPayload = nonce_msg || ciphertext || tag_msg
|
| For each recipient:
| shared = ECDH(priv_e, pub_recipient)
| wrap = KDF-SHA256(shared)
| AES-256-GCM(K_msg, wrap, nonce_r)
v
recipientKeys[hash160(addr)] = nonce_r || enc_K_msg || tag_r
|
+-------------------------------> recipientKeys + encryptedPayload
(via pool)
|
| Find own hash160(addr)
v
nonce_r || enc_K_msg || tag_r
|
| shared = ECDH(priv_recipient, pub_e)
| wrap = KDF-SHA256(shared)
| AES-256-GCM-Decrypt(enc_K_msg, wrap, nonce_r, tag_r)
v
K_msg
|
| AES-256-GCM-Decrypt(ciphertext, K_msg, nonce_msg, tag_msg)
v
plaintext

Pool validation and limits

When adding a message, the pool enforces:

  • Token must match the pool token.
  • Timestamp cannot be more than 60 seconds in the future.
  • Message must not be expired (default 168 hours).
  • Payload cannot be empty and must fit size limits.
    • Total payload limit: maxMessageSize * maxRecipients.
  • Pool size cannot exceed maxPoolSizeMB.
  • Signature must verify (unless skipped by a server-authenticated path).
  • Sender must hold the token.

Addresses must have a revealed public key in the pubkeyindex, otherwise:

  • They cannot be used as recipients.
  • Their signatures cannot be verified.

RPC surface (core)

  • depingetmsginfo: pool status, limits, cipher, memory usage.
  • depinsubmitmsg: accepts a pre-encrypted, pre-signed CDepinMessage.
  • depinreceivemsg: returns encrypted messages as JSON (client decrypts).
  • depinsendmsg: node prepares encryption and signature on behalf of the sender.
  • depingetmsg: decrypts messages for local wallet addresses (local or remote).
  • depinclearmsg: removes expired messages or clears the pool.
  • depinpoolstats: aggregate pool stats (counts, sizes, age buckets).

depinsubmitmsg verifies the message signature and token ownership before storing.

DePIN port protocol (TCP + JSON-RPC)

The DePIN gateway listens on depinmsgport (default 19002). It supports:

  • JSON-RPC: depinsubmitmsg, depinsendmsg, depingetmsg
  • Plain text commands (newline-terminated):
    • AUTH|token|address[|SEND] -> CHALLENGE|nonce|timeout
    • GETMESSAGES|token|addr1,addr2|authAddress|signature|challenge
    • INFO, PING

Challenge/response authentication

  1. Client requests AUTH.
  2. Server checks:
    • token matches pool token
    • address owns the token
    • address has a revealed pubkey
  3. Server issues a short-lived nonce (30s).
  4. Client signs:
    • DEPIN-GET|token|address|challenge for receive
    • DEPIN-SEND|token|address|challenge for send
  5. Server recovers pubkey from the compact signature and verifies it matches the address.

This reduces unauthenticated sends/reads and limits some DoS vectors.

Local and remote message flows

Local send:

  • Client encrypts and signs.
  • pDepinMsgPool->AddMessage(...).

Remote send (depinsendmsg):

  • Client performs challenge/response with the remote pool.
  • Node encrypts and signs on the sender wallet.

Remote send (secure depinsubmitmsg):

  • Client encrypts and signs.
  • Submit serialized hex message to the remote gateway.

Remote receive:

  • Client performs challenge/response.
  • Pool returns encrypted messages in bulk.
  • Client attempts ECIES decryption per address.

Diagram: End-to-end send/receive flow

Sender Client                        Pool Node                           Recipient Client
------------- ---------- -----------------
1) Build ECIES payload
2) Build CDepinMessage
3) Sign message hash
4) depinsubmitmsg(hex) -----------> [DePIN gateway]
- validate token
- verify signature
- check ownership
- enforce size/expiry/limits
- store in CDepinMsgPool
- return success

5) depinreceivemsg(token, addr, t) -------------------------------> [Core RPC]
- read from pool (no filtering)
- return encrypted payloads
6) Attempt ECIES decrypt
7) Ignore non-matching payloads
8) Use plaintext message

Persistence and cleanup

If -depinpoolpersist=1:

  • Pool is saved to depinpool.dat on shutdown.
  • File format includes magic bytes 0xD0D1D2D3, version, timestamp, and messages.
  • Expired messages are dropped during load, and the file is compacted if needed.

Expired messages are also cleaned periodically via -depinmsgcleanupinterval (default 300s).

Configuration flags

Key flags (from init.cpp):

  • -depinmsg=1 enable DePIN messaging.
  • -depinmsgtoken=TOKEN required token name.
  • -depinmsgport=19002 DePIN TCP/JSON-RPC port.
  • -depinmsgmaxusers recipient limit.
  • -depinmsgsize max message size (bytes).
  • -depinmsgexpire expiry hours.
  • -depinpoolsize max pool size (MB).
  • -depinpoolpersist enable pool persistence.
  • -depinmsgcleanupinterval expiry sweep interval (seconds).

Required indexes:

  • -assetindex for token ownership and holders.
  • -pubkeyindex to verify signatures and encrypt to recipients.

Messaging activation flags

Core messaging features are gated by a version-bits deployment named messaging_restricted. Asset transfers can automatically subscribe wallets to message channels when messaging is enabled.

MCP worker (optional AI integration)

When -depinmcp=1, a background worker:

  • Polls the pool every -depinmcpinterval seconds.
  • Decrypts messages for the configured node address.
  • Filters commands by -depinmcpkey (default /ai).
  • Sends prompts to the MCP server and posts responses back to the pool.
  • Applies optional per-address rate limiting and deduplicates processed messages.