[DOCS] cofhe-components/verify-commitments: recompute a ciphertext and check its commitment - #81
Conversation
|
Preview deployment for your docs. Learn more about Mintlify Previews.
|
|
|
||
| | Ciphertext | Handle | Where its commitment lives | | ||
| | --- | --- | --- | | ||
| | Encrypted input | The commitment with its last two bytes replaced by metadata: byte 30 carries the type, byte 31 carries the security zone | `InputVerified` on the host chain, and the registry | |
There was a problem hiding this comment.
Byte 30 isn't only the type — its top bit is the trivial-encrypt flag.
fhe-engine src/types.rs:31-34:
TRIVIAL_ENCRYPT_AND_TYPE_BYTE = 30
TYPE_MASK = 0x7f
TRIVIAL_ENCRYPT_FLAG = 0x80
So byte 30 is trivial_flag | type. A trivially-encrypted euint64 handle ends 85 00, not 05 00, and matches no ICofhe constant. This bites readers at line 98, where you tell them to read byte 30 against those constants.
Suggest: "byte 30 carries the type in its low 7 bits, with the high bit set when the value was trivially encrypted; mask with 0x7f before comparing to the ICofhe constants."
There was a problem hiding this comment.
Fixed in both places. The handle table now says byte 30 carries the type and the trivial-encrypt flag. The text at line 98 says byte 30 holds the type in its low seven bits, with the high bit set when the value was trivially encrypted, so mask with 0x7f before comparing to the ICofhe constants.
I added your 85 00 example there, since that is where it bites: EUINT64_TFHE = 5 in ICofhe.sol:89, so 0x80 | 0x05. The term links to the Trivial Encryption page on first mention.
| $VERSION 0 100 --rpc-url $REGISTRY_RPC | ||
| ``` | ||
|
|
||
| Keep `limit` small. See [CommitmentRegistry](/deep-dive/cofhe-components/commitment-registry) for the write rules, the version state machine, and the rest of the read surface. |
There was a problem hiding this comment.
Missing read: getVersionStatus(bytes32).
CommitmentRegistry.sol:26 has enum VersionStatus { Unset, Active, Deprecated, Revoked }, and teecryptor's own note on that enum says commitments under a Revoked version must not be trusted. Right now the page tells the reader to look up version 2 (hard-coded) and trust a match — but a matching hash under a revoked version proves nothing.
Worth one more cast call here:
cast call $REGISTRY "getVersionStatus(bytes32)(uint8)" $VERSION --rpc-url $REGISTRY_RPCThere was a problem hiding this comment.
Added. "Read the commitment" now carries a getVersionStatus(bytes32)(uint8) call after the version-tag warning, with the four values and the forward-only transitions from setVersionStatus (Unset->Active, Active->Deprecated or Revoked, Deprecated->Revoked), and the line that a match under a revoked version proves nothing.
One thing I deliberately did not write: teecryptor probes the status once at boot and only logs a warning when it is not Active (src/main.rs:708-723). It does not re-check per request. So the page tells the auditor to read the status, and stops short of claiming the enclave enforces it.
|
|
||
| Compression is deterministic. It is a modulus switch whose noise-reduction step picks its candidate by a fixed measure, with no randomness. The same inputs give the same bytes on every run and every machine. | ||
|
|
||
| If the value this returns equals what `getCommitment` gave you for the result handle, the coprocessor computed honestly for that task. Walk the graph up from the encrypted inputs and you have checked the whole computation. |
There was a problem hiding this comment.
The page never says what to do when the hashes don't match, and line 20 ("A mismatch means it did not") points straight at fraud.
Realistically the first several mismatches anyone hits will be their own setup: wrong tfhe-rs patch version, wrong security zone's keyset, wrong EUINT* type for the handle, or hashing expanded bytes instead of the stored compressed form. A short checklist before "the coprocessor lied" would save readers a lot of grief and save us the reports:
- confirm byte 30's type matches the type you deserialized as
- confirm the keyset zone matches byte 31
- confirm you hashed
safe_serialize(compress(result)), not the expanded value - confirm the version is Active and the task actually finished
...then, if it still mismatches, here's where to report it.
There was a problem hiding this comment.
Added ## When the hashes do not match, with your four checks plus the tfhe-rs pin. Line 20 now links to it instead of pointing straight at fraud.
I left out the "where to report it" line. There is no channel we can point a reader at today, and inventing one fails the STYLE.md rule on unverifiable claims. It goes in as soon as there is one.
| use tfhe::{CompressedFheUint64, CompressedServerKey}; | ||
| use tiny_keccak::{Hasher, Keccak}; | ||
|
|
||
| const SIZE_LIMIT: u64 = 1 << 32; |
There was a problem hiding this comment.
Nit: the network uses 1 << 30 (1 GB) — rust-common's SAFE_SERIALIZATION_SIZE_LIMIT, which is what every cofhe service passes to safe_serialize/safe_deserialize.
The limit is only a guard and isn't part of the serialized bytes, so the hash is unaffected either way. But there's no reason for the sample to diverge from the constant the network actually uses — suggest 1 << 30.
There was a problem hiding this comment.
Changed to 1 << 30, matching rust-common/src/safe_serde.rs:13. I also added a line to the new checklist saying the limit never reaches the hash, so nobody chases it as a mismatch cause.
…a ciphertext and checking its commitment
…ut archive are separate, and one registry serves every chain
…ersion status, and add a mismatch checklist
|
|
||
| ## Where this check runs in production | ||
|
|
||
| [Teecryptor](/deep-dive/cofhe-components/teecryptor) makes the same comparison inside its enclave on every decryption request. It hashes the ciphertext bytes it is about to decrypt and refuses any that do not match the commitment anchored onchain. Your audit and its gate read the same record. |
There was a problem hiding this comment.
Teecryptor's baked testnet and staging policies both set warning_instead_of_enforcement, so the gate logs a mismatch and still decrypts (src/env_policy.rs, testnet_policy_is_commitment_warn_only). A public page must not claim an enforcement we do not run yet; say the gate runs in warn-only mode during rollout.
There was a problem hiding this comment.
Confirmed and fixed. env_policy.rs bakes warning_instead_of_enforcement = true for testnet and staging, the test at line 275 pins it, and main.rs:224 says failures are logged but the decrypt is allowed.
The section no longer claims a refusal. It says the gate hashes and checks, and a Warning underneath says the gate runs warn-only during rollout: a mismatch is logged and the decryption still proceeds, moving to enforcement once handles under the earlier version have aged out.
|
|
||
| The answer is `0` for Unset, `1` for Active, `2` for Deprecated, and `3` for Revoked. A status only moves forward: Active to Deprecated or Revoked, and Deprecated to Revoked. `Deprecated` means the version was superseded and its commitments stay readable. `Revoked` means the opposite. Do not trust a match under a revoked version. | ||
|
|
||
| A zero answer is not proof of tampering. A result that is still being computed has no commitment yet, because the coprocessor posts the commitment after the compute stage finishes. |
There was a problem hiding this comment.
+The answer is 0 for Unset, 1 for Active, 2 for Deprecated, and 3 for Revoked. A status only moves forward: Active to Deprecated or Revoked, and Deprecated to Revoked. Deprecated means the version was superseded and its commitments stay readable. Revoked means the opposite. Do not trust a match under a revoked version.
+
+A zero answer is not proof of tampering. A result that is still being computed has no commitment yet, because the coprocessor posts the commitment after the compute stage finishes.
+
+To audit in bulk rather than one handle at a time, enumerate the version. getSize reports the total, and getHandles pages through it, clamping offset + limit at the total and returning an empty array once offset runs past the end.
+
+```bash
Version 1 is still Active on the testnet registry and holds most of the history, so pre-cutover handles return zero under version 2. This paragraph names only "still computing" as a cause of zero, which sends readers to the wrong conclusion. Add the pre-cutover case and tell them to retry under version 1.
There was a problem hiding this comment.
Right, and worse than you stated. I queried the registry: version 1 is Active with 34,474,546 commitments, version 2 Active with 50,229,974. So a pre-cutover handle is a normal, common case, not an edge case.
The paragraph now gives two causes for a zero. Timing, as before, and the version: version 1 is still Active and holds the bulk of the earlier history, a handle committed before the cutover resolves only under version 1, and the reader is told to retry under version 1 before concluding anything.
|
|
||
| | Value | Why it is missing | | ||
| | --- | --- | | ||
| | CommitmentRegistry address | The registry chain is not fixed yet. The contract, its interface, and the version tag are settled; only the deployment target is open. | |
There was a problem hiding this comment.
Teecryptor bakes a deployed registry address per environment, so the deployment target is decided, not open. Reword this as a publication decision we have not made, or publish the testnet address and let the page work end to end.
There was a problem hiding this comment.
Correct, the target is decided: the address is baked per environment via include_str!, and the env-var path is #[cfg(feature = "mock")] only.
Reworded as a publication decision rather than an open deployment target: the registry is deployed and the coprocessor already writes to it, the contract and interface and version tag are settled, and what is open is publishing the address on this page.
We are holding the address until Haim confirms which one to publish, so the page still ships with it pending and the three values stay in the table.
| "deep-dive/cofhe-components/acl", | ||
| "deep-dive/cofhe-components/plaintext-storage", | ||
| "deep-dive/cofhe-components/commitment-registry", | ||
| "deep-dive/cofhe-components/verify-commitments", |
There was a problem hiding this comment.
Indent this to 14 spaces to match the nine sibling entries.
There was a problem hiding this comment.
Fixed, 8 to 14 spaces, matching the nine siblings.
…on 1 zero case, and the designed archify diagram
7ef7746 to
4f27c49
Compare
…o boundary labels and component titles
…reatment, zone-filled header band per component
Adds a Deep Dive page that shows a reader how to audit CoFHE without trusting it.
DOC-62.
Why
We claim every ciphertext is anchored in the CommitmentRegistry, and that the decryption path enforces the check. A reader had no way to test that claim. The accountability story stayed theoretical.
What the page does
It documents the audit as a recomputation, not a byte fetch:
TaskCreatedon the host chain.A match means the coprocessor ran the operation the contract asked for. The reader never learns a plaintext, so the computation is auditable while the data stays confidential.
Three values we are waiting on
The page is written in full, with these marked as pending in one table.
There is no way for a reader to discover the registry address today. It is not in the storage key, not in the SDK chain config, and not reachable onchain. The poster reads it from config, either inline or from a file the deployer writes. If we add it to the SDK chain config alongside
coFheUrl, the page can resolve it the same way it already resolves the CoFHE URL, and it stops being a hardcoded value we have to maintain here.Worth flagging beyond the values:
GetNetworkPublicKeyandGetCrsdo not serve the evaluation key, and you cannot recompute without it. It only lives in the key store. Both stores currently refuse anonymous reads, so publishing the URLs is necessary but not sufficient. Someone has to open read access, or a reader following the page gets a 403.Everything else works today
Verified live:
cast logscommands, against Arbitrum Sepolia. The example handles and commitments in the page are real events.@cofhe/sdk@0.7.1already ships for the chain.tfhe-zk-pokthat fails to build.Checks
lint-docs.py,vale,check-versions.py --docs,mint validate, andmint broken-links --check-anchorsall clean.