Documentation
¶
Overview ¶
Package adratchet implements an asynchronous double ratchet mechanism with Thyrse, X25519, and ML-KEM-768.
Hybrid ratchet steps alternate between the initiator and responder. A single root protocol absorbs each X25519 DH and ML-KEM shared secret and forks independent chain protocols; each chain in turn forks an independent protocol for every message.
Example ¶
package main
import (
"crypto/ecdh"
"crypto/mlkem"
"fmt"
"github.com/codahale/thyrse"
"github.com/codahale/thyrse/internal/testdata"
"github.com/codahale/thyrse/schemes/complex/adratchet"
)
func main() {
drbg := testdata.New("thyrse async double ratchet")
// Bea publishes initial X25519 and ML-KEM keys.
dB, qB := x25519KeyPair(drbg)
kB, _ := mlkem.NewDecapsulationKey768(drbg.Data(mlkem.SeedSize))
// Alice and Bea have a shared protocol state, probably thanks to an ECDH handshake.
p := thyrse.New("example")
p.Mix("shared key", []byte("ok then"))
// Alice initiates the ratchet with her first message.
a, msgA := adratchet.Initiate(
p.Clone(), qB, kB.EncapsulationKey(), []byte("this is my first message"),
)
// Bea receives the first message and establishes her ratchet state.
b, v, err := adratchet.Respond(p.Clone(), dB, kB, msgA)
if err != nil {
panic(err)
}
fmt.Printf("message from A: %q\n", v)
// Bea sends Alice a message.
msgB := b.SendMessage([]byte("no, this is _my_ first message"))
// Alice reads Bea's message.
v, err = a.ReceiveMessage(msgB)
if err != nil {
panic(err)
}
fmt.Printf("message from B: %q\n", v)
}
func x25519KeyPair(drbg *testdata.DRBG) (*ecdh.PrivateKey, *ecdh.PublicKey) {
private, err := ecdh.X25519().NewPrivateKey(drbg.Data(32))
if err != nil {
panic(err)
}
return private, private.PublicKey()
}
Output: message from A: "this is my first message" message from B: "no, this is _my_ first message"
Index ¶
Examples ¶
Constants ¶
const ( // MaxSkip is the maximum number of skipped message states retained across all chains, as well as the maximum gap // accepted in a single chain. MaxSkip = 1000 // Overhead is the number of bytes added to a message by State.SendMessage. Overhead = headerSize + thyrse.TagSize )
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type State ¶
type State struct {
// contains filtered or unexported fields
}
State maintains the state of an asynchronous double ratchet.
func Initiate ¶
func Initiate( p *thyrse.Protocol, remote *ecdh.PublicKey, remoteKEM *mlkem.EncapsulationKey768, plaintext []byte, ) (*State, []byte)
Initiate creates a double ratchet state for the initiating party and sends the first message using the responder's X25519 and ML-KEM public keys. The given root protocol is consumed by the returned state. Panics if the X25519 public key produces an invalid shared secret.
func Respond ¶
func Respond( p *thyrse.Protocol, local *ecdh.PrivateKey, localKEM *mlkem.DecapsulationKey768, ciphertext []byte, ) (*State, []byte, error)
Respond receives the initiator's first message using the responder's X25519 and ML-KEM private keys and creates a double ratchet state. The given root protocol is consumed by the returned state on success and remains unchanged on failure. Returns an error if the message is invalid.
func (*State) ReceiveMessage ¶
ReceiveMessage decrypts the given ciphertext and returns the plaintext. It handles out-of-order messages and performs ratchet steps as needed. State changes are committed only after the message authenticates successfully.
func (*State) SendMessage ¶
SendMessage encrypts the given plaintext and returns the ciphertext, which includes a header with the current ratchet state.