LuxDA Availability Certificates
LuxDA Availability Certificates specification for LuxDA Bus
Abstract
This LP defines the availability certificate system for LuxDA v1. In certificate mode, a committee of DA operators attests to blob availability, providing a trust-based but efficient availability guarantee with a clear upgrade path to DAS (LP-6433).
Motivation
Certificate-based DA provides:
- Simplicity: Single attestation round, no sampling protocol
- Speed: Fast availability confirmation (~500ms)
- Compatibility: Works with existing validator infrastructure
- Upgrade Path: Same API, swap to DAS later
The trade-off is trust in the operator committee rather than cryptoeconomic guarantees.
Specification
1. Operator Committee
1.1 Operator Registration
type DAOperator struct {
// Operator identity
OperatorID [32]byte
PublicKey []byte
SignatureKey []byte // For attestations
// Endpoints
RPCEndpoint string
P2PEndpoint string
// Stake
StakeAmount *big.Int
StakeExpiry uint64
// Capacity
StorageBytes uint64
BandwidthBps uint64
// Status
Active bool
RegisteredAt uint64
}
1.2 Committee Selection
type Committee struct {
Epoch uint64
Members []DAOperator
Threshold uint32 // Signatures needed for cert
TotalWeight uint64
}
func SelectCommittee(epoch uint64, operators []DAOperator) *Committee {
// Filter active operators
active := filter(operators, func(op DAOperator) bool {
return op.Active && op.StakeExpiry > epoch
})
// Sort by stake-weighted random selection
seed := sha3.Sum256(epoch || "committee")
sorted := stakeSortWithSeed(active, seed)
// Select top N
members := sorted[:min(len(sorted), TargetCommitteeSize)]
// Calculate threshold (2/3 by stake)
totalStake := sumStake(members)
threshold := calculateThreshold(members, totalStake * 2 / 3)
return &Committee{
Epoch: epoch,
Members: members,
Threshold: threshold,
TotalWeight: totalStake,
}
}
2. Certificate Structure
2.1 Availability Certificate
type AvailabilityCert struct {
// Blob identification
BlobCommitment [32]byte
BlobLen uint32
// Timing
Epoch uint64
Timestamp uint64
// Attestations
Signers []uint32 // Committee member indices
AggSignature []byte // Aggregated signature
SignerBitfield []byte // Bitfield of signers
// Erasure coding info
NumChunks uint32
ParityChunks uint32
ChunkSize uint32
// Committee reference
CommitteeRoot [32]byte // Merkle root of committee
}
2.2 Wire Format
AvailabilityCertV1 := {
version: uint8 [1 byte]
blobCommitment: bytes32 [32 bytes]
blobLen: uint32 [4 bytes]
epoch: uint64 [8 bytes]
timestamp: uint64 [8 bytes]
numSigners: uint16 [2 bytes]
signerBitfield: bytes [ceil(committeeSize/8) bytes]
aggSignatureLen: uint16 [2 bytes]
aggSignature: bytes [aggSignatureLen bytes]
numChunks: uint32 [4 bytes]
parityChunks: uint32 [4 bytes]
chunkSize: uint32 [4 bytes]
committeeRoot: bytes32 [32 bytes]
}
3. Dispersal Protocol
3.1 Dispersal Request
type DispersalRequest struct {
// Blob data
BlobCommitment [32]byte
Chunks [][]byte // Erasure-coded chunks
Proofs [][]byte // Chunk validity proofs (optional)
// Metadata
NamespaceId [20]byte
Epoch uint64
Timestamp uint64
// Authentication
DisperserSig []byte
}
3.2 Dispersal Flow
Disperser
│
┌──────────┼──────────┐
│ │ │
▼ ▼ ▼
Operator1 Operator2 Operator3
│ │ │
└──────────┼──────────┘
│
Attestations
│
┌──────────┼──────────┐
│ │ │
▼ ▼ ▼
Sign(chunk) Sign(chunk) Sign(chunk)
│ │ │
└──────────┼──────────┘
│
Aggregation
│
▼
AvailabilityCert
3.3 Operator Storage
func (op *Operator) HandleDispersalRequest(req *DispersalRequest) (*Attestation, error) {
// Verify request
if !op.VerifyDisperserSig(req) {
return nil, ErrInvalidSignature
}
// Identify my chunks
myChunks := op.SelectMyChunks(req.BlobCommitment, req.Chunks)
// Verify chunk proofs (if KZG)
for _, chunk := range myChunks {
if !op.VerifyChunkProof(chunk) {
return nil, ErrInvalidChunkProof
}
}
// Store chunks
for _, chunk := range myChunks {
op.Store.Put(req.BlobCommitment, chunk.Index, chunk.Data)
}
// Create attestation
attestation := &Attestation{
BlobCommitment: req.BlobCommitment,
OperatorID: op.ID,
ChunkIndices: extractIndices(myChunks),
Timestamp: time.Now().Unix(),
}
// Sign attestation
attestation.Signature = op.Sign(attestation.Hash())
return attestation, nil
}
4. Attestation Protocol
4.1 Attestation Structure
type Attestation struct {
BlobCommitment [32]byte
OperatorID [32]byte
ChunkIndices []uint32
Timestamp uint64
Signature []byte
}
func (a *Attestation) Hash() [32]byte {
return sha3.Sum256(
a.BlobCommitment[:],
a.OperatorID[:],
encodeUints(a.ChunkIndices),
uint64ToBytes(a.Timestamp),
)
}
4.2 Attestation Aggregation
func AggregateAttestations(attestations []*Attestation, committee *Committee) (*AvailabilityCert, error) {
// Verify quorum
totalWeight := uint64(0)
for _, att := range attestations {
member := committee.GetMember(att.OperatorID)
if member == nil {
continue
}
totalWeight += member.StakeAmount.Uint64()
}
if totalWeight < committee.TotalWeight * 2 / 3 {
return nil, ErrInsufficientQuorum
}
// Aggregate signatures (BLS)
sigs := make([][]byte, len(attestations))
for i, att := range attestations {
sigs[i] = att.Signature
}
aggSig := BLSAggregate(sigs)
// Build signer bitfield
bitfield := make([]byte, (len(committee.Members)+7)/8)
for _, att := range attestations {
idx := committee.GetMemberIndex(att.OperatorID)
bitfield[idx/8] |= 1 << (idx % 8)
}
return &AvailabilityCert{
BlobCommitment: attestations[0].BlobCommitment,
AggSignature: aggSig,
SignerBitfield: bitfield,
// ... other fields
}, nil
}
5. Certificate Verification
5.1 Verification Algorithm
func VerifyCert(cert *AvailabilityCert, committee *Committee) error {
// 1. Verify committee reference
if MerkleRoot(committee.Members) != cert.CommitteeRoot {
return ErrCommitteeMismatch
}
// 2. Extract signers from bitfield
signers := extractSigners(cert.SignerBitfield, committee)
// 3. Verify quorum
signerWeight := sumStake(signers)
if signerWeight < committee.TotalWeight * 2 / 3 {
return ErrInsufficientQuorum
}
// 4. Verify aggregate signature
message := CertMessage(cert.BlobCommitment, cert.Epoch, cert.Timestamp)
pubkeys := extractPubkeys(signers)
if !BLSVerify(pubkeys, message, cert.AggSignature) {
return ErrInvalidSignature
}
return nil
}
5.2 Light Client Verification
Light clients can verify with:
- Current committee root (from header chain)
- Certificate
- No need for full blob
func LightVerifyCert(cert *AvailabilityCert, committeeRoot [32]byte) bool {
// Trust the committee root from header chain
if cert.CommitteeRoot != committeeRoot {
return false
}
// Verify signature and quorum
// (requires committee member pubkeys - can be cached)
return VerifySignatureAndQuorum(cert)
}
6. Challenge Protocol
6.1 Availability Challenge
When a client cannot retrieve data:
type AvailabilityChallenge struct {
BlobCommitment [32]byte
CertRef [32]byte // Reference to certificate
Challenger Identity
Bond *big.Int
Deadline uint64
Status ChallengeStatus
}
type ChallengeStatus uint8
const (
ChallengePending ChallengeStatus = 0
ChallengeResponded ChallengeStatus = 1
ChallengeSuccess ChallengeStatus = 2
ChallengeFailed ChallengeStatus = 3
)
6.2 Challenge Response
Operators must respond with data or be slashed:
func (op *Operator) RespondToChallenge(challenge *AvailabilityChallenge) (*ChallengeResponse, error) {
// Retrieve chunks
chunks := op.Store.GetChunks(challenge.BlobCommitment)
// Create response with proofs
response := &ChallengeResponse{
ChallengeID: challenge.ID,
Chunks: chunks,
ChunkProofs: generateProofs(chunks, challenge.BlobCommitment),
OperatorID: op.ID,
Signature: op.Sign(challengeMessage(challenge)),
}
return response, nil
}
6.3 Slashing
func ProcessChallenge(challenge *AvailabilityChallenge, responses []*ChallengeResponse) SlashingResult {
// Check if enough responses to reconstruct
validChunks := collectValidChunks(responses)
if len(validChunks) >= DataChunkThreshold {
// Data is available - slash challenger
return SlashingResult{
SlashChallenger: true,
Amount: challenge.Bond,
}
}
// Identify non-responding operators
nonResponders := findNonResponders(challenge, responses)
return SlashingResult{
SlashOperators: nonResponders,
Amount: OperatorSlashAmount,
}
}
7. Committee Rotation
7.1 Epoch Transition
func RotateCommittee(currentEpoch uint64, operators []DAOperator) *Committee {
newEpoch := currentEpoch + 1
// Select new committee
newCommittee := SelectCommittee(newEpoch, operators)
// Handoff period: both committees active
handoffDuration := EpochDuration / 4
return newCommittee
}
7.2 Data Migration
During rotation, data must be available from both committees:
func MigrateData(oldCommittee, newCommittee *Committee, blobs []BlobCommitment) error {
for _, blob := range blobs {
// New operators fetch from old
for _, newOp := range newCommittee.Members {
if !oldCommittee.HasMember(newOp.OperatorID) {
chunks := fetchFromOldCommittee(blob, oldCommittee)
newOp.Store.Put(blob, chunks)
}
}
}
return nil
}
8. Metrics
| Metric | Description |
|---|---|
da_cert_created_total | Certificates created |
da_cert_verification_latency | Certificate verification time |
da_attestation_latency | Time to collect attestations |
da_quorum_ratio | Ratio of responding operators |
da_challenge_total | Challenges submitted |
da_challenge_success_rate | Successful challenges |
Rationale
Why BLS Aggregation?
- Constant-size signatures regardless of signer count
- Efficient verification
- Standard in blockchain systems
Why 2/3 Quorum?
- Matches BFT assumptions
- Tolerates 1/3 Byzantine operators
- Standard threshold for safety
Why Challenge Protocol?
- Creates accountability
- Enables slashing for dishonest operators
- Provides recourse for unavailability
Security Considerations
Committee Collusion
Risk: 2/3 of committee colludes to attest unavailable data
Mitigations:
- Large, diverse committee
- Stake slashing
- Challenge mechanism
- Eventual DAS upgrade
Attestation Replay
Risk: Replay old attestation for new blob
Mitigations:
- Blob commitment in attestation
- Epoch binding
- Timestamp freshness
Eclipse Attack on Disperser
Risk: Prevent disperser from reaching operators
Mitigations:
- Multiple dispersers
- Diverse network paths
- Timeout and retry
Test Plan
Unit Tests
- Attestation Signing: Correct signature generation
- Aggregation: Correct BLS aggregation
- Verification: Accept valid, reject invalid certs
Integration Tests
- Full Dispersal: End-to-end dispersal and retrieval
- Committee Rotation: Smooth handoff between epochs
- Challenge Resolution: Challenge and response flow
Adversarial Tests
- Byzantine Operators: 1/3 operators malicious
- Network Partition: Committee partially reachable
- Data Withholding: Operators attest but don't serve
References
LP-6431 v1.0.0 - 2026-01-02