Skip to content

Abstract

This PIP defines the Pars Session Protocol - a native L1 daemon providing private, permissionless communications. Inspired by Session (getsession.org), the Pars session layer provides:

  • No phone number or email required
  • No metadata collection
  • Mandatory post-quantum encryption
  • Onion routing for sender/receiver unlinkability
  • Decentralized infrastructure (no servers to seize)

Motivation

Why a Native Session Layer?

The EVM provides programmable money. But diaspora coordination needs more than financial transactions:

  • Private messaging: Plan, coordinate, discuss
  • Group communications: Organize communities
  • File sharing: Distribute documents, media
  • Voice/video: Real-time communication (future)

These cannot be built on transparent EVM:

  • Every transaction is public
  • Metadata reveals who interacts
  • No privacy by default

The session layer provides what EVM cannot: communication without surveillance.

Session vs Signal vs Telegram

FeatureSignalTelegramSessionPars Session
Phone number requiredYesYesNoNo
Central serversYesYesNoNo
E2E encryptionYesOptionalYesMandatory
Post-quantumNoNoNoYes
Metadata protectionPartialNoYesYes
Offline operationNoNoPartialFull
Coercion resistanceNoNoPartialFull

Specification

Protocol Architecture

┌─────────────────────────────────────────────────────────────────────────────────────┐
│                           SESSION PROTOCOL STACK                                     │
├─────────────────────────────────────────────────────────────────────────────────────┤
│                                                                                      │
│  APPLICATION LAYER                                                                  │
│  ┌───────────────────────────────────────────────────────────────────────────────┐  │
│  │  Direct Messages • Group Chats • File Sharing • Voice (future)                │  │
│  └───────────────────────────────────────────────────────────────────────────────┘  │
│                                      │                                               │
│  SESSION LAYER                                                                      │
│  ┌───────────────────────────────────────────────────────────────────────────────┐  │
│  │  Session Manager • Key Ratcheting • Message Queuing • Sync                    │  │
│  └───────────────────────────────────────────────────────────────────────────────┘  │
│                                      │                                               │
│  ENCRYPTION LAYER                                                                   │
│  ┌───────────────────────────────────────────────────────────────────────────────┐  │
│  │  ML-KEM Key Exchange • ML-DSA Signatures • Double Ratchet • PFS              │  │
│  └───────────────────────────────────────────────────────────────────────────────┘  │
│                                      │                                               │
│  ROUTING LAYER                                                                      │
│  ┌───────────────────────────────────────────────────────────────────────────────┐  │
│  │  Onion Routing • Path Selection • Guard Nodes • Exit Handling                 │  │
│  └───────────────────────────────────────────────────────────────────────────────┘  │
│                                      │                                               │
│  NETWORK LAYER                                                                      │
│  ┌───────────────────────────────────────────────────────────────────────────────┐  │
│  │  DHT Discovery • Gossip Protocol • Service Nodes • Swarm                      │  │
│  └───────────────────────────────────────────────────────────────────────────────┘  │
│                                                                                      │
└─────────────────────────────────────────────────────────────────────────────────────┘

Identity

Session ID

Users identified by cryptographic Session ID:

go
// Session identity
type SessionID struct {
    // Post-quantum keys
    MLKEM  *MLKEMPublicKey  // For encryption
    MLDSA  *MLDSAPublicKey  // For signatures

    // Classical keys (hybrid mode)
    X25519 *X25519PublicKey
    Ed25519 *Ed25519PublicKey

    // Derived identifier
    ID     [32]byte  // SHA3-256(MLDSA.PublicKey)
}

// Generate new identity
func GenerateSessionID() (*SessionID, *SessionPrivateKey, error) {
    // Generate PQ keys
    mlkemPub, mlkemPriv, _ := mlkem.GenerateKey(mlkem.Level5)
    mldsaPub, mldsaPriv, _ := mldsa.GenerateKey(mldsa.Mode5)

    // Generate classical keys
    x25519Pub, x25519Priv := x25519.GenerateKey()
    ed25519Pub, ed25519Priv := ed25519.GenerateKey()

    // Derive ID
    id := sha3.Sum256(mldsaPub)

    return &SessionID{
        MLKEM:   mlkemPub,
        MLDSA:   mldsaPub,
        X25519:  x25519Pub,
        Ed25519: ed25519Pub,
        ID:      id,
    }, &SessionPrivateKey{
        MLKEM:   mlkemPriv,
        MLDSA:   mldsaPriv,
        X25519:  x25519Priv,
        Ed25519: ed25519Priv,
    }, nil
}

Display Format

Session IDs displayed as human-readable strings:

05a1b2c3d4e5f6...  (66 hex characters)

