Tin Can

Peer-to-peer terminal communication. Two cans, one string.

Direct encrypted text chat and voice calls — no accounts, no servers, no middlemen. Both peers agree on a shared secret. One starts the session, the other joins. That's it.

danlafeir/tin-can

Install

curl -sSL https://raw.githubusercontent.com/danlafeir/tin-can/main/scripts/install.sh | sh

Installs the latest release binary to ~/.local/bin/tin-can. To upgrade an existing install, run tin-can upgrade.

Voice calls also require brew install opus on macOS.

Usage

Both peers agree on a secret phrase — say it over the phone, text it, anything. Then each runs one command.

Text chat

# Peer A — create the session
tin-can attach-string "our secret phrase"

# Peer B — join it
tin-can tap "our secret phrase"

Peer A waits. The moment Peer B runs their command, both are connected directly.

Voice call

# Either peer goes first — whoever goes first creates the session
tin-can talk "our secret phrase"

# The other peer runs the same command to join
tin-can talk "our secret phrase"

You can still send text messages during a voice call.

How a connection is established

Under the hood, tin-can uses WebRTC to punch through NAT and create a direct encrypted channel between two machines. The process has two phases: signaling (exchanging network metadata through this server) and connection (everything after that is direct).

Alice (attach-string)
Bob (tap)
1

Alice

Hashes the shared secret into a 12-hex-digit string code. The secret itself never leaves the machine.

2

Alice

Gathers local network candidates (IP addresses and ports) via STUN (stun.l.google.com). Packages them into a WebRTC SDP offer and uploads it to lafeir.com under the string code. Then waits.

3

Bob

Hashes the same secret → same string code. Fetches Alice's offer from lafeir.com.

4

Bob

Gathers their own network candidates. Generates a WebRTC SDP answer(a knot-tie) incorporating Alice's offer, and uploads it to lafeir.com under the same string code.

5

Alice

Polls lafeir.com until the knot-tie appears. Accepts it.

6

Both

WebRTC performs ICE negotiation — both sides try the candidate pairs until a direct UDP path works. The signaling server is no longer involved. All subsequent traffic is encrypted end-to-end via DTLS-SRTP.

Alice Bob
│ │
│── tin-can attach-string "secret" │
│ hashes secret → string code │
│ generates SDP offer │
│ uploads offer to lafeir.com ──────► │
│ polls for knot-tie... tin-can tap "secret"
│ hashes secret → same string code
│ fetches offer from lafeir.com
│ generates SDP answer (knot-tie)
│ ◄──────── knot-tie uploaded uploads knot-tie to lafeir.com
│ accepts knot-tie │
│ │
│◄══════════ WebRTC (direct UDP) ════════│

Signaling

SDP offer/answer relayed through lafeir.com. Blobs expire after 10 minutes. The string code is a hash of the secret — the secret never touches the server.

Transport

Direct UDP between peers via WebRTC (str0m). Once connected, all traffic bypasses the relay entirely.

Encryption

DTLS-SRTP, built into WebRTC. Neither peer can be impersonated — the key exchange happens over the direct channel.

NAT traversal

STUN via stun.l.google.com discovers your public address. ICE tries all candidate pairs to find a route through firewalls.