From 1ea9a0bdfa8a429468eade32640280d6b23295c9 Mon Sep 17 00:00:00 2001 From: Roee Zolantz Date: Sun, 15 Mar 2026 20:09:47 +0200 Subject: [PATCH] Add ECDSA decrypt result signature docs --- .../cofhe-components/result-processor.mdx | 4 + deep-dive/cofhe-components/task-manager.mdx | 37 ++++++- .../cofhe-components/threshold-network.mdx | 34 +++++++ deep-dive/data-flows/decrypt-from-cofhejs.mdx | 4 +- .../data-flows/decryption-request-flow.mdx | 19 +++- .../core-concepts/decryption-operations.mdx | 66 ++++++++++++- fhe-library/reference/fhe-sol.mdx | 97 +++++++++++++++++++ 7 files changed, 253 insertions(+), 8 deletions(-) diff --git a/deep-dive/cofhe-components/result-processor.mdx b/deep-dive/cofhe-components/result-processor.mdx index cc97a29..1e574bc 100644 --- a/deep-dive/cofhe-components/result-processor.mdx +++ b/deep-dive/cofhe-components/result-processor.mdx @@ -10,3 +10,7 @@ description: "Off-chain service that handles FHE operation results and publishes | **Responsibilities** | • Receives FHE operation results from the fheOS server
• Sends results to the Data Availability layer
• Publishes decryption results back to the Task Manager on the host chain | The Result Processor ensures that computation results from the off-chain fheOS server are properly relayed back to the blockchain, completing the FHE operation lifecycle. + + +Decrypt results can also be published on-chain by clients using `FHE.publishDecryptResult()` with a valid ECDSA signature from the Threshold Network's Dispatcher. See [Decryption Operations](/fhe-library/core-concepts/decryption-operations) for details on this alternative delivery path. + diff --git a/deep-dive/cofhe-components/task-manager.mdx b/deep-dive/cofhe-components/task-manager.mdx index 700c8f8..60fa619 100644 --- a/deep-dive/cofhe-components/task-manager.mdx +++ b/deep-dive/cofhe-components/task-manager.mdx @@ -1,6 +1,6 @@ --- title: TaskManager -description: "On-chain entry point for CoFHE integration that initiates FHE operations and generates unique handles for encrypted computation tasks" +description: "On-chain entry point for CoFHE integration that initiates FHE operations, generates unique handles, and verifies decrypt result signatures" --- @@ -8,6 +8,39 @@ description: "On-chain entry point for CoFHE integration that initiates FHE oper |---------|-------------| | **Type** | Contract deployed on the destination blockchain | | **Function** | Acts as the on-chain entry point for CoFHE integration | -| **Responsibilities** | • Initiates FHE operations by serving as the on-chain entry point. The dApp contract calls the FHE.sol library which triggers the TaskManager contract to submit a new encrypted computation task.
• Generates unique handles that act as references to the results of FHE operations. These results are computed asynchronously off-chain.
• Emits structured events containing the unique handle of the ciphertext, operation type, and other required metadata. | +| **Responsibilities** | • Initiates FHE operations by serving as the on-chain entry point. The dApp contract calls the FHE.sol library which triggers the TaskManager contract to submit a new encrypted computation task.
• Generates unique handles that act as references to the results of FHE operations. These results are computed asynchronously off-chain.
• Emits structured events containing the unique handle of the ciphertext, operation type, and other required metadata.
• Verifies ECDSA signatures on client-published decrypt results and stores them on-chain. | | **Deployment** | A separate Task Manager Contract is deployed for each supported destination chain, enabling chain-specific integrations | +## Decrypt Result Signature Verification + +The TaskManager supports **permissionless publishing of decrypt results**. Anyone holding a valid ECDSA signature from the Threshold Network's Dispatcher can publish a decrypt result on-chain. The TaskManager verifies the signature before storing the result. + +### Key State + +| Variable | Description | +|----------|-------------| +| `decryptResultSigner` | Address of the authorized Threshold Network signer. Set to `address(0)` to skip verification (debug mode). | + +### Functions + +| Function | Description | +|----------|-------------| +| `publishDecryptResult(ctHash, result, signature)` | Verify signature and store the decrypt result on-chain. Emits `DecryptionResult`. | +| `publishDecryptResultBatch(ctHashes[], results[], signatures[])` | Batch publish multiple results in one transaction for gas efficiency. | +| `verifyDecryptResult(ctHash, result, signature)` | Verify a signature without publishing (view). Reverts on failure. | +| `verifyDecryptResultSafe(ctHash, result, signature)` | Verify a signature without publishing (view). Returns `false` on failure. | +| `setDecryptResultSigner(address)` | Admin-only. Set the authorized signer address. | + +### Signature Message Format + +The signed message is a fixed **76-byte** buffer: + +| Field | Size | Encoding | +|-------|------|----------| +| `result` | 32 bytes | uint256, big-endian, left-padded with zeros | +| `enc_type` | 4 bytes | i32, big-endian (extracted from ctHash metadata) | +| `chain_id` | 8 bytes | u64, big-endian (from `block.chainid`) | +| `ct_hash` | 32 bytes | uint256, big-endian | + +The message is hashed with `keccak256` and verified using OpenZeppelin's `ECDSA.tryRecover`. The `enc_type` and `chain_id` are derived on-chain, binding each signature to a specific ciphertext type and chain. + diff --git a/deep-dive/cofhe-components/threshold-network.mdx b/deep-dive/cofhe-components/threshold-network.mdx index a51ca5b..084ed52 100644 --- a/deep-dive/cofhe-components/threshold-network.mdx +++ b/deep-dive/cofhe-components/threshold-network.mdx @@ -39,3 +39,37 @@ The coordinator splits the CT into individual Learning With Errors (LWE) CT bloc The MPC protocol consists of multiple stages. In each stage, a partymember performs a calculation on a received input and returns the result (a.k.a. intermediate result) to the Coordinator. Each intermediate result gets sent back to the coordinator in order to get distributed among other partymembers as an input for the next stage. +## Dispatcher Signing + +The Threshold Network's **Dispatcher** component signs every decrypt and sealoutput result with an ECDSA key. This signature enables on-chain verification — clients can publish signed decrypt results directly to the TaskManager contract via `FHE.publishDecryptResult()`. + +### Signed Message Format + +For decrypt results, the Dispatcher produces a fixed **76-byte** message before signing: + +| Field | Size | Encoding | +|-------|------|----------| +| `result` | 32 bytes | uint256, big-endian, left-padded with zeros | +| `enc_type` | 4 bytes | i32, big-endian | +| `chain_id` | 8 bytes | u64, big-endian | +| `ct_hash` | 32 bytes | uint256, big-endian | + +This format is aligned with Solidity types so the TaskManager can reconstruct and verify the same hash on-chain using `_computeDecryptResultHash`. + +### Signature V Format + +The ECDSA recovery ID (`v` value) can be returned in two formats, controlled by the HTTP header `X-Signature-V-Format`: + +| Header Value | V Format | Use Case | +|-------------|----------|----------| +| `"raw"` (default) | 0-3 | General purpose, k256 native | +| `"evm"` | 27-28 | Direct use with Solidity's `ecrecover` / OpenZeppelin `ECDSA.recover` | + + +For on-chain verification via `FHE.publishDecryptResult()`, use `"evm"` format so the signature is directly compatible with the TaskManager's ECDSA verification. + + +### Signer Registration + +The Dispatcher's signing key address is registered on-chain as `decryptResultSigner` in the TaskManager contract. Only results signed by this address are accepted. Setting it to `address(0)` disables verification (debug mode only). + diff --git a/deep-dive/data-flows/decrypt-from-cofhejs.mdx b/deep-dive/data-flows/decrypt-from-cofhejs.mdx index 5112588..8192e73 100644 --- a/deep-dive/data-flows/decrypt-from-cofhejs.mdx +++ b/deep-dive/data-flows/decrypt-from-cofhejs.mdx @@ -105,7 +105,9 @@ export const convertViaUtype = ( }; ``` -3. **The result is returned as a `Result` type**. The `Result` type looks like this: +3. **The signature is available for on-chain publishing.** The Dispatcher's ECDSA signature is included in the response. If you need the decrypt result available on-chain (e.g., for use in smart contract logic), you can publish it using `FHE.publishDecryptResult(ctHash, result, signature)`. See [Decryption Operations](/fhe-library/core-concepts/decryption-operations) for details. + +4. **The result is returned as a `Result` type**. The `Result` type looks like this: ```typescript export type Result = diff --git a/deep-dive/data-flows/decryption-request-flow.mdx b/deep-dive/data-flows/decryption-request-flow.mdx index 33bb688..1649dec 100644 --- a/deep-dive/data-flows/decryption-request-flow.mdx +++ b/deep-dive/data-flows/decryption-request-flow.mdx @@ -55,11 +55,21 @@ The Threshold Network performs secure decryption: - Perform secure decryption - -After decryption is complete: 7️⃣ + +After decryption is complete, the result can reach the chain via two paths: 7️⃣ -- Call appropriate callback function on the Result Processor -- The Result Processor publishes the result back to the TaskManager on the host chain +**Path A — Result Processor (default):** +- FheOS calls the Result Processor with the decrypt result +- The Result Processor publishes the result to the TaskManager on the host chain + +**Path B — Client-Published with Signature:** +- The Dispatcher returns the decrypt result along with an **ECDSA signature** to the client (via cofhejs HTTP) +- The client (or any relayer) calls `FHE.publishDecryptResult(ctHash, result, signature)` on-chain +- The TaskManager verifies the signature against the registered `decryptResultSigner` before storing the result + + +Path B enables permissionless result delivery — anyone holding a valid signature can publish. This is useful for client-driven settlement or relayer patterns. + @@ -67,6 +77,7 @@ The TaskManager finalizes the decryption process: 8️⃣ - Provide decrypted result by emitting an event `DecryptionResult` - The event consists of `ciphertext handle`, `result`, `requestor` (of that decrypt operation) +- In Path B, `requestor` is `msg.sender` (the publisher), not the original decrypt requester diff --git a/fhe-library/core-concepts/decryption-operations.mdx b/fhe-library/core-concepts/decryption-operations.mdx index 398b24b..9071ee3 100644 --- a/fhe-library/core-concepts/decryption-operations.mdx +++ b/fhe-library/core-concepts/decryption-operations.mdx @@ -43,12 +43,23 @@ Decryption is requested off-chain via an RPC query, returning the result only to Read more about RPC query decryption and get examples in the [CoFHEjs](/cofhejs) documentation. +### 3. Client-Published Decryption (Signature-Verified) + +The client decrypts off-chain via cofhejs, receives the plaintext result along with an **ECDSA signature** from the Threshold Network's Dispatcher, and then publishes the result on-chain by calling `FHE.publishDecryptResult()`. The TaskManager verifies the signature on-chain before storing the result. + +This combines the best of both worlds: the client controls when the result lands on-chain, while the contract can still use the decrypted value. + + +The signature cryptographically proves the result came from the authorized Threshold Network. No trust in the publisher is required — anyone holding a valid signature can submit it. + + ### Comparison Table | Method | Visibility | Gas Cost | Smart Contract Usable | Best For | |--------|-----------|----------|----------------------|----------| | **Transaction (on-chain)** | Public (on-chain) | High | Yes | Public results, contract logic | | **Query (off-chain)** | Private (off-chain) | None | No | Confidential data, external apps | +| **Client-Published (signature)** | Public (on-chain) | Medium | Yes | Client-driven settlement, permissionless delivery | --- @@ -282,7 +293,35 @@ function getRevealedValue() external view onlyOwner returns (uint32, bool) { } ``` -### Pattern 2: Batch Decryption +### Pattern 2: Client-Published Decryption with Signature Verification + +In this pattern, the client decrypts off-chain and publishes the signed result on-chain. This is useful when you want the client to control result delivery timing, or when building permissionless relayer patterns. + +```solidity +// Contract that accepts client-published decrypt results +contract SignatureVerifiedDecrypt { + euint64 public encryptedValue; + uint64 public revealedValue; + + // The client calls cofhejs.decrypt(), receives result + signature, + // then submits this transaction + function publishResult(uint64 result, bytes memory signature) external { + FHE.publishDecryptResult(encryptedValue, result, signature); + revealedValue = result; + } + + // Optionally verify without publishing + function checkSignature(uint64 result, bytes memory signature) external view returns (bool) { + return FHE.verifyDecryptResultSafe(encryptedValue, result, signature); + } +} +``` + + +The signature is verified against the `decryptResultSigner` registered in the TaskManager. If the signer is `address(0)`, verification is skipped (debug mode only). + + +### Pattern 3: Batch Decryption ```solidity function decryptMultipleValues() external onlyOwner { @@ -303,6 +342,31 @@ function retrieveDecryptedValues() external view onlyOwner } ``` +### Pattern 4: Batch Client-Published Decryption + +```solidity +function publishMultipleResults( + uint256[] memory ctHashes, + uint256[] memory results, + bytes[] memory signatures +) external { + FHE.publishDecryptResultBatch(ctHashes, results, signatures); +} +``` + +--- + +## Signature Verification Functions + +When using client-published decryption, two verification functions are available: + +| Function | Behavior on Invalid Signature | +|----------|-------------------------------| +| `FHE.verifyDecryptResult(ctHash, result, signature)` | Reverts | +| `FHE.verifyDecryptResultSafe(ctHash, result, signature)` | Returns `false` | + +Both functions accept type-specific overloads for `ebool`, `euint8`, `euint16`, `euint32`, `euint64`, `euint128`, and `eaddress`. + --- ## Related Topics diff --git a/fhe-library/reference/fhe-sol.mdx b/fhe-library/reference/fhe-sol.mdx index ea2f333..104efab 100644 --- a/fhe-library/reference/fhe-sol.mdx +++ b/fhe-library/reference/fhe-sol.mdx @@ -957,6 +957,103 @@ if (decrypted) { } ``` +## Decrypt Result Publishing & Verification + +### publishDecryptResult + +Publishes a signed decrypt result from the Threshold Network to the chain. The TaskManager verifies the ECDSA signature before storing the result. Anyone holding a valid signature can call this. + + +The ciphertext hash to publish a result for + + + +The decrypted plaintext value (type matches the ctHash type) + + + +The ECDSA signature from the Threshold Network's Dispatcher + + +```solidity +// Publish a decrypt result for a euint64 +FHE.publishDecryptResult(myEncryptedValue, uint64(42), signature); + +// Publish for an ebool +FHE.publishDecryptResult(myEncryptedBool, true, signature); +``` + +### publishDecryptResultBatch + +Publishes multiple signed decrypt results in a single transaction for gas efficiency. + + +Array of ciphertext hashes + + + +Array of decrypted plaintext values + + + +Array of ECDSA signatures (one per result) + + +```solidity +FHE.publishDecryptResultBatch(ctHashes, results, signatures); +``` + +### verifyDecryptResult + +Verifies a decrypt result signature without publishing. Reverts if the signature is invalid. + + +The ciphertext hash + + + +The decrypted plaintext value + + + +The ECDSA signature to verify + + + +Returns `true` if the signature is valid. Reverts otherwise. + + +```solidity +bool valid = FHE.verifyDecryptResult(myEncryptedValue, uint64(42), signature); +``` + +### verifyDecryptResultSafe + +Same as `verifyDecryptResult`, but returns `false` instead of reverting on invalid signatures. + + +The ciphertext hash + + + +The decrypted plaintext value + + + +The ECDSA signature to verify + + + +Returns `true` if valid, `false` if invalid (never reverts on bad signature) + + +```solidity +bool valid = FHE.verifyDecryptResultSafe(myEncryptedValue, uint64(42), signature); +if (!valid) { + revert("Invalid decrypt result signature"); +} +``` + ## Access Control ### allow