Skip to content

Federated Learning Gradient Attestation

**AI · Federated learning · Multi-party cryptographic attestation**

Overview

Federated Learning Gradient Attestation

AI · Federated learning · Multi-party cryptographic attestation

The problem

Multiple organizations want to train a shared machine-learning model without pooling their raw data. Federated learning (FL) is the pattern:

  • Each participant trains on their own local data.
  • Participants share model updates (gradients) with a coordinator.
  • Coordinator aggregates gradients, updates global model.
  • Process repeats until convergence.

Example: 20 banks collaborate on a fraud-detection model. Each bank’s transaction data is sensitive; FL lets them contribute without exposing individual records.

The trust problems:

  1. Who actually contributed what? The coordinator claims bank A submitted gradient update g_A. Bank A can deny it. Bank B can claim they submitted when they didn’t. Credit allocation for training contribution is contested.

  2. Was the contribution real? A bank claiming it trained on 10M transactions but actually only used 10k could be freeriding.

  3. Coordinator trust. Participants trust the coordinator to aggregate fairly. But who watches the coordinator? A compromised coordinator could bias the global model.

  4. Adversarial gradients. A participant could submit manipulated gradients designed to degrade or bias the model. Detection requires comparison against normal distributions.

  5. Privacy vs. auditability. Stronger crypto (homomorphic encryption, secure aggregation) hides individual gradients but then there’s no evidence of individual contribution.

Why Quidnug fits

Each gradient update is a signed event on the training round’s stream. Each participant signs their own gradient; the signature is the proof-of-contribution. The coordinator aggregates and signs the result. All observable, all auditable.

ProblemQuidnug primitive
”Did participant X really contribute?”Signed gradient.submitted event
”How much did they contribute?”Payload metadata + attestation metrics
”Was the aggregation fair?”Coordinator’s signed round.aggregated event
”Credit allocation for trained model”Event stream is the ledger of contributions
”Compromised coordinator?”Push gossip → participants see same state
”Re-create the round after dispute”Replay the event stream

High-level architecture

Training round as a quid
("fl-round-2026-04-18-fraud")
┌──────────────────────────┐
│ Round's event stream │
│ │
│ 1. round.opened │
│ 2. participant.registered │
│ 3. model.state.published │
│ 4. gradient.submitted (×N) │
│ 5. round.aggregated │
│ 6. model.state.updated │
│ 7. round.closed │
└──────────────────────────┘
┌────────────────┼────────────────┬────────────────┐
│ │ │ │
▼ ▼ ▼ ▼
bank-A quid bank-B quid bank-C quid coordinator quid
(participant) (participant) (participant) (coordinator)

Data model

Quids

  • Coordinator, runs the aggregation; has a quid.
  • Participant, each participating organization; has a quid with its own guardian set for key recovery.
  • Training round, each round of FL has its own quid. The round’s event stream is the canonical record.

Domain

ai.federated-learning.fraud-detection
ai.federated-learning.drug-discovery
ai.federated-learning.industrial-iot

Round lifecycle events

Event 1: round.opened
subjectId: fl-round-2026-04-18-fraud
payload:
roundNumber: 47
modelStateHash: <hash of starting weights>
deadline: 2026-04-18T18:00:00Z
eligibleParticipants: [bank-A, bank-B, bank-C, ...]
minParticipants: 5
signer: coordinator
Event 2: participant.registered (each participant)
payload:
participant: bank-A
attestedDataSize: 10000000
attestedDataHash: <hash of training set schema>
signer: bank-A
Event 3: model.state.published (coordinator)
payload:
modelWeightsCID: <IPFS CID of current weights>
schema: <hash>
signer: coordinator
Event 4a: gradient.submitted (bank-A)
payload:
participant: bank-A
gradientCID: <IPFS CID of encrypted gradient>
gradientHash: <sha256 of plaintext gradient for later reveal>
trainingDataSize: 10000000
trainingDuration: 3600
signatureOfLocalModel: <sig>
signer: bank-A
(Repeat for each participant)
Event 5: round.aggregated
payload:
participatingRoundMembers: [bank-A, bank-B, ...]
aggregateGradientCID: <IPFS CID>
aggregationMethod: "fedavg"
weightedByContribution: true
weightingSchemeHash: <hash>
signer: coordinator
Event 6: model.state.updated
payload:
newModelWeightsCID: <CID>
newStateHash: <hash>
roundNumber: 47
improvementMetric: 0.023
signer: coordinator
Event 7: round.closed
signer: coordinator

Per-participant credit tracking

At the round’s close, each participant’s contribution is provably recorded:

