This document explains how the CoFHE SDK components connect in both mock (local testing) and production modes.
graph TB
subgraph Client["🖥️ Client Application"]
CofheSDK["CofheClient SDK"]
CofheSDK -->|uses| Encrypt["encryptInputs()"]
CofheSDK -->|uses| DecryptView["decryptForView()"]
CofheSDK -->|uses| DecryptTx["decryptForTx()"]
end
subgraph Mode["Execution Mode"]
IsMock{Is Mock Mode?}
end
subgraph MockMode["🧪 Mock Mode (Local Testing)"]
MockContracts["Mock Contracts on Hardhat"]
MockTaskMgr["MockTaskManager"]
MockACL["MockACL"]
MockZkVerifier["MockZkVerifier"]
MockThresholdNet["MockThresholdNetwork"]
MockTaskMgr -->|manages access| MockACL
MockACL -->|stores permissions| Handles["Handle Storage"]
MockZkVerifier -->|calculates ctHashes| Hashes["ctHash Values"]
MockThresholdNet -->|decrypts| Plaintext["Plaintext Results"]
end
subgraph ProdMode["🚀 Production Mode (Testnet/Mainnet)"]
SmartContracts["User Smart Contracts"]
ThresholdNetwork["Threshold Network Coprocessor"]
CoFHEAPI["CoFHE API"]
SmartContracts -->|reads encrypted| State["Encrypted State"]
ThresholdNetwork -->|computes FHE ops| Results["FHE Results"]
CoFHEAPI -->|verifies proofs| Verification["Proof Verification"]
end
CofheSDK -->|checks config| IsMock
IsMock -->|yes| MockMode
IsMock -->|no| ProdMode
linkStyle default stroke:#000,stroke-width:3px
style Client fill:#e1f5ff,stroke:#0277bd,stroke-width:3px,color:#000
style MockMode fill:#f3e5f5,stroke:#7b1fa2,stroke-width:3px,color:#000
style ProdMode fill:#e8f5e9,stroke:#388e3c,stroke-width:3px,color:#000
style Mode fill:#455a64,stroke:#263238,stroke-width:2px,color:#fff
%%{init: {"flowchart": {"nodeSpacing": 90, "rankSpacing": 90}}}%%
graph TD
Start["EncryptInputsBuilder.execute()<br/>(entrypoint)"]
Start -->|hardhat chain| MExec
Start -->|other chains| PExec
subgraph Mock["📋 MOCK MODE"]
direction TB
MExec["1. mocksExecute()<br/>EncryptInputsBuilder.mocksExecute()"]
MBits["2. Check encryptable bits<br/>cofheMocksCheckEncryptableBits(items)"]
M0["3. cofheMocksZkVerifySign()<br/>Mock verifier+sign entrypoint"]
subgraph MInner["Inside cofheMocksZkVerifySign()"]
direction TB
M1["3.1 calcCtHashes()<br/>(readContract zkVerifyCalcCtHashesPacked)"]
M2["3.2 insertCtHashes()<br/>(writeContract insertPackedCtHashes)"]
M3["3.3 createProofSignatures()<br/>(signMessage)"]
MRet["3.4 Return VerifyResult[]<br/>(ct_hash + signature)"]
end
MPackage["4. Map VerifyResult[] -> EncryptedInputs"]
EndMock["EncryptedInputs ready for tx"]
MExec --> MBits
MBits --> M0
M0 --> M1
M1 --> M2
M2 --> M3
M3 --> MRet
MRet --> MPackage
MPackage --> EndMock
end
subgraph Prod["🌐 PRODUCTION MODE"]
direction TB
PExec["1. productionExecute()<br/>EncryptInputsBuilder.productionExecute()"]
P0["2. ZK proof + verifier signature<br/>(pack + prove + verify)"]
subgraph PInner["Inside productionExecute()"]
direction TB
PInit["2.1 Init TFHE WASM<br/>initTfheOrThrow()"]
PKeys["2.2 Fetch FHE key + CRS<br/>fetchFheKeyAndCrs()"]
PBuild["2.3 Build ZK builder + CRS<br/>zkBuilderAndCrsGenerator()"]
PPack["2.4 Pack inputs<br/>zkPack(items, builder)"]
PMeta["2.5 Construct metadata<br/>constructZkPoKMetadata()"]
PProve["2.6 Prove (worker or main)<br/>zkProveWithWorker()/zkProve()"]
PVerify["2.7 Verify proof (resolve URL + POST /verify)<br/>getZkVerifierUrl() + zkVerify(...)"]
PRet["2.8 Return VerifyResult[]<br/>(ct_hash + signature)"]
end
PPackage["3. Map VerifyResult[] -> EncryptedInputs"]
EndProd["EncryptedInputs ready for tx"]
PExec --> P0
P0 --> PInit
PInit --> PKeys
PKeys --> PBuild
PBuild --> PPack
PPack --> PMeta
PMeta --> PProve
PProve --> PVerify
PVerify --> PRet
PRet --> PPackage
PPackage --> EndProd
end
linkStyle default stroke:#000,stroke-width:3px
%% Clickable nodes (GitHub-friendly links + line anchors)
click Start "https://github.com/FhenixProtocol/cofhesdk/blob/decrypt-with-proof-refinements/packages/sdk/core/encrypt/encryptInputsBuilder.ts#L553" "Entry point: EncryptInputsBuilder.execute()"
click MExec "https://github.com/FhenixProtocol/cofhesdk/blob/decrypt-with-proof-refinements/packages/sdk/core/encrypt/encryptInputsBuilder.ts#L407" "Mock path: EncryptInputsBuilder.mocksExecute()"
click MBits "https://github.com/FhenixProtocol/cofhesdk/blob/decrypt-with-proof-refinements/packages/sdk/core/encrypt/encryptInputsBuilder.ts#L421" "Mock: check encryptable bits"
click M0 "https://github.com/FhenixProtocol/cofhesdk/blob/decrypt-with-proof-refinements/packages/sdk/core/encrypt/encryptInputsBuilder.ts#L431" "Mock: calls cofheMocksZkVerifySign"
click M1 "https://github.com/FhenixProtocol/cofhesdk/blob/decrypt-with-proof-refinements/packages/sdk/core/encrypt/cofheMocksZkVerifySign.ts#L264" "Mock: calcCtHashes call site"
click M2 "https://github.com/FhenixProtocol/cofhesdk/blob/decrypt-with-proof-refinements/packages/sdk/core/encrypt/cofheMocksZkVerifySign.ts#L267" "Mock: insertCtHashes call site"
click M3 "https://github.com/FhenixProtocol/cofhesdk/blob/decrypt-with-proof-refinements/packages/sdk/core/encrypt/cofheMocksZkVerifySign.ts#L270" "Mock: createProofSignatures call site"
click PExec "https://github.com/FhenixProtocol/cofhesdk/blob/decrypt-with-proof-refinements/packages/sdk/core/encrypt/encryptInputsBuilder.ts#L453" "Production path: EncryptInputsBuilder.productionExecute()"
click P0 "https://github.com/FhenixProtocol/cofhesdk/blob/decrypt-with-proof-refinements/packages/sdk/core/encrypt/encryptInputsBuilder.ts#L461" "Prod: starts the init/pack/prove/verify sequence"
click PInit "https://github.com/FhenixProtocol/cofhesdk/blob/decrypt-with-proof-refinements/packages/sdk/core/encrypt/encryptInputsBuilder.ts#L461" "Prod: init TFHE wasm"
click PKeys "https://github.com/FhenixProtocol/cofhesdk/blob/decrypt-with-proof-refinements/packages/sdk/core/encrypt/encryptInputsBuilder.ts#L469" "Prod: fetch FHE key + CRS"
click PBuild "https://github.com/FhenixProtocol/cofhesdk/blob/decrypt-with-proof-refinements/packages/sdk/core/encrypt/encryptInputsBuilder.ts#L471" "Prod: build ZK builder + CRS"
click PPack "https://github.com/FhenixProtocol/cofhesdk/blob/decrypt-with-proof-refinements/packages/sdk/core/encrypt/encryptInputsBuilder.ts#L477" "Prod: zkPack call site"
click PMeta "https://github.com/FhenixProtocol/cofhesdk/blob/decrypt-with-proof-refinements/packages/sdk/core/encrypt/encryptInputsBuilder.ts#L484" "Prod: metadata construction"
click PProve "https://github.com/FhenixProtocol/cofhesdk/blob/decrypt-with-proof-refinements/packages/sdk/core/encrypt/encryptInputsBuilder.ts#L491" "Prod: worker decision + prove"
click PVerify "https://github.com/FhenixProtocol/cofhesdk/blob/decrypt-with-proof-refinements/packages/sdk/core/encrypt/encryptInputsBuilder.ts#L516" "Prod: resolve verifier URL + call zkVerify"
click PRet "https://github.com/FhenixProtocol/cofhesdk/blob/decrypt-with-proof-refinements/packages/sdk/core/encrypt/zkPackProveVerify.ts#L268" "Prod: zkVerify returns ct_hash + signature"
click PPackage "https://github.com/FhenixProtocol/cofhesdk/blob/decrypt-with-proof-refinements/packages/sdk/core/encrypt/encryptInputsBuilder.ts#L520" "Prod: package EncryptedInputs"
click MRet "https://github.com/FhenixProtocol/cofhesdk/blob/decrypt-with-proof-refinements/packages/sdk/core/encrypt/cofheMocksZkVerifySign.ts#L273" "Mock: returns ct_hash + signature"
click MPackage "https://github.com/FhenixProtocol/cofhesdk/blob/decrypt-with-proof-refinements/packages/sdk/core/encrypt/encryptInputsBuilder.ts#L439" "Mock: package EncryptedInputs"
click EndMock "https://github.com/FhenixProtocol/cofhesdk/blob/decrypt-with-proof-refinements/packages/sdk/core/encrypt/encryptInputsBuilder.ts#L447" "Mock: EncryptedInputs ready for tx"
click EndProd "https://github.com/FhenixProtocol/cofhesdk/blob/decrypt-with-proof-refinements/packages/sdk/core/encrypt/encryptInputsBuilder.ts#L531" "Prod: EncryptedInputs ready for tx"
style Mock fill:#f3e5f5,stroke:#7b1fa2,stroke-width:3px,color:#000
style Prod fill:#e8f5e9,stroke:#388e3c,stroke-width:3px,color:#000
style EndMock fill:#fff9c4,stroke:#f57f17,stroke-width:3px,color:#000
style EndProd fill:#fff9c4,stroke:#f57f17,stroke-width:3px,color:#000
graph TD
Start["decryptForView(ctHash, utype)"]
subgraph Check["Check Mode"]
IsMock{Mock Mode?}
end
subgraph Mock["📋 MOCK MODE"]
M1["Get plaintext from<br/>MockZkVerifier storage"]
M2["Validate permission:<br/>MockACL.isAllowed()"]
M3["Unseal output using<br/>mock sealing"]
M4["Return plaintext"]
end
subgraph Prod["🌐 PRODUCTION MODE"]
P1["Query Threshold Network<br/>via CoFHE API"]
P2["Validate permission<br/>with permit if needed"]
P3["Unseal output<br/>using TN response"]
P4["Return plaintext"]
end
Start --> IsMock
IsMock -->|yes| M1
IsMock -->|no| P1
M1 --> M2
M2 -->|allowed| M3
M2 -->|denied| Error["❌ NotAllowed Error"]
M3 --> M4
P1 --> P2
P2 -->|allowed| P3
P2 -->|denied| Error
P3 --> P4
M4 --> End["plaintext value"]
P4 --> End
linkStyle default stroke:#000,stroke-width:3px
style Mock fill:#f3e5f5,stroke:#7b1fa2,stroke-width:3px,color:#000
style Prod fill:#e8f5e9,stroke:#388e3c,stroke-width:3px,color:#000
style Error fill:#ffebee,stroke:#c62828,stroke-width:3px,color:#000
style Check fill:#455a64,stroke:#263238,stroke-width:2px,color:#fff
style End fill:#fff9c4,stroke:#f57f17,stroke-width:3px,color:#000
graph TD
Start["decryptForTx(ctHash)"]
subgraph Check["Check Mode"]
IsMock{Mock Mode?}
end
subgraph Mock["📋 MOCK MODE"]
M1["Query MockThresholdNetwork<br/>via publicClient.readContract()"]
M2["Check permission:<br/>isAllowedWithPermission()<br/>or globallyAllowed()"]
M3["Get plaintext from storage"]
M4["Generate signature<br/>using MOCKS_DECRYPT_RESULT_SIGNER_KEY"]
M5["Return DecryptForTxResult<br/>ctHash + decryptedValue + signature"]
end
subgraph Prod["🌐 PRODUCTION MODE"]
P1["Query Threshold Network<br/>for encrypted result"]
P2["Validate permission<br/>with permit"]
P3["Get plaintext from TN"]
P4["Receive TN signature<br/>in response"]
P5["Return DecryptForTxResult<br/>ctHash + decryptedValue + signature"]
end
Start --> IsMock
IsMock -->|yes| M1
IsMock -->|no| P1
M1 --> M2
M2 -->|allowed| M3
M2 -->|denied| Error["❌ NotAllowed Error"]
M3 --> M4
M4 --> M5
P1 --> P2
P2 -->|allowed| P3
P2 -->|denied| Error
P3 --> P4
P4 --> P5
M5 --> End["DecryptForTxResult<br/>ready to publish"]
P5 --> End
linkStyle default stroke:#000,stroke-width:3px
style Mock fill:#f3e5f5,stroke:#7b1fa2,stroke-width:3px,color:#000
style Prod fill:#e8f5e9,stroke:#388e3c,stroke-width:3px,color:#000
style Error fill:#ffebee,stroke:#c62828,stroke-width:3px,color:#000
style Check fill:#455a64,stroke:#263238,stroke-width:2px,color:#fff
style End fill:#fff9c4,stroke:#f57f17,stroke-width:3px,color:#000
graph TB
Query["Decrypt Request"]
subgraph Scenarios["Access Control Scenarios"]
NoPerm["No Permit Provided"]
WithPerm["Permit Provided"]
end
Query --> Scenarios
subgraph GlobalCheck["Global Allowance Check"]
CheckGlobal["MockACL.globalAllowed(ctHash)?"]
CheckGlobal -->|yes| AllowGlobal["✅ Decrypt allowed"]
CheckGlobal -->|no| DenyGlobal["❌ NotAllowed"]
end
subgraph PermCheck["Permit-Based Check"]
ValidatePerm["Validate Permit:<br/>signature, expiration"]
ValidatePerm -->|valid| CheckPerm["MockTaskManager<br/>.isAllowedWithPermission()"]
ValidatePerm -->|invalid| DenyPerm["❌ Permission Invalid"]
CheckPerm -->|allowed| AllowPerm["✅ Decrypt allowed"]
CheckPerm -->|denied| DenyAccess["❌ Permission Denied"]
end
NoPerm --> GlobalCheck
WithPerm --> PermCheck
AllowGlobal --> Decrypt["Proceed with decryption"]
AllowPerm --> Decrypt
DenyGlobal --> Error["Error returned"]
DenyPerm --> Error
DenyAccess --> Error
linkStyle default stroke:#000,stroke-width:3px
style GlobalCheck fill:#fff3e0,stroke:#f57c00,stroke-width:3px,color:#000
style PermCheck fill:#e0f2f1,stroke:#00897b,stroke-width:3px,color:#000
style Decrypt fill:#e8f5e9,stroke:#388e3c,stroke-width:3px,color:#000
style Error fill:#ffebee,stroke:#c62828,stroke-width:3px,color:#000
style Scenarios fill:#455a64,stroke:#263238,stroke-width:2px,color:#fff
The ZK Verifier is responsible for the encryption phase - converting plaintext values into encrypted ciphertext handles (ctHashes) that can be used in smart contracts.
The SDK uses the term "ZK Verifier" for the component that ultimately produces on-chain verifiable attestations that an encrypted handle (ctHash) is well-formed.
- Production (testnet/mainnet): the verifier is an off-chain verifier service (configured via
supportedChains[].verifierUrl).- The SDK calls
POST {verifierUrl}/verify(seezkPackProveVerify.ts). - That service verifies the ZK proof and returns
(ct_hash, signature)for each input.
- The SDK calls
- Mock mode (Hardhat/local testing): there is no real ZK proof verification.
MockZkVerifier(contract) exists only to deterministically derive ctHashes and store ctHash→plaintext mappings for mock FHE operations.- The SDK produces a mock signature using
MOCKS_ZK_VERIFIER_SIGNER_PRIVATE_KEYso you can still exercise the "signed input" plumbing.
In other words: in production, the verifier lives off-chain; on-chain contracts never call it directly. Contracts only validate a signature that originates from the verifier.
In production mode, ZK proofs are generated locally in the client environment:
- Web: proof generation runs in the browser and can be offloaded to a Web Worker.
- Node: proof generation runs in-process.
At a high level, the SDK:
- Fetches network parameters required for proving (FHE public key and CRS).
- Packs the inputs into a ciphertext list.
- Runs the proving algorithm (WASM-backed) and produces a serialized
packed_listblob that contains ciphertexts + proof.
This design keeps plaintext values on the client and makes proof generation the main “heavy” step.
The SDK sends the verifier:
packed_list: serialized ciphertext/proof blobaccount_addr,security_zone,chain_id: metadata that is also bound into the proof statement (seeconstructZkPoKMetadata(...))
The verifier checks that the blob is a valid ZK proof for the expected statement. The exact statement/circuit is verifier-defined, but conceptually it covers:
- Proof validity under expected parameters (CRS and the network’s public FHE key).
- Well-formed encryption (the ciphertext encoding is valid; the prover demonstrates knowledge of plaintext(s) and randomness consistent with the ciphertext list).
- Metadata binding (the statement is bound to
(account_addr, security_zone, chain_id)to prevent out-of-context reuse).
If verification succeeds, the verifier returns a ctHash (handle derived from the proven ciphertext) and a signature that attests that this ctHash passed verification under the expected metadata.
Verification does not re-run proof generation. Proving and verifying are different algorithms:
- Proving: expensive; run by the client to produce a proof.
- Verifying: cheaper; run by the verifier to check the proof against public parameters and the expected statement.
The returned signature is the artifact that the on-chain Task Manager ultimately authenticates.
The protocol’s trust boundary here is not “an HTTP endpoint”, it’s an authorization check enforced on-chain: encrypted inputs are accepted only if they carry a signature that verifies against an authorized verifier identity configured in the CoFHE system contracts.
- The on-chain trust anchor is the Task Manager contract (the CoFHE system contract your app uses via
FHE.sol). - When a contract receives an
EncryptedInput(ctHash + metadata + signature), the Task Manager verifies the signature and accepts the input only if it was signed by the configured verifier signer.- You can see the exact mechanism in the mocks:
MockTaskManager.verifyInput()recovers the signer and compares it againstverifierSigner.
- You can see the exact mechanism in the mocks:
Operationally, in production the verifier service/API returns signatures that are produced by the verifier’s authorized signing authority after the ZK proof checks pass. How that signing authority is implemented is intentionally an off-chain concern (it could be a single signer, an HSM-backed key, or a distributed/threshold signer), but the on-chain rule is stable: only signatures from the authorized verifier identity are accepted.
The contract “trusts the verifier” in the same way it “trusts an oracle”: through governance/deployment configuration plus cryptographic authentication.
- The contract already trusts the Task Manager system contract.
- The Task Manager is configured (by the protocol / deployment owner) with the expected
verifierSigneraddress. - A valid signature from that address is treated as an attestation: “this ctHash corresponds to a correctly generated ciphertext under the expected metadata (chain/security zone/account)”.
This turns “trust the verifier” into a standard on-chain pattern: trust a key that can be rotated and governed, rather than trusting an off-chain process directly.
In Mock Mode (Local Testing):
- Calculates ctHashes: Takes plaintext values and generates deterministic ciphertext handles
- Stores Mappings: Writes
ctHash → plaintextinto the mock on-chain storage (viaMockZkVerifier→MockTaskManager) so mock FHE ops can “decrypt” later - Signs Inputs: Creates a signature using
MOCKS_ZK_VERIFIER_SIGNER_PRIVATE_KEYto prove the encrypted inputs are valid
In Production:
- TFHE Encryption: Performs actual Fully Homomorphic Encryption using the network's public key
- Zero-Knowledge Proofs: Generates cryptographic proofs that the encryption was done correctly
- CoFHE Verification: Submits proofs to the CoFHE verifier service/API (
supportedChains[].verifierUrl) which verifies and returns signatures for on-chain validation
The name comes from Zero-Knowledge Proof Verification - in production, this component verifies that:
- The encrypted data was created correctly
- The encryption matches the claimed plaintext structure
- No one can learn anything about the plaintext from the proof
In mock mode, we skip the heavy cryptographic operations but maintain the same API structure.
Without ZK Verifier, there's no way to trust encrypted inputs:
❌ Attack Without ZK Verifier:
// Malicious user could submit fake encrypted data:
bytes32 fakeCtHash = 0xabcd1234...; // Just random bytes, not actual encryption
contract.storeValue(fakeCtHash); // Contract accepts it blindly
// Later: Decryption fails or returns garbage✅ Protection With ZK Verifier:
// ZK Verifier ensures the ctHash is legitimate:
EncryptedInputs memory inputs = client.encryptInputs([42, 100]).execute();
// inputs.ctHashes[0] comes with a valid signature/proof
// CoFHE system contracts (Task Manager via FHE.sol) verify the signature on-chain
contract.storeValue(inputs.ctHashes[0], inputs.signatures[0]);
// If signature is invalid → the CoFHE verification step revertsThe Core Security Guarantee:
The ZK Verifier solves the "Who encrypted this?" problem:
- 🔴 Without it: Anyone can create arbitrary ctHash values and claim they're encrypted
- 🟢 With it: Only properly encrypted data (with valid signatures/proofs) is accepted
In Production: The ZK proof mathematically guarantees that:
- The ctHash was generated from actual encrypted data
- The encryption used the correct public key
- The data structure matches what the smart contract expects
In Mock Mode: The signature from MOCKS_ZK_VERIFIER_SIGNER_PRIVATE_KEY serves the same purpose:
- Only SDK-generated ctHashes have valid signatures
- Smart contracts (via the CoFHE Task Manager) can verify the signature before accepting encrypted inputs
- Tests can't accidentally use invalid/corrupted encrypted data
Note: in some mock/debug deployments the verifier signer may be intentionally unset (verifierSigner == address(0)), which disables signature checks.
Think of ZK Verifier as the "encryption gateway":
- Before: You have plaintext numbers (42, 100, 256)
- After: You have encrypted handles (ctHashes) that can be safely used in smart contracts
- Guarantee: The ZK proof ensures the encryption is valid without revealing the plaintext
graph LR
Plaintext["Plaintext Values<br/>42, 100, 256"]
subgraph ZKVerifier["🔐 ZK Verifier"]
Encrypt["Encrypt Each Value"]
Proof["Generate Proof"]
Sign["Sign with ZK Signer"]
end
Ciphertext["Encrypted Handles<br/>0xabc..., 0xdef..., 0x123..."]
SmartContract["Smart Contract<br/>Can use these safely"]
Plaintext --> Encrypt
Encrypt --> Proof
Proof --> Sign
Sign --> Ciphertext
Ciphertext --> SmartContract
linkStyle default stroke:#000,stroke-width:3px
style ZKVerifier fill:#e1f5ff,stroke:#0277bd,stroke-width:3px,color:#000
style Plaintext fill:#fff9c4,stroke:#f57f17,stroke-width:3px,color:#000
style Ciphertext fill:#e8f5e9,stroke:#388e3c,stroke-width:3px,color:#000
style SmartContract fill:#f3e5f5,stroke:#7b1fa2,stroke-width:3px,color:#000
Related Components:
- MockZkVerifier (contract): Stores the ctHash→plaintext mappings in mock mode
- MOCKS_ZK_VERIFIER_SIGNER_PRIVATE_KEY (constant): Used to sign encrypted inputs
- cofheMocksZkVerifySign() (function): SDK function that performs mock encryption
graph LR
subgraph Constants["🔑 Mock Constants"]
DecryptSigner["MOCKS_DECRYPT_RESULT_SIGNER_PRIVATE_KEY<br/>0x59c6995e..."]
ZkVerifierSigner["MOCKS_ZK_VERIFIER_SIGNER_PRIVATE_KEY<br/>0x6C8D7F76..."]
end
subgraph Usage["📍 Where Used"]
ZkSign["cofheMocksZkVerifySign()<br/>Signs encrypted inputs"]
DecryptSign["cofheMocksDecryptForTx()<br/>Signs decrypt results"]
HardhatAccts["Hardhat Plugin Default Accounts"]
end
subgraph Contracts["📄 Mock Contracts"]
MockZk["MockZkVerifier<br/>Calculates ctHashes"]
MockTM["MockTaskManager<br/>Manages decryption"]
end
ZkVerifierSigner --> ZkSign
DecryptSigner --> DecryptSign
ZkSign --> MockZk
DecryptSign --> MockTM
HardhatAccts --> Contracts
linkStyle default stroke:#000,stroke-width:3px
style Constants fill:#ffe0b2,stroke:#ef6c00,stroke-width:3px,color:#000
style Usage fill:#b3e5fc,stroke:#0277bd,stroke-width:3px,color:#000
style Contracts fill:#f3e5f5,stroke:#7b1fa2,stroke-width:3px,color:#000
This sequence diagram shows the complete flow in mock/testing mode. In production, the steps are similar but use Threshold Network API instead of mock contracts.
sequenceDiagram
participant User as User Code
participant SDK as CofheSDK
participant Mock as Mock Contracts
participant Storage as Storage
User->>SDK: encryptInputs([plaintext1, plaintext2])
Note over SDK,Mock: ENCRYPTION PHASE
SDK->>Mock: MockZkVerifier.zkVerifyCalcCtHashesPacked()
Mock-->>SDK: [ctHash1, ctHash2]
SDK->>Mock: MockZkVerifier.insertPackedCtHashes()
Mock->>Storage: store ctHash → plaintext mapping
SDK->>SDK: Sign with ZK Verifier Key
SDK-->>User: EncryptedInputs[ctHash1, ctHash2]
User->>User: Write encrypted inputs to smart contract
Note over SDK,Mock: DECRYPTION PHASE (decryptForView)
User->>SDK: decryptForView(ctHash1, utype)
SDK->>Mock: MockACL.isAllowed(ctHash1, account)
Mock-->>SDK: allowed=true
SDK->>Mock: MockZkVerifier.getPlaintext(ctHash1)
Mock->>Storage: retrieve mapping
Storage-->>Mock: plaintext1
Mock-->>SDK: plaintext1
SDK-->>User: plaintext1
Note over SDK,Mock: DECRYPTION PHASE (decryptForTx)
User->>SDK: decryptForTx(ctHash1)
SDK->>Mock: MockThresholdNetwork.decryptForTx(ctHash1)
Mock->>Mock: Check permission
Mock->>Storage: Get plaintext
Mock->>SDK: plaintext + signature
SDK-->>User: DecryptForTxResult{ctHash, value, signature}
User->>User: publishDecryptResult(ctHash1, plaintext1, sig)
| Component | Mock Mode | Production Mode | Purpose |
|---|---|---|---|
| EncryptInputs | Uses MockZkVerifier to calculate ctHashes | Uses TFHE + ZK proofs | Generate encrypted inputs |
| decryptForView | Reads from MockZkVerifier storage + checks MockACL, returns sealed plaintext, unseals with permit key | Queries Threshold Network for sealed plaintext, unseals with permit sealing key | View calls (read & unseal plaintext, no proof) |
| decryptForTx | Calls MockThresholdNetwork with permission check, gets plaintext + signature | Queries Threshold Network for plaintext + signature | Transaction submission (needs signature for on-chain verification) |
| Permits | Stored in-memory + validated against MockACL | Stored on-chain + validated by TN | Access control mechanism |
| Signatures | Mock signer key (hardcoded for testing) | Real TN signer (from network) | Proof of decryption |
| State Storage | In-memory maps in mock contracts | On-chain encrypted state | Where encrypted values live |
The CoFHE SDK provides a unified API that works identically in both modes:
// Same code works in both mock and production!
const encrypted = await client.encryptInputs([Encryptable.uint32(42)]).execute();
const plaintext = await client.decryptForView(encrypted[0].ctHash, FheTypes.Uint32).execute();The difference is implementation:
- Mock: Direct function calls to in-memory contracts
- Production: RPC calls to network (Threshold Network, CoFHE API)
This allows developers to:
- ✅ Test locally with mocks (fast, no network)
- ✅ Deploy same code to production (testnet/mainnet)
- ✅ Debug with complete visibility in mock mode
- ✅ Trust that production will work the same way
Mock Implementations:
packages/sdk/core/encrypt/cofheMocksZkVerifySign.ts- Encryption in mock modepackages/sdk/core/decrypt/cofheMocksDecryptForTx.ts- decryptForTx in mock modepackages/sdk/core/decrypt/cofheMocksDecryptForView.ts- Decrypting in view calls (mock mode)packages/mock-contracts/contracts/MockTaskManager.sol- Main mock contractpackages/mock-contracts/contracts/MockACL.sol- Permission management
Client API:
packages/sdk/core/client.ts- CofheClient implementationpackages/sdk/core/decrypt/decryptForViewBuilder.ts- decryptForView builderpackages/sdk/core/decrypt/decryptForTxBuilder.ts- decryptForTx builder
Tests:
packages/hardhat-plugin-test/test/decryptForTx-builder.test.ts- Builder testspackages/hardhat-plugin-test/test/decryptForTx-publish.test.ts- Publish flow test