Skip to content

Quickstart

This page takes you from an empty directory to a Go program that stores an encrypted message for a peer who is offline, then brings that peer online to collect and acknowledge it.

There is no public Ricochet server, so you need your own. Follow Install and First run & your peer ID. When the server starts it prints its peer ID:

Ricochet server dev running. Peer ID: 12D3KooWBgHdpNPM9MyKVwxMqsBjaBoiU4F2MxcAhC57ALTaSn4M

The address your program dials is the server’s listen address followed by /p2p/ and that peer ID. For a server on the same machine on the default port:

Terminal window
export RICOCHET_SERVER=/ip4/127.0.0.1/udp/55223/udx/p2p/12D3KooWBgHdpNPM9MyKVwxMqsBjaBoiU4F2MxcAhC57ALTaSn4M
Terminal window
mkdir quickstart && cd quickstart
go mod init example.com/quickstart
go get github.com/stephanfeb/go-ricochet@v1.1.0
go get github.com/stephanfeb/go-libp2p-udx-transport@v0.1.8

The server listens only on UDX, a reliable transport over UDP. A libp2p host built with the default transports (TCP, QUIC) cannot reach it, so the program builds its host with the UDX transport, Noise and Yamux, the way examples/mailbox does. See UDX: Go, Dart, JS for details and Transport for the server side.

main.go
// Command quickstart stores an encrypted message for a peer that is offline,
// then brings that peer online to collect and acknowledge it.
package main
import (
"context"
"fmt"
"log"
"os"
"time"
"github.com/libp2p/go-libp2p"
"github.com/libp2p/go-libp2p/core/crypto"
"github.com/libp2p/go-libp2p/core/host"
"github.com/libp2p/go-libp2p/core/peer"
"github.com/libp2p/go-libp2p/p2p/muxer/yamux"
"github.com/libp2p/go-libp2p/p2p/security/noise"
udxtransport "github.com/stephanfeb/go-libp2p-udx-transport"
"github.com/stephanfeb/go-ricochet/pkg/client"
)
func main() {
server, err := peer.AddrInfoFromString(os.Getenv("RICOCHET_SERVER"))
if err != nil {
log.Fatalf("RICOCHET_SERVER: %v", err)
}
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
defer cancel()
// Two identities. Bob's peer ID is all Alice needs to write to him.
alicePriv, _, _ := crypto.GenerateEd25519Key(nil)
bobPriv, _, _ := crypto.GenerateEd25519Key(nil)
bobID, _ := peer.IDFromPrivateKey(bobPriv)
// Alice comes online and sends. Bob has no host yet: he is offline.
alice, aliceHost := connect(ctx, alicePriv, *server)
res, err := alice.SendMessage(ctx, bobID, []byte("Hi Bob, this waited for you."),
client.WithEncryption())
if err != nil {
log.Fatal(err) // the request failed: no server reachable, timeout, ...
}
if err := res.Err(); err != nil {
log.Fatal(err) // the server refused the message: full mailbox, rate limit, ...
}
fmt.Println("stored as", res.MessageID, "on", res.StoredAtServer)
alice.Close()
aliceHost.Close()
// Bob comes online and collects what is waiting. Payloads arrive decrypted.
bob, bobHost := connect(ctx, bobPriv, *server)
defer bobHost.Close()
defer bob.Close()
msgs, err := bob.RetrieveMessages(ctx)
if err != nil {
log.Fatal(err)
}
ids := make([]string, 0, len(msgs))
for _, m := range msgs {
fmt.Printf("from %s: %s\n", m.SenderPeerID, m.Payload)
ids = append(ids, m.MessageID)
}
// Retrieval removes nothing. The acknowledgement does.
if len(ids) > 0 {
ack, err := bob.MarkDelivered(ctx, ids)
if err != nil {
log.Fatal(err)
}
fmt.Println("acknowledged", ack.UpdatedCount)
}
}
// connect creates a host that speaks what a Ricochet server speaks (UDX,
// Noise, Yamux), dials the server and wraps the host in a client.
func connect(ctx context.Context, priv crypto.PrivKey, server peer.AddrInfo) (*client.Client, host.Host) {
h, err := libp2p.New(
libp2p.Identity(priv),
libp2p.NoTransports,
libp2p.Transport(udxtransport.NewTransport),
libp2p.ListenAddrStrings("/ip4/0.0.0.0/udp/0/udx"),
libp2p.Security(noise.ID, noise.New),
libp2p.Muxer("/yamux/1.0.0", yamux.DefaultTransport),
)
if err != nil {
log.Fatal(err)
}
if err := h.Connect(ctx, server); err != nil {
log.Fatalf("connect to server: %v", err)
}
cl := client.New(h, client.Config{
PreferredServers: []client.ServerPreference{{PeerID: server.ID, Priority: 1}},
})
return cl, h
}
Terminal window
go mod tidy
go run .
stored as aaef9d7f-9705-4179-b2db-d6000416cc2e on 12D3KooWBgHdpNPM9MyKVwxMqsBjaBoiU4F2MxcAhC57ALTaSn4M
from 12D3KooWCycXDfAanLjb8jrDqXsaw1VkUTQciCCJCw93rukm6FKh: Hi Bob, this waited for you.
acknowledged 1
  • client.New wrapped a host you created. The client never closes the host; you do.
  • h.Connect put the server’s address in the host’s peerstore. The client opens one stream per request to a server it can already reach, so connect first or add the address to the peerstore yourself.
  • SendMessage with WithEncryption sealed the payload with NaCl box for Bob’s key, which comes from his peer ID. The server stored ciphertext it cannot open.
  • SendMessage reports two kinds of failure. The error return means the request did not complete. A refusal by the server (res.Success == false) is in the result, and res.Err() turns it into a typed error such as client.ErrMailboxFull. See Errors.
  • RetrieveMessages read Bob’s inbox and decrypted each payload.
  • MarkDelivered deleted the message, because it was not sent with WithPersistent(true).

The identities here are thrown away when the program exits. A real app stores the key, as the mailbox example does, because the peer ID is the user’s address.