func (p *Participant) MyContributions(modelRef string) []Contribution {
rounds := p.GetRoundsForModel(modelRef)
contributions := []Contribution{}
for _, round := range rounds {
events := p.GetEvents(round.Quid, "QUID")
for _, ev := range events {
if ev.EventType == "gradient.submitted" &&
ev.Payload["participant"] == p.quid {
contributions = append(contributions, Contribution{
RoundID: round.Quid,
DataSize: ev.Payload["trainingDataSize"],
TrainingDuration: ev.Payload["trainingDuration"],
GradientHash: ev.Payload["gradientHash"],
})
}
}
}
return contributions
}

Commercial arrangement: revenue from the trained model is distributed pro-rata based on contribution metrics. The event stream is the arbitrable record.

Byzantine-robust aggregation

Coordinator must defend against participants submitting malicious gradients.

Strategies (application-layer, supported by Quidnug’s record-keeping):

  1. Outlier detection. Compare each participant’s gradient against the distribution. Extreme outliers flagged in gradient.flagged events.
  2. Trimmed aggregation. Coordinator discards top-K% outliers; records what was discarded in round.aggregated event.
  3. Reputation weighting. Participants with history of flagged gradients get lower weight in future rounds. Quidnug’s trust edges encode this.
coordinator ──0.9──► bank-A (consistently good gradients)
coordinator ──0.3──► bank-outlier-y (often flagged; deprioritized)

Coordinator accountability

Participants verify the coordinator is playing fair:

  1. All participants see the same event stream (push gossip).
  2. Each participant can re-run the aggregation locally using the submitted gradients (if decrypted under the consortium’s agreed protocol) and verify the coordinator’s claimed result.
  3. If the coordinator’s aggregate doesn’t match participants’ independent re-aggregation: round.disputed event. Other participants can vote to switch coordinators in the next round.

Privacy-preserving variant

For stronger privacy (gradients never revealed to peers or coordinator), layer homomorphic encryption or secure aggregation on top. Quidnug still records:

  • Who participated (signed registration).
  • Each participant’s ciphertext submission hash.
  • Coordinator’s aggregate result.

The contribution metadata (size, duration) stays on-chain; the raw gradient stays encrypted. Best-of-both: accountability

  • privacy.

Key Quidnug features

  • Event streams, round = subject, contributions = events.
  • Signed events, cryptographic proof of contribution.
  • Push gossip (QDP-0005), all participants see the same round state within seconds.
  • Relational trust, weighting bad-gradient contributors lower.
  • Guardian recovery, participant HSM loss doesn’t orphan their history.
  • Domain hierarchy, scope federated-learning rounds to specific topics.
  • K-of-K bootstrap, new participants onboard by fetching consensus state from existing participants.

Value delivered

DimensionBeforeWith Quidnug
Contribution proofCoordinator’s wordSigned events, cryptographic
Revenue sharing disputesContractual + auditsDeterministic replay of chain
Byzantine gradient detectionCoordinator-internalFlagged events visible to all
Coordinator accountabilityTrust the operatorPeers re-verify aggregation
Cross-round reputationNot trackedTrust edges evolve over time
PrivacyEither all-or-nothingMetadata on-chain, gradients encrypted
Dispute resolutionArbitration ± logsCryptographic event chain replay

What’s in this folder

Runnable POC

Full end-to-end demo at examples/federated-learning-attestation/:

  • fl_audit.py, pure audit logic: registration / submission matching, suspicious-gradient flagging by median-norm ratio, fair-weights computation for coordinator-bias detection.
  • fl_audit_test.py, 11 pytest cases.
  • demo.py, four rounds showing valid / insufficient / suspicious-gradient-flagged / strict-registration-violation.
Terminal window
cd examples/federated-learning-attestation
python demo.py

Implementation

Concrete API calls, pseudocode, signing shape.

Implementation: Federated Learning Attestation

1. Setup

Each participant runs a Quidnug node. Coordinator runs one too. All are on the same consortium domain, e.g. ai.federated-learning.fraud-detection.

Terminal window
# Each participant creates identity + guardian set
curl -X POST $NODE/api/identities -d '{
"quidId":"bank-a",
"name":"Bank A",
"homeDomain":"ai.federated-learning.fraud-detection",
"creator":"bank-a","updateNonce":1
}'
# ... + guardian set for key recovery
# Coordinator likewise
curl -X POST $NODE/api/identities -d '{
"quidId":"fl-coordinator",
"name":"FL Coordinator Acme",
"homeDomain":"ai.federated-learning.fraud-detection",
"creator":"fl-coordinator","updateNonce":1
}'

