Help us shape the future of Go! Take the 2026 Go Developer Survey

neverlur

package module
v0.0.0-...-486effe Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 25, 2026 License: AGPL-3.0-or-later Imports: 44 Imported by: 0

README

Neverlur

Norwegian / Sami birch-bark shepherd's horn — the Nordic call across the mountains.

Neverlur is a friend-discovery service for metadata-private messaging: a post-quantum fork of Alpenhorn. It is the companion piece to gjallarhorn (post-quantum fork of Vuvuzela) — once two users have bootstrapped a shared secret through Neverlur, they can hold conversations through Gjallarhorn without ever revealing who they are talking to, even to a powerful nation-state adversary with future quantum capability.

This fork tracks the upstream Alpenhorn design (see the OSDI 2016 paper for the threat model and the friend-discovery primitive) and migrates the classical primitives toward hybrid X25519 + ML-KEM-768 for key agreement and hybrid Ed25519 + ML-DSA-65 for signed configuration chains and friend-request handshakes. The migration plan is tracked alongside the gjallarhorn migration plan; see the companion fork for the cross-system roadmap.

In short, Neverlur works well for bootstrapping conversations in Gjallarhorn. Users can start chatting without having to exchange keys in person or over some less secure channel.

Lineage

Neverlur is a fork of github.com/vuvuzela/alpenhorn, originally written by David Lazar with contributions from collaborators at MIT CSAIL. All upstream code retains its original copyright notices and the AGPL-3.0 license. See NOTICE for the complete lineage and attribution.

Acknowledgements

Upstream Alpenhorn is the work of David Lazar and collaborators at MIT CSAIL. This fork exists to evolve the friend-discovery layer with post-quantum primitives while preserving the original design, threat model, and license.

See also

  • gjallarhorn — paired post-quantum fork of Vuvuzela (mixnet conversations)
  • Alpenhorn — upstream
  • Vuvuzela — upstream conversation mixnet

Documentation

Overview

Package alpenhorn implements an Alpenhorn client.

Index

Constants

View Source
const IntentMax = 3

Intents are the dialing intents passed to Call.

View Source
const (

	// SizeIntroV2 is the fixed on-wire size of an introductionV2 in bytes
	// under the option-F static-ephemeral hybrid design (see
	// docs/wire-introduction-v2.md for the field-by-field layout and the
	// design discussion).
	//
	// Layout: 1 (Version) + 64 (Username) + 32 (DHPublicKey)
	//       + pqkem.CiphertextSize (1088)  // encap'd to recipient's LT ML-KEM pub
	//       + 4 (DialingRound) + 32 (LongTermKey)
	//       + pqsig.PublicKeySize (1952)
	//       + 64 (Signature) + pqsig.SignatureSize (3309)
	//       + 32 (ServerMultisig) + 2 (reserved padding)
	//       = 6580.
	//
	// Note: the SENDER's long-term ML-KEM-768 pub is NOT carried in the
	// intro; recipients look it up from the PKG-published identity
	// record. The MLKEMCiphertext below was encapsulated to the
	// RECIPIENT's long-term ML-KEM pub by the sender.
	SizeIntroV2 = 1 + 64 + 32 + pqkem.CiphertextSize + 4 + 32 + pqsig.PublicKeySize + 64 + pqsig.SignatureSize + 32 + 2
)

Variables

View Source
var ErrIntroV2Size = errors.New("neverlur: introduction v2 wrong size")

ErrIntroV2Size indicates a wrong-sized v2 introduction blob.

View Source
var ErrIntroV2Version = errors.New("neverlur: introduction is not v2")

ErrIntroV2Version indicates a v2 unmarshaler was handed bytes whose leading version byte was not 0x02.

View Source
var ErrTooLate = errors.New("too late")

Functions

This section is empty.

Types

type Client

type Client struct {
	Username           string
	LongTermPublicKey  ed25519.PublicKey
	LongTermPrivateKey ed25519.PrivateKey
	PKGLoginKey        ed25519.PrivateKey

	ConfigClient *config.Client

	Handler EventHandler

	// ClientPersistPath is where the client writes its state when it changes.
	// If empty, the client does not persist state.
	ClientPersistPath string

	// KeywheelPersistPath is the path where the client's keywheel is stored.
	// This field is not persisted along with the rest of the client's state,
	// so it must be set before calling Connect.
	//
	// The client state and keywheel are persisted in separate files for
	// forward secrecy. The client state is long-term and should be backed
	// up regularly. The keywheel is ephemeral and should not be backed up
	// (doing so hurts forward secrecy, and the keywheel can be recreated
	// from the client state).
	KeywheelPersistPath string
	// contains filtered or unexported fields
}

func LoadClient

func LoadClient(clientPersistPath, keywheelPersistPath string) (*Client, error)

