Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 34 additions & 2 deletions deep-dive/cofhe-components/task-manager.mdx
Original file line number Diff line number Diff line change
@@ -1,13 +1,45 @@
---
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"
---


| Aspect | Description |
|---------|-------------|
| **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. <br/>• Generates unique handles that act as references to the results of FHE operations. These results are computed asynchronously off-chain. <br/>• 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. <br/>• Generates unique handles that act as references to the results of FHE operations. These results are computed asynchronously off-chain. <br/>• Emits structured events containing the unique handle of the ciphertext, operation type, and other required metadata. <br/>• 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.
33 changes: 33 additions & 0 deletions deep-dive/cofhe-components/threshold-network.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,36 @@ 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` |

<Note>
For on-chain verification via `FHE.publishDecryptResult()`, use `"evm"` format so the signature is directly compatible with the TaskManager's ECDSA verification.
</Note>

### 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).
19 changes: 15 additions & 4 deletions deep-dive/data-flows/decryption-request-flow.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -55,18 +55,29 @@ The Threshold Network performs secure decryption:
- Perform secure decryption
</Step>

<Step title="FheOS Notifies the Result Processor with the decrypt result">
After decryption is complete: 7️⃣
<Step title="Result Delivery (Two Paths)">
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):**
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we keep this Path? @roeezolantz

- 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

<Note>
Path B enables permissionless result delivery — anyone holding a valid signature can publish. This is useful for client-driven settlement or relayer patterns.
</Note>
</Step>

<Step title="TaskManager emit event with decryption result">
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
</Step>
</Steps>

2 changes: 1 addition & 1 deletion docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,7 @@
"deep-dive/data-flows/encryption-request-flow",
"deep-dive/data-flows/fhe-operation-request-flow",
"deep-dive/data-flows/decryption-request-flow",
"deep-dive/data-flows/decrypt-from-cofhejs"
"deep-dive/data-flows/off-chain-decryption-flow"
]
},
{
Expand Down
38 changes: 38 additions & 0 deletions fhe-library/core-concepts/decryption-operations.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,23 @@ The client calls `decryptForView` off-chain to obtain the plaintext for display
Use `decryptForTx` when you need to act on the decrypted value in a smart contract. Use `decryptForView` when you only need to display the value in a UI.
</Note>

### 3. Client-Published Decryption (Signature-Verified)

The client decrypts off-chain via `decryptForTx`, 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.

<Note>
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.
</Note>

### Comparison Table

| Method | Visibility | Gas Cost | Smart Contract Usable | Best For |
|--------|-----------|----------|----------------------|----------|
| **`decryptForTx`** | Public (once published on-chain) | Gas for the publish/verify tx | Yes | Public results, contract logic |
| **`decryptForView`** | Private (off-chain only) | None | No | UI display, confidential data |
| **Client-Published (signature)** | Public (on-chain) | Medium | Yes | Client-driven settlement, permissionless delivery |

---

Expand Down Expand Up @@ -237,6 +248,33 @@ await tx.wait();

---

## 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`.

---

## Best Practices

<CardGroup cols={2}>
Expand Down
94 changes: 68 additions & 26 deletions fhe-library/reference/fhe-sol.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -957,59 +957,101 @@ if (decrypted) {
}
```

## Decrypt Result Publishing & Verification

### publishDecryptResult

Publishes a decrypted result on-chain by verifying the Threshold Network signature. The plaintext is stored on-chain and can be read by anyone. Use this when the decrypted value should be publicly visible after verification.
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.

<ParamField body="ctHash" type="ebool | euint8 | euint16 | euint32 | euint64 | euint128 | eaddress" required>
Ciphertext handle of the encrypted value that was decrypted off-chain
<ParamField body="ctHash" type="ebool | euint8 | euint16 | euint32 | euint64 | euint128 | eaddress | uint256" required>
The ciphertext hash to publish a result for
</ParamField>

<ParamField body="plaintext" type="bool | uint8 | uint16 | uint32 | uint64 | uint128 | address" required>
The decrypted plaintext value returned by `decryptForTx`
<ParamField body="result" type="bool | uint8 | uint16 | uint32 | uint64 | uint128 | address | uint256" required>
The decrypted plaintext value (type matches the ctHash type)
</ParamField>

<ParamField body="signature" type="bytes" required>
The Threshold Network signature proving the plaintext is authentic
The ECDSA signature from the Threshold Network's Dispatcher
</ParamField>

<Warning>
The transaction reverts if the signature is invalid. The encrypted value must have been granted public access via `allowPublic` before decryption was requested off-chain.
</Warning>

```solidity
// Publish the decrypted result — plaintext is stored on-chain
FHE.publishDecryptResult(encryptedBid, bidPlaintext, bidSignature);
// Publish a decrypt result for a euint64
FHE.publishDecryptResult(myEncryptedValue, uint64(42), signature);

// The value is now publicly readable
// Publish for an ebool
FHE.publishDecryptResult(myEncryptedBool, true, signature);
```

### verifyDecryptResult

Verifies a Threshold Network signature for a decrypted value without storing the plaintext on-chain. Use this when you need to act on the decrypted value within the transaction but don't want to persist it publicly.
Verifies a decrypt result signature without publishing. Reverts if the signature is invalid.

<ParamField body="ctHash" type="ebool | euint8 | euint16 | euint32 | euint64 | euint128 | eaddress" required>
Ciphertext handle of the encrypted value that was decrypted off-chain
<ParamField body="ctHash" type="ebool | euint8 | euint16 | euint32 | euint64 | euint128 | eaddress | uint256" required>
The ciphertext hash
</ParamField>

<ParamField body="plaintext" type="bool | uint8 | uint16 | uint32 | uint64 | uint128 | address" required>
The decrypted plaintext value returned by `decryptForTx`
<ParamField body="result" type="bool | uint8 | uint16 | uint32 | uint64 | uint128 | address | uint256" required>
The decrypted plaintext value
</ParamField>

<ParamField body="signature" type="bytes" required>
The Threshold Network signature proving the plaintext is authentic
The ECDSA signature to verify
</ParamField>

<Tip>
Use `verifyDecryptResult` instead of `publishDecryptResult` when you only need to use the decrypted value within the transaction logic without making it permanently public.
</Tip>
<ResponseField name="valid" type="bool">
Returns `true` if the signature is valid. Reverts otherwise.
</ResponseField>

```solidity
bool valid = FHE.verifyDecryptResult(myEncryptedValue, uint64(42), signature);
```

### publishDecryptResultBatch

Publishes multiple signed decrypt results in a single transaction for gas efficiency.

<ParamField body="ctHashes" type="uint256[]" required>
Array of ciphertext hashes
</ParamField>

<ParamField body="results" type="uint256[]" required>
Array of decrypted plaintext values
</ParamField>

<ParamField body="signatures" type="bytes[]" required>
Array of ECDSA signatures (one per result)
</ParamField>

```solidity
// Verify the proof and use the value — plaintext is NOT stored on-chain
FHE.verifyDecryptResult(encryptedAmount, amount, signature);
FHE.publishDecryptResultBatch(ctHashes, results, signatures);
```

### verifyDecryptResultSafe

Same as `verifyDecryptResult`, but returns `false` instead of reverting on invalid signatures.

<ParamField body="ctHash" type="ebool | euint8 | euint16 | euint32 | euint64 | euint128 | eaddress | uint256" required>
The ciphertext hash
</ParamField>

<ParamField body="plaintext" type="bool | uint8 | uint16 | uint32 | uint64 | uint128 | address | uint256" required>
The decrypted plaintext value
</ParamField>

<ParamField body="signature" type="bytes" required>
The ECDSA signature to verify
</ParamField>

// Use the verified plaintext in transaction logic
require(amount >= minThreshold, "Below minimum");
<ResponseField name="valid" type="bool">
Returns `true` if valid, `false` if invalid (never reverts on bad signature)
</ResponseField>

```solidity
bool valid = FHE.verifyDecryptResultSafe(myEncryptedValue, uint64(42), signature);
if (!valid) {
revert("Invalid decrypt result signature");
}
```

## Access Control
Expand Down