Skip to content

Latest commit

 

History

History
106 lines (73 loc) · 13.5 KB

task_240826_10.md

File metadata and controls

106 lines (73 loc) · 13.5 KB

Task: Real Encrypted Test-Data Key Distribution + Dispute Resolution (Issue #40, continuation of PR #63)

ID: task_240826_10 Assigned to: Robbert Abimbola (@robertocarlous) Created: 2026-08-24 Assigned: 2026-08-28 Status: Open (assigned) Repo: https://github.com/InfiniteZeroFoundation/DevNet Base branch: develop — PR #63 merged 2026-08-27 (8f2f9f7 + deviation 963ef2b), PR #64 merged 2026-08-28 (9f2142f + deviation 6af077a); both land cleanly on current develop, branch from there directly. Roadmap ref: P3 auditing (issue #40), whitepaper §5.2.3b (encrypted test dataset), reuses the P3-4.3 dispute-resolution shape from Developer/design/MECHANISM_DESIGN.md §4 Tracking: Issue #40 · builds on PR #63 (task_210726_6 §2b) · design source is the three PR #63 review comments cited throughout this task


Context

task_210726_6 §2b asked PR #63 to add on-chain plumbing for per-auditor encrypted test-data keys, explicitly scoped as on-chain plumbing only, no real encryption — encryptedTestDataKey[gi][batchId][auditor] (foundry/src/DINTaskAuditor.sol:157-163) gets written on every assignAuditTestDataset call, but the actual dincli caller submits encrypted_keys = [b"" for _ in batch_auditors] — empty placeholder bytes for everyone. testDataCID itself also remains a plain, publicly-readable bytes32 pointing at plaintext IPFS content (dincli/cli/auditor.py:334-335 reads it and fetches it directly, no decryption step anywhere).

During re-review of PR #63 after it was otherwise approved, three follow-up comments worked out what the real (non-stub) version of this needs to look like, and this task turns that discussion into a build:

  1. Comment 5382717784 (Umer) — the encryption design: both content-secrecy (encrypt the test file itself with a per-batch key K) and address-secrecy (the whitepaper §5.2.3b encryptedCID = SymmetricEncrypt(K, Sign(ownerSK, rawCID)) construction, so testDataCID itself stops being a plain public pointer). Decision recorded there: build both layers, not content-secrecy alone — a leaked testDataCID should give an attacker nothing without K.
  2. Comment 5383065186 (Umer) — encryptedTestDataKey has no dispute-resolution path: neither an auditor's "this is garbage/wrong" claim nor a model owner's "I submitted the right thing" claim can be independently checked today. Lays out 6 concrete failure modes and a staged, stake-gated disputeTestData mechanism (cheapest check first) to resolve them.
  3. Comment 5385033910 (Robbert) — confirms the staged-reveal design is fine for now (privacy cost of a Stage 2 disclosure is bounded by the resampling policy already in PR #63), adds one refinement (bind keccak256(K) into the on-chain commitment, not just the plaintext hash — tightens the stale-data-reuse check for free), and recommends a full zkVM-based private dispute path be tracked as a separate Phase 2/3 issue, not built now.

This task is Part 1 of finishing that thread — more parts (at minimum, the zkVM follow-up Robbert flagged) will be filed as separate task files/issues later, not folded in here.


Scope for this task

0. Auditor public-key registration (prerequisite — flagged here, not explicit in the PR thread)

None of the three comments say where an auditor's public encryption key comes from — "encrypt K to each assigned auditor's public key" is stated as a given. There is currently no on-chain public-key registration for auditors anywhere in foundry/src/ (checked DINTaskAuditor.sol, DinValidatorStake.sol). Comment 5382717784 does note in passing that this could be "ECIES over their registered chain key, or a separate registered encryption key/X25519 if signing and encryption keys should be kept apart" — but that's a suggestion, not a decision. You'll need to add a registration mechanism (mapping + a registerEncryptionKey-style function) before the encryption step below has anything to encrypt to. See Open Questions §1 for where it should live.

A. Real content-secrecy + address-secrecy encryption

Per comment 5382717784's plan, mapped onto this codebase's actual shape (testDataCID + encryptedTestDataKey, no schema change needed to assignAuditTestDataset itself):

Model-owner side (cache_model_0/services/modelowner.py's create_audit_testDataCIDs, currently uploads the sampled test file to IPFS as plaintext at line 161; dincli/cli/modelownerd/auditor_batches.py's create_testdataset, currently builds the b"" placeholder array):

  1. Generate a fresh, random per-batch symmetric key K (e.g. AES-256-GCM) — one-time, never reused across batches or GIs.
  2. Encrypt the sampled test file's bytes with K before upload_to_ipfs — this alone turns testDataCID into "the ciphertext's CID" with no other change to how the CID is produced.
  3. Build encryptedCID = SymmetricEncrypt(K, Sign(ownerSK, rawCID)) — sign the raw (plaintext-file) CID with the model owner's key, then symmetrically encrypt that signed payload with K. This is what actually gets submitted as testDataCID on-chain going forward, not the raw CID.
  4. For each batch.auditors[i], encrypt K to that auditor's registered public key (from §0) to produce real encryptedKeys[i], replacing the current b"" placeholders.
  5. Call assignAuditTestDataset(gi, batchId, encryptedCID, encryptedKeys) — same signature PR #63 already added, no Solidity change needed for this part alone (the dispute commitment in §B is the actual on-chain addition).

Auditor side (dincli/cli/auditor.py around the testDataCID read at line 334-335; cache_model_0/services/auditor.py, which currently never touches encryptedTestDataKey at all):

  1. Alongside testDataCID, read encryptedTestDataKey[gi][batchId][msg.sender].
  2. Decrypt it with the auditor's private key to recover K.
  3. Decrypt testDataCID with K to recover (rawCID, signature); verify the signature against the model owner's known public key before trusting it.
  4. Fetch the ciphertext from IPFS via rawCID (this replaces the current direct plaintext fetch) and decrypt it locally with K before handing it to Score_model_by_auditor — everything downstream of that call is unchanged.

B. Test-data dispute resolution

Per comment 5383065186, refined by comment 5385033910. At the same assignAuditTestDataset call, also publish an on-chain commitment, bound to gi, batchId, and keccak256(K) (Robbert's refinement — not just the plaintext hash, so revealing K at Stage 1 simultaneously proves the commitment was honestly formed):

commitment = keccak256(abi.encodePacked(gi, batchId, keccak256(K), keccak256(plaintext_test_data)))

New disputeTestData(gi, batchId), gated by a stake bond, opens a challenge window. Staged resolution, cheapest check first:

  • Stage 0 (free, no stake): anyone can check encryptedTestDataKey[gi][batchId][auditor].length > 0 via a plain view call. Empty → resolves in the auditor's favor immediately (covers dispute #2: auditor got no key at all — the exact bug the current b"" placeholder produces today).
  • Stage 1 (stake required from here): disputer reveals K; anyone decrypts testDataCID with it and checks the signature against the model owner's known key. Fails → resolves against the model owner, without ever touching IPFS (covers #3 bad signature / #4 auditor-ordering mismatch).
  • Stage 2: signature checks out, so fetch the ciphertext via rawCID, decrypt with K, and compare keccak256(gi, batchId, keccak256(K), keccak256(plaintext)) against the stored commitment. Mismatch → model owner supplied bad or stale data (covers #1 garbage content, #5 stale/reused round via the round-binding); the round-binding is what stops a copy-pasted old blob from passing as fresh even if the file bytes are identical to a previous round's. Match → the dispute was false, disputer's stake is forfeited instead.

Explicitly out of scope for dispute #6 (model owner never calls assignAuditTestDataset at all for a batch) — that's a liveness gap, not a correctness dispute; same shape as the already-tracked slashAggregators/wedged-GI liveness question elsewhere in this repo (see backlog), not something this mechanism needs to solve.


Explicitly out of scope for this task

  • zkVM-based private dispute resolution. Comment 5385033910's own recommendation: Stage 2 makes that batch's test data public once disputed; the privacy cost is bounded by the resampling policy (fine for now), but a real "prove the ciphertext matches the committed hash without decrypting anything" guarantee needs a RISC Zero/SP1 zkVM circuit — Robbert's explicit call is to open that as a separate tracking issue, Phase 2/3 scope, not build it here.
  • Any economic-penalty wiring beyond what's needed to make the dispute resolve (see Open Question 2 below) — this task builds the mechanism, not a full model-owner slashing/penalty redesign.
  • Choosing/vetting a specific ECIES/X25519 library is in scope to actually build, but which primitive is used is not mandated — see Open Question 1.

Open questions — ask rather than guess

Per repo convention (see task_050826_9.md): if any of these is ambiguous once you're in the code, stop and ask rather than picking a default silently.

  1. Where does auditor public-key registration live, and what key type? DinValidatorStake (validator identity is stake-scoped, spans models) vs. DINTaskAuditor (task-scoped, simpler but duplicated per model). Comment 5382717784 floats X25519 as a separate key from the auditor's chain signing key but doesn't decide it — confirm with Umer before building.
  2. What actually happens to the model owner when Stage 2 resolves against them? Per Developer/design/MECHANISM_DESIGN.md §1's role matrix, model owners are explicitly not in the stake/slash system — they only "lose fees/deposit on abandonment." None of the three PR #63 comments specify a concrete penalty for a model owner losing a test-data dispute (they use the word "slashed" loosely for the auditor's false-dispute case, which does map cleanly to the existing stake system — the model-owner side doesn't). Needs a real answer before wiring up the Stage 2 losing-outcome path.
  3. Dispute bond size and challenge-window length. No numbers given in any of the three comments. MECHANISM_DESIGN.md §4's existing P3-4.3 dispute-resolution design (bond forfeited if frivolous, returned + bounty from treasury if upheld) is the closest existing pattern in this repo — reuse its shape/parameters if reasonable, flag if it isn't a good fit here.
  4. Batch reassignment mechanics after a losing model-owner outcome. Comment 5383065186 says "batch reassigned with fresh data and a fresh K" but doesn't specify whether that blocks the GI until the model owner acts, or how re-assignment is triggered.

Deliverables

  • Auditor public-key registration: mapping + registration function (location per Open Question 1)
  • Model-owner side real encryption: per-batch K generation, content encryption before IPFS upload, encryptedCID construction (sign + symmetric-encrypt), real per-auditor key encryption replacing the b"" placeholders — cache_model_0/services/modelowner.py, dincli/cli/modelownerd/auditor_batches.py
  • Auditor side decryption: read encryptedTestDataKey, recover K, verify signature, decrypt content before scoring — dincli/cli/auditor.py, cache_model_0/services/auditor.py
  • On-chain: round-and-key-bound commitment storage at assignAuditTestDataset, disputeTestData staged-resolution function(s) in foundry/src/DINTaskAuditor.sol, stake bond handling
  • Foundry tests covering all 3 stages (auditor-favor at Stage 0, model-owner-favor at Stage 1, both content-mismatch and stale-round-reuse at Stage 2, false-dispute stake forfeiture)
  • Python tests: encrypt/decrypt round-trip, signature verification, empty/malformed-key handling
  • forge build && forge test and the relevant pytest suite green
  • PR description explicitly answers or flags each of the four Open Questions above with whatever was decided
  • PR references this task file, Issue #40, and PR #63

Notes

  • This directly continues PR #63 (feature/auditor-commit-reveal), which merged into develop on 2026-08-27 (merge commit 8f2f9f7, deviation commit 963ef2b) — the textual conflicts and cross-PR compile break noted in the PR's own review comments were resolved as part of that merge, so commitAuditScore/revealAuditScore, encryptedTestDataKey, and assignAuditTestDataset are all live on develop now. Branch straight from develop.
  • Full design provenance is inlined above, but the three source comments are the canonical reference if anything here reads as ambiguous or under-specified: 5382717784, 5383065186, 5385033910.
  • This is Part 1 of a larger effort. At minimum, the zkVM-based private dispute follow-up is expected as a separate future task/issue — don't start it here.