Skip to content

Repository files navigation

Reticulum in Haskell

A Reticulum node: wire format, identity and link cryptography,
destinations, transport, resources, interfaces.

Compatible means identical to python-rns 1.4.2 at commit b48b96e6,
openssl backend. The corpus in CORPUS is what says so, and the count
below is the whole claim.


Where it stands

    104 passed, 0 failed, corpus 72746484

Every kind the corpus holds, in both directions.


Build

Nix is the only thing that has to be installed.

    nix develop
    cabal build
    ./check

The shell carries the compiler, cabal, and the C compiler and POSIX
tools the corpus builds itself with. flake.nix names the compiler and
flake.lock pins the package set.


check

    ./check                                 clone the pin and measure
    CORPUS_DIR=../reticulum-vectors ./check use that checkout as it is

CORPUS holds two lines and is the only place they are written: the
corpus commit to measure against, and the number printed on its result
line. A count is a count against one corpus, so check refuses a result
that carries another.

    exit 0    every vector that was not skipped passed
    exit 1    a vector failed
    exit 2    nothing was measured, or the fingerprint is not the one
              on file


Using it

A node that dials one peer, holds a destination, announces it, and
answers one request path:

    main :: IO ()
    main = do
        private <- Node.keypair
        started <- Node.start Node.Settings {Node.transport = False} private (\_ _ _ -> pure ())
        case started of
            Left reason -> putStrLn reason
            Right node -> do
                holder <- newEmptyMVar
                tcp <- Tcp.start (Tcp.Peer "127.0.0.1" "4242") $ \raw -> do
                    through <- readMVar holder
                    Node.inbound node through raw
                through <- Node.interface "peer" (Tcp.transmit tcp)
                putMVar holder through
                Node.attach node through
                held <- Node.serve node (Destination.name (C.pack "example.echo")) B.empty answers
                Node.announce node held
      where
        answers =
            Node.silent
                { Node.delivered = print
                , Node.requested = Map.singleton (Request.named (C.pack "echo")) (pure . Just)
                }

Node.silent hears everything and says nothing, so a caller writes down
only what it answers. A packet is filed under the interface it arrived
on, which is why the reader waits on an MVar for the interface it is
reading for: the socket exists before the interface does.

The other end of that is Node.open, which gives back a link to speak
on: Node.speak sends a packet, Node.ask sends a request, Node.hand
hands over a resource of any length, and Node.close ends it. cmd/talk
is that side, whole, in a hundred lines.

The node speaks no interface access codes: a frame that carries one is
dropped. Reticulum.Interface holds the codec for them, and the corpus
is what exercises it.


Layout

    src/Reticulum/     the library: the codec is pure, the node is IO
    cmd/harness/       the corpus harness, linked to ./harness by check
    cmd/node/          a node on TCP interfaces, dialled and answered
    cmd/talk/          opens a link to a destination and speaks on it
    test/              what the corpus cannot hold
    reference/         this node with python-rns nodes around it
    .github/workflows/ one job, and it runs ./check
    CLAUDE.md          the rules this repository is written under
    CORPUS             the pin
    DEPENDS            every dependency past base and the crypto library,
                       and what needs it


The harness

One program taking the arguments the corpus's cmd/dump takes:

    ./harness <kind> <rawfile>       fields out
    ./harness -e <kind> <expectfile> raw back

It prints one field per line from this implementation's own code, and
reads those fields back into the library's types to write the bytes
again. The contract is the corpus's doc/harness; the three rules it
binds this repository to are in CLAUDE.md.


The commands

    node [-t] [-l <port>] [-n <name>] [-k <file>] [<host>:<port> ...]

    -t          be a transport node
    -l <port>   listen, and take every connection as an interface
    -n <name>   hold this destination, and announce it once
    -k <file>   the identity, written if the file is not there

    talk <hex> <host>:<port> ...

Dials every peer given, opens a link to that destination, sends a
packet, asks two requests, hands over two resources -- one of them
longer than a segment -- and closes the link. Every step prints a line.

Both need at least one peer. Log lines go to standard error.


Tests

    cabal test

There are none here that a vector already proves. What belongs here is
what the corpus cannot hold: properties, and the layers above link --
the transport node, both ends of a link, and the tables behind them.

    ./reference/check

is the same node between two python-rns nodes that cannot reach each
other any other way, a third that talks to the node itself, and a
fourth that talk opens a link to. It needs python-rns importable, takes
about three minutes, and is not run in CI. Thirty-one lines are
checked, and every one of them is a node saying what it got.


License

LICENSE, and its two restrictions. Copying upstream code or
documentation text requires attribution.

About

The complete reticulum network stack written in haskell

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Contributors

Languages