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:
Index ¶
- Constants
- type MessageBoxIndex
- func (m *MessageBoxIndex) AdvanceIndexTo(to uint64) (*MessageBoxIndex, error)
- func (m *MessageBoxIndex) BoxIDForContext(cap *ReadCap, ctx []byte) *ed25519.PublicKey
- func (m *MessageBoxIndex) DecryptForContext(box [BoxIDSize]byte, ctx []byte, ciphertext []byte, sig []byte) (plaintext []byte, err error)
- func (m *MessageBoxIndex) DeriveMessageBoxID(rootPublicKey *ed25519.PublicKey) *ed25519.PublicKey
- func (m *MessageBoxIndex) EncryptForContext(owner *WriteCap, ctx []byte, plaintext []byte) (mICtx [32]byte, cICtx []byte, sICtx []byte)
- func (m *MessageBoxIndex) MarshalBinary() ([]byte, error)
- func (m *MessageBoxIndex) NextIndex() (*MessageBoxIndex, error)
- func (m *MessageBoxIndex) SignBox(owner *WriteCap, ctx []byte, ciphertext []byte) (mICtx [32]byte, sICtx []byte)
- func (m *MessageBoxIndex) UnmarshalBinary(data []byte) error
- func (m *MessageBoxIndex) VerifyBox(box [BoxIDSize]byte, ciphertext []byte, sig []byte) (ok bool, err error)
- type ReadCap
- type StatefulReader
- func (sr *StatefulReader) DecryptNext(ctx []byte, box [BoxIDSize]byte, ciphertext []byte, sig [SignatureSize]byte) ([]byte, error)
- func (sr *StatefulReader) GetCurrentMessageIndex() *MessageBoxIndex
- func (sr *StatefulReader) GetNextMessageIndex() (*MessageBoxIndex, error)
- func (sr *StatefulReader) Marshal() ([]byte, error)
- func (sr *StatefulReader) NextBoxID() (*[BoxIDSize]byte, error)
- type StatefulWriter
- func (sw *StatefulWriter) AdvanceState() error
- func (sw *StatefulWriter) EncryptNext(plaintext []byte) (boxID [BoxIDSize]byte, ciphertext []byte, sig []byte, err error)
- func (sw *StatefulWriter) GetCurrentMessageIndex() *MessageBoxIndex
- func (sw *StatefulWriter) GetNextMessageIndex() (*MessageBoxIndex, error)
- func (sw *StatefulWriter) Marshal() ([]byte, error)
- func (sw *StatefulWriter) NextBoxID() (*ed25519.PublicKey, error)
- func (sw *StatefulWriter) PrepareNext(plaintext []byte) (boxID [BoxIDSize]byte, ciphertext []byte, sig []byte, err error)
- type WriteCap
Constants ¶
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 )
const ReadCapSize = ed25519.PublicKeySize + MessageBoxIndexSize
ReadCapSize is the size in bytes of the ReadCap struct type.
const WriteCapSize = ed25519.PrivateKeySize + MessageBoxIndexSize
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
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
MarshalBinary returns a binary blob of the given type.
func (*ReadCap) UnmarshalBinary ¶ added in v0.0.66
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.
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
NewWriteCap creates a new BoxOwnerCap
func NewWriteCapFromBytes ¶ added in v0.0.66
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
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) UnmarshalBinary ¶ added in v0.0.66
UnmarshalBinary deserializes a blob into the given type. Here we derive our public key from the given private key.