2. Open a round

Each training round is a new quid.

Terminal window
curl -X POST $NODE/api/identities -d '{
"quidId":"fl-round-2026-04-18-fraud-47",
"name":"FL Fraud Detection Round 47",
"creator":"fl-coordinator","updateNonce":1,
"attributes":{
"modelRef":"model-consortium-fraud-v1",
"roundNumber":47
}
}'
# Coordinator opens the round
curl -X POST $NODE/api/v1/events -d '{
"subjectId":"fl-round-2026-04-18-fraud-47",
"subjectType":"QUID",
"eventType":"round.opened",
"payload":{
"roundNumber":47,
"modelStateHash":"<sha256>",
"deadline":1713484800,
"eligibleParticipants":["bank-a","bank-b","bank-c","bank-d","bank-e"],
"minParticipants":4,
"startingWeightsCID":"bafy..."
},
"creator":"fl-coordinator","signature":"<sig>"
}'

3. Participants register + submit

Terminal window
# Bank A registers
curl -X POST $NODE/api/v1/events -d '{
"subjectId":"fl-round-2026-04-18-fraud-47",
"subjectType":"QUID",
"eventType":"participant.registered",
"payload":{
"participant":"bank-a",
"attestedDataSize":10000000,
"attestedDataSchemaHash":"<sha256>"
},
"creator":"bank-a","signature":"<sig>"
}'
# Bank A does local training (off-chain), then submits gradient
curl -X POST $NODE/api/v1/events -d '{
"subjectId":"fl-round-2026-04-18-fraud-47",
"subjectType":"QUID",
"eventType":"gradient.submitted",
"payload":{
"participant":"bank-a",
"gradientCID":"bafy... (encrypted gradient in IPFS)",
"gradientHash":"<sha256 of plaintext>",
"trainingDataSize":10000000,
"trainingDurationSec":3600,
"framework":"PyTorch",
"localModelHash":"<sha256 post-training>",
"dpNoiseScale":0.001
},
"creator":"bank-a","signature":"<sig>"
}'

4. Coordinator aggregates

func (c *Coordinator) RunRound(ctx context.Context, roundID string) error {
events, _ := c.GetEvents(ctx, roundID)
// Gather submitted gradients
submissions := []Submission{}
for _, ev := range events {
if ev.EventType == "gradient.submitted" {
submissions = append(submissions, parseSubmission(ev))
}
}
if len(submissions) < c.minParticipants {
return fmt.Errorf("round aborted: below min participants")
}
// Detect outliers (Byzantine robust)
flagged := c.DetectOutliers(submissions)
for _, f := range flagged {
c.SubmitEvent(ctx, roundID, "gradient.flagged", map[string]interface{}{
"participant": f.Participant,
"reason": f.Reason,
"deviation": f.Deviation,
})
}
// Aggregate non-flagged gradients
accepted := filterOut(submissions, flagged)
aggregate := c.FedAvg(accepted)
aggregateCID := c.UploadIPFS(aggregate)
// Emit aggregation result
return c.SubmitEvent(ctx, roundID, "round.aggregated", map[string]interface{}{
"participatingMembers": participantIDs(accepted),
"aggregateGradientCID": aggregateCID,
"aggregationMethod": "fedavg",
"weightingScheme": c.WeightingSchemeHash(),
"flaggedMembers": participantIDs(flagged),
})
}

5. Participants verify

func (p *Participant) VerifyRound(ctx context.Context, roundID string) error {
events, _ := p.GetEvents(ctx, roundID)
var aggEvent *Event
submissions := []Submission{}
for _, ev := range events {
switch ev.EventType {
case "round.aggregated":
aggEvent = ev
case "gradient.submitted":
submissions = append(submissions, parseSubmission(ev))
}
}
if aggEvent == nil {
return fmt.Errorf("no aggregation event")
}
// Participants that can decrypt (consortium key) can
// independently re-run the aggregation.
recomputed := p.FedAvg(filterAccepted(submissions, aggEvent))
claimedCID := aggEvent.Payload["aggregateGradientCID"].(string)
claimed := p.DownloadIPFS(claimedCID)
if !bytes.Equal(p.Hash(recomputed), p.Hash(claimed)) {
// Coordinator's aggregation differs from our computation.
// Emit a dispute event.
return p.SubmitEvent(ctx, roundID, "round.disputed", map[string]interface{}{
"participant": p.quid,
"claimedHash": sha256sum(claimed),
"recomputedHash": sha256sum(recomputed),
"reason": "aggregation-mismatch",
})
}
return nil
}

