Application-controlled encrypted P2P networking on libp2p. Provides named multiplexed streams with ChaCha20-Poly1305 authenticated encryption and a two-phase handshake driven by the application protocol.
Used as the network layer of blockberry and through it, the entire Stealth stack.
- Wraps a libp2p
host.Hostbehind a single*NodeAPI. - Exposes named multiplexed streams: applications register stream names
(e.g.
consensus,looseberry-batches) and Glueberry routes incoming frames per name. - Encrypts every non-handshake stream with ChaCha20-Poly1305 using a per-peer X25519 ECDH-derived key.
- Persists known peers and their public keys in a JSON address book with
flock-protected atomic updates. - Surfaces connection state as a channel of events; surfaces incoming
messages as a channel of
(peerID, streamName, data)tuples. - Handles reconnection with exponential backoff + jitter.
- It does not define an application protocol. Handshake content (chain ID,
validator attestation, version negotiation) rides the unencrypted
handshakestream and is the application's responsibility. - It does not provide replay protection, sequence numbers in AAD, or built-in DoS limits on the receive side. (Tracked in PLAN T1-12, T1-13.)
- It does not include built-in eclipse-attack mitigation; that belongs to
the consumer (blockberry has an
EclipseProtector).
1. Connect(peerID) → libp2p dials, opens "handshake" stream
2. App exchanges Hello + pubKey + Complete on the handshake stream
3. Both sides call PrepareStreams(peerID, peerPubKey, streamNames)
→ derives shared key (X25519 ECDH + HKDF-SHA256)
→ registers handlers for each stream name
4. App exchanges "Complete" sentinel
5. Both sides call FinalizeHandshake(peerID)
→ cancels handshake timer
→ moves connection to StateEstablished
→ handshake stream is closed
6. App sends/receives on encrypted streams via Send / Messages
import (
"github.com/blockberries/glueberry"
)
cfg := glueberry.NewConfig(
glueberry.WithListenAddr("/ip4/0.0.0.0/tcp/26656"),
glueberry.WithIdentity(privKey),
glueberry.WithMaxMessageSize(1 << 20),
)
node, err := glueberry.New(cfg)
err = node.Start()
defer node.Stop()
err = node.AddPeer(peerID, addrs, metadata)
err = node.Connect(peerID)
// Application protocol over handshake stream:
err = node.Send(peerID, "handshake", helloBytes)
msg := <-node.Messages() // {PeerID, StreamName: "handshake", Data: ...}
// Set up encrypted streams:
err = node.PrepareStreams(peerID, peerPubKey, []string{"consensus", "blocks"})
err = node.FinalizeHandshake(peerID)
// Now talk on encrypted streams:
err = node.Send(peerID, "consensus", proposalBytes)| Operation | Algorithm |
|---|---|
| Identity | Ed25519 (app-supplied) |
| Key exchange | X25519 ECDH with low-order point check |
| Key derivation | HKDF-SHA256, info "glueberry-v1-stream-key" |
| Encryption | ChaCha20-Poly1305, random 12-byte nonce per msg |
glueberry/
├── node.go Public API, lifecycle, forward goroutines
├── config.go Config + With* options
├── events.go errors.go Connection events, typed errors
├── pkg/crypto/ Ed25519↔X25519, ECDH, HKDF, ChaCha20-Poly1305
├── pkg/streams/ Stream manager, encrypted/unencrypted streams
├── pkg/connection/ Per-peer connection state machine
├── pkg/addressbook/ JSON file storage with flock
├── pkg/protocol/ libp2p host wrapper, connection gater
├── internal/eventdispatch/ Buffered event channel
├── internal/flow/ High/low watermark backpressure
└── examples/ Two-node usage examples
See CLAUDE.md for development guidelines.
ARCHITECTURE.md for design details.
CHANGELOG.md for release history.
Apache-2.0.