Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 8 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ For an overview of the protocol, see [protocol/protocol.md](protocol/protocol.md

## Overview

ADCNet enables participants to broadcast messages anonymously. Message sender identity remains hidden as long as one server is honest. The protocol uses an Invertible Bloom Filter (IBF) based auction system for fair and efficient message scheduling.
ADCNet enables participants to broadcast messages anonymously. Message sender identity remains hidden as long as one server is honest. The protocol uses an Invertible Bloom Lookup Table (IBLT) based auction system for fair and efficient message scheduling.

## Architecture

Expand All @@ -23,7 +23,7 @@ ADCNet consists of three main components operating in a round-based protocol:

Clients prepare messages for anonymous broadcast by:
- XOR-blinding messages with one-time pads derived from shared secrets with all servers
- Participating in auctions for message slots by encoding bids into IBF chunks
- Participating in auctions for message slots by encoding bids into IBLT chunks
- Encoding messages at auction-determined offsets if they won slots in previous rounds

### 2. Aggregators
Expand All @@ -40,14 +40,14 @@ Servers collaborate to reconstruct messages:
- Each server removes its XOR blinding factors from aggregated messages
- All servers must contribute their blinding vectors
- Combined unblinding recovers the original message vector
- The reconstructed IBF is inverted to determine next round's message scheduling
- The reconstructed IBLT is inverted to determine next round's message scheduling

## Key Features

- **XOR-Based Blinding**: Messages blinded with one-time pads from all server shared secrets
- **Anytrust Server Group**: Anonymity preserved as long as a single server is honest
- **Finite Field Arithmetic**: 384-bit field for auction IBF operations
- **IBF-based Scheduling**: Distributed auction mechanism using Invertible Bloom Filters
- **Finite Field Arithmetic**: 384-bit field for auction IBLT operations
- **IBLT-based Scheduling**: Distributed auction mechanism using Invertible Bloom Lookup Tables
- **Dynamic Message Sizing**: Variable-length messages allocated through auction weights
- **TEE Attestation**: Optional TDX attestation for service verification

Expand Down Expand Up @@ -276,7 +276,7 @@ Main protocol implementation including:

### `blind_auction`
Distributed auction mechanism featuring:
- `IBFVector`: Multi-level Invertible Bloom Filter implementation
- `IBLTVector`: Multi-level Invertible Bloom Lookup Table implementation
- `AuctionEngine`: Knapsack-based slot allocation

### `crypto`
Expand Down Expand Up @@ -309,10 +309,10 @@ Standalone CLI commands:

## Implementation Details

- **Field Order**: 384-bit prime field (48-byte IBF chunks)
- **Field Order**: 384-bit prime field (48-byte IBLT chunks)
- **Message Blinding**: XOR with PRF-derived one-time pads unique per server/round
- **Auction Blinding**: Field addition with server-specific blinding vectors
- **IBF Structure**: 4-level filter with 0.75 shrink factor between levels
- **IBLT Structure**: 4-level filter with 0.75 shrink factor between levels
- **Signatures**: Ed25519 for all protocol messages
- **Key Exchange**: ECDH P-256 for shared secret derivation

Expand Down
12 changes: 6 additions & 6 deletions blind-auction/auction.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,17 @@ type AuctionData struct {
Size uint32
}

// EncodeToChunk serializes auction data into a 48-byte chunk for IBF insertion.
func (a *AuctionData) EncodeToChunk() [IBFChunkSize]byte {
var res [IBFChunkSize]byte
// EncodeToChunk serializes auction data into a 48-byte chunk for IBLT insertion.
func (a *AuctionData) EncodeToChunk() [IBLTChunkSize]byte {
var res [IBLTChunkSize]byte
binary.BigEndian.PutUint32(res[0:4], a.Weight)
binary.BigEndian.PutUint32(res[4:8], a.Size)
copy(res[8:40], a.MessageHash[:])
return res
}

// AuctionDataFromChunk deserializes auction data from an IBF chunk.
func AuctionDataFromChunk(chunk [IBFChunkSize]byte) *AuctionData {
// AuctionDataFromChunk deserializes auction data from an IBLT chunk.
func AuctionDataFromChunk(chunk [IBLTChunkSize]byte) *AuctionData {
var res AuctionData

copy(res.MessageHash[:], chunk[8:40])
Expand All @@ -52,7 +52,7 @@ type AuctionWinner struct {
SlotSize uint32 // Allocated bandwidth in bytes
}

// AuctionEngine determines winning bids after IBF reconstruction.
// AuctionEngine determines winning bids after IBLT reconstruction.
// Uses dynamic programming to solve the knapsack problem for optimal bandwidth allocation.
type AuctionEngine struct {
totalBandwidth uint32
Expand Down
6 changes: 3 additions & 3 deletions blind-auction/doc.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
// Package blind_auction implements distributed auction scheduling for ADCNet
// using Invertible Bloom Filters (IBF).
// using Invertible Bloom Lookup Tables (IBLT).
//
// The auction mechanism enables fair bandwidth allocation without revealing
// individual bids until after aggregation. Clients encode their bids into
// IBF chunks that are secret-shared and aggregated alongside message data.
// After threshold reconstruction, the IBF is inverted to recover auction
// IBLT chunks that are secret-shared and aggregated alongside message data.
// After threshold reconstruction, the IBLT is inverted to recover auction
// entries and determine message scheduling for the next round.
package blind_auction
86 changes: 43 additions & 43 deletions blind-auction/ibf.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,40 +10,40 @@ import (
"github.com/flashbots/adcnet/crypto"
)

// IBFNChunks is the number of levels in the multi-level IBF structure.
// TODO: rename to IBFDepth
const IBFNChunks int = 4
// IBLTNChunks is the number of levels in the multi-level IBLT structure.
// TODO: rename to IBLTDepth
const IBLTNChunks int = 4

// IBFShrinkFactor is the size reduction factor between IBF levels.
const IBFShrinkFactor float64 = 0.75
// IBLTShrinkFactor is the size reduction factor between IBLT levels.
const IBLTShrinkFactor float64 = 0.75

// IBFChunkSize is the byte size of each IBF element (384 bits).
const IBFChunkSize uint32 = 48
// IBLTChunkSize is the byte size of each IBLT element (384 bits).
const IBLTChunkSize uint32 = 48

// IBFVectorLength calculates the total number of buckets across all IBF levels.
func IBFVectorLength(nBuckets uint32) int {
// IBLTVectorLength calculates the total number of buckets across all IBLT levels.
func IBLTVectorLength(nBuckets uint32) int {
n := 0
fac := 1.0
for i := 0; i < IBFNChunks; i++ {
for i := 0; i < IBLTNChunks; i++ {
n += int(float64(nBuckets) * fac)
fac *= IBFShrinkFactor
fac *= IBLTShrinkFactor
}
return n
}

// IBFVectorSize calculates the total byte size of an IBF vector.
func IBFVectorSize(nBuckets uint32) uint32 {
return uint32(IBFVectorLength(nBuckets)) * IBFChunkSize
// IBLTVectorSize calculates the total byte size of an IBLT vector.
func IBLTVectorSize(nBuckets uint32) uint32 {
return uint32(IBLTVectorLength(nBuckets)) * IBLTChunkSize
}

// IBFVector implements a multi-level Invertible Bloom Filter for auction scheduling.
type IBFVector struct {
Chunks [IBFNChunks][]big.Int
Counters [IBFNChunks][]uint64
// IBLTVector implements a multi-level Invertible Bloom Lookup Table for auction scheduling.
type IBLTVector struct {
Chunks [IBLTNChunks][]big.Int
Counters [IBLTNChunks][]uint64
}

// String returns a hex-encoded representation of the IBF state.
func (v *IBFVector) String() string {
// String returns a hex-encoded representation of the IBLT state.
func (v *IBLTVector) String() string {
res := ""
for level := range v.Chunks {
res += fmt.Sprintf("L%d: ", level)
Expand All @@ -57,45 +57,45 @@ func (v *IBFVector) String() string {
return res
}

// NewIBFVector creates an IBF sized for the expected number of messages.
func NewIBFVector(messageSlots uint32) *IBFVector {
res := &IBFVector{}
// NewIBLTVector creates an IBLT sized for the expected number of messages.
func NewIBLTVector(messageSlots uint32) *IBLTVector {
res := &IBLTVector{}

fac := 1.0
for level := range res.Chunks {
slotsInLevel := int(float64(messageSlots) * fac)
res.Chunks[level] = make([]big.Int, slotsInLevel)
res.Counters[level] = make([]uint64, slotsInLevel)
fac *= IBFShrinkFactor
fac *= IBLTShrinkFactor
}

return res
}

// ChunkToElement converts a chunk to a field element.
func ChunkToElement(data [IBFChunkSize]byte) *big.Int {
func ChunkToElement(data [IBLTChunkSize]byte) *big.Int {
return new(big.Int).SetBytes(data[:])
}

// ElementToChunk converts a field element back to a chunk, preserving leading zeros.
func ElementToChunk(el *big.Int) [IBFChunkSize]byte {
var data [IBFChunkSize]byte
func ElementToChunk(el *big.Int) [IBLTChunkSize]byte {
var data [IBLTChunkSize]byte
el.FillBytes(data[:])
return data
}

// InsertChunk adds a chunk to the IBF using field addition.
func (v *IBFVector) InsertChunk(msg [IBFChunkSize]byte) {
// InsertChunk adds a chunk to the IBLT using field addition.
func (v *IBLTVector) InsertChunk(msg [IBLTChunkSize]byte) {
msgAsEl := ChunkToElement(msg)
for level := 0; level < IBFNChunks; level++ {
for level := 0; level < IBLTNChunks; level++ {
index := ChunkIndex(msg, level, len(v.Chunks[level]))
crypto.FieldAddInplace(&v.Chunks[level][index], msgAsEl, crypto.AuctionFieldOrder)
v.Counters[level][index]++
}
}

// EncodeAsFieldElements serializes the IBF as field elements for blinding.
func (v *IBFVector) EncodeAsFieldElements() []*big.Int {
// EncodeAsFieldElements serializes the IBLT as field elements for blinding.
func (v *IBLTVector) EncodeAsFieldElements() []*big.Int {
res := []*big.Int{}
for level := range v.Chunks {
for chunk := range v.Chunks[level] {
Expand All @@ -111,8 +111,8 @@ func (v *IBFVector) EncodeAsFieldElements() []*big.Int {
return res
}

// DecodeFromElements reconstructs an IBF from field elements.
func (v *IBFVector) DecodeFromElements(elements []*big.Int) *IBFVector {
// DecodeFromElements reconstructs an IBLT from field elements.
func (v *IBLTVector) DecodeFromElements(elements []*big.Int) *IBLTVector {
index := uint32(0)
for level := range v.Chunks {
for chunk := range v.Chunks[level] {
Expand All @@ -131,24 +131,24 @@ func (v *IBFVector) DecodeFromElements(elements []*big.Int) *IBFVector {
return v
}

// ChunkIndex computes the bucket index for a chunk at a specific IBF level.
func ChunkIndex(chunk [IBFChunkSize]byte, level int, itemsInLevel int) uint64 {
// ChunkIndex computes the bucket index for a chunk at a specific IBLT level.
func ChunkIndex(chunk [IBLTChunkSize]byte, level int, itemsInLevel int) uint64 {
dataToHash := append([]byte(fmt.Sprintf("%d", level)), chunk[:]...)
innerIndexSeed := sha256.Sum256(dataToHash)
return uint64(binary.BigEndian.Uint64(innerIndexSeed[0:8])) % uint64(itemsInLevel)
}

// pureCell represents a cell that can be peeled during IBF recovery.
// pureCell represents a cell that can be peeled during IBLT recovery.
type pureCell struct {
level int
index int
}

// Recover extracts auction entries using queue-based peeling algorithm.
// This is O(n) where n is the number of entries, avoiding the O(n²) restart approach.
func (v *IBFVector) Recover() ([][IBFChunkSize]byte, error) {
func (v *IBLTVector) Recover() ([][IBLTChunkSize]byte, error) {
// Deep copy to avoid modifying original
working := &IBFVector{}
working := &IBLTVector{}
for level := range v.Chunks {
working.Chunks[level] = make([]big.Int, len(v.Chunks[level]))
working.Counters[level] = make([]uint64, len(v.Counters[level]))
Expand All @@ -158,7 +158,7 @@ func (v *IBFVector) Recover() ([][IBFChunkSize]byte, error) {
}
}

recovered := make([][IBFChunkSize]byte, 0)
recovered := make([][IBLTChunkSize]byte, 0)

// Initialize queue with all pure cells (counter == 1)
queue := make([]pureCell, 0)
Expand Down Expand Up @@ -192,7 +192,7 @@ func (v *IBFVector) Recover() ([][IBFChunkSize]byte, error) {
innerIndex := ChunkIndex(chunk, innerLevel, len(working.Chunks[innerLevel]))

if working.Counters[innerLevel][innerIndex] == 0 {
return nil, errors.New("unexpected zero counter while recovering IBF")
return nil, errors.New("unexpected zero counter while recovering IBLT")
}

crypto.FieldSubInplace(&working.Chunks[innerLevel][innerIndex], chunkEl, crypto.AuctionFieldOrder)
Expand All @@ -208,8 +208,8 @@ func (v *IBFVector) Recover() ([][IBFChunkSize]byte, error) {
return recovered, nil
}

// Bytes serializes the IBF to a byte slice.
func (v *IBFVector) Bytes() []byte {
// Bytes serializes the IBLT to a byte slice.
func (v *IBLTVector) Bytes() []byte {
res := binary.BigEndian.AppendUint32([]byte{}, uint32(len(v.Chunks)))
res = binary.BigEndian.AppendUint32(res, uint32(len(v.Chunks[0])))
for level := range v.Chunks {
Expand Down
Loading
Loading