6. Trust-based reputation weighting

Over many rounds, coordinator builds trust edges:

Terminal window
# After Round 50, coordinator updates trust in bank-a
curl -X POST $NODE/api/trust -d '{
"truster":"fl-coordinator",
"trustee":"bank-a",
"trustLevel":0.92,
"domain":"ai.federated-learning.fraud-detection",
"description":"50 rounds, 2 flagged, high-quality gradients",
"validUntil":<now + 90d>
}'
# A participant with many flagged rounds gets lower trust
curl -X POST $NODE/api/trust -d '{
"truster":"fl-coordinator",
"trustee":"bank-outlier-y",
"trustLevel":0.3,
"description":"Frequent outliers; quality concerns"
}'

In the next round, coordinator’s aggregation weights gradients by trust.

7. Credit allocation report

func (c *Coordinator) CreditReport(modelRef string) CreditAllocation {
rounds := c.GetRoundsForModel(modelRef)
contributions := map[string]Contribution{}
for _, round := range rounds {
events, _ := c.GetEvents(round.Quid, "QUID")
for _, ev := range events {
if ev.EventType == "gradient.submitted" {
p := ev.Payload["participant"].(string)
ds := int64(ev.Payload["trainingDataSize"].(float64))
contributions[p] = contributions[p].Add(Contribution{
Rounds: 1,
DataSize: ds,
Duration: int64(ev.Payload["trainingDurationSec"].(float64)),
})
}
}
}
return normalizeAllocation(contributions)
}

Credit allocation drives revenue sharing when the final model is commercialized.

8. Testing

func TestFL_GradientAttestationSigned(t *testing.T) {
// Bank submits gradient; event stored
// Verify signature matches bank's current epoch key
}
func TestFL_ByzantineOutlierFlagged(t *testing.T) {
// 4 normal gradients, 1 adversarial
// Verify `gradient.flagged` emitted for the outlier
// Verify `round.aggregated` excludes the outlier
}
func TestFL_CoordinatorDisputeDetectable(t *testing.T) {
// Coordinator publishes bogus aggregation
// Participant verifies, emits round.disputed
}

Where to go next

Threat model

Adversaries, assumed capabilities, mitigations.

Threat Model: Federated Learning Attestation

Assets

  1. Gradient-contribution integrity, the record of what each participant submitted.
  2. Trained model quality, prevented from being poisoned by Byzantine gradients.
  3. Credit allocation fairness, revenue sharing based on contributions.
  4. Participant data privacy, raw data stays local.

Attackers

AttackerCapabilityGoal
Compromised participantValid key of a participantSubmit biased/adversarial gradients
Malicious coordinatorAggregation authorityBias model, misreport
Free-riderLegitimate participant, low effortClaim credit without training
ExternalNetwork observerInfer data from gradients

Threats

T1. Byzantine gradients

Attack. Participant submits crafted gradient to degrade global model. Mitigation. Outlier detection on coordinator side; gradient.flagged events documented in stream. Repeated flagging lowers participant’s trust and their effective weight in future rounds.

T2. Malicious coordinator bias

Attack. Coordinator publishes aggregation that favors certain participants. Mitigation. Every participant can independently re-run aggregation and emit round.disputed if results differ. Next round, participants may vote to replace coordinator.

T3. Free-rider

Attack. Participant submits minimal or zero-effort gradient to claim credit. Mitigation. Attested training metadata (data size, duration) + coordinator validation. Gradients of abnormally small effect detected as outliers.

T4. Participant replay

Attack. Participant re-submits a prior round’s gradient. Mitigation. Event is bound to the round’s quid; replay in a different round has wrong subjectId. Anchor nonce + event dedup.

T5. Gradient inversion attack

Attack. Observer with gradient access reconstructs original training data. Mitigation. Protocol doesn’t prevent this; participants should apply differential privacy noise or secure aggregation at the application layer. Quidnug records metadata including DP noise scale as attestation.

T6. Coordinator compromise

Attack. Coordinator’s signing key stolen. Mitigation. Guardian recovery. Post-rotation, old coordinator signatures rejected.

T7. Round-hijack

Attack. Attacker publishes fake round.opened event impersonating coordinator. Mitigation. Event signed by coordinator’s key; participants verify before engaging. Forged signatures fail.

Not defended against

  • Privacy of raw data, that’s local to each participant. Quidnug doesn’t see it.
  • Correctness of participant’s local training, if participant claims to train on X but trains on Y, we only catch it if gradient behavior is anomalous.
  • Deep adversarial ML attacks (hidden triggers, backdoors), detection is an active research area.

References