{"id":41771157,"url":"https://github.com/codahale/yrgourd","last_synced_at":"2026-01-25T03:08:14.000Z","repository":{"id":208309642,"uuid":"721317839","full_name":"codahale/yrgourd","owner":"codahale","description":"yrgourd uses Lockstitch to establish mutually-authenticated, forward-secure, confidential, high-performance connections. Like a toy Wireguard.","archived":false,"fork":false,"pushed_at":"2025-08-04T17:31:34.000Z","size":521,"stargazers_count":3,"open_issues_count":3,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-08-04T20:49:36.307Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Rust","has_issues":false,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/codahale.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE-APACHE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2023-11-20T20:08:46.000Z","updated_at":"2025-08-04T17:31:36.000Z","dependencies_parsed_at":"2023-11-20T21:29:18.449Z","dependency_job_id":"6786c9ef-bf25-46ba-8fad-04ece9b77edf","html_url":"https://github.com/codahale/yrgourd","commit_stats":null,"previous_names":["codahale/yrgourd"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/codahale/yrgourd","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/codahale%2Fyrgourd","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/codahale%2Fyrgourd/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/codahale%2Fyrgourd/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/codahale%2Fyrgourd/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/codahale","download_url":"https://codeload.github.com/codahale/yrgourd/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/codahale%2Fyrgourd/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28742973,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-25T02:46:29.005Z","status":"ssl_error","status_checked_at":"2026-01-25T02:44:29.968Z","response_time":113,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-01-25T03:08:13.866Z","updated_at":"2026-01-25T03:08:13.995Z","avatar_url":"https://github.com/codahale.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Yrgourd\n\nYrgourd uses [Lockstitch][] to establish mutually-authenticated, forward-secure, confidential,\nhigh-performance connections secure against both classical and post-quantum adversaries. Like a toy\nWireguard.\n\n[Lockstitch]: https://github.com/codahale/lockstitch\n\n## ⚠️ WARNING: You should not use this. ⚠️\n\nNeither the design nor the implementation of this library have been independently evaluated. It uses\nvery recent cryptographic algorithms in slightly heterodox ways and may well be just an absolutely\nterrible idea. The design is documented [below](#design); read it and see if the arguments therein\nare convincing.\n\nIn addition, there is absolutely no guarantee of backwards compatibility.\n\n## Things It Does\n\n* Uses [ML-KEM-768][] for asymmetric operations and [SHA-256][]/[AEGIS-128L][] for symmetric\n  operations.\n* Capable of \u003e10 Gb/sec throughput.\n* Everything in a connection is encrypted.\n* Handshakes use a static/ephemeral ML-KEM handshake to authenticate both sender and receiver with\n  forward security for both.\n* Uses ML-KEM ciphertexts to ratchet the connection state every `N` seconds or `M` bytes.\n* Responders can restrict handshakes to a set of valid initiator public keys.\n* Core logic for handshakes and transport is \u003c500 LoC.\n\n[SHA-256]: https://doi.org/10.6028/NIST.FIPS.180-4\n[ML-KEM-768]: https://csrc.nist.gov/pubs/fips/203/ipd\n[AEGIS-128L]: https://www.ietf.org/archive/id/draft-irtf-cfrg-aegis-aead-15.html\n\n## Demo\n\nFirst, generate a couple of key pairs:\n\n```shell\nyrgourd-cli generate-key\n```\n\nSecond, start up a plaintext echo server:\n\n```shell\nyrgourd-cli echo\n```\n\nThird, start up an encrypted reverse proxy server:\n\n```shell\nyrgourd-cli reverse-proxy --private-key=${PRIVATE_KEY_A}\n```\n\nFourth, start up an encrypted proxy server:\n\n```shell\nyrgourd-cli proxy --private-key=${PRIVATE_KEY_B} --server-public-key=${PUBLIC_KEY_A}\n```\n\nFinally, start up a plaintext connect client:\n\n```shell\nyrgourd-cli connect\n```\n\nAnything you write to `STDIN` will be sent via the proxy server and reverse proxy server to the echo\nserver and returned.\n\n```text\nconnect \u003c--plaintext--\u003e proxy \u003c--encrypted--\u003e reverse-proxy \u003c--plaintext--\u003e echo\n```\n\n## Design\n\nBoth initiator and responder have [ML-KEM-768][] key pairs; the initiator knows the\nresponder's public key. The handshake is the `Kyber.AKE` construction from the original [Kyber][]\npaper with the addition of encrypting the initiator's public keys and a confirmation tag in the\nresponse.\n\n[Kyber]: \u003chttps://eprint.iacr.org/2017/634\u003e\n\nThe initiator starts with a static private key `(is, IS)` and the responder's static public key\n`RS`:\n\n```text\nfunction initiate((is, IS), RS):\n  (ie, IE) ← ml_kem::keygen()      // Generate a random ephemeral key.\n  yg ← init(\"yrgourd.v1\")          // Initialize a protocol.\n  yg ← mix(yg, \"rs\", RS)           // Mix in the responder's static public key,\n  (c0, ss) ← ml_kem::encap(RS)     // Encapsulate a random key with ML-KEM-768.\n  yg ← mix(yg, \"rs-ct\", c0)        // Mix in the ML-KEM ciphertext.\n  yg ← mix(yg, \"rs-ss\", ss)        // Mix in the ML-KEM shared secret.\n  (yg, c1) ← encrypt(yg, \"is\", IS) // Encrypt the initiator's static public key.\n  (yg, c2) ← seal(yg, \"ie\", IE)    // Seal the initiator's ephemeral public key.\n  return ((yg, ie), c0, c1, c2)\n```\n\nThe initiator sends the ML-KEM-768 ciphertext `c0`, the encrypted static public key `c1`, and the\nsealed ephemeral public key `c2` to the responder and keeps `yg` and `ie` as private state.\n\nThe responder starts with a static private key `(rs, RS)`:\n\n```text\nfunction accept((rs, RS), c0, c1, c2):\n  yg ← init(\"yrgourd.v1\")                  // Initialize a protocol.\n  yg ← mix(yg, \"rs\", RS)                   // Mix in the responder's static public key.\n\n  rs_ss ← ml_kem::decap(rs, c0)            // Decapsulate the ML-KEM_768 ciphertext.\n  yg ← mix(yg, \"rs-ct\", c0)                // Mix in the ML-KEM ciphertext.\n  yg ← mix(yg, \"rs-ss\", rs_ss)             // Mix in the ML-KEM shared secret.\n  (yg, IE) ← decrypt(yg, \"ia\", c1)         // Decrypt the initiator's static public key.\n  (yg, IS) ← open(yg, \"ie\", c2)            // Open the initiator's ephemeral public key.\n\n  (is_ct, is_ss) ← ml_kem::encap(IS)       // Encapsulate a random key with ML-KEM-768.\n  (yg, c3) ← encrypt(yg, \"is-ct\", is_ct)   // Encrypt the ML-KEM ciphertext.\n  yg ← mix(yg, \"is-ss\", is_ss)             // Mix in the ML-KEM shared secret.\n\n  (ie_ct, ie_ss) ← ml_kem::encap(IE)       // Encapsulate a random key with ML-KEM-768.\n  (yg, c4) ← seal(yg, \"ie-ct\", ie_ct)      // Seal the ML-KEM ciphertext.\n  yg ← mix(yg, \"ie-ss\", ie_ss)             // Mix in the ML-KEM shared secret.\n\n  yg_recv ← mix(yg, \"sender\", \"responder\") // Fork the protocol into a (recv, send) pair.\n  yg_send ← mix(yg, \"sender\", \"initiator\")\n  return ((yg_recv, yg_send), c3, c4)\n```\n\nThe responder sends the ML-KEM-768 ciphertexts `c3` and `c4` to the initiator.\n\nThe initiator performs the following:\n\n```text\nfunction finalize(yg, is, ie, c3, c4):\n  (ig, is_ct) ← decrypt(yg, \"is-ct\", c3)   // Decrypt the ciphertext.\n  is_ss ← ml_kem::decap(is_ct)             // Decapsulate the shared secret.\n  yg ← mix(yg, \"is-ss\", is_ss)             // Mix in the ML-KEM shared secret.\n\n  (ig, ie_ct) ← open(yg, \"ie-ct\", c4)      // Open the ciphertext.\n  ie_ss ← ml_kem::decap(ie_ct)             // Decapsulate the shared secret.\n  yg ← mix(yg, \"ie-ss\", ie_ss)             // Mix in the ML-KEM shared secret.\n\n  yg_recv ← mix(yg, \"sender\", \"responder\") // Fork the protocol into a (recv, send) pair.\n  yg_send ← mix(yg, \"sender\", \"initiator\")\n  return (yg_recv, yg_send)\n```\n\nNow the initiator and responder each have two protocols: `yg_recv` for decrypting received packets,\nand `yg_send` for encrypting sent packets. The initiator discards their ephemeral keys and both\nparties discard their shared secrets. An adversary who recovers the initiator's private key will be\nunable to decapsulate the first shared secret (`rs_ss`) and thus unable to decrypt the rest of the\ndata. An adversary who recovers the responder's private key will be able to recover `rs_ss` but\nunable to decapsulate the next two shared secrets (`is_ss` and `ie_ss`). An adversary who recovers\nboth will be able to decapsulate both `rs_ss` and `is_ss` but unable to decapsulate `ie_ss`.\n\nTransport between the initiator and responder uses length-delimited frames with a 3-byte big-endian\nlength prepended to each packet. (The length does not include these 3 bytes.)\n\nTo send a frame, the sender would perform the following:\n\n```text\n(yg_send, len) ← encrypt(yg_send, \"len\", u24_le(|frame|))\n(yg_send, frame) ← seal(yg_send, \"frame\", frame)\n```\n\nTo receive a packet, the receiver would perform the following:\n\n```text\n(yg_recv, len) ← decrypt(yg_recv, \"len\", len)\n(yg_recv, frame) ← open(yg_recv, \"frame\", frame)\n```\n\nThe initiator's `yg_send` and the responder's `yg_recv` stay synchronized, likewise with the\ninitiator's `yg_recv` and the responder's `yg_send`.\n\nEach frame begins with a frame type. A frame which begins with a `1` contains only data. A frame\nwith a `2` contains an ML-KEM-768 ciphertext prepended to the data for ratcheting. To initiate a\nratchet, the transport sends a `2` frame and then performs the following:\n\n```text\nlet (ct, ss) ← ml_kem::encap(remote.pub)\nyg_send ← mix(yg_send, \"ratchet-ss\", ss)\n```\n\nThe receiver, upon decrypting a `2` frame performs the following:\n\n```text\nss ← ml_kem::decap(local.priv, ct)\nyg_recv ← mix(yg_recv, \"ratchet-ss\", ss)\n```\n\nRatchets are performed every two minutes, or on every frame if fewer than one frame is sent every\ntwo minutes.\n\n## Performance\n\nOn my M3 MacBook Pro:\n\n```text\nhandshake               time:   [261.38 µs 261.49 µs 261.62 µs]\n\ntransfer/1MiB           time:   [524.25 µs 525.74 µs 527.98 µs]\n                        thrpt:  [1.8496 GiB/s 1.8575 GiB/s 1.8628 GiB/s]\ntransfer/10MiB          time:   [2.2045 ms 2.2097 ms 2.2147 ms]\n                        thrpt:  [4.4094 GiB/s 4.4195 GiB/s 4.4298 GiB/s]\ntransfer/100MiB         time:   [17.670 ms 17.794 ms 17.975 ms]\n                        thrpt:  [5.4329 GiB/s 5.4882 GiB/s 5.5267 GiB/s]\ntransfer/1GiB           time:   [177.49 ms 178.14 ms 178.84 ms]\n                        thrpt:  [5.5917 GiB/s 5.6136 GiB/s 5.6343 GiB/s]\n```\n\n`handshake` measures the time it takes to establish a Yrgourd connection over a Tokio duplex stream;\n`transfer` measures the time it takes to transfer 100MiB via a Yrgourd connection over a Tokio\nduplex stream.\n\nSee the [Lockstitch][] documentation for specifics on compiler options for performance.\n\n## License\n\nCopyright © 2024-2025 Coda Hale\n\nDistributed under the Apache License 2.0 or MIT License.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcodahale%2Fyrgourd","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcodahale%2Fyrgourd","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcodahale%2Fyrgourd/lists"}