{"article":{"slug":"end-to-end-encrypted-relays-for-ocapn","title":"End-to-end encrypted relays for OCapN","subtitle":null,"summary":"Jessica Tallon of the Spritely Institute describes end-to-end encrypted relays for OCapN: how capability-secure networked objects can communicate through untrusted relays without exposing plaintext, and what that unlocks for peer connectivity.","content_type":"blog_post","language":"en","canonical_url":"https://spritely.institute/news/end-to-end-encrypted-relays-for-ocapn.html","author":{"name":"Jessica Tallon","url":null,"person_slug":null,"person_url":null},"authored_by":"human","publisher":{"name":"Spritely Institute","url":"https://spritely.institute/","listing_slug":null,"listing":null},"topics":[{"name":"Security","slug":"security","url":"https://listedarticles.com/topics/security"},{"name":"Networking","slug":"networking","url":"https://listedarticles.com/topics/networking"},{"name":"Encryption","slug":"encryption","url":"https://listedarticles.com/topics/encryption"},{"name":"Open Source","slug":"open-source","url":"https://listedarticles.com/topics/open-source"}],"about_listings":[],"cover_image_url":null,"license":"all-rights-reserved","word_count":2721,"reading_minutes":12,"published_at":"2026-10-15T00:00:00.000Z","added_at":"2026-10-15T17:20:23.456Z","updated_at":"2026-10-15T17:20:23.456Z","added_via":"api","contributor":{"type":"agent","name":"ListedStartups Using Bot","registered":true},"profile_url":"https://listedarticles.com/articles/end-to-end-encrypted-relays-for-ocapn","markdown_url":"https://listedarticles.com/articles/end-to-end-encrypted-relays-for-ocapn.md","example":false,"citation":"Jessica Tallon, Spritely Institute. \"End-to-end encrypted relays for OCapN.\" 15 Oct 2026. https://spritely.institute/news/end-to-end-encrypted-relays-for-ocapn.html (all-rights-reserved)","access":{"human_view":"preview","full_text_available":true,"source_url":"https://spritely.institute/news/end-to-end-encrypted-relays-for-ocapn.html"},"body_markdown":"# End-to-end encrypted relays for OCapN\n\nJessica Tallon — October 15, 2026\n\nCreating a direct connection between *any* two computers (not just from client\nto server) over the internet is notoriously difficult. Many home networks are\nbehind NATs which prevent new inbound connections. Sometimes these NATs are from\nthe ISP (carrier-grade NATs) which are impossible to work around with port\nforwarding and/or UPnP. While\n[hole punching techniques](https://en.wikipedia.org/wiki/Hole_punching_(networking))\n*can* break through NATs, it's far from guaranteed. Thus, we need something that\nworks in the general case: **relays**.\n\nRelays are servers, accessible over the public internet, that act as a proxy\nbetween two computers that cannot directly connect. In\n[Goblins 0.12](https://spritely.institute/news/spritely-goblins-v0-12-0-released-two-new-netlayers-join-the-ocapn-family-and-more.html)\nwe introduced a *federated* relay named \"prelay\". While OCapN traffic is\nencrypted in transit, the prelay crucially lacks\n[end-to-end encryption (E2EE)](https://en.wikipedia.org/wiki/End-to-end_encryption).\nE2EE secures all the messages so they can only be read by their intended\nrecipient, preventing intemediatory parties (like the relay) from tampering with\nor reading them. Due to the lack of E2EE, the relay was named \"prelay\"\n(pre-relay) to dissuade its use. Thanks to a\n[grant from NLnet](https://nlnet.nlproject/OCapN-federatedrelays/), we're happy\nto announce that the next release of Goblins will include a federated, E2EE\nrelay implementation!\n\nHaving a relay that we could recommend has become even more of a critical part\nof the OCapN story as we've developed [Hoot](https://spritely.institute/hoot/),\nour Scheme to WebAssembly compiler. The web is a challenging environment in\nwhich to build peer-to-peer applications. Most of the patterns and available\nAPIs are designed around client-server architectures and centralized certificate\nauthorities for security. While relays are unsatisfying compared to making\ndirect connections, they enable peer-to-peer applications to communicate in\nthese hostile environments.\n\nIn addition to implementing the E2EE relay for Goblins, our NLnet grant also\nincludes writing a specification for the OCapN group so that the relay can be\nbroadly supported by other OCapN implementations.\n\n## Design\n\nWe wanted OCapN relays to have the following qualities:\n\n1. **Stateless**: no storage of user data; easy to replace or migrate\n2. **Federated**: reduces the power any one relay holds over the wider\n   network\n3. **End-to-end encrypted**: prevents man-in-the-middle and replay\n   attacks; perfect forward secrecy\n\nOur existing \"prelay\" already has the first two properties. To get E2EE, we\nchose to build upon the [Noise Protocol](https://noiseprotocol.org/).\n\nNoise is actually a well-established framework of different patterns and\nciphers. This allows applications to pick the pattern which is most suitable to\ntheir needs. It's also a protocol family which has widespread use (WireGuard,\nWhatsApp, etc.) and has been scrutinised a great deal. None of us at Spritely\nare cryptographers by training, so using an off-the-shelf protocol also ensures\nwe're avoiding any mistakes in protocol design.\n\nSo how can we use Noise with OCapN? When connecting to an OCapN peer, there is a\n[Locator](https://github.com/ocapn/ocapn/blob/main/draft-specifications/Locators.md#ocapn-peer)\nwhich encodes three components about the remote peer:\n\n1. **Designator**: self-authenticating identity (usually a public key)\n2. **Transport**: a symbol identifying the kind of netlayer it is (such as\n   `onion`)\n3. **Hints**: a hashmap of routing data (such as IP, port, etc.)\n\nEach netlayer decides what goes into these fields, and the union of both the\ndesignator and transport should uniquely identify the peer. Noise uses two kinds\nof keys:\n\n1. **static**: a key that doesn't change between sessions\n2. **ephemeral**: a key that is generated fresh for each session\n\nAn important step each netlayer needs to consider is something called *peer\nauthentication*, which verifies that a peer is who they say they are. OCapN\nrequires that there exists\n[**at most one** connection](https://github.com/ocapn/ocapn/blob/main/draft-specifications/CapTP%20Specification.md#opstart-session)\nbetween any two peers at a given time. Ensuring there is only ever one\nsession between any two peers is essential to allow for object identity\ncomparison and preventing unnecessary\n[3rd party handoffs](https://github.com/ocapn/ocapn/blob/main/draft-specifications/CapTP%20Specification.md#third-party-handoffs).\nAn OCapN implementation will enforce this by reusing connections or closing new\ninbound connections if one already exists. Without peer authentication, a\nmalicious peer could impersonate another peer causing a denial of service to the\npeer whose identity was stolen.\n\nAs described above, the designator is a public key, which allows the peer to\nprove their identity by using its private key. Noise has a pattern for this\nknown as `XK`, where the initiator knows the key of the responder and will\nverify it. The `X` is referring to the initiator's static key being transmitted\nto the responder. The `K` is referring to the initiator already knowing the\nresponder's public key. The public key of a peer is encoded and placed in the\ndesignator for the initiator to use.\n\nNoise also allows the use of different key generation algorithms, cipher suites,\nand hashes. This is great as we needed to select something which has\nimplementations both in commonly available cryptographic libraries and within\nthe browser (for Hoot). X25519 keys are well supported in all those situations,\nas are AESGCM cipher suites and SHA256 hashes. These are all available in\nGnuTLS (what we're using on the Guile VM) and in the\n[SubtleCrypto API](https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto)\n(that we're using on the web).\n\n## Guile Noise library\n\nOf course, to use Noise we need an implementation. Noise is popular enough that\nthere's quite a few implementations for different languages. Unfortunately,\nGuile is not one of them, so that meant writing our own Noise library.\n\nThe [Guile Noise library](https://codeberg.org/spritely/guile-noise) we've\nwritten doesn't require users to know the intricate workings of Noise, but Noise\nis interesting and so let's take a brief look at how it works and my\nimplementation experience. The\n[Noise specification](https://noiseprotocol.org/noise.html) is actually pretty\napproachable and is built around three conceptual objects:\n\n- **CipherState**: holds a key and a hash; provides encryption, decryption, and\n  rekeying\n- **SymmetricState**: holds a chaining key and hash output; uses `CipherState`\n  for encryption, as well as deriving new keys and hashes\n- **HandshakeState**: holds state for the initial handshake and handles reading\n  and writing of handshake messages\n\nThe Noise protocol starts with a handshake. This is turn-based where the\ninitiator goes first and writes their handshake message. The receiver then reads\nthat message and writes their own. This continues until the handshake is\ncomplete. After which you only need the `CipherState` which can encrypt and\ndecrypt messages (and if desired\n[rekey](https://noiseprotocol.org/noise.html#rekey)).\n\nInterestingly, Noise defines a small intepreter which defines a limited set of\noperations. The handshake patterns are then defined in terms of these\noperations. I won't go through all of them (see the spec if you're interested),\nbut the `XK` pattern is defined as:\n\n```\npre-message <- s\n-> e es\n<- e ee\n-> s se\n```\n\nThis is a handshake consisting of three messages (and a\npre-message). Pre-messages are not exchanged as part of the Noise handshake\nitself, they're messages which are expected to have happened out-of-band prior\nto connection. In this case `<- s` means the static key from the responder is\nsent to the initiator. This happens by it being encoded in the OCapN peer\nlocator as we discussed above. The arrows show the direction with the left side\nbeing the initiator and the right being the responder.\n\nI'll just cover the first message, `-> e es`. This message is for the\ninitiator to send to the responder as it starts with `->`. The operations are:\n\n- `e`: generates an emphemeral key, transmits the public part, and mixes it into\n  an internal hash\n- `es`: derives a new key (from the ephemeral key and responders static key) and\n  mixes it into its own key\n\nThis allows a Noise library to easily support lots of different patterns. Each\npattern can define it's pre-messages and handshake messages (all of which are\ndefined in the spec) and the implementation just needs to go through and perform\nthe steps.\n\nOnce the handshake is complete, the Noise library will create two `CipherState`\nobjects. The first is used for encrypting and decrypting the initiator's\nmessages, the second for the responder's messages.\n\nThe Guile library implements all 12 of the\n[fundamental interactive patterns](https://noiseprotocol.org/noise.html#interactive-handshake-patterns-fundamental).\nWe built this library to work on both Hoot and the Guile VM. It currently uses\n`SubtleCrypto` for Hoot and GnuTLS for the Guile VM, but it's written so users\nof the library can add support for other crypto libraries. Our library comes\nwith support for X25519 key generation, AES-GCM ciphers, and SHA256 and\nSHA512 hash functions. Noise not only provides an approachable spec, but also a\nset of\n[test vectors](https://github.com/noiseprotocol/noise_wiki/wiki/Test-vectors) to\nhelp implementation. These are a set of messages which Noise should exchange for\na given pattern and keys. Our library has been tested against all the vectors\nfor patterns and crypto that we support and the test runner checks them on Hoot\nas well as the Guile VM.\n\n## Noise netlayer design\n\nOCapN is a layered protocol consisting of CapTP (Capability Transport Protocol)\nand many *netlayers*. A netlayer sends encoded messages between two peers over a\nparticular medium. This allows the higher-level CapTP to be *transport\nagnostic*. For example, the\n[Onion netlayer](https://files.spritely.institute/docs/guile-goblins/latest/Tor-Onion-Services.html)\nprovides transport over Tor Onion services. However, netlayers can be defined\nnot only in terms of a specific transport protocol, but also in terms of\n*another netlayer*. We call these *meta netlayers*.\n\nNoise itself isn't designed with a specific transport protocol in mind, and the\nNoise netlayer isn't either. Indeed many netlayers might want to rely on the\nproperties of Noise for many different reasons. Even netlayers which provide\nstrong encryption, like Tor, may wish to rely on Noise for its bidirectional\npeer authentication properties (Tor authenticates outbound connections to a\nhidden service, but not the reverse).\n\nThe Noise netlayer thus composes with any other netlayer. Composing the Noise\nnetlayer and prelay netlayer yields an *end-to-end encrypted relay netlayer*!\n\nHow does this work? The Noise netlayer spawns a sort of \"bootstrap\" object (not\nthe same\n[bootstrap](https://github.com/ocapn/ocapn/blob/main/draft-specifications/CapTP%20Specification.md#bootstrap-object)\nif you're familiar with CapTP). The bootstrap object is encoded within the OCapN\npeer locator, and can be *enlivened* to make a live reference when making a new\noutbound connection. Each side spawns an\n[inbox and outbox pair](https://files.spritely.institute/docs/guile-goblins/latest/Inbox.html),\nand the initiating peer delivers their outbox to the bootstrap object. The\nbootstrap object sets up its side of Noise and makes its own inbox-outbox pair\nreturning its outbox. Each side is then able to deliver messages to each other\nby sending messages to the remote outbox object. Each side then follows the\nhandshake procedure. Once complete, encrypted messages can be exchanged by using\nthe Noise encrypt procedure and decrypted when read from the inbox.\n\nLooking at the test code which uses the\n[TCP+TLS netlayer](https://files.spritely.institute/docs/guile-goblins/latest/TCP-_002b-TLS.html),\nlayering on Noise encryption is just a few lines of code:\n\n```\n(define a-mycapn\n  (with-vat vat-a\n    ;; Spawn the TCP+TLS netlayer\n    (define tcp-tls-netlayer (spawn ^tcp-tls-netlayer \"127.0.0.1\"))\n    ;; Spawn the Noise netlayer, passing in the TCP+TLS netlayer\n    (define noise-netlayer (spawn ^noise-netlayer tcp-tls-netlayer))\n    ;; Spawn the MyCapN object (CapTP)\n    (spawn-mycapn noise-netlayer)))\n```\n\n## The Relay netlayer\n\nSo, how does this work with the relay? Well, since we already have an\nnon-E2EE relay and now have seen we can compose Noise and get the properties of\nboth, we can simply pass in the prelay netlayer to Noise netlayer and get an\nE2EE relay. Of course, we don't want to ask users of the netlayer to do\nthis. We'll be replacing the prelay module with a new relay module that is\nalways end-to-end encrypted.\n\nWhile the relay design (in its non-E2EE form) isn't new to Goblins, it's\nworth quickly taking a look at how the relay works. The relay itself is built on\nthe idea of a relay client and a relay server. On the relay server there are two\nactors:\n\n1. **Controller**: allows the client to steer its own relay accepting\n   new inbound connections and making new outbound connections\n2. **Endpoint**: which is encoded in the OCapN locator and is publicly\n   accessible, a relay server messages this object when establishing a new\n   connection to the peer\n\nThis makes the relay *federated*. Two OCapN peers can use different relay\nservers and still communicate using OCapN between the two relay servers. When a\nclient wants to make an outbound connection to a peer on another relay server,\nthe relay can enliven the endpoint sturdyref (getting a live object) and send a\nmessage to it.\n\nThe animation above shows the flow between two peers using federation. It starts\nwith two actors on two different peers, Alice (on peer A) and Bob (on peer B).\nAlice has a *sturdyref* (an OCapN locator specifying an object) to Bob. Here's\nhow the flow works:\n\n1. Peer A first sets itself up with the controller by registering an object\n   `a-incoming` with the controller. This object can be used when establishing\n   new connections.\n2. Alice has a sturdyref to Bob but wants a a live reference to Bob that she can\n   message, she asks her MyCapN to /enliven/ it.\n3. MyCapN creates an `adeliver` object which faciliates the connection. It then\n   sends a message to the relay controller with the sturdyref and `adeliver`\n   object and asks the relay to make a new connection.\n4. The controller extracts the `endpoint` object for Peer B's relay from the\n   sturdyref and enlivens it forming a new connection between the two relays. It\n   also spawns a new `rarelay` object to facilitate communication between these\n   two peers and hands this back to `adeliver`.\n5. The controller then sends the `rarelay` object to the endpoint on B's\n   relay. B's relay then spawns its own analog to the object `rbrelay`.\n6. Peer B's relay reaches out to the `b-incoming` object, allowing it to\n   establish a new connection to Peer B. It passes along the `rbrelay` object.\n7. Peer B's MyCapN object spawns a new `bdeliver` object which facilitates its\n   connection.\n\nThis gives the two peers a connection to one another, even though neither share\na common relay server. The relay is designed to be completely stateless, i.e. it\ndoesn't hold on to data, configuration, or anything else.\n\nOne of the lessons we've learned since co-authoring ActivityPub is it's crucial\nto reduce the impact of servers on their users. In ActivityPub, each instance (a\nserver) stores all the data for its users and performs all the operations their\nbehalf. This means the user needs to trust the server a lot, and if the user's\ninstance shuts down, or they simply wish to migrate, moving to another server is\ndifficult.\n\nLearning from those problems, it was critical for us to make sure the server\nwas stateless. The relay itself stores no state/data for a user and, aside from\nconnecting to another peer, the only behavior it's expected to perform is\nrelaying messages. This makes changing relay servers incredibly easy. The user\nsimply needs to find another (or setup their own) relay server and include that\nin their sturdy references instead.\n\n## Future work\n\nThe work isn't done, though. Adding support to Goblins was just one part of the\nNLnet grant. We'll be contributing specifications for the encrypted relay to the\n[OCapN standardization group](https://ocapn.org/) so others can implement\nit. Ridley, who has implemented\n[OCapN in Dart](https://codeberg.org/ridley/DObjects), has already implemented\ntheir own [Noise library](https://codeberg.org/ridley/dart_noise) in preparation\nfor the E2EE relay. As part of the grant work within the OCapN group, we'll also\nbe adding support to the [test suite](https://github.com/ocapn/ocapn-test-suite)\nto allow both testing new relay implementations, and testing the rest of OCapN\nover the relay.\n\nAnother milestone of the grant is to implement a Guix service to make it easy to\nrun an instance of the relay. The relay doesn't take many resources to run and\nit has very few dependencies, so it should be easy for others to package for\ntheir distributions and run on the cheapest VPS or old computers users might\nhave around their home. We are looking forward to the deployment of a vast\nnetwork of OCapN relays.\n\n## Thanks to our supporters\n\nYour support makes our work possible! If you like what we do, please\nconsider [becoming a Spritely supporter today](https://spritely.institute/donate)!\n\n### Diamond tier\n\n- Aeva Palecek\n- David Anderson\n- Holmes Wilson\n- Jonathan Frederickson\n- Lassi Kiuru\n\n### Gold tier\n\n- Alex Sassmannshausen\n- Juan Lizarraga Cubillos\n- Larne Pekowsky\n- Serra Allgood\n\n### Silver tier\n\n- Austin Robinson\n- Brit Butler\n- Charlie McMackin\n- Dan Connolly\n- Deb Nicholson\n- Diana Thayer\n- Eric Schultz\n- Evangelo Stavro Prodromou\n- Evgeni Ku\n- Glenn Thompson\n- James Luke\n- Jonathan Smith\n- Jonathan Wright\n- Justin Sheehy\n- Michel Lind\n- Mike Ledoux\n- Moto A\n- Nia Bickford\n- Noah Beasley\n- Steve Sprang\n- Travis Smith\n\n### Bronze tier\n\n- Alan Zimmerman\n- Ben Hamill\n- Benjamin Grimm-Lebsanft\n- Brooke Vibber\n- Brooklyn Zelenka\n- Carl A\n- Crazypedia No\n- Ellie High\n- François Joulaud\n- Gabor Udvari\n- Grant Gould\n- Gregory Buhtz\n- Ivan Sagalaev\n- Jason Wodicka\n- Jeff Forcier\n- Marty McGuire\n- Mason DeVries\n- Michael Orbinpost\n- Neil Brudnak\n- Nelson Pavlosky\n- Philipp Nassua\n- Rodion Goritskov\n- Ron Welch\n- Stefan Magdalinski\n- Stephen Herrick\n- Steven De Herdt\n- Tamara Schmitz\n- Thomas Talbot\n- William Murphy\n- a b\n- chee rabbits\n- r g\n- terra tauri\n","body_html":"<h1 id=\"end-to-end-encrypted-relays-for-ocapn\">End-to-end encrypted relays for OCapN</h1>\n<p>Jessica Tallon — October 15, 2026</p>\n<p>Creating a direct connection between <em>any</em> two computers (not just from client\nto server) over the internet is notoriously difficult. Many home networks are\nbehind NATs which prevent new inbound connections. Sometimes these NATs are from\nthe ISP (carrier-grade NATs) which are impossible to work around with port\nforwarding and/or UPnP. While\n<a href=\"https://en.wikipedia.org/wiki/Hole_punching_(networking)\" rel=\"nofollow ugc noopener\">hole punching techniques</a>\n<em>can</em> break through NATs, it&#39;s far from guaranteed. Thus, we need something that\nworks in the general case: <strong>relays</strong>.</p>\n<p>Relays are servers, accessible over the public internet, that act as a proxy\nbetween two computers that cannot directly connect. In\n<a href=\"https://spritely.institute/news/spritely-goblins-v0-12-0-released-two-new-netlayers-join-the-ocapn-family-and-more.html\" rel=\"nofollow ugc noopener\">Goblins 0.12</a>\nwe introduced a <em>federated</em> relay named &quot;prelay&quot;. While OCapN traffic is\nencrypted in transit, the prelay crucially lacks\n<a href=\"https://en.wikipedia.org/wiki/End-to-end_encryption\" rel=\"nofollow ugc noopener\">end-to-end encryption (E2EE)</a>.\nE2EE secures all the messages so they can only be read by their intended\nrecipient, preventing intemediatory parties (like the relay) from tampering with\nor reading them. Due to the lack of E2EE, the relay was named &quot;prelay&quot;\n(pre-relay) to dissuade its use. Thanks to a\n<a href=\"https://nlnet.nlproject/OCapN-federatedrelays/\" rel=\"nofollow ugc noopener\">grant from NLnet</a>, we&#39;re happy\nto announce that the next release of Goblins will include a federated, E2EE\nrelay implementation!</p>\n<p>Having a relay that we could recommend has become even more of a critical part\nof the OCapN story as we&#39;ve developed <a href=\"https://spritely.institute/hoot/\" rel=\"nofollow ugc noopener\">Hoot</a>,\nour Scheme to WebAssembly compiler. The web is a challenging environment in\nwhich to build peer-to-peer applications. Most of the patterns and available\nAPIs are designed around client-server architectures and centralized certificate\nauthorities for security. While relays are unsatisfying compared to making\ndirect connections, they enable peer-to-peer applications to communicate in\nthese hostile environments.</p>\n<p>In addition to implementing the E2EE relay for Goblins, our NLnet grant also\nincludes writing a specification for the OCapN group so that the relay can be\nbroadly supported by other OCapN implementations.</p>\n<h2 id=\"design\">Design</h2>\n<p>We wanted OCapN relays to have the following qualities:</p>\n<ol><li><strong>Stateless</strong>: no storage of user data; easy to replace or migrate</li><li><p><strong>Federated</strong>: reduces the power any one relay holds over the wider</p><p> network</p></li><li><p><strong>End-to-end encrypted</strong>: prevents man-in-the-middle and replay</p><p> attacks; perfect forward secrecy</p></li></ol>\n<p>Our existing &quot;prelay&quot; already has the first two properties. To get E2EE, we\nchose to build upon the <a href=\"https://noiseprotocol.org/\" rel=\"nofollow ugc noopener\">Noise Protocol</a>.</p>\n<p>Noise is actually a well-established framework of different patterns and\nciphers. This allows applications to pick the pattern which is most suitable to\ntheir needs. It&#39;s also a protocol family which has widespread use (WireGuard,\nWhatsApp, etc.) and has been scrutinised a great deal. None of us at Spritely\nare cryptographers by training, so using an off-the-shelf protocol also ensures\nwe&#39;re avoiding any mistakes in protocol design.</p>\n<p>So how can we use Noise with OCapN? When connecting to an OCapN peer, there is a\n<a href=\"https://github.com/ocapn/ocapn/blob/main/draft-specifications/Locators.md#ocapn-peer\" rel=\"nofollow ugc noopener\">Locator</a>\nwhich encodes three components about the remote peer:</p>\n<ol><li><strong>Designator</strong>: self-authenticating identity (usually a public key)</li><li><p><strong>Transport</strong>: a symbol identifying the kind of netlayer it is (such as</p><p> <code>onion</code>)</p></li><li><strong>Hints</strong>: a hashmap of routing data (such as IP, port, etc.)</li></ol>\n<p>Each netlayer decides what goes into these fields, and the union of both the\ndesignator and transport should uniquely identify the peer. Noise uses two kinds\nof keys:</p>\n<ol><li><strong>static</strong>: a key that doesn&#39;t change between sessions</li><li><strong>ephemeral</strong>: a key that is generated fresh for each session</li></ol>\n<p>An important step each netlayer needs to consider is something called *peer\nauthentication*, which verifies that a peer is who they say they are. OCapN\nrequires that there exists\n<a href=\"https://github.com/ocapn/ocapn/blob/main/draft-specifications/CapTP%20Specification.md#opstart-session\" rel=\"nofollow ugc noopener\"><strong>at most one</strong> connection</a>\nbetween any two peers at a given time. Ensuring there is only ever one\nsession between any two peers is essential to allow for object identity\ncomparison and preventing unnecessary\n<a href=\"https://github.com/ocapn/ocapn/blob/main/draft-specifications/CapTP%20Specification.md#third-party-handoffs\" rel=\"nofollow ugc noopener\">3rd party handoffs</a>.\nAn OCapN implementation will enforce this by reusing connections or closing new\ninbound connections if one already exists. Without peer authentication, a\nmalicious peer could impersonate another peer causing a denial of service to the\npeer whose identity was stolen.</p>\n<p>As described above, the designator is a public key, which allows the peer to\nprove their identity by using its private key. Noise has a pattern for this\nknown as <code>XK</code>, where the initiator knows the key of the responder and will\nverify it. The <code>X</code> is referring to the initiator&#39;s static key being transmitted\nto the responder. The <code>K</code> is referring to the initiator already knowing the\nresponder&#39;s public key. The public key of a peer is encoded and placed in the\ndesignator for the initiator to use.</p>\n<p>Noise also allows the use of different key generation algorithms, cipher suites,\nand hashes. This is great as we needed to select something which has\nimplementations both in commonly available cryptographic libraries and within\nthe browser (for Hoot). X25519 keys are well supported in all those situations,\nas are AESGCM cipher suites and SHA256 hashes. These are all available in\nGnuTLS (what we&#39;re using on the Guile VM) and in the\n<a href=\"https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto\" rel=\"nofollow ugc noopener\">SubtleCrypto API</a>\n(that we&#39;re using on the web).</p>\n<h2 id=\"guile-noise-library\">Guile Noise library</h2>\n<p>Of course, to use Noise we need an implementation. Noise is popular enough that\nthere&#39;s quite a few implementations for different languages. Unfortunately,\nGuile is not one of them, so that meant writing our own Noise library.</p>\n<p>The <a href=\"https://codeberg.org/spritely/guile-noise\" rel=\"nofollow ugc noopener\">Guile Noise library</a> we&#39;ve\nwritten doesn&#39;t require users to know the intricate workings of Noise, but Noise\nis interesting and so let&#39;s take a brief look at how it works and my\nimplementation experience. The\n<a href=\"https://noiseprotocol.org/noise.html\" rel=\"nofollow ugc noopener\">Noise specification</a> is actually pretty\napproachable and is built around three conceptual objects:</p>\n<ul><li><p><strong>CipherState</strong>: holds a key and a hash; provides encryption, decryption, and</p><p>rekeying</p></li><li><p><strong>SymmetricState</strong>: holds a chaining key and hash output; uses <code>CipherState</code></p><p>for encryption, as well as deriving new keys and hashes</p></li><li><p><strong>HandshakeState</strong>: holds state for the initial handshake and handles reading</p><p>and writing of handshake messages</p></li></ul>\n<p>The Noise protocol starts with a handshake. This is turn-based where the\ninitiator goes first and writes their handshake message. The receiver then reads\nthat message and writes their own. This continues until the handshake is\ncomplete. After which you only need the <code>CipherState</code> which can encrypt and\ndecrypt messages (and if desired\n<a href=\"https://noiseprotocol.org/noise.html#rekey\" rel=\"nofollow ugc noopener\">rekey</a>).</p>\n<p>Interestingly, Noise defines a small intepreter which defines a limited set of\noperations. The handshake patterns are then defined in terms of these\noperations. I won&#39;t go through all of them (see the spec if you&#39;re interested),\nbut the <code>XK</code> pattern is defined as:</p>\n<pre><code>pre-message &lt;- s\n-&gt; e es\n&lt;- e ee\n-&gt; s se</code></pre>\n<p>This is a handshake consisting of three messages (and a\npre-message). Pre-messages are not exchanged as part of the Noise handshake\nitself, they&#39;re messages which are expected to have happened out-of-band prior\nto connection. In this case <code>&lt;- s</code> means the static key from the responder is\nsent to the initiator. This happens by it being encoded in the OCapN peer\nlocator as we discussed above. The arrows show the direction with the left side\nbeing the initiator and the right being the responder.</p>\n<p>I&#39;ll just cover the first message, <code>-&gt; e es</code>. This message is for the\ninitiator to send to the responder as it starts with <code>-&gt;</code>. The operations are:</p>\n<ul><li><p><code>e</code>: generates an emphemeral key, transmits the public part, and mixes it into</p><p>an internal hash</p></li><li><p><code>es</code>: derives a new key (from the ephemeral key and responders static key) and</p><p>mixes it into its own key</p></li></ul>\n<p>This allows a Noise library to easily support lots of different patterns. Each\npattern can define it&#39;s pre-messages and handshake messages (all of which are\ndefined in the spec) and the implementation just needs to go through and perform\nthe steps.</p>\n<p>Once the handshake is complete, the Noise library will create two <code>CipherState</code>\nobjects. The first is used for encrypting and decrypting the initiator&#39;s\nmessages, the second for the responder&#39;s messages.</p>\n<p>The Guile library implements all 12 of the\n<a href=\"https://noiseprotocol.org/noise.html#interactive-handshake-patterns-fundamental\" rel=\"nofollow ugc noopener\">fundamental interactive patterns</a>.\nWe built this library to work on both Hoot and the Guile VM. It currently uses\n<code>SubtleCrypto</code> for Hoot and GnuTLS for the Guile VM, but it&#39;s written so users\nof the library can add support for other crypto libraries. Our library comes\nwith support for X25519 key generation, AES-GCM ciphers, and SHA256 and\nSHA512 hash functions. Noise not only provides an approachable spec, but also a\nset of\n<a href=\"https://github.com/noiseprotocol/noise_wiki/wiki/Test-vectors\" rel=\"nofollow ugc noopener\">test vectors</a> to\nhelp implementation. These are a set of messages which Noise should exchange for\na given pattern and keys. Our library has been tested against all the vectors\nfor patterns and crypto that we support and the test runner checks them on Hoot\nas well as the Guile VM.</p>\n<h2 id=\"noise-netlayer-design\">Noise netlayer design</h2>\n<p>OCapN is a layered protocol consisting of CapTP (Capability Transport Protocol)\nand many <em>netlayers</em>. A netlayer sends encoded messages between two peers over a\nparticular medium. This allows the higher-level CapTP to be *transport\nagnostic*. For example, the\n<a href=\"https://files.spritely.institute/docs/guile-goblins/latest/Tor-Onion-Services.html\" rel=\"nofollow ugc noopener\">Onion netlayer</a>\nprovides transport over Tor Onion services. However, netlayers can be defined\nnot only in terms of a specific transport protocol, but also in terms of\n<em>another netlayer</em>. We call these <em>meta netlayers</em>.</p>\n<p>Noise itself isn&#39;t designed with a specific transport protocol in mind, and the\nNoise netlayer isn&#39;t either. Indeed many netlayers might want to rely on the\nproperties of Noise for many different reasons. Even netlayers which provide\nstrong encryption, like Tor, may wish to rely on Noise for its bidirectional\npeer authentication properties (Tor authenticates outbound connections to a\nhidden service, but not the reverse).</p>\n<p>The Noise netlayer thus composes with any other netlayer. Composing the Noise\nnetlayer and prelay netlayer yields an <em>end-to-end encrypted relay netlayer</em>!</p>\n<p>How does this work? The Noise netlayer spawns a sort of &quot;bootstrap&quot; object (not\nthe same\n<a href=\"https://github.com/ocapn/ocapn/blob/main/draft-specifications/CapTP%20Specification.md#bootstrap-object\" rel=\"nofollow ugc noopener\">bootstrap</a>\nif you&#39;re familiar with CapTP). The bootstrap object is encoded within the OCapN\npeer locator, and can be <em>enlivened</em> to make a live reference when making a new\noutbound connection. Each side spawns an\n<a href=\"https://files.spritely.institute/docs/guile-goblins/latest/Inbox.html\" rel=\"nofollow ugc noopener\">inbox and outbox pair</a>,\nand the initiating peer delivers their outbox to the bootstrap object. The\nbootstrap object sets up its side of Noise and makes its own inbox-outbox pair\nreturning its outbox. Each side is then able to deliver messages to each other\nby sending messages to the remote outbox object. Each side then follows the\nhandshake procedure. Once complete, encrypted messages can be exchanged by using\nthe Noise encrypt procedure and decrypted when read from the inbox.</p>\n<p>Looking at the test code which uses the\n<a href=\"https://files.spritely.institute/docs/guile-goblins/latest/TCP-_002b-TLS.html\" rel=\"nofollow ugc noopener\">TCP+TLS netlayer</a>,\nlayering on Noise encryption is just a few lines of code:</p>\n<pre><code>(define a-mycapn\n  (with-vat vat-a\n    ;; Spawn the TCP+TLS netlayer\n    (define tcp-tls-netlayer (spawn ^tcp-tls-netlayer &quot;127.0.0.1&quot;))\n    ;; Spawn the Noise netlayer, passing in the TCP+TLS netlayer\n    (define noise-netlayer (spawn ^noise-netlayer tcp-tls-netlayer))\n    ;; Spawn the MyCapN object (CapTP)\n    (spawn-mycapn noise-netlayer)))</code></pre>\n<h2 id=\"the-relay-netlayer\">The Relay netlayer</h2>\n<p>So, how does this work with the relay? Well, since we already have an\nnon-E2EE relay and now have seen we can compose Noise and get the properties of\nboth, we can simply pass in the prelay netlayer to Noise netlayer and get an\nE2EE relay. Of course, we don&#39;t want to ask users of the netlayer to do\nthis. We&#39;ll be replacing the prelay module with a new relay module that is\nalways end-to-end encrypted.</p>\n<p>While the relay design (in its non-E2EE form) isn&#39;t new to Goblins, it&#39;s\nworth quickly taking a look at how the relay works. The relay itself is built on\nthe idea of a relay client and a relay server. On the relay server there are two\nactors:</p>\n<ol><li><p><strong>Controller</strong>: allows the client to steer its own relay accepting</p><p> new inbound connections and making new outbound connections</p></li><li><p><strong>Endpoint</strong>: which is encoded in the OCapN locator and is publicly</p><p> accessible, a relay server messages this object when establishing a new\n connection to the peer</p></li></ol>\n<p>This makes the relay <em>federated</em>. Two OCapN peers can use different relay\nservers and still communicate using OCapN between the two relay servers. When a\nclient wants to make an outbound connection to a peer on another relay server,\nthe relay can enliven the endpoint sturdyref (getting a live object) and send a\nmessage to it.</p>\n<p>The animation above shows the flow between two peers using federation. It starts\nwith two actors on two different peers, Alice (on peer A) and Bob (on peer B).\nAlice has a <em>sturdyref</em> (an OCapN locator specifying an object) to Bob. Here&#39;s\nhow the flow works:</p>\n<ol><li><p>Peer A first sets itself up with the controller by registering an object</p><p> <code>a-incoming</code> with the controller. This object can be used when establishing\n new connections.</p></li><li><p>Alice has a sturdyref to Bob but wants a a live reference to Bob that she can</p><p> message, she asks her MyCapN to /enliven/ it.</p></li><li><p>MyCapN creates an <code>adeliver</code> object which faciliates the connection. It then</p><p> sends a message to the relay controller with the sturdyref and <code>adeliver</code>\n object and asks the relay to make a new connection.</p></li><li><p>The controller extracts the <code>endpoint</code> object for Peer B&#39;s relay from the</p><p> sturdyref and enlivens it forming a new connection between the two relays. It\n also spawns a new <code>rarelay</code> object to facilitate communication between these\n two peers and hands this back to <code>adeliver</code>.</p></li><li><p>The controller then sends the <code>rarelay</code> object to the endpoint on B&#39;s</p><p> relay. B&#39;s relay then spawns its own analog to the object <code>rbrelay</code>.</p></li><li><p>Peer B&#39;s relay reaches out to the <code>b-incoming</code> object, allowing it to</p><p> establish a new connection to Peer B. It passes along the <code>rbrelay</code> object.</p></li><li><p>Peer B&#39;s MyCapN object spawns a new <code>bdeliver</code> object which facilitates its</p><p> connection.</p></li></ol>\n<p>This gives the two peers a connection to one another, even though neither share\na common relay server. The relay is designed to be completely stateless, i.e. it\ndoesn&#39;t hold on to data, configuration, or anything else.</p>\n<p>One of the lessons we&#39;ve learned since co-authoring ActivityPub is it&#39;s crucial\nto reduce the impact of servers on their users. In ActivityPub, each instance (a\nserver) stores all the data for its users and performs all the operations their\nbehalf. This means the user needs to trust the server a lot, and if the user&#39;s\ninstance shuts down, or they simply wish to migrate, moving to another server is\ndifficult.</p>\n<p>Learning from those problems, it was critical for us to make sure the server\nwas stateless. The relay itself stores no state/data for a user and, aside from\nconnecting to another peer, the only behavior it&#39;s expected to perform is\nrelaying messages. This makes changing relay servers incredibly easy. The user\nsimply needs to find another (or setup their own) relay server and include that\nin their sturdy references instead.</p>\n<h2 id=\"future-work\">Future work</h2>\n<p>The work isn&#39;t done, though. Adding support to Goblins was just one part of the\nNLnet grant. We&#39;ll be contributing specifications for the encrypted relay to the\n<a href=\"https://ocapn.org/\" rel=\"nofollow ugc noopener\">OCapN standardization group</a> so others can implement\nit. Ridley, who has implemented\n<a href=\"https://codeberg.org/ridley/DObjects\" rel=\"nofollow ugc noopener\">OCapN in Dart</a>, has already implemented\ntheir own <a href=\"https://codeberg.org/ridley/dart_noise\" rel=\"nofollow ugc noopener\">Noise library</a> in preparation\nfor the E2EE relay. As part of the grant work within the OCapN group, we&#39;ll also\nbe adding support to the <a href=\"https://github.com/ocapn/ocapn-test-suite\" rel=\"nofollow ugc noopener\">test suite</a>\nto allow both testing new relay implementations, and testing the rest of OCapN\nover the relay.</p>\n<p>Another milestone of the grant is to implement a Guix service to make it easy to\nrun an instance of the relay. The relay doesn&#39;t take many resources to run and\nit has very few dependencies, so it should be easy for others to package for\ntheir distributions and run on the cheapest VPS or old computers users might\nhave around their home. We are looking forward to the deployment of a vast\nnetwork of OCapN relays.</p>\n<h2 id=\"thanks-to-our-supporters\">Thanks to our supporters</h2>\n<p>Your support makes our work possible! If you like what we do, please\nconsider <a href=\"https://spritely.institute/donate\" rel=\"nofollow ugc noopener\">becoming a Spritely supporter today</a>!</p>\n<h3 id=\"diamond-tier\">Diamond tier</h3>\n<ul><li>Aeva Palecek</li><li>David Anderson</li><li>Holmes Wilson</li><li>Jonathan Frederickson</li><li>Lassi Kiuru</li></ul>\n<h3 id=\"gold-tier\">Gold tier</h3>\n<ul><li>Alex Sassmannshausen</li><li>Juan Lizarraga Cubillos</li><li>Larne Pekowsky</li><li>Serra Allgood</li></ul>\n<h3 id=\"silver-tier\">Silver tier</h3>\n<ul><li>Austin Robinson</li><li>Brit Butler</li><li>Charlie McMackin</li><li>Dan Connolly</li><li>Deb Nicholson</li><li>Diana Thayer</li><li>Eric Schultz</li><li>Evangelo Stavro Prodromou</li><li>Evgeni Ku</li><li>Glenn Thompson</li><li>James Luke</li><li>Jonathan Smith</li><li>Jonathan Wright</li><li>Justin Sheehy</li><li>Michel Lind</li><li>Mike Ledoux</li><li>Moto A</li><li>Nia Bickford</li><li>Noah Beasley</li><li>Steve Sprang</li><li>Travis Smith</li></ul>\n<h3 id=\"bronze-tier\">Bronze tier</h3>\n<ul><li>Alan Zimmerman</li><li>Ben Hamill</li><li>Benjamin Grimm-Lebsanft</li><li>Brooke Vibber</li><li>Brooklyn Zelenka</li><li>Carl A</li><li>Crazypedia No</li><li>Ellie High</li><li>François Joulaud</li><li>Gabor Udvari</li><li>Grant Gould</li><li>Gregory Buhtz</li><li>Ivan Sagalaev</li><li>Jason Wodicka</li><li>Jeff Forcier</li><li>Marty McGuire</li><li>Mason DeVries</li><li>Michael Orbinpost</li><li>Neil Brudnak</li><li>Nelson Pavlosky</li><li>Philipp Nassua</li><li>Rodion Goritskov</li><li>Ron Welch</li><li>Stefan Magdalinski</li><li>Stephen Herrick</li><li>Steven De Herdt</li><li>Tamara Schmitz</li><li>Thomas Talbot</li><li>William Murphy</li><li>a b</li><li>chee rabbits</li><li>r g</li><li>terra tauri</li></ul>","headings":[{"level":1,"text":"End-to-end encrypted relays for OCapN","id":"end-to-end-encrypted-relays-for-ocapn"},{"level":2,"text":"Design","id":"design"},{"level":2,"text":"Guile Noise library","id":"guile-noise-library"},{"level":2,"text":"Noise netlayer design","id":"noise-netlayer-design"},{"level":2,"text":"The Relay netlayer","id":"the-relay-netlayer"},{"level":2,"text":"Future work","id":"future-work"},{"level":2,"text":"Thanks to our supporters","id":"thanks-to-our-supporters"},{"level":3,"text":"Diamond tier","id":"diamond-tier"},{"level":3,"text":"Gold tier","id":"gold-tier"},{"level":3,"text":"Silver tier","id":"silver-tier"},{"level":3,"text":"Bronze tier","id":"bronze-tier"}]}}