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) MutateKDFState(ctx []byte) *MessageBoxIndex
- 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
- func (u *ReadCap) DeriveBoxID(messageBoxIndex *MessageBoxIndex) *ed25519.PublicKey
- func (u *ReadCap) GetMessageBoxIndex() *MessageBoxIndex
- func (u *ReadCap) MarshalBinary() ([]byte, error)
- func (u *ReadCap) MutateKDFState(ctx []byte) *ReadCap
- func (u *ReadCap) RootPublicKey() *ed25519.PublicKey
- func (u *ReadCap) UnmarshalBinary(data []byte) error
- func (u *ReadCap) WithMessageBoxIndex(idx *MessageBoxIndex) *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
- func (o *WriteCap) DeriveBoxID(messageBoxIndex *MessageBoxIndex) *ed25519.PublicKey
- func (o *WriteCap) GetMessageBoxIndex() *MessageBoxIndex
- func (o *WriteCap) MarshalBinary() ([]byte, error)
- func (o *WriteCap) MutateKDFState(ctx []byte) *WriteCap
- func (o *WriteCap) ReadCap() *ReadCap
- func (o *WriteCap) RootPrivateKey() *ed25519.PrivateKey
- func (o *WriteCap) UnmarshalBinary(data []byte) error
- func (o *WriteCap) WithMessageBoxIndex(idx *MessageBoxIndex) *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)
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
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
MarshalBinary returns a binary blob of the given type.
func (*ReadCap) MutateKDFState ¶ added in v0.0.84
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
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
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.
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) 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
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
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) 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
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.