{"id":47896202,"url":"https://github.com/thomas-vilte/mls-go","last_synced_at":"2026-04-04T03:46:17.418Z","repository":{"id":344030014,"uuid":"1173635334","full_name":"thomas-vilte/mls-go","owner":"thomas-vilte","description":"MLS Protocol (RFC 9420) implementation in Go. Secure group key exchange with forward secrecy and post-compromise security for E2EE messaging.","archived":false,"fork":false,"pushed_at":"2026-04-01T15:15:25.000Z","size":7872,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2026-04-04T03:46:15.419Z","etag":null,"topics":["cryptography","encryption","end-to-end-encryption","go","golang","messaging-layer-security","mls","rfc-9420","security"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/thomas-vilte.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-03-05T15:30:29.000Z","updated_at":"2026-04-01T15:15:30.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/thomas-vilte/mls-go","commit_stats":null,"previous_names":["thomas-vilte/mls-go"],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/thomas-vilte/mls-go","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thomas-vilte%2Fmls-go","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thomas-vilte%2Fmls-go/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thomas-vilte%2Fmls-go/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thomas-vilte%2Fmls-go/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/thomas-vilte","download_url":"https://codeload.github.com/thomas-vilte/mls-go/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thomas-vilte%2Fmls-go/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31387023,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-04T01:22:39.193Z","status":"online","status_checked_at":"2026-04-04T02:00:07.569Z","response_time":60,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"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":["cryptography","encryption","end-to-end-encryption","go","golang","messaging-layer-security","mls","rfc-9420","security"],"created_at":"2026-04-04T03:46:16.223Z","updated_at":"2026-04-04T03:46:17.413Z","avatar_url":"https://github.com/thomas-vilte.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# mls-go\n\n[![Go Reference](https://pkg.go.dev/badge/github.com/thomas-vilte/mls-go.svg)](https://pkg.go.dev/github.com/thomas-vilte/mls-go)\n[![Go Report Card](https://goreportcard.com/badge/github.com/thomas-vilte/mls-go)](https://goreportcard.com/report/github.com/thomas-vilte/mls-go)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\n\nPure Go implementation of Messaging Layer Security (MLS) per [RFC 9420](https://www.rfc-editor.org/rfc/rfc9420).\n\n**Current status:** `v1.0.0` — stable, interop-verified.\n\n## Overview\n\nmls-go is a native Go MLS library with no CGO dependency. It targets applications that need RFC 9420-compliant group key exchange: encrypted messaging, audio/video call encryption (DAVE protocol), collaborative tools, and any E2EE system that needs a standard group ratchet.\n\nMain packages:\n\n| Package           | Purpose                                                         |\n|-------------------|-----------------------------------------------------------------|\n| `mls` (root)      | High-level thread-safe `Client` API                             |\n| `group`           | Low-level group lifecycle, commits, proposals, Welcome          |\n| `keypackages`     | KeyPackage generation, validation, and lifetime options         |\n| `credentials`     | BasicCredential and X.509 credential support                    |\n| `ciphersuite`     | AEAD, HPKE, HKDF, signatures, hash references                   |\n| `extensions`      | Extension types (ExternalSenders, RequiredCapabilities, …)      |\n| `framing`         | MLSMessage, PublicMessage, PrivateMessage wire format           |\n| `schedule`        | Key schedule and MLS-Exporter (RFC 9420 §8)                     |\n| `secrettree`      | Per-sender secret tree ratchets                                 |\n| `treesync`        | Ratchet tree and TreeKEM                                        |\n| `storage`         | Pluggable storage interfaces + file, memory, encrypted backends |\n| `testing/mlstest` | Testing helpers for MLS scenarios                               |\n\n## What Works\n\n- RFC 9420 compliant group creation, Add/Update/Remove proposals, commits\n- Welcome creation and join-from-Welcome\n- External Join (join without Welcome via GroupInfo)\n- ReInit (group migration to new cipher suite or parameters)\n- PSK proposals and pre-shared key bootstrapping\n- Branch (group fork from existing state)\n- PrivateMessage protection for application data (with configurable padding)\n- PublicMessage handling for handshake messages\n- MLS-Exporter (`group.Export`) and EpochAuthenticator\n- External Senders extension (RFC 9420 §12.1.8.1)\n- Proposal revocation by ProposalRef (`Group.RevokeProposal`)\n- State serialization with SecretTree generation counters (no nonce reuse on restore)\n- Thread-safe `Client` with per-group mutex striping\n- Cipher suites:\n  - `MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519` (CS 1)\n  - `MLS_128_DHKEMP256_AES128GCM_SHA256_P256` (CS 2)\n  - `MLS_128_DHKEMX25519_CHACHA20POLY1305_SHA256_Ed25519` (CS 3)\n\n## Interoperability\n\nVerified by Docker-based test suite:\n\n| Target        | Suites  | Result                                                   |\n|---------------|---------|----------------------------------------------------------|\n| mls-go self   | 1, 2, 3 | 21/21 PASS                                               |\n| mlspp cross   | 1, 2, 3 | 21/21 PASS                                               |\n| OpenMLS cross | 1, 2, 3 | 12/12 PASS (subset supported by upstream interop client) |\n\nScenarios covered: `welcome_join`, `application`, `commit`, `external_join`, `external_proposals`, `reinit`, `branch`.\n\n## Quick Start\n\nThe recommended entry point is the `mls.Client` API.\n\n```go\npackage main\n\nimport (\n    \"context\"\n    \"fmt\"\n    \"log\"\n\n    mls \"github.com/thomas-vilte/mls-go\"\n    \"github.com/thomas-vilte/mls-go/ciphersuite\"\n)\n\nfunc main() {\n    ctx := context.Background()\n    cs := ciphersuite.MLS128DHKEMP256\n\n    alice, err := mls.NewClient([]byte(\"alice\"), cs)\n    if err != nil {\n        log.Fatal(err)\n    }\n    bob, err := mls.NewClient([]byte(\"bob\"), cs)\n    if err != nil {\n        log.Fatal(err)\n    }\n\n    bobKP, _ := bob.FreshKeyPackageBytes(ctx)\n    groupID, _ := alice.CreateGroup(ctx)\n    _, welcome, _ := alice.InviteMember(ctx, groupID, bobKP)\n    bob.JoinGroup(ctx, welcome)\n\n    ciphertext, _ := alice.SendMessage(ctx, groupID, []byte(\"hello\"))\n    msg, _ := bob.ReceiveMessage(ctx, groupID, ciphertext)\n    fmt.Println(string(msg.Plaintext)) // hello\n}\n```\n\n## Client API\n\n```go\n// Identity\nclient.Epoch(ctx, groupID)          // current epoch number\nclient.OwnLeafIndex(ctx, groupID)   // my position in the ratchet tree\nclient.ListMembers(ctx, groupID)    // active members with identity + signing key\n\n// Membership\nclient.CreateGroup(ctx)\nclient.InviteMember(ctx, groupID, memberKPBytes)        // → commit, welcome\nclient.JoinGroup(ctx, welcomeBytes)                     // → groupID\nclient.ExternalJoin(ctx, groupInfoBytes)                // → groupID, commit\nclient.RemoveMember(ctx, groupID, memberIdentity)       // → commit\nclient.LeaveGroup(ctx, groupID)                         // local-only state cleanup\n\n// Proposals (batch flow)\nclient.ProposeAddMember(ctx, groupID, memberKPBytes)    // → signed PublicMessage\nclient.ProposeRemoveMember(ctx, groupID, memberIdentity)\nclient.CommitPendingProposals(ctx, groupID)             // → commit, welcome\nclient.CancelPendingProposals(ctx, groupID)             // discard without committing\n\n// Maintenance\nclient.SelfUpdate(ctx, groupID)                         // rotate leaf encryption key\n\n// Messaging\nclient.SendMessage(ctx, groupID, plaintext)             // → ciphertext\nclient.SendMessageWithAAD(ctx, groupID, plaintext, aad)\nclient.ReceiveMessage(ctx, groupID, ciphertext)         // → ReceivedMessage\n\n// Crypto material\nclient.Export(ctx, groupID, label, context, length)     // MLS-Exporter\nclient.EpochAuthenticator(ctx, groupID)\nclient.GroupInfo(ctx, groupID)                          // signed GroupInfo bytes\n\n// Process incoming\nclient.ProcessCommit(ctx, groupID, commitBytes)\n```\n\n### Options\n\n```go\nmls.NewClient(identity, cs,\n    mls.WithStorage(groupStorage, keyStore),       // durable storage\n    mls.WithCredentialValidator(validator),         // allowlist / cert policy\n    mls.WithX509Credential(certDER, privKey),       // X.509 instead of Basic\n    mls.WithPaddingSize(32),                        // AEAD padding in bytes\n    mls.WithCacheStrategy(mls.CacheAlways),         // keep state in memory\n    mls.WithEventHandler(func(e mls.GroupEvent) {  // lifecycle callbacks\n        // EventMemberJoined, EventMemberRemoved, EventEpochAdvanced,\n        // EventMessageReceived, EventSelfUpdated\n    }),\n)\n```\n\n## KeyPackage Options\n\n```go\n// Default: now-1h / now+83d (interop-safe margin)\nkp, priv, err := keypackages.Generate(credWithKey, cs)\n\n// Custom window\nkp, priv, err := keypackages.Generate(credWithKey, cs,\n    keypackages.WithLifetime(notBefore, notAfter))\n\n// No expiry (not_before=0, not_after=2^64-1)\nkp, priv, err := keypackages.Generate(credWithKey, cs,\n    keypackages.InfiniteLifetime())\n```\n\n## Low-Level API\n\nFor advanced use cases (custom wire protocol, external commits, group inspection) use `group.Group` directly:\n\n```go\ng, err := group.NewGroupWithExtensions(groupID, cs, kp, kpPriv, extensions)\ng.Export(\"My App v1\", senderIDBytes, 16)         // derive sender key\ng.EpochAuthenticator()                           // authentication tag\ng.RevokeProposal(ref)                            // remove in-flight proposal\ng.MarshalState() / group.UnmarshalGroupState()   // persist / restore\n```\n\n## Storage\n\n```go\n// In-memory (tests / demos)\nstore := memorystore.NewStore()\n\n// File-backed (durable)\nstore, err := filestore.NewStore(\"/var/lib/myapp/mls\")\n\n// Encrypted file-backed (recommended for production)\nencStore, err := storage.NewEncryptedStore(store, encryptionKey)\n\nclient, err := mls.NewClient(identity, cs, mls.WithStorage(encStore, encStore))\n```\n\n## Build And Test\n\n```bash\ngo build ./...\ngo test ./...\ngo test -race ./...\ngolangci-lint run ./...\n```\n\n## Interop Tests\n\n```bash\n# Build the server image after local changes\ndocker compose -f docker/docker-compose.yml build mls-go\n\n# Self-interop (all suites in parallel, ~8 min)\n./docker/run-interop.sh self\n\n# Cross-interop against mlspp\n./docker/run-interop.sh cross\n\n# Cross-interop against OpenMLS\nCROSS_TARGET=openmls ./docker/run-interop.sh cross\n\n# Single suite\nSUITES=\"2\" ./docker/run-interop.sh self\n\n# Stress mode (includes deep_random, takes longer)\nRUN_STRESS=1 ./docker/run-interop.sh self\n```\n\n## Security\n\nSee [SECURITY.md](SECURITY.md) for deployment caveats, state encryption guidance, and known limitations.\n\n## Integration Guide\n\nSee [INTEGRATION.md](INTEGRATION.md) for storage patterns, delivery service architecture, and multi-device considerations.\n\n## Contributing\n\nSee [CONTRIBUTING.md](CONTRIBUTING.md). All code, comments, errors, tests, and docs must be in English.\n\n## License\n\nMIT. See [LICENSE](LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fthomas-vilte%2Fmls-go","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fthomas-vilte%2Fmls-go","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fthomas-vilte%2Fmls-go/lists"}