bacap

package
v0.0.85 Latest Latest
Warning

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

Go to latest
Published: Jul 15, 2026 License: AGPL-3.0 Imports: 12 Imported by: 6

Documentation

Overview

Package bacap provides the Blinded Cryptographic Capability system (BACAP).

BACAP is the Blinded Cryptographic Capability system with some resistance against quantum adversaries whose design is expounded upon in section 4 of our paper:

BACAP (Blinding-and-Capability scheme) allows us
to deterministically derive a sequence of key pairs using
blinding, built upon Ed25519, and suitable for un-
linkable messaging. It enables participants to derive box
IDs and corresponding encryption keys for independent,
single-use boxes using shared symmetric keys.

A box consists of an ID, a message payload, and a
signature over the payload. There are two basic capabili-
ties - one that lets a party derive the box IDs and decrypt
the messages, and one that additionally lets the holder
derive private keys to sign the messages. The signatures
are universally verifiable, as the box ID for each box
doubles as the public key for the signatures.

In the context of a messaging system, the protocol is
used by Alice to send an infinite sequence of messages
to Bob, one per box, with Bob using a separate, second
instance of the protocol to send messages to Alice.

Our paper

Echomix: a Strong Anonymity System with Messaging

https://arxiv.org/abs/2501.02933 https://arxiv.org/pdf/2501.02933

API Design

Two Capability types:

1. ReadCap: The Read Capability allows the bearer to generate an infinite sequence of verification and decryption keys for message boxes in a deterministic sequence.

2. WriteCap: The Write Capability allows the bearer to generate an infinite sequence of signing and encryption keys for messages boxes in a deterministic sequence.

Each of the above two capabilities are used with the MessageBoxIndex to perform their respective encrypt and sign vs verify and decrypt operations.

Beyond that we have two high-level types: StatefulReader and StatefulWriter, which encapsulate all the operational details of advancing state after message processing.

TODOs

This BACAP implementation could possibly be improved, here's a ticket for completing the TODO tasks written by its original author:

https://github.com/katzenpost/hpqc/issues/55

Index

Constants

View Source
const (
	// MessageBoxIndexSize is the size in bytes of one MessageBoxIndex struct.
	MessageBoxIndexSize = 8 + 32 + 32 + 32

	// BoxIDSize is the size in bytes of our Box IDs.
	BoxIDSize = ed25519.PublicKeySize

	// SignatureSize is the size in bytes of our signatures.
	SignatureSize = ed25519.SignatureSize
)

ReadCapSize is the size in bytes of the ReadCap struct type.

WriteCapSize is the size in bytes of a serialized BoxOwnerCap not counting it's rootPublicKey field.

Variables

This section is empty.

Functions

This section is empty.

Types

type MessageBoxIndex

type MessageBoxIndex struct {
	// i_{0..2^64}: the message counter / index
	Idx64 uint64

	// K_i: blinding value used to derive mailboxID by blinding ed25519 keys
	CurBlindingFactor [32]byte

	// E_i: for encryption message payloads
	CurEncryptionKey [32]byte

	// H_{i+1}, the HKDF key used to calculate MessageBoxIndex for Idx61 + 1
	HKDFState [32]byte // H_i, for computing the next mailbox
}

MessageBoxIndex type encapsulates all the various low level cryptographic operations such as progressing the HKDF hash object states, encryption/decryption of messages, signing and verifying messages.

func NewEmptyMessageBoxIndex added in v0.0.62

func NewEmptyMessageBoxIndex() *MessageBoxIndex

NewEmptyMessageBoxIndex returns an empty MessageBoxIndex which can be used with the UnmarshalBinary method.

func NewEmptyMessageBoxIndexFromBytes added in v0.0.66

func NewEmptyMessageBoxIndexFromBytes(b []byte) (*MessageBoxIndex, error)

func NewMessageBoxIndex