Can be shared via:

  • QR code
  • Copy/paste
  • NFC tap
  • Link (pars://session/05a1b2...)

Session Establishment

┌─────────────────────────────────────────────────────────────────────────────────────┐
│                           SESSION ESTABLISHMENT                                      │
├─────────────────────────────────────────────────────────────────────────────────────┤
│                                                                                      │
│  Alice                               Network                             Bob        │
│    │                                    │                                  │         │
│    │  1. Alice knows Bob's Session ID   │                                  │         │
│    │                                    │                                  │         │
│    │  2. Generate ephemeral keys        │                                  │         │
│    │     (ML-KEM + X25519)             │                                  │         │
│    │                                    │                                  │         │
│    │  3. Create session request         │                                  │         │
│    │     Encrypt(Bob.PK, request)      │                                  │         │
│    │                                    │                                  │         │
│    │  4. Route through onion network    │                                  │         │
│    │ ─────────────────────────────────► │ ─────────────────────────────► │         │
│    │                                    │                                  │         │
│    │                                    │  5. Bob decrypts request        │         │
│    │                                    │     Verifies Alice's signature  │         │
│    │                                    │                                  │         │
│    │                                    │  6. Bob generates ephemeral     │         │
│    │                                    │     Creates session response    │         │
│    │                                    │                                  │         │
│    │  7. Alice receives response        │                                  │         │
│    │ ◄───────────────────────────────── │ ◄───────────────────────────── │         │
│    │                                    │                                  │         │
│    │  8. Both derive shared secrets     │                                  │         │
│    │     Initialize double ratchet     │                                  │         │
│    │                                    │                                  │         │
│    │  SESSION ESTABLISHED               │               SESSION ESTABLISHED│         │
│    │  (PQ-secure, forward secret)      │                                  │         │
│                                                                                      │
└─────────────────────────────────────────────────────────────────────────────────────┘

Message Format

go
// Session message structure
type SessionMessage struct {
    // Header (encrypted with session key)
    Header MessageHeader

    // Body (encrypted with message key)
    Body []byte

    // Signature (over encrypted header + body)
    Signature []byte
}

type MessageHeader struct {
    // Message identification
    MessageID [16]byte  // Random unique ID
    Timestamp int64     // Unix timestamp (coarse for privacy)
    Sequence  uint64    // Sequence number in session

    // Ratchet state
    RatchetPK []byte    // Current ratchet public key
    Counter   uint32    // Message counter in ratchet epoch

    // Metadata
    ContentType ContentType  // Text, file, etc.
    Flags       uint32       // Read receipt, etc.
}

Double Ratchet with PQ Keys

┌─────────────────────────────────────────────────────────────────────────────────────┐
│                           PQ DOUBLE RATCHET                                          │
├─────────────────────────────────────────────────────────────────────────────────────┤
│                                                                                      │
│  HYBRID KEY DERIVATION:                                                             │
│                                                                                      │
│  Classical:  X25519(ephemeral_a, ephemeral_b) → ss_classical                       │
│  PQ:         ML-KEM.Decap(ct, sk) → ss_pq                                          │
│  Combined:   HKDF(ss_classical || ss_pq, "pars-ratchet-v1") → shared_secret        │
│                                                                                      │
│  RATCHET OPERATION:                                                                 │
│  ┌───────────────────────────────────────────────────────────────────────────────┐  │
│  │                                                                                │  │
│  │  Root Key ────────┬─────────────────┬─────────────────┬──────────────►        │  │
│  │                   │                 │                 │                        │  │
│  │                   ▼                 ▼                 ▼                        │  │
│  │              Chain Key_0       Chain Key_1       Chain Key_2                   │  │
│  │                   │                 │                 │                        │  │
│  │         ┌────────┼────────┐  ┌─────┼─────┐    ┌─────┼─────┐                   │  │
│  │         ▼        ▼        ▼  ▼     ▼     ▼    ▼     ▼     ▼                   │  │
│  │       MK_0    MK_1    MK_2  MK_0  MK_1  MK_2  MK_0  MK_1  MK_2               │  │
│  │                                                                                │  │
│  │  Each DH ratchet step generates new chain key                                 │  │
│  │  Each message derives unique message key (MK)                                 │  │
│  │  Keys deleted after use → forward secrecy                                     │  │
│  │                                                                                │  │
│  └───────────────────────────────────────────────────────────────────────────────┘  │
│                                                                                      │
└─────────────────────────────────────────────────────────────────────────────────────┘

Onion Routing

Messages routed through multiple hops:

┌─────────────────────────────────────────────────────────────────────────────────────┐
│                           ONION ROUTING                                              │
├─────────────────────────────────────────────────────────────────────────────────────┤
│                                                                                      │
│  SENDER → HOP 1 → HOP 2 → HOP 3 → RECIPIENT                                        │
│                                                                                      │
│  Message structure (onion layers):                                                  │
│  ┌───────────────────────────────────────────────────────────────────────────────┐  │
│  │  Layer 3 (outermost): Encrypted with Hop1 key                                 │  │
│  │  ┌─────────────────────────────────────────────────────────────────────────┐  │  │
│  │  │  Layer 2: Encrypted with Hop2 key                                       │  │  │
│  │  │  ┌───────────────────────────────────────────────────────────────────┐  │  │  │
│  │  │  │  Layer 1: Encrypted with Hop3 key                                 │  │  │  │
│  │  │  │  ┌─────────────────────────────────────────────────────────────┐  │  │  │  │
│  │  │  │  │  Core: Encrypted with Recipient key                         │  │  │  │  │
│  │  │  │  │  ┌───────────────────────────────────────────────────────┐  │  │  │  │  │
│  │  │  │  │  │  Actual message content                               │  │  │  │  │  │
│  │  │  │  │  └───────────────────────────────────────────────────────┘  │  │  │  │  │
│  │  │  │  └─────────────────────────────────────────────────────────────┘  │  │  │  │
│  │  │  └───────────────────────────────────────────────────────────────────┘  │  │  │
│  │  └─────────────────────────────────────────────────────────────────────────┘  │  │
│  └───────────────────────────────────────────────────────────────────────────────┘  │
│                                                                                      │
│  Each hop:                                                                          │
│  1. Decrypts their layer                                                            │
│  2. Learns only next hop address                                                    │
│  3. Cannot see sender, recipient, or content                                        │
│  4. Cannot correlate request/response                                               │
│                                                                                      │
└─────────────────────────────────────────────────────────────────────────────────────┘

Service Nodes (Swarm)

Service nodes store/forward messages for offline recipients:

go
// Service node swarm
type Swarm struct {
    nodes    []*ServiceNode
    dht      *DHT
    storage  *MessageStore
}

// Store message for offline recipient
func (s *Swarm) StoreMessage(recipientID SessionID, msg *EncryptedMessage) error {
    // Find swarm responsible for recipient
    swarmNodes := s.dht.FindSwarm(recipientID)

    // Store on multiple nodes for redundancy
    stored := 0
    for _, node := range swarmNodes {
        if err := node.Store(recipientID, msg); err == nil {
            stored++
        }
        if stored >= RequiredCopies {
            break
        }
    }

    if stored < MinCopies {
        return ErrInsufficientStorage
    }
    return nil
}

// Retrieve messages when recipient comes online
func (s *Swarm) RetrieveMessages(recipientID SessionID, since time.Time) ([]*EncryptedMessage, error) {
    swarmNodes := s.dht.FindSwarm(recipientID)

    var messages []*EncryptedMessage
    for _, node := range swarmNodes {
        msgs, err := node.Retrieve(recipientID, since)
        if err == nil {
            messages = append(messages, msgs...)
        }
    }

    // Deduplicate by message ID
    return deduplicate(messages), nil
}

Group Sessions

┌─────────────────────────────────────────────────────────────────────────────────────┐
│                           GROUP SESSION                                              │
├─────────────────────────────────────────────────────────────────────────────────────┤
│                                                                                      │
│  GROUP KEY MANAGEMENT:                                                              │
│                                                                                      │
│  Admin creates group:                                                               │
│  ┌───────────────────────────────────────────────────────────────────────────────┐  │
│  │  1. Generate group keypair (ML-KEM + X25519)                                   │  │
│  │  2. Create member list (encrypted)                                             │  │
│  │  3. Distribute group key to initial members via pairwise session              │  │
│  └───────────────────────────────────────────────────────────────────────────────┘  │
│                                                                                      │
│  Adding member:                                                                     │
│  ┌───────────────────────────────────────────────────────────────────────────────┐  │
│  │  1. Admin sends group key to new member via pairwise session                  │  │
│  │  2. Admin sends "member added" message to group                               │  │
│  │  3. Group key rotated for forward secrecy                                     │  │
│  └───────────────────────────────────────────────────────────────────────────────┘  │
│                                                                                      │
│  Removing member:                                                                   │
│  ┌───────────────────────────────────────────────────────────────────────────────┐  │
│  │  1. Admin sends "member removed" message                                       │  │
│  │  2. New group key generated                                                    │  │
│  │  3. New key distributed to remaining members                                  │  │
│  │  4. Removed member cannot decrypt future messages                             │  │
│  └───────────────────────────────────────────────────────────────────────────────┘  │
│                                                                                      │
│  MESSAGE FLOW:                                                                      │
│  ┌───────────────────────────────────────────────────────────────────────────────┐  │
│  │  Sender → Encrypt(group_key, message) → Swarm → Members retrieve              │  │
│  │                                                                                │  │
│  │  Single encryption, multiple recipients                                        │  │
│  │  Members poll swarm or receive push notification                              │  │
│  └───────────────────────────────────────────────────────────────────────────────┘  │
│                                                                                      │
└─────────────────────────────────────────────────────────────────────────────────────┘

File Sharing

go
// File transfer via session
type FileTransfer struct {
    FileID      [32]byte  // SHA3-256 of encrypted file
    FileName    string    // Encrypted filename
    MimeType    string    // Content type
    Size        uint64    // Total size
    ChunkCount  uint32    // Number of chunks
    Key         []byte    // Symmetric key for file
}

// Send large file
func (s *Session) SendFile(path string) error {
    // 1. Read and encrypt file
    key := randomKey()
    encryptedData := encrypt(key, readFile(path))

    // 2. Split into chunks
    chunks := splitChunks(encryptedData, ChunkSize)

    // 3. Upload chunks to swarm
    for i, chunk := range chunks {
        s.swarm.StoreChunk(fileID, i, chunk)
    }

    // 4. Send file metadata via session
    return s.SendMessage(&FileTransfer{
        FileID:     hash(encryptedData),
        FileName:   encrypt(s.sessionKey, filepath.Base(path)),
        Size:       uint64(len(encryptedData)),
        ChunkCount: uint32(len(chunks)),
        Key:        key,
    })
}

Daemon Interface

go
// Session daemon RPC interface
type SessionDaemon interface {
    // Identity
    CreateIdentity() (*SessionID, error)
    GetIdentity() (*SessionID, error)

    // Sessions
    CreateSession(recipient SessionID) (*Session, error)
    GetSessions() ([]*Session, error)
    CloseSession(sessionID [32]byte) error

    // Messages
    SendMessage(sessionID [32]byte, content []byte) error
    GetMessages(sessionID [32]byte, since time.Time) ([]*Message, error)
    MarkRead(sessionID [32]byte, messageID [16]byte) error

    // Groups
    CreateGroup(name string, members []SessionID) (*Group, error)
    JoinGroup(groupID [32]byte, invite []byte) error
    LeaveGroup(groupID [32]byte) error

    // Files
    SendFile(sessionID [32]byte, path string) error
    ReceiveFile(fileID [32]byte, path string) error

    // Status
    GetStatus() (*DaemonStatus, error)
    GetPeers() ([]*PeerInfo, error)
}

Integration with EVM

The session layer can trigger EVM actions:

solidity
// Session-triggered contract execution
contract SessionTrigger {
    // Validate session signature and execute
    function executeFromSession(
        bytes memory sessionMessage,
        bytes memory mldsaSignature,
        bytes memory mldsaPublicKey
    ) external {
        // Verify PQ signature via precompile
        require(
            IMLDSA(PQ_PRECOMPILE).verify(mldsaPublicKey, sessionMessage, mldsaSignature),
            "Invalid session signature"
        );

        // Decode and execute action
        Action memory action = abi.decode(sessionMessage, (Action));
        _execute(action);
    }
}

Security Considerations

Metadata Protection

MetadataProtection
Who sentOnion routing hides sender
Who receivedSwarm storage hides recipient
When sentTimestamp coarsened
Message sizePadding to fixed sizes
Communication patternCover traffic

Key Compromise

CompromiseImpactMitigation
Message keyOne messageForward secrecy
Session keyOne sessionRatcheting
Identity keyIdentity compromiseRevocation, re-keying

Network Attacks

AttackMitigation
Traffic analysisOnion routing, cover traffic
Sybil on DHTProof-of-stake, reputation
EclipseDiverse peer selection
DoSRate limiting, proof-of-work

Implementation

Running the Daemon

bash
# Start session daemon
sessiond --datadir ~/.pars/session \
         --network mainnet \
         --listen /ip4/0.0.0.0/tcp/9000

# Generate identity
parsctl session identity create

# Start a session
parsctl session create --recipient 05a1b2c3d4e5f6...

# Send message
parsctl session send --session <id> --message "Hello, world"

Client Libraries

typescript
// TypeScript client
import { ParsSession } from '@pars/session';

const session = new ParsSession({
  dataDir: '~/.pars/session',
  network: 'mainnet'
});

// Create or load identity
const identity = await session.getOrCreateIdentity();

// Start session with contact
const chat = await session.createSession(recipientID);

// Send message
await chat.sendMessage('Hello from Pars!');

// Receive messages
chat.onMessage((msg) => {
  console.log(`${msg.sender}: ${msg.content}`);
});

References

Copyright and related rights waived via CC0.

Released under the MIT License.