bacap

package
v0.0.77 Latest Latest
Warning

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

Go to latest
Published: Apr 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)

NewEmptyMessageBoxIndexFromBytes returns a new MessageBoxIndex from a binary blob.

func NewMessageBoxIndex

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

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) 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 >= firstMessageBoxIndex

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) GetFirstMessageBoxIndex added in v0.0.69

func (u *ReadCap) GetFirstMessageBoxIndex() *MessageBoxIndex

GetFirstMessageBoxIndex returns a copy of the first message box index.

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) UnmarshalBinary added in v0.0.66

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

UnmarshalBinary populates our types fields from the given binary blob.

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) GetFirstMessageBoxIndex added in v0.0.69

func (o *WriteCap) GetFirstMessageBoxIndex() *MessageBoxIndex

GetFirstMessageBoxIndex returns a copy of the first message box index.

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) ReadCap added in v0.0.66

func (o *WriteCap) ReadCap() *ReadCap

ReadCap returns our ReadCap

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.

Jump to

Keyboard shortcuts

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