func NewMessageBoxIndex(rng io.Reader) (*MessageBoxIndex, error)

NewEmptyMessageBoxIndexFromBytes returns a new MessageBoxIndex from a binary blob. NewMessageBoxIndex returns a new MessageBoxIndex

func (*MessageBoxIndex) AdvanceIndexTo

func (m *MessageBoxIndex) AdvanceIndexTo(to uint64) (*MessageBoxIndex, error)

AdvanceIndexTo returns a MessageBoxIndex with it's state advanced to the specified index.

func (*MessageBoxIndex) BoxIDForContext

func (m *MessageBoxIndex) BoxIDForContext(cap *ReadCap, ctx []byte) *ed25519.PublicKey

BoxIDForContext returns a new box ID given a read cap and a cryptographic context.

func (*MessageBoxIndex) DecryptForContext

func (m *MessageBoxIndex) DecryptForContext(box [BoxIDSize]byte, ctx []byte, ciphertext []byte, sig []byte) (plaintext []byte, err error)

DecryptForContext decrypts the given ciphertext and verifies the given signature using a key derived from the context and other cryptographic materials. For tombstones (empty ciphertext), it only verifies the signature and returns empty plaintext without attempting decryption.

func (*MessageBoxIndex) DeriveMessageBoxID

func (m *MessageBoxIndex) DeriveMessageBoxID(rootPublicKey *ed25519.PublicKey) *ed25519.PublicKey

DeriveMessageBoxID derives the blinded public key, the mailbox ID, given the root public key.

func (*MessageBoxIndex) EncryptForContext

func (m *MessageBoxIndex) EncryptForContext(owner *WriteCap, ctx []byte, plaintext []byte) (mICtx [32]byte, cICtx []byte, sICtx []byte)

EncryptForContext encrypts the given plaintext. The given BoxOwnerCap type and context are used here in the encryption key derivation.

func (*MessageBoxIndex) MarshalBinary

func (m *MessageBoxIndex) MarshalBinary() ([]byte, error)

MarshalBinary returns a binary blob of the given type.

func (*MessageBoxIndex) MutateKDFState added in v0.0.84

func (m *MessageBoxIndex) MutateKDFState(ctx []byte) *MessageBoxIndex

MutateKDFState returns a copy of this MessageBoxIndex whose KDF ratchet state has been re-seeded by the given context. It mixes the current HKDFState with ctx through HKDF-BLAKE2b under a dedicated domain label, then re-derives the per-box encryption key and blinding factor from the result; Idx64 is preserved. The mutated index addresses a fresh sequence of boxes that cannot be found without ctx, while the root key (held by the cap) is untouched.

This is the BACAP primitive behind the Contact Voucher's VoucherSalt: the joiner mutates their WriteCap and the inductor mutates the paired ReadCap by the same ctx, so writer and readers land on the same mutated sequence. Unlike BoxIDForContext, which derives a single box ID without disturbing the ratchet, this advances the ratchet itself.

func (*MessageBoxIndex) NextIndex

func (m *MessageBoxIndex) NextIndex() (*MessageBoxIndex, error)

NextIndex returns a MessageBoxIndex type with it's state advanced to the next box.

func (*MessageBoxIndex) SignBox added in v0.0.55

func (m *MessageBoxIndex) SignBox(owner *WriteCap, ctx []byte, ciphertext []byte) (mICtx [32]byte, sICtx []byte)

SignCiphertextForContext signs the given ciphertext with the blinded private key. It also returns the appropriate blinded public key which can verify the signature. In our paper that's called the Box ID. This method is provided along VerifyCiphertextForContext such that you can use BACAP with an alternate encryption scheme. BACAP's default encryption scheme uses AES GCM SIV.

func (*MessageBoxIndex) UnmarshalBinary

func (m *MessageBoxIndex) UnmarshalBinary(data []byte) error

