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.
1. Run a server
Section titled “1. Run a server”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: 12D3KooWBgHdpNPM9MyKVwxMqsBjaBoiU4F2MxcAhC57ALTaSn4MThe 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:
export RICOCHET_SERVER=/ip4/127.0.0.1/udp/55223/udx/p2p/12D3KooWBgHdpNPM9MyKVwxMqsBjaBoiU4F2MxcAhC57ALTaSn4M2. Create the module
Section titled “2. Create the module”mkdir quickstart && cd quickstartgo mod init example.com/quickstartgo get github.com/stephanfeb/go-ricochet@v1.1.0go get github.com/stephanfeb/go-libp2p-udx-transport@v0.1.8The 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.
3. Write the program
Section titled “3. Write the program”// 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}4. Run it
Section titled “4. Run it”go mod tidygo run .stored as aaef9d7f-9705-4179-b2db-d6000416cc2e on 12D3KooWBgHdpNPM9MyKVwxMqsBjaBoiU4F2MxcAhC57ALTaSn4Mfrom 12D3KooWCycXDfAanLjb8jrDqXsaw1VkUTQciCCJCw93rukm6FKh: Hi Bob, this waited for you.acknowledged 1What happened
Section titled “What happened”client.Newwrapped a host you created. The client never closes the host; you do.h.Connectput 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.SendMessagewithWithEncryptionsealed the payload with NaCl box for Bob’s key, which comes from his peer ID. The server stored ciphertext it cannot open.SendMessagereports two kinds of failure. Theerrorreturn means the request did not complete. A refusal by the server (res.Success == false) is in the result, andres.Err()turns it into a typed error such asclient.ErrMailboxFull. See Errors.RetrieveMessagesread Bob’sinboxand decrypted each payload.MarkDelivereddeleted the message, because it was not sent withWithPersistent(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.
Next steps
Section titled “Next steps”- Get messages pushed instead of polling: Push notifications.
- Folders, priorities, expiry and paging: Messages & retrieval.
- Full signatures: pkg.go.dev.