# Submission privacy

> How a Submission is sealed, why nobody can open it before the deadline, and who can read it afterwards.

A Solver's work is encrypted on their own machine before anything leaves it.
Elgora stores ciphertext; no plaintext artifact and no artifact location ever
goes on-chain.

Two mechanisms combine. Understanding both is the difference between "they
promise not to look" and "they cannot look yet".

## Sealing, in order

<Steps>
  <Step>
    ### A fresh content key encrypts one archive of your files

    The client packs your files into one `.tar.gz` and encrypts its raw bytes under
    a randomly generated AES-256-GCM key that exists only for this Submission. The
    encryption is bound to the
    deployment context — chain, contract, bounty, your address — the archive's
    digest, the roster commitment, the reveal policy and the envelope's `alg`,
    which names the Submission format, so ciphertext cannot be lifted into a
    different bounty, or read as a different format, and still open.
  </Step>

  <Step>
    ### The content key is wrapped once per Guardian

    Each Guardian on the bounty's pinned roster gets their own copy of that key,
    encrypted to their X25519 public key. Only that Guardian's private key can
    unwrap their copy.
  </Step>

  <Step>
    ### The whole bag of wrapped keys is time-locked

    The set of per-Guardian wrapped keys is then encrypted *again*, to a future
    round of a public randomness beacon — the first round at or after the bounty's
    submission deadline. The decryption key for that round does not exist yet;
    the beacon publishes it, on schedule, when the round arrives.
  </Step>

  <Step>
    ### Only ciphertext is uploaded

    The encrypted bundle goes to private object storage at a deterministic,
    content-addressed locator. The envelope — context, digests, recipients
    commitment, reveal policy, the time-locked key bag, and the locator — is stored
    as canonical JSON. The `keccak256` of that JSON is your
    [`submission_commitment`](/docs/how-it-works/commitments), and it is the only
    part that goes on-chain.
  </Step>
</Steps>

That second layer is what makes the guarantee structural. Before the deadline,
a Guardian holding their own private key still cannot reach their wrapped copy,
because the bag itself is locked. Neither can Elgora, the Poster, another
Solver, or anyone who steals the ciphertext.

## Opening, after the deadline

The beacon publishes the round. A Guardian on the pinned roster then:

1. verifies the bounty and the exact Solver and Submission against the
   contract;
2. signs a request and fetches the stored envelope and ciphertext;
3. re-derives the commitment and confirms it matches the on-chain one, and
   checks the ciphertext against the digest inside the envelope;
4. opens the time-locked bag, unwraps their own copy of the content key with
   their retained private key, and decrypts the archive locally;
5. checks the archive — regular files only, every path safe to write, within
   the limits — and only then extracts it into a fresh folder.

Each of those steps can fail, and what a failure means depends on which one it
is. An unavailable beacon, a missing artifact, or a decryption that does not go
through is a reported operational blocker, never a Verdict against the
Submission. A commitment or ciphertext digest that does not match what the
Solver committed is that one Submission's own failure: it is disqualified, and
the bounty's other Submissions are opened and judged as normal.

Guardian private keys stay on Guardian machines, never in Elgora's web app,
API, or database, and no administrative role on the contract can decrypt a
Submission. But right now only Molecule team members operate the Guardians,
for our initial evaluations, so they hold those keys until the roster is handed
over — see [Risk disclosure](/docs/risks#who-can-read-your-work).

## Who can read your work

Before the deadline, nobody — the time-lock sees to that. After it, the
bounty's pinned Guardians (for now, all operated by Molecule), the AI model
providers those Guardians send work to while judging, and the protocol layer
that brokers delivery between them and the winning Poster.

That is the whole list. Not other Solvers, not the Poster before finality, and
not anyone holding the ciphertext without a wrapped key. Losing work is never
published, and Guardians must write their Verdicts without revealing any
Submission's method, code, data, or results.

Two things the encryption does not do, worth knowing before you submit:

* **It is not anonymity.** Your Solver address, your `submission_commitment`,
  and the time you submitted are public on-chain from the moment you submit.
* **Winning means delivery.** If you win, the funding Poster receives your
  artifacts. That is the point of the bounty — see
  [Delivering the winning work](/docs/how-it-works/delivery).

## Envelope internals, for implementers

The stored envelope has a fixed, strictly validated shape. Every field is
checked server-side and re-checked by clients:

| Field                                                    | Purpose                                                                                                                                      |
| -------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
| `context`                                                | chain id, Hub address, bounty id, Solver address                                                                                             |
| `public_manifest` + its digest                           | the non-secret context, hashed                                                                                                               |
| `alg`                                                    | the Submission format the bounty's declared profile names: `guardian_timelock_hpke_aes_256_gcm_v1` for `elgora_markdown_bounty_challenge_v1` |
| `encrypted_bundle_sha256`                                | SHA-256 of the archive that was encrypted                                                                                                    |
| `ciphertext_sha256`                                      | digest of the uploaded ciphertext                                                                                                            |
| `guardian_recipients` + `guardian_recipients_commitment` | the exact roster keys sealed for, and `keccak256` of their canonical JSON after sorting by `key_id`, then `public_key`                       |
| `reveal_policy`                                          | beacon network, chain hash, round, and `not_before`                                                                                          |
| `envelope_nonce`, `iv`                                   | per-envelope randomness                                                                                                                      |
| `wrapped_key_bag`                                        | the time-locked bag of per-Guardian wrapped keys                                                                                             |
| `ciphertext_locator`                                     | deterministic storage key: `{chain_id}/{hub_address}/{bounty_id}/{actor_address}/{ciphertext_sha256}.bin`, with lowercase addresses          |

The API independently verifies that the recipients commitment matches the
bounty's pinned roster, that the reveal policy matches the bounty's deadline,
that the locator is the one those inputs derive, and that the uploaded bytes
hash to `ciphertext_sha256` — before it stores anything. A Submission that
would be unopenable by the roster is rejected at the boundary rather than
discovered later by a Guardian.

Content identity is the files, not the archive: `content_sha256`, a digest of
each file's path, size and SHA-256, is reported to the Solver and again to each
opener. It never appears in the envelope, the API or on chain, where it would
let anyone confirm a guessed Submission.

The ceiling on the encrypted archive is 50 MiB across at most 10,000 files, and
on what it extracts to 250 MB.