UnmarshalBinary populates the given MessageBoxIndex from the given serialized blob or it returns an error.

func (*MessageBoxIndex) VerifyBox added in v0.0.55

func (m *MessageBoxIndex) VerifyBox(box [BoxIDSize]byte, ciphertext []byte, sig []byte) (ok bool, err error)

VerifyCiphertextForContext veridies the given ciphertext using the given box ID which is a public key. This method is provided along SignCiphertextForContext above, so that you can use BACAP with an alternate encryption scheme. BACAP's default encryption scheme uses AES GCM SIV.

type ReadCap added in v0.0.66

type ReadCap struct {
	// contains filtered or unexported fields
}

ReadCap is a read capability can be used to compute BACAP boxes and decrypt their message payloads for indices >= messageBoxIndex

func NewEmptyReadCap added in v0.0.66

func NewEmptyReadCap() *ReadCap

func ReadCapFromBytes added in v0.0.66

func ReadCapFromBytes(data []byte) (*ReadCap, error)

ReadCapFromBytes deserialize the read cap from a blob or return an error.

func (*ReadCap) DeriveBoxID added in v0.0.71

func (u *ReadCap) DeriveBoxID(messageBoxIndex *MessageBoxIndex) *ed25519.PublicKey

DeriveBoxID derives the box ID for a given message box index.

func (*ReadCap) GetMessageBoxIndex added in v0.0.84

func (u *ReadCap) GetMessageBoxIndex() *MessageBoxIndex

GetMessageBoxIndex returns a copy of the cap's message box index (the index the cap currently points at, which is the first only for a freshly-minted cap).

func (*ReadCap) MarshalBinary added in v0.0.66

func (u *ReadCap) MarshalBinary() ([]byte, error)

MarshalBinary returns a binary blob of the given type.

func (*ReadCap) MutateKDFState added in v0.0.84

func (u *ReadCap) MutateKDFState(ctx []byte) *ReadCap

MutateKDFState returns a new ReadCap whose first MessageBoxIndex has had its KDF ratchet state re-seeded by ctx (see MessageBoxIndex.MutateKDFState), matching WriteCap.MutateKDFState applied to the paired write cap with the same ctx. The root public key is shared with the receiver.

func (*ReadCap) RootPublicKey added in v0.0.84

func (u *ReadCap) RootPublicKey() *ed25519.PublicKey

RootPublicKey returns the read cap's root public key, against which a signature made by the corresponding WriteCap.RootPrivateKey verifies. It lets callers verify such a signature without knowing the read cap's serialized byte layout.

func (*ReadCap) UnmarshalBinary added in v0.0.66

func (u *ReadCap) UnmarshalBinary(data []byte) error

UnmarshalBinary populates our types fields from the given binary blob.

func (*ReadCap) WithMessageBoxIndex added in v0.0.84

func (u *ReadCap) WithMessageBoxIndex(idx *MessageBoxIndex) *ReadCap

WithMessageBoxIndex returns a copy of the read cap whose embedded message box index is a copy of idx, leaving the receiver unchanged. Re-basing a read cap to the current position before sharing it means the recipient starts there and cannot iterate the one-way ratchet back to count earlier messages.

type StatefulReader

type StatefulReader struct {
	Rcap          *ReadCap
	LastInboxRead *MessageBoxIndex
	NextIndex     *MessageBoxIndex
	Ctx           []byte
	// contains filtered or unexported fields
}

StatefulReader is a helper type with mutable state for sequential reading

func NewStatefulReader

func NewStatefulReader(urcap *ReadCap, ctx []byte) (*StatefulReader, error)

NewStatefulReader initializes a StatefulReader for the given ReadCap and context.

func NewStatefulReaderFromBytes added in v0.0.66

func NewStatefulReaderFromBytes(data []byte) (*StatefulReader, error)

NewStatefulReaderFromBytes initializes a StatefulReader from a CBOR blob.