LoadClient loads a client from persisted state at the given path. You should set the client's KeywheelPersistPath before connecting.

func (*Client) Bootstrap

func (c *Client) Bootstrap(addFriendConfig, dialingConfig *config.SignedConfig) error

func (*Client) CloseAddFriend

func (c *Client) CloseAddFriend() error

func (*Client) CloseDialing

func (c *Client) CloseDialing() error

func (*Client) ConnectAddFriend

func (c *Client) ConnectAddFriend() (chan error, error)

func (*Client) ConnectDialing

func (c *Client) ConnectDialing() (chan error, error)

func (*Client) GetFriend

func (c *Client) GetFriend(username string) *Friend

GetFriend returns the friend object for the given username, or nil if username is not in the client's address book.

func (*Client) GetFriends

func (c *Client) GetFriends() []*Friend

GetFriends returns all the friends in the client's address book.

func (*Client) GetIncomingFriendRequests

func (c *Client) GetIncomingFriendRequests() []*IncomingFriendRequest

func (*Client) GetOutgoingFriendRequests

func (c *Client) GetOutgoingFriendRequests() []*OutgoingFriendRequest

func (*Client) GetSentFriendRequests

func (c *Client) GetSentFriendRequests() []*OutgoingFriendRequest

func (*Client) PKGStatus

func (c *Client) PKGStatus() []PKGStatus

func (*Client) Persist

func (c *Client) Persist() error

Persist writes the client's state to disk. The client persists itself automatically, so Persist is only needed when creating a new client.

func (*Client) Register

func (c *Client) Register(server pkg.PublicServerConfig, token string) error

Register registers the username with the given PKG.

func (*Client) SendFriendRequest

func (c *Client) SendFriendRequest(username string, key ed25519.PublicKey) (*OutgoingFriendRequest, error)

SendFriendRequest sends a friend request to the given username using Alpenhorn's add-friend protocol. The key is optional and specifies the username's long-term public key if it is known ahead of time.

The friend request is not sent right away but queued for an upcoming add-friend round. The resulting OutgoingFriendRequest is the queued friend request.

type EventHandler

type EventHandler interface {
	// Error is called when the Alpenhorn client experiences an error.
	Error(error)

	// ConfirmedFriend is called when the add-friend protocol is completed
	// between two friends, resulting in a new Friend object.
	ConfirmedFriend(*Friend)

	// SentFriendRequest is called when an OutgoingFriendRequest is sent
	// to the entry server.
	SentFriendRequest(*OutgoingFriendRequest)

	// ReceivedFriendRequest is called when the client receives a friend request.
	// The application should eventually call .Approve() or .Remove() on the
	// IncomingFriendRequest.
	ReceivedFriendRequest(*IncomingFriendRequest)

	// UnexpectedSigningKey is called when an incoming friend request corresponds
	// to a friend request the user sent but has a different long term key than
	// what the user specified.
	UnexpectedSigningKey(*IncomingFriendRequest, *OutgoingFriendRequest)

	// SendingCall is called when an OutgoingCall is about to be sent to the
	// entry server. The application can finalize the call to get its session key.
	SendingCall(*OutgoingCall)

	// ReceivedCall is called when the client receives a call from a friend.
	ReceivedCall(*IncomingCall)

	// NewConfig is called when the configuration for the add-friend or dialing
	// protocol changes. The chain starts with the new config and ends with the
	// client's previous config.
	NewConfig(chain []*config.SignedConfig)
}

An EventHandler specifies how an application should react to events in the Alpenhorn client.

type Friend

type Friend struct {
	Username    string
	LongTermKey ed25519.PublicKey
	// contains filtered or unexported fields
}

Friend is an entry in the client's address book.

func (*Friend) Call

func (f *Friend) Call(intent int) *OutgoingCall

Call is used to call a friend using Alpenhorn's dialing protocol. Call does not send the call right away but queues the call for an upcoming dialing round. The resulting OutgoingCall is the queued call object. Call does nothing and returns nil if the friend is not in the client's address book.

func (*Friend) ExtraData

func (f *Friend) ExtraData() []byte

ExtraData returns a copy of the extra data field for the friend.

func (*Friend) Remove

func (f *Friend) Remove() error

Remove removes the friend from the client's address book.

func (*Friend) SessionKey

func (f *Friend) SessionKey(round uint32) *[32]byte

SessionKey returns the shared key at the given round. This should only be used for debugging.

func (*Friend) SetExtraData

func (f *Friend) SetExtraData(data []byte) error

SetExtraData overwrites the friend's extra data field with the given data. The extra data field is useful for application-specific data about the friend, such as additional contact info, notes, or a photo.

Applications should use the extra data field to store information about friends instead of maintaining a separate friend list because the Alpenhorn client will (eventually) ensure that the size of the persisted data on disk does not leak metadata.

func (*Friend) UnsafeKeywheelState

func (f *Friend) UnsafeKeywheelState() (uint32, *[32]byte)

UnsafeKeywheelState exposes the internal keywheel state for this friend. This should only be used for debugging.

type IncomingCall

type IncomingCall struct {
	Username string
	Intent   int
	// Round is the dialing round at which this call was received.
	// Conversation peers use it as the keywheel seed-round when
	// rolling per-round keys for subsequent conversation rounds.
	Round      uint32
	SessionKey *[32]byte
}

type IncomingFriendRequest

type IncomingFriendRequest struct {
	Username    string
	LongTermKey ed25519.PublicKey
	DHPublicKey *[32]byte
	DialRound   uint32
	Verifiers   []pkg.PublicServerConfig

	// LongTermKeyPQ is the recipient's ML-DSA-65 long-term identity half
	// (the post-quantum sibling of LongTermKey). Populated once the wire
	// format moves to introductionV2 in the US1+US2 joint commit; nil
	// during the foundational v1 wire path.
	LongTermKeyPQ []byte `json:"-"`

	// MLKEMCiphertext, when non-nil, carries the ML-KEM-768 ciphertext
	// the recipient produced by encapsulating to the initiator's ML-KEM
	// public key (read out of the v2 introduction). The initiator
	// decapsulates against its sentFriendRequest.MLKEMPrivateKey to
	// recover the PQ half of the hybrid keywheel seed.
	//
	// Dormant in v1 wire mode. Not yet wired into easyjson.
	MLKEMCiphertext []byte `json:"-"`
	// contains filtered or unexported fields
}

func (*IncomingFriendRequest) Approve

Approve accepts the friend request and queues a confirmation friend request. The add-friend protocol is complete for this friend when the confirmation request is sent. Approve assumes that the friend request has not been previously rejected.

func (IncomingFriendRequest) MarshalEasyJSON

func (v IncomingFriendRequest) MarshalEasyJSON(w *jwriter.Writer)

MarshalEasyJSON supports easyjson.Marshaler interface

func (IncomingFriendRequest) MarshalJSON

func (v IncomingFriendRequest) MarshalJSON() ([]byte, error)

MarshalJSON supports json.Marshaler interface

func (*IncomingFriendRequest) Reject

func (r *IncomingFriendRequest) Reject() error

Reject rejects the friend request, returning ErrTooLate if the friend request is not found in the client's queue.

func (*IncomingFriendRequest) UnmarshalEasyJSON

func (v *IncomingFriendRequest) UnmarshalEasyJSON(l *jlexer.Lexer)

UnmarshalEasyJSON supports easyjson.Unmarshaler interface

func (*IncomingFriendRequest) UnmarshalJSON

func (v *IncomingFriendRequest) UnmarshalJSON(data []byte) error

UnmarshalJSON supports json.Unmarshaler interface

type OutgoingCall

type OutgoingCall struct {
	Username string
	Created  time.Time
	// contains filtered or unexported fields
}

func (*OutgoingCall) Cancel

func (r *OutgoingCall) Cancel() error

Cancel removes the call from the outgoing queue, returning ErrTooLate if the call is not found in the queue.

func (*OutgoingCall) Intent

func (r *OutgoingCall) Intent() int

func (*OutgoingCall) Round

func (r *OutgoingCall) Round() uint32

Round returns the dialing round at which the call was sent, or 0 if the call has not been sent yet. Callers should pair this with SessionKey to seed a conversation-layer keywheel.

func (*OutgoingCall) Sent

func (r *OutgoingCall) Sent() bool

Sent returns true if the call has been sent and false otherwise.

func (*OutgoingCall) SessionKey

func (r *OutgoingCall) SessionKey() *[32]byte

SessionKey returns the session key established for this call, or nil if the call has not been sent yet.

func (*OutgoingCall) UpdateIntent

func (r *OutgoingCall) UpdateIntent(intent int) error

type OutgoingFriendRequest

type OutgoingFriendRequest struct {
	Username    string
	ExpectedKey ed25519.PublicKey

	// ExpectedIdentity, when non-nil, is the recipient's expected hybrid
	// identity (Ed25519 + ML-DSA-65). Once the wire format moves to
	// introductionV2 (US1+US2 joint commit), ExpectedIdentity takes
	// precedence over ExpectedKey. Until then ExpectedKey is authoritative
	// and ExpectedIdentity is recorded for the upgrade.
	//
	// Field is not yet wired into client_json.go (easyjson). Persisted
	// requests round-trip without the PQ half until easyjson is regenerated;
	// the omission is intentional during the foundational migration.
	ExpectedIdentity *hybrid.HybridIdentityPublic `json:"-"`

	// Confirmation indicates whether this request is in response to an
	// incoming friend request.
	Confirmation bool

	// DialRound is the round that the resulting shared key between friends
	// corresponds to. This field is only used when Confirmation is true.
	// Otherwise, the client uses the latest dialing round when the friend
	// request is sent.
	DialRound uint32
	// contains filtered or unexported fields
}

func (*OutgoingFriendRequest) Cancel

func (r *OutgoingFriendRequest) Cancel() error

Cancel cancels the friend request by removing it from the queue. It returns ErrTooLate if the request is not found in the queue.

func (OutgoingFriendRequest) MarshalEasyJSON

func (v OutgoingFriendRequest) MarshalEasyJSON(w *jwriter.Writer)

MarshalEasyJSON supports easyjson.Marshaler interface

func (OutgoingFriendRequest) MarshalJSON

func (v OutgoingFriendRequest) MarshalJSON() ([]byte, error)

MarshalJSON supports json.Marshaler interface

func (*OutgoingFriendRequest) UnmarshalEasyJSON

func (v *OutgoingFriendRequest) UnmarshalEasyJSON(l *jlexer.Lexer)

UnmarshalEasyJSON supports easyjson.Unmarshaler interface

func (*OutgoingFriendRequest) UnmarshalJSON

func (v *OutgoingFriendRequest) UnmarshalJSON(data []byte) error

UnmarshalJSON supports json.Unmarshaler interface

type PKGStatus

type PKGStatus struct {
	Server pkg.PublicServerConfig
	Error  error
}

Directories

Path Synopsis
Package addfriend provides functionality for Alpenhorn's add-friend protocol.
Package addfriend provides functionality for Alpenhorn's add-friend protocol.
Package bloom implements Bloom filters.
Package bloom implements Bloom filters.
Package cdn simulates a basic CDN server.
Package cdn simulates a basic CDN server.
cmd
neverlur-cdn command
neverlur-mixer command
neverlur-pkg command
Package coordinator implements the entry/coordinator server.
Package coordinator implements the entry/coordinator server.
Package dialing provides functionality for Alpenhorn's dialing protocol.
Package dialing provides functionality for Alpenhorn's dialing protocol.
Package edhttp is an HTTP client that connects to HTTP servers on edtls listeners.
Package edhttp is an HTTP client that connects to HTTP servers on edtls listeners.
Package edtls provides ed25519 signatures on top of TLS certificates.
Package edtls provides ed25519 signatures on top of TLS certificates.
encoding
toml
Package toml implements Tom's Obvious Minimal Language.
Package toml implements Tom's Obvious Minimal Language.
Package errors implements basic error handling.
Package errors implements basic error handling.
Package hybrid combines classical (X25519, Ed25519) and post-quantum (ML-KEM-768, ML-DSA-65) primitives into the hybrid constructions used across Neverlur.
Package hybrid combines classical (X25519, Ed25519) and post-quantum (ML-KEM-768, ML-DSA-65) primitives into the hybrid constructions used across Neverlur.
internal
pg
Package ioutil2 provides extra functionality along similar lines to io/ioutil.
Package ioutil2 provides extra functionality along similar lines to io/ioutil.
Package keywheel implements Alpenhorn's keywheel construction.
Package keywheel implements Alpenhorn's keywheel construction.
log
Package log provides structured logging.
Package log provides structured logging.
ansi
Package ansi implements ANSI escape codes for terminal colors.
Package ansi implements ANSI escape codes for terminal colors.
Package pkg implements a Private Key Generator (PKG) for Identity-Based Encryption (IBE).
Package pkg implements a Private Key Generator (PKG) for Identity-Based Encryption (IBE).
Package pqkem wraps CIRCL's ML-KEM-768 (FIPS 203) primitive with the minimal API that the Neverlur protocols use: generate, encapsulate, decapsulate, plus a deterministic-encapsulation entry point for known- answer testing.
Package pqkem wraps CIRCL's ML-KEM-768 (FIPS 203) primitive with the minimal API that the Neverlur protocols use: generate, encapsulate, decapsulate, plus a deterministic-encapsulation entry point for known- answer testing.
Package pqsig wraps CIRCL's ML-DSA-65 (FIPS 204) primitive and supplies the Ed25519-seed-to-ML-DSA-65 binding derivation used to give every existing classical Neverlur identity a deterministic post-quantum counterpart.
Package pqsig wraps CIRCL's ML-DSA-65 (FIPS 204) primitive and supplies the Ed25519-seed-to-ML-DSA-65 binding derivation used to give every existing classical Neverlur identity a deterministic post-quantum counterpart.
Package typesocket implements a websocket server and client.
Package typesocket implements a websocket server and client.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL