Threshold Cryptography
LP-6470
DraftLuxDA TFHE Sidecar Format
LuxDA TFHE Sidecar Format specification for LuxDA Bus
Abstract
This LP defines the TFHE (Threshold Fully Homomorphic Encryption) sidecar format for LuxDA Bus messages. TFHE sidecars enable encrypted computation and conditional reveal of data across namespaces.
Motivation
Private coordination requires:
- Encrypted State: Data encrypted under threshold FHE
- Homomorphic Operations: Compute on encrypted data
- Conditional Reveal: Decrypt when conditions met
- Epoch-Based Keys: Key rotation without data loss
Specification
1. Sidecar Structure
1.1 TFHE Sidecar
type TFHESidecar struct {
// Version
Version uint8
// Epoch for key binding
Epoch uint64
// Ciphertext type
CiphertextType TFHECiphertextType
// Encrypted data
Ciphertext []byte
// Binding to header
HeaderBinding []byte
// Proof of valid encryption (optional)
Proof []byte
}
type TFHECiphertextType uint8
const (
TFHEBool TFHECiphertextType = 1
TFHEUint4 TFHECiphertextType = 2
TFHEUint8 TFHECiphertextType = 3
TFHEUint16 TFHECiphertextType = 4
TFHEUint32 TFHECiphertextType = 5
TFHEUint64 TFHECiphertextType = 6
TFHEUint128 TFHECiphertextType = 7
TFHEUint160 TFHECiphertextType = 8 // Ethereum address
TFHEUint256 TFHECiphertextType = 9 // EVM word
TFHEBytes TFHECiphertextType = 10 // Variable length
)
1.2 Sidecar Reference in Header
From LP-6411, headers can reference TFHE sidecars:
type TFHESidecarRef struct {
SidecarCommitment [32]byte // SHA3-256 of sidecar
Epoch uint64
}
2. Encryption
2.1 Encrypt with Namespace Key
func EncryptTFHE(plaintext []byte, nsId [20]byte, epoch uint64, ctype TFHECiphertextType) (*TFHESidecar, error) {
// Get namespace public key for epoch
pk, err := keyRegistry.GetPublicKey(nsId, epoch)
if err != nil {
return nil, err
}
// Encrypt based on type
var ciphertext []byte
switch ctype {
case TFHEBool:
ciphertext = tfhe.EncryptBool(pk, plaintext[0] != 0)
case TFHEUint8:
ciphertext = tfhe.EncryptUint8(pk, plaintext[0])
case TFHEUint32:
val := binary.BigEndian.Uint32(plaintext)
ciphertext = tfhe.EncryptUint32(pk, val)
case TFHEUint64:
val := binary.BigEndian.Uint64(plaintext)
ciphertext = tfhe.EncryptUint64(pk, val)
case TFHEUint256:
ciphertext = tfhe.EncryptUint256(pk, plaintext)
case TFHEBytes:
ciphertext = tfhe.EncryptBytes(pk, plaintext)
default:
return nil, ErrUnsupportedType
}
return &TFHESidecar{
Version: 1,
Epoch: epoch,
CiphertextType: ctype,
Ciphertext: ciphertext,
}, nil
}
2.2 Header Binding
Bind sidecar to header to prevent mix-and-match:
func (s *TFHESidecar) BindToHeader(header *MsgHeader) {
s.HeaderBinding = sha3.Sum256(
header.NamespaceId[:],
uint64ToBytes(header.Seq),
header.BlobCommitment[:],
uint64ToBytes(s.Epoch),
)
}
func (s *TFHESidecar) VerifyBinding(header *MsgHeader) bool {
expected := sha3.Sum256(
header.NamespaceId[:],
uint64ToBytes(header.Seq),
header.BlobCommitment[:],
uint64ToBytes(s.Epoch),
)
return bytes.Equal(s.HeaderBinding, expected[:])
}
3. Wire Format
3.1 Sidecar Encoding
TFHESidecarV1 := {
version: uint8 [1 byte]
epoch: uint64 [8 bytes]
ciphertextType: uint8 [1 byte]
ciphertextLen: uint32 [4 bytes]
ciphertext: bytes [ciphertextLen bytes]
headerBinding: bytes32 [32 bytes]
proofLen: uint16 [2 bytes]
proof: bytes [proofLen bytes]
}
3.2 Ciphertext Size
| Type | Ciphertext Size |
|---|---|
| TFHEBool | ~32 KiB |
| TFHEUint8 | ~64 KiB |
| TFHEUint32 | ~256 KiB |
| TFHEUint64 | ~512 KiB |
| TFHEUint256 | ~2 MiB |
4. Compact Encoding
4.1 Batched Ciphertexts
For efficiency, batch multiple values:
type TFHEBatch struct {
Version uint8
Epoch uint64
Types []TFHECiphertextType
Ciphertexts [][]byte
BatchProof []byte // Single proof for all
}
func BatchEncrypt(values [][]byte, types []TFHECiphertextType, pk *PublicKey) (*TFHEBatch, error) {
ciphertexts := make([][]byte, len(values))
for i, val := range values {
ct, _ := EncryptSingle(val, types[i], pk)
ciphertexts[i] = ct
}
return &TFHEBatch{
Types: types,
Ciphertexts: ciphertexts,
}, nil
}
5. Validation
5.1 Sidecar Validation
func ValidateSidecar(sidecar *TFHESidecar, header *MsgHeader, keyRegistry *KeyRegistry) error {
// Check version
if sidecar.Version != 1 {
return ErrUnsupportedVersion
}
// Check epoch is valid
currentEpoch := keyRegistry.CurrentEpoch(header.NamespaceId)
if sidecar.Epoch > currentEpoch {
return ErrFutureEpoch
}
if currentEpoch - sidecar.Epoch > MaxEpochAge {
return ErrExpiredEpoch
}
// Verify header binding
if !sidecar.VerifyBinding(header) {
return ErrBindingMismatch
}
// Verify ciphertext structure
if err := tfhe.ValidateCiphertext(sidecar.Ciphertext, sidecar.CiphertextType); err != nil {
return err
}
// Verify proof (if present)
if len(sidecar.Proof) > 0 {
if err := tfhe.VerifyEncryptionProof(sidecar.Proof, sidecar.Ciphertext); err != nil {
return err
}
}
return nil
}
Rationale
Why Epoch-Based Keys?
- Enable key rotation
- Limit blast radius of compromise
- Support committee changes
Why Header Binding?
- Prevent replay attacks
- Prevent ciphertext substitution
- Ensure context integrity
Why Type System?
- Different operations for different types
- Optimize ciphertext size
- Enable type-safe computation
Security Considerations
Malleability
TFHE ciphertexts are malleable (by design for homomorphic ops). Header binding prevents unauthorized modification.
Side Channels
Ciphertext size reveals type information. Batching can help hide individual values.
Key Compromise
Epoch-based keys limit exposure. Forward secrecy via epoch rotation.
Test Plan
Unit Tests
- Encrypt/Decrypt: Round-trip for all types
- Binding: Verify binding checks
- Validation: Reject invalid sidecars
Integration Tests
- With Header: Sidecar referenced in header
- With Operations: Homomorphic computation
- Epoch Transition: Key rotation
References
LP-6470 v1.0.0 - 2026-01-02