func NewStatefulReaderWithIndex added in v0.0.66

func NewStatefulReaderWithIndex(urcap *ReadCap, ctx []byte, nextIndex *MessageBoxIndex) (*StatefulReader, error)

NewStatefulReaderWithIndex initializes a StatefulReader with a specific next index. Note: This creates a reader that starts from the given nextIndex, with LastInboxRead set to nil. This means the reader has no history of previously read messages, which is appropriate for scenarios where you're resuming from a known checkpoint or starting fresh from a specific index.

func (*StatefulReader) DecryptNext

func (sr *StatefulReader) DecryptNext(ctx []byte, box [BoxIDSize]byte, ciphertext []byte, sig [SignatureSize]byte) ([]byte, error)

ParseReply advances state if reading was successful.

func (*StatefulReader) GetCurrentMessageIndex added in v0.0.66

func (sr *StatefulReader) GetCurrentMessageIndex() *MessageBoxIndex

GetCurrentMessageIndex returns the current MessageBoxIndex. This is the index that will be used for the next read.

func (*StatefulReader) GetNextMessageIndex added in v0.0.66

func (sr *StatefulReader) GetNextMessageIndex() (*MessageBoxIndex, error)

GetNextMessageIndex returns what the next MessageBoxIndex will be after advancing state. This does NOT advance state - it's used for crash consistency planning.

func (*StatefulReader) Marshal added in v0.0.62

func (sr *StatefulReader) Marshal() ([]byte, error)

Marshal uses a CBOR blob to serialize into the StatefulReader.

func (*StatefulReader) NextBoxID

func (sr *StatefulReader) NextBoxID() (*[BoxIDSize]byte, error)

ReadNext gets the next box ID to read.

type StatefulWriter

type StatefulWriter struct {
	Wcap          *WriteCap
	LastOutboxIdx *MessageBoxIndex
	NextIndex     *MessageBoxIndex
	Ctx           []byte
	// contains filtered or unexported fields
}

StatefulWriter maintains sequential state for encrypting messages.

func NewStatefulWriter

func NewStatefulWriter(owner *WriteCap, ctx []byte) (*StatefulWriter, error)

NewStatefulWriter initializes a StatefulWriter for the given owner and context.

func NewStatefulWriterFromBytes added in v0.0.66

func NewStatefulWriterFromBytes(data []byte) (*StatefulWriter, error)

func NewStatefulWriterWithIndex added in v0.0.66

func NewStatefulWriterWithIndex(owner *WriteCap, ctx []byte, nextIndex *MessageBoxIndex) (*StatefulWriter, error)

NewStatefulWriterWithIndex initializes a StatefulWriter with a specific next index.

func (*StatefulWriter) AdvanceState added in v0.0.66

func (sw *StatefulWriter) AdvanceState() error

AdvanceState advances the writer state to the next index. This should be called only after courier acknowledgment.

func (*StatefulWriter) EncryptNext

func (sw *StatefulWriter) EncryptNext(plaintext []byte) (boxID [BoxIDSize]byte, ciphertext []byte, sig []byte, err error)

EncryptNext encrypts a message, advancing state after success. This is the original method that immediately advances state.

func (*StatefulWriter) GetCurrentMessageIndex added in v0.0.66

func (sw *StatefulWriter) GetCurrentMessageIndex() *MessageBoxIndex

GetCurrentMessageIndex returns the current MessageBoxIndex. This is the index that will be used for the next message.

func (*StatefulWriter) GetNextMessageIndex added in v0.0.66

func (sw *StatefulWriter) GetNextMessageIndex() (*MessageBoxIndex, error)

GetNextMessageIndex returns what the next MessageBoxIndex will be after advancing state. This does NOT advance state - it's used for crash consistency planning.

func (*StatefulWriter) Marshal added in v0.0.62

func (sw *StatefulWriter) Marshal() ([]byte, error)

