GitHub - IdanKoblik/tin: e2ee device communication tool · GitHub
Skip to content
IdanKoblikPublic

About

e2ee device communication tool

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

Tin

Hello? Does anyone hear me??

CI Language Platform Crypto License


Table of Contents


About

Tin is a small peer-to-peer communication tool for two machines on the same network. One side hosts, the other connects, and everything between them travels encrypted over a single TCP connection.

Two independent links ride that connection: audio, so one machine's microphone reaches the other's speakers, and input, so one machine's keyboard and mouse drive the other. A node sits on exactly one end of each link.

There is no server to sign up for, no account, and no discovery service. Each node owns a long-term Ed25519 identity generated on first run, and every session derives fresh keys from it. It is a tin-can telephone with a real cryptographic string.

How It Works

Tin is symmetric once the connection is up. The only asymmetry is who binds the listening socket and who dials it.

   host                                        connect
  ------                                       ---------
  bind + listen                                connect
       \                                          /
        \-------- TCP accept / connect ----------/
                          |
              signed handshake, both ways
                          |
             X25519 -> per-direction session keys
                          |
      ChaCha20-Poly1305 over audio and input packets

Every packet goes on the wire the same way, whatever it carries: the payload is encrypted under the session keys and written as [nonce][ciphertext || tag]. Because both ends know the plaintext size in advance, no length prefix is needed.

Audio frames are captured at exactly 1920 bytes, which is 20 ms of 48 kHz 16-bit mono PCM. Each frame is wrapped in an audio packet carrying a version byte and a 32-bit sequence number. The capture side computes the RMS amplitude of every frame and drops anything at or below -50 dBFS, keeping a ten-frame hangover so word endings are not clipped. The playback side logs sequence gaps rather than trying to conceal them.

Input events are read straight from evdev and re-emitted on the far side through a uinput virtual device. Only EV_KEY, EV_REL and EV_SYN are forwarded; each one becomes a 13-byte input packet:

version (u8) | seq (u32) | type (u16) | code (u16) | value (i32)

Requirements

Linux, plus the following development packages:

Dependency Used for
libsodium Ed25519, X25519, ChaCha20-Poly1305
PulseAudio Capture and playback (libpulse-simple)
ncurses Interactive source and device selection menus
libudev Input device discovery
wayland Reading the display size for edge switching
pkg-config Locating libsodium and wayland at build time

Debian and Ubuntu:

sudo apt install build-essential pkg-config libsodium-dev \
                 libpulse-dev libncurses-dev libudev-dev libwayland-dev

Fedora:

sudo dnf install gcc make pkgconf-pkg-config libsodium-devel \
                 pulseaudio-libs-devel ncurses-devel systemd-devel \
                 wayland-devel

Arch:

sudo pacman -S base-devel pkgconf libsodium libpulse ncurses systemd-libs wayland

Optional: clang-format for the formatting targets, and lcov for coverage.

Building

make

That produces the tin binary in the project root and regenerates compile_commands.json. Sources and headers are discovered recursively, so new files under src/ and include/ are picked up without editing the Makefile.

make clean

Demo

WhatsApp.Video.2026-08-06.at.22.53.00.mp4

Usage

./tin <role> <addr[:port]> <caps> [edge]

Addresses are IPv4 literals. The default port is 6969 when none is given, and host binds to whatever address you pass, so use 0.0.0.0 to accept a peer on any interface.

Roles

Role Meaning
host Bind and listen for one incoming peer
connect Dial the given address

Capabilities

Capabilities are a comma-separated list describing what this node contributes to the call.

Capability Meaning
mic Capture from a local source and send it to the peer
speaker Receive from the peer and play it back locally
input-send Capture a local keyboard and mouse and drive the peer
input-recv Replay the peer's events on a local virtual device

A node holds at most one end of each link: mic,speaker and input-send,input-recv are rejected as misconfigurations, while combinations like mic,input-send are fine. The peer is expected to take the other end.

Passing mic opens an ncurses menu listing the available capture sources, and input-send opens one menu per device class, so you pick the keyboard and mouse to share before the call starts. Either menu can be left with q or Esc.

Edges

The optional fourth argument only means something to an input-send node. It names the screen side that hands control over:

Edge Take the peer over by pushing the pointer into the ...
left left edge of the screen
right right edge of the screen
top top edge of the screen
bottom bottom edge of the screen

Leave it out and there is no switching at all: every event is forwarded and acted on locally, so both machines react to the same keystroke.

Examples

Host a call and speak into a local microphone:

./tin host 0.0.0.0 mic

Connect from the other machine and listen:

./tin connect 192.168.1.20 speaker

Use a non-default port:

./tin connect 192.168.1.20:7000 speaker

Share this machine's keyboard and mouse, handing them over at the right edge:

./tin host 0.0.0.0 input-send right

Let the peer drive this machine:

./tin connect 192.168.1.20 input-recv

Talk and share input over the same connection:

./tin host 0.0.0.0 mic,input-send right

Stop either side with Ctrl-C. The signal handler tears the socket down so the blocked audio and input threads wake up and exit cleanly.

Permissions

Input forwarding touches device nodes that are not world-accessible by default:

Node Needed by Access
/dev/input/event* input-send read
/dev/uinput input-recv write

Adding your user to the input group covers the first; /dev/uinput usually needs a udev rule or running as root. Audio-only calls need neither.

Display size is read over the Wayland protocol, so edge switching needs XDG_SESSION_TYPE=wayland. On anything else the size stays unknown, and Tin warns that it cannot switch on the given edge and falls back to forwarding everything to both machines.

Keys and Configuration

Tin keeps its identity in $HOME/.config/tin, creating the directory with mode 0700 on first run:

File Mode Contents
private.pem 0600 Ed25519 secret key, base64 in PEM
public.pem 0644 Ed25519 public key, base64 in PEM

On startup the pair is loaded and validated: the public key must be derivable from the secret key, and a half-present pair is treated as an error rather than silently regenerated. Delete both files to rotate your identity.

Development

Tests

make test

Every production object except main.o is linked into the test runner, which is built on the vendored greatest header.

Coverage

Requires lcov:

make coverage

The HTML report lands in coverage/html/index.html.

Formatting

make format        # rewrite sources in place
make format-check  # fail if anything is unformatted

The vendored tests/greatest.h is excluded from both.

Logs

Tin logs to syslog. To pull its entries into tin.log:

make logs

About

e2ee device communication tool

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages