解析:可組合運行時(FhevmRuntime)與可擴展客戶端(FhevmClient)的設計與實現(xiàn))
fhevm js-sdk 架構(gòu)解析可組合運行時FhevmRuntime與可擴展客戶端FhevmClient的設計與實現(xiàn)【免費下載鏈接】fhevmFHEVM, a full-stack framework for integrating Fully Homomorphic Encryption (FHE) with blockchain applications項目地址: https://gitcode.com/GitHub_Trending/fh/fhevm導讀fhevm js-sdk 是 fhEVM 全棧框架的前端 JavaScript SDK負責在鏈下完成 TFHE 密文的加密、解密、簽名許可生成與密鑰管理。本文以其架構(gòu)設計文檔 sdk/js-sdk/notes/ARCHITECTURE.md 為骨架講解 SDK 的兩大核心抽象——可組合的運行時FhevmRuntime與可擴展的客戶端FhevmClient并對照倉庫源碼運行時實現(xiàn)、模塊工廠、初始化鏈路逐條印證其設計原則。讀完本文你將理解 SDK 為何能做到零配置可用、按需加載 WASM、惰性冪等初始化、順序無關的可鏈式配置并能正確使用createFhevmClient/createFhevmEncryptClient/createFhevmDecryptClient三種工廠以及extend()、init()、ready等生命周期 API。SDK 設計原則一份可驗證的契約清單架構(gòu)文檔開篇即列出約四十條設計原則它們是理解后續(xù)所有代碼實現(xiàn)的驗收標準。將其歸納為幾個主題并與源碼一一對應順序無關與可鏈式配置Order-independent API / Configuration is chainable and order-independent配置項通過withXXX(...)形式鏈式設置且withPublicKey、fetchPublicKey當前實現(xiàn)為fetchFheEncryptionKeyBytes的調(diào)用順序不影響最終結(jié)果配置在調(diào)用時解析resolve config at call time而非創(chuàng)建時捕獲Extensions must not capture config at creation time。惰性、冪等、共享的初始化Lazy, Idempotent, Sharedinit()無論被手動調(diào)用還是被內(nèi)部首次調(diào)用觸發(fā)永遠返回同一個 Promise多個并發(fā)調(diào)用共享同一次初始化。對應源碼見 CoreFhevm-p.ts 中的init/ready實現(xiàn)。創(chuàng)建必須純凈Creation must be pure構(gòu)造客戶端不執(zhí)行任何異步操作、不加載 WASM、不發(fā) RPCno async at construction一切延后到首次使用。可樹搖Treeshackable加密與解密是兩個相互獨立的模塊各自綁定一個獨立的 WASM 模塊TFHE 與 TKMS未使用的模塊絕不加載SDK constraints條目以此避免不必要的網(wǎng)絡與內(nèi)存開銷。可組合、可擴展Composable Extensions / Composable runtime modules能力以模塊為單位掛載到運行時以動作組actions為單位掛載到客戶端擴展物可復用、可自由組合。錯誤可見性配置缺失或錯誤時拋出清晰錯誤信息Throw clear error messages運行時執(zhí)行校驗Validation is performed at runtime絕不靜默誤用no silent misuse。TypeScript 不做過度約束Avoid over-constraining Typescript類型只描述能力邊界不限制擴展組合方式。多運行時共存生產(chǎn)運行時與 mock 運行時可同時存在多個客戶端可共享同一個運行時模塊可以是 JS 運行時內(nèi)的單例如 WASM 模塊。這些原則并非停留在文檔層面下文將從源碼結(jié)構(gòu)上逐一給出實現(xiàn)證據(jù)。架構(gòu)總覽runtime 與 clients 的兩層模型架構(gòu)文檔將 SDK 分為兩層runtimeFhevmRuntime可組合的運行時。一個運行時由一組模塊module構(gòu)成模塊可動態(tài)添加多個運行時可共享同一模塊實例某些模塊在 JS 運行時內(nèi)是全局唯一的例如 WASM 模塊。運行時的創(chuàng)建與擴展都必須是純凈的不產(chǎn)生副作用。每個模塊可能有 CPU 密集的初始化步驟初始化遵循冪等、惰性或手動。clientsFhevmClient每個客戶端擁有一個運行時一個運行時可被多個客戶端共享。客戶端本質(zhì)上是運行時 一組用于啟用特定功能的附加參數(shù)。普通 SDK 使用者操作的是客戶端而非運行時——運行時始終是內(nèi)部組件Runtime should remain an internal component。客戶端通過extend(...)擴展新函數(shù)通過withXXX(...)設置配置參數(shù)。這一分層在類型定義中體現(xiàn)得十分清晰FhevmBase持有runtime、chain、client、options四個只讀字段見 coreFhevmClient.ts而運行時接口FhevmRuntime只暴露ethereum、relayer兩個基礎模塊、uid、config以及按模塊名重載的extend(factory)見 coreFhevmRuntime.ts。// runtime 的類型骨架簡化 interface FhevmRuntime { readonly ethereum: EthereumModule; // 鏈上合約讀取模塊 readonly relayer: RelayerModule; // 中繼器 HTTP 模塊 readonly uid: string; readonly config: FhevmRuntimeConfig; extend(factory: DecryptModuleFactory): this { readonly decrypt: DecryptModule }; extend(factory: EncryptModuleFactory): this { readonly encrypt: EncryptModule }; }注意extend返回類型是this { readonly decrypt: ... }——這正是 TypeScript 層面實現(xiàn)組合式擴展、且不破壞原有類型的機制每次擴展都會在類型上累加模塊能力而無需在構(gòu)造時聲明全部模塊避免構(gòu)造函數(shù)爆炸no constructor explosion。三種工廠函數(shù)與部分客戶端架構(gòu)文檔給出了三類客戶端的構(gòu)建方式全量客戶端、僅解密客戶端、僅加密客戶端。當前倉庫中 ethers 與 viem 兩個適配層各有一套同名工廠位于 sdk/js-sdk/src/ethers/clients 與 sdk/js-sdk/src/viem/clients。全量客戶端createFhevmClient// full client (chain, provider, encrypt module, decrypt module) const fhevmFull createFhevmClient({ chain, provider });其實現(xiàn)正是基礎客戶端 解密動作組 加密動作組的兩次extend見 ethers/clients/createFhevmClient.tsexport function createFhevmClientchain extends FhevmChain, provider extends EthersT.ContractRunner(parameters: { readonly provider: provider; readonly chain: chain; readonly options?: FhevmOptions | undefined; }): FhevmClientchain, WithAll, provider { const c createFhevmBaseClient(parameters); return c.extend(decryptActions).extend(encryptActions); }而createFhevmBaseClient見 ethers/clients/createFhevmBaseClient.ts先通過createCoreFhevm創(chuàng)建裸客戶端再extend(baseActions)掛載基礎動作組。也就是說任何客戶端都必然包含 base 層extend只會在其之上繼續(xù)疊加能力。部分客戶端按需加載 WASM 的關鍵// partial decrypt client (chain, provider, decrypt module, no encrypt module) const fhevmDecrypt createFhevmDecryptClient({ chain, provider }); // partial encrypt client (chain, provider, encrypt module, no decrypt module) const fhevmEncrypt createFhevmEncryptClient({ chain, provider }); // create with optional publicKeyBytes, const fhevmEncrypt createFhevmEncryptClient({ chain, provider, publicKeyBytes, });viem 版加密工廠見 viem/clients/createFhevmEncryptClient.ts結(jié)構(gòu)相同createFhevmBaseClient(parameters)后僅extend(encryptActions)。這樣創(chuàng)建的加密客戶端不會加載 TKMS 解密 WASM解密客戶端不會加載 TFHE 加密 WASM——這是兩個模塊、兩個 WASM、按需加載原則的直接落地。說明publicKeyBytes只是架構(gòu)筆記中描述的預期 API 形態(tài)。當前實現(xiàn)中加密公鑰通過createFhevmEncryptClient的options.fheEncryptionKey傳入或由客戶端從 relayer 拉取fetchFheEncryptionKeyBytes見 base.ts。文檔中publicKeyBytes can be fetched independently的描述與當前fetchFheEncryptionKeyBytes的設計一致——公鑰可獨立于客戶端獲取。部分客戶端到全量客戶端extend 的升級通道架構(gòu)文檔強調(diào)Given clientA and clientB it should always be possible to extend clientA and/or clientB so that clientA clientB給定任意兩個客戶端總能通過擴展使它們的能力相等并且當部分客戶端被創(chuàng)建后SDK 應允許將其擴展為全量客戶端。// Convert partial client to full client const fhevmFull fhevmEncrypt.extend(decryptActions);其類型層面等價于FhevmEncryptClientWithEncrypt→FhevmClientWithAll完全符合extend的類型累加語義。decryptActions是一個接收客戶端為參數(shù)、返回一組閉包捕獲客戶端的函數(shù)Function groups usually depends on modules that must be extended to the client runtime to run properly例如解密動作組依賴decryptModule擴展后客戶端必須重新初始化因為底層新增的 decrypt 模塊需要初始化after client extend, the client must be initialized again。這一點在源碼中有明確的強制約束extendCoreFhevm見 CoreFhevm-p.ts要求 actionsFactory 返回的runtime必須與客戶端的 runtime 是同一個實例否則拋錯并且把擴展攜帶的init函數(shù)注冊進#initFns集合、同時將#readyPromise置為undefined——強制下一次調(diào)用必須重新走一遍初始化。extend唯一合法的能力擴展通道運行時層面的extend實現(xiàn)位于 CoreFhevmRuntime-p.ts其機制可以概括為占位符placeholder單次填充 工廠引用冪等構(gòu)造運行時CoreFhevmRuntimeImpl時#encrypt、#decrypt都是空對象占位符對應模塊槽位slot注冊在一個Map中L146-L149。createExtendFnL32-L79調(diào)用模塊工廠moduleFactory(runtime)工廠必須恰好返回一個鍵如encryptSDK 據(jù)此查找對應槽位同一工廠引用再次 extend → 冪等 no-opfactories.has(moduleFactory)直接返回自身槽位已被不同工廠填充 → 拋錯Already extended: moduleName不允許二次擴展同一模塊未知模塊名 → 拋錯Unknown module: moduleName。填充后的占位符被Object.freeze運行時實例也在構(gòu)造末尾Object.freeze(this)L157并凍結(jié)類與原型L187-L188——運行時不變量不可被外部篡改。對外校驗通過instanceof加私有 token 完成createFhevmRuntime需要調(diào)用方持有 owner tokenassertIsFhevmRuntime/verifyFhevmRuntime保證傳入的是真實 SDK 運行時L200-L233。模塊工廠的真實形態(tài)可在加密模塊看到encryptModule: EncryptModuleFactory (runtime) Object.freeze({ encrypt: Object.freeze({ initTfheModule, getTfheModuleInfo, parseTFHEProvenCompactCiphertextList, buildWithProofPacked, serialize/deserialize 密鑰與 CRS }) })見 modules/encrypt/module/index.ts解密模塊則暴露initTkmsModule、getTkmsModuleInfo、decryptAndReconstruct、TKMS 私鑰的生成/序列化/校驗等見 modules/decrypt/module/index.ts。客戶端層面的extend則由extendCoreFhevm實現(xiàn)把 actions 中每個函數(shù)通過Object.defineProperty不可寫、不可配置掛到客戶端實例上并跳過已存在的鍵if (key in client) continue從而避免動作組之間的命名沖突。init / ready惰性、冪等、共享的初始化鏈路客戶端生命周期 API架構(gòu)文檔給出了完整的生命周期調(diào)用方式// returns a promise (eq to { return init(); }) await fhevmEncrypt.ready; // manual init call (fetch key if needed) await fhevmEncrypt.init();源碼中CoreFhevm-p.tsinit與ready的實現(xiàn)印證了冪等、共享、惰性三條原則init: { value: (): Promisevoid { this.#readyPromise ?? Promise.all([...this.#initFns].map((fn) fn(this))).then(() {}); return this.#readyPromise; }, }, ready: { get: (): Promisevoid this.init(), },惰性構(gòu)造時不執(zhí)行任何初始化#readyPromise初始為undefined冪等??保證無論init()被調(diào)用多少次都返回同一個 Promise共享并發(fā)調(diào)用者await的是同一個 in-flight Promise天然去重可預測每次extend會注冊新的 init 函數(shù)并清空#readyPromise從而保證擴展后的模塊一定被初始化。initPublicAction每個公開動作的標準前奏架構(gòu)文檔強調(diào)fhevmClient初始化是可選的嗎——若不調(diào)用首次調(diào)用時自動執(zhí)行at first call。這一首次使用即初始化由 CoreFhevm-p.ts 的initPublicAction統(tǒng)一保證所有公開 API 動作加密、解密、簽名等第一步都調(diào)用它await fhevm.ready——觸發(fā)惰性、共享的初始化讀取初始化期間解析并緩存在客戶端上的 frozen context版本快照缺失即視為內(nèi)部不變量被違反拋出明確錯誤返回一份深拷貝的 frozen contextcloneFhevmClientFrozenContext使動作在整個異步執(zhí)行期間持有穩(wěn)定的版本視圖不受后續(xù)上下文刷新的影響。frozen context一次性解析的版本快照初始化期間SDK 需要從鏈上解析協(xié)議版本、PubKey/CRS 版本、TFHE/TKMS 模塊版本與各宿主合約版本打包成不可變的FhevmClientFrozenContext見 fhevmClientFrozenContext-p.ts。其解析只執(zhí)行一次并做并發(fā)去重ensureFrozenContext見 ensureFrozenContext-p.ts在客戶端實例上維護已解析的數(shù)據(jù) 進行中的單一 Promise多個 init 函數(shù)并發(fā)到達時共享同一個解析 Promise成功后將數(shù)據(jù)落盤為同步可讀狀態(tài)解析過程純鏈上讀取、無可重置副作用因此臨時 RPC 失敗不會污染后續(xù)重試。不同路徑只解析自己需要的版本子集加密路徑需要tfheVersion與協(xié)議/ACL 版本解密路徑則是tkmsVersion與 KMSVerifier 版本見 fhevmClientFrozenContext-p.ts 的類型注釋從而把鏈上getVersion()調(diào)用次數(shù)降到最低。客戶端protocolVersion、tfheVersion、tkmsVersion等 getter 在 frozen context 未解析時拋出Fhevm context has not been resolved. Await client.ready before.L40。各層的 init 職責base 層_initBase僅解析 frozen contextbase.tsencrypt 層_initEncrypt并行執(zhí)行拉取約 50MB 的全局 FHE 加密公鑰fetchFheEncryptionKeyBytes 初始化 TFHE WASM 模塊initTfheModule見 encrypt-p.tsdecrypt 層_initDecrypt類似地初始化 TKMS WASM 模塊。這也解釋了文檔中任何對withPublicKey或fetchPublicKey的調(diào)用在init()之后應拋出錯誤的設計意圖配置必須在調(diào)用時解析、在初始化前完成固化初始化后變更配置會破壞已建立的版本/密鑰快照一致性。WASM 模塊的按需加載、單例約束與線程配置兩個模塊、兩個 WASM、一個運行時獨占架構(gòu)文檔明確要求encryptModule 和 decryptModule 是兩個獨立模塊分別與兩個不同的 WASM 模塊交互各一個必須避免在不需要時加載某個 WASM 模塊因此 SDK 采用擴展原則extension principle。倉庫的 wasm 資產(chǎn)目錄印證了這一點sdk/js-sdk/src/wasm/tfhe 存放多個版本的 TFHE WASM如 v1.5.3、v1.6.0-dev、v1.6.2含tfhe_bg.wasm、worker 腳本與 base64 內(nèi)嵌版本sdk/js-sdk/src/wasm/tkms 存放多個版本的 TKMS WASM如 v0.13.10、v0.13.20-0、v0.14.0-1。每個模塊的初始化都按版本緩存整個初始化 PromisecachedTfheModulePromiseByVersion/cachedTkmsModulePromiseByVersion且每個版本的 WASM 模塊在同一時刻只能被一個運行時獨占initTfheModule/initTkmsModule會檢查ownerUidByVersion若該版本已被其他運行時的uid占用則拋錯Encrypt WASM module is already owned by runtime ... and cannot be shared with runtime ...見 modules/encrypt/module/init-p.ts。這與文檔有些模塊在 JS 運行時內(nèi)是全局唯一的完全對應——WASM 實例及其 worker 池無法安全地在多個運行時間共享。資產(chǎn)加載、SHA 校驗與單線程降級TFHE 模塊的初始化modules/encrypt/module/init-p.ts定義了完整的資產(chǎn)解析與降級策略資產(chǎn) URL 解析提供locateFile時按每個資產(chǎn)獨立解析返回URL走 URL 加載返回null/undefined走內(nèi)嵌 base64未提供時Node 端自動推導file://URL 并做磁盤存在性檢查任一缺失則整體回退 base64兼容 Turbopack 等打包器搬移場景瀏覽器端直接使用內(nèi)嵌 base64WASM 編譯有 URL 則isomorphicCompileVerifiedWasmSHA-256 校驗后編譯否則從內(nèi)嵌 base64 編譯worker 加載模式wasmAssetLoadMode支持auto、embedded-base64、verified-blob、precheck-direct-url、trusted-direct-url五種定義見 wasmAssets.ts其中verified-blob提供真正的完整性保證校驗后以 Blob worker 執(zhí)行precheck-direct-url僅是預檢失敗即快速報錯而非完整性校驗trusted-direct-url完全信任運行時加載線程singleThread與numberOfThreads控制線程池檢測到不支持 SharedArrayBuffer缺少 COOP/COEP 頭或無 worker 來源時自動降級單線程顯式 URL 模式_requiresAssetUrl若無 worker URL 則直接拋錯而非靜默降級L104-L106, L293-L330。這些實現(xiàn)細節(jié)共同支撐了初始化惰性、冪等、可預測的文檔承諾初始化失敗會被緩存為 rejected Promise不重試半初始化狀態(tài)避免二次錯誤如 Already started見 L553-L560 注釋。鏈配置與零配置默認路徑SDK 內(nèi)置了四條鏈定義chains/index.tsmainnet、sepolia、polygon、polygonAmoy另有l(wèi)ocalTestnet定義文件。以 sepolia 為例chains/definitions/sepolia.ts每條鏈攜帶 fhEVM 相關宿主合約地址ACL、InputVerifier、KMSVerifier、ProtocolConfig、relayer URL如https://relayer.testnet.zama.org以及網(wǎng)關側(cè)合約Decryption、InputVerification。加密所需的全局公鑰正來源于 relayer 服務這回答了架構(gòu)文檔中的問題Problem: how to get the publicKeyBytes? —— publicKeyBytes can be fetched independently。零配置必須可用Zero config must work的路徑是createFhevmClient({ chain, provider })→ 首次調(diào)用任意動作 → 惰性 init 自動完成 frozen context 解析與模塊初始化 → 從鏈定義中的 relayerUrl 拉取公鑰。而顯式初始化Lazy init or Explicitly init must be supported則為高級用戶提供兩條途徑init()手動預熱用于預加載、避免延遲尖峰、SSR/受控環(huán)境見設計原則Power-user explicit init (this is useful for preloading, avoiding latency spikes, SSR/controlled environments)以及通過options預注入公鑰等配置。客戶端配置FhevmOptions見 coreFhevmClient.ts包含batchRpcCallsRPC 批量調(diào)用默認 false、fheEncryptionKey預置加密公鑰可避免后續(xù) 50MB 拉取、moduleVersions模塊版本覆蓋。運行時配置FhevmRuntimeConfig見 coreFhevmRuntime.ts包含locateFile、wasmAssetLoadMode、moduleVersions、logger、singleThread、numberOfThreads、auth。配置對象在創(chuàng)建時被防御性拷貝并凍結(jié)resolveOptions返回Object.freeze結(jié)果見 CoreFhevm-p.ts與擴展不得在創(chuàng)建時捕獲配置、配置應在調(diào)用時解析的原則一致。典型動作加密與解密的最小調(diào)用路徑作為設計落地的實例看兩個代表性動作均以initPublicAction開頭印證每個公開動作自動觸發(fā)惰性初始化加密單個值encryptValueactions/encrypt/encryptValue.ts校驗value類型與地址 →await initPublicAction(fhevm)觸發(fā)初始化并取得版本快照 → 調(diào)用coprocessor/encrypt生成密文 → 返回{ encryptedValue, inputProof }。批量版本encryptValues結(jié)構(gòu)相同返回encryptedValues數(shù)組見 actions/encrypt/encryptValues.ts。解密單個值decryptValueactions/decrypt/decryptValue.ts將encryptedValue歸一化為 fhEVM handle與合約地址、密文所有者地址組成pairs配合transportKeyPair與signedPermit交給 TKMS 解密decryptValuesFromPairs返回帶類型的明文。配套的generateTransportKeyPair用于生成端到端傳輸密鑰對見 actions/decrypt/generateTransportKeyPair.ts。基礎動作組base.ts還提供decryptPublicValue(s)讀取已公開的密文明文、decryptPublicValuesWithSignatures明文 可上鏈校驗的簽名證明、signLegacyDecryptionPermit/signUnifiedDecryptionPermitV1/V2 解密許可簽名后者需協(xié)議 API v0.14.0 且鏈上支持 unified extraData v2、傳輸密鑰對與許可的序列化/反序列化等。小結(jié)一張圖理解 SDK 的生命周期可以把整個設計收斂為一條主線創(chuàng)建純函數(shù)、無副作用createFhevmBaseClient→createCoreFhevm構(gòu)造不可變核心extend(baseActions)掛基礎能力加密/解密能力由extend(encryptActions/decryptActions)按需疊加運行時同步填充對應模塊占位符首次使用惰性自動初始化任意公開動作調(diào)用initPublicAction→ready返回共享的單一 Promise → init 函數(shù)集并行執(zhí)行解析并凍結(jié) frozen context 版本快照、拉取加密公鑰加密路徑、初始化 TFHE/TKMS WASM 模塊含 worker 池擴展隨時允許extend()注冊新 init 函數(shù)并作廢#readyPromise下次使用自動補齊新模塊初始化使用動作從深拷貝的 frozen context 讀取穩(wěn)定版本視圖完成加密、解密、簽名、公鑰管理等操作。文檔中SDK design: initialization dependency orchestration problem的結(jié)論在此閉環(huán)默認路徑全惰性自動顯式控制權(quán)留給高級用戶。這也是 fhevm js-sdk 在零配置可用與面向功率用戶的顯式控制之間取得平衡的完整答案。若需深入代碼建議按以下順序閱讀CoreFhevmRuntime-p.ts運行時與模塊槽位→ CoreFhevm-p.ts客戶端生命周期與動作前奏→ ethers/clients三種工廠→ modules/encrypt/module/init-p.tsWASM 加載與降級細節(jié)。【免費下載鏈接】fhevmFHEVM, a full-stack framework for integrating Fully Homomorphic Encryption (FHE) with blockchain applications項目地址: https://gitcode.com/GitHub_Trending/fh/fhevm創(chuàng)作聲明:本文部分內(nèi)容由AI輔助生成(AIGC),僅供參考