Marshal uses a CBOR blob to serialize into the StatefulWriter.

func (*StatefulWriter) NextBoxID

func (sw *StatefulWriter) NextBoxID() (*ed25519.PublicKey, error)

NextBoxID returns the next mailbox ID for writing.

func (*StatefulWriter) PrepareNext added in v0.0.66

func (sw *StatefulWriter) PrepareNext(plaintext []byte) (boxID [BoxIDSize]byte, ciphertext []byte, sig []byte, err error)

PrepareNext encrypts a message WITHOUT advancing state. This allows for deferred state advancement until courier acknowledgment.

type WriteCap added in v0.0.66

type WriteCap struct {
	// contains filtered or unexported fields
}

WriteCap is used by the creator of the message box. It encapsulates private key material.

func NewEmptyWriteCap added in v0.0.66

func NewEmptyWriteCap() *WriteCap

NewEmptyWriteCap returns an empty WriteCap which is can be used with the UnmarshalBinary method.

func NewWriteCap added in v0.0.66

func NewWriteCap(rng io.Reader) (*WriteCap, error)

NewWriteCap creates a new BoxOwnerCap

func NewWriteCapFromBytes added in v0.0.66

func NewWriteCapFromBytes(data []byte) (*WriteCap, error)

NewWriteCapFromBytes deserializes a blob into a WriteCap type.

func (*WriteCap) DeriveBoxID added in v0.0.71

func (o *WriteCap) DeriveBoxID(messageBoxIndex *MessageBoxIndex) *ed25519.PublicKey

DeriveBoxID derives the box ID for a given message box index.

func (*WriteCap) GetMessageBoxIndex added in v0.0.84

func (o *WriteCap) GetMessageBoxIndex() *MessageBoxIndex

GetMessageBoxIndex returns a copy of the cap's message box index (the index the cap currently points at, which is the first only for a freshly-minted cap).

func (*WriteCap) MarshalBinary added in v0.0.66

func (o *WriteCap) MarshalBinary() ([]byte, error)

MarshalBinary returns a binary blob of the BoxOwnerCap type. Only serialize the rootPrivateKey. We do not serialize the rootPublicKey because it can be derived from the private key.

func (*WriteCap) MutateKDFState added in v0.0.84

func (o *WriteCap) MutateKDFState(ctx []byte) *WriteCap

MutateKDFState returns a new WriteCap whose first MessageBoxIndex has had its KDF ratchet state re-seeded by ctx (see MessageBoxIndex.MutateKDFState). The root key is shared with the receiver; only the index is mutated. Applying this with the same ctx as ReadCap.MutateKDFState on the paired read cap keeps writer and readers in lockstep.

func (*WriteCap) ReadCap added in v0.0.66

func (o *WriteCap) ReadCap() *ReadCap

ReadCap returns our ReadCap

func (*WriteCap) RootPrivateKey added in v0.0.84

func (o *WriteCap) RootPrivateKey() *ed25519.PrivateKey

RootPrivateKey returns the write cap's root signing key. A plain signature made with it verifies against the corresponding read cap's root public key (see ReadCap.RootPublicKey), so callers need not know the write cap's serialized byte layout to sign with the root key.

func (*WriteCap) UnmarshalBinary added in v0.0.66

func (o *WriteCap) UnmarshalBinary(data []byte) error

UnmarshalBinary deserializes a blob into the given type. Here we derive our public key from the given private key.

func (*WriteCap) WithMessageBoxIndex added in v0.0.84

func (o *WriteCap) WithMessageBoxIndex(idx *MessageBoxIndex) *WriteCap

WithMessageBoxIndex returns a copy of the write cap whose embedded message box index is a copy of idx, leaving the receiver unchanged. Use it to re-base a cap to a chosen position (e.g. the live edge) before handing it out, so the holder of the new cap learns nothing about indices before idx.

Jump to

Keyboard shortcuts

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