news 2026/9/13 4:43:31

fhEVM SDK 用户解密 API 设计详解:从 `userDecrypt` 到 `FhevmWalletClient` 的三层封装

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
fhEVM SDK 用户解密 API 设计详解:从 `userDecrypt` 到 `FhevmWalletClient` 的三层封装

fhEVM SDK 用户解密 API 设计详解:从userDecryptFhevmWalletClient的三层封装

【免费下载链接】fhevmFHEVM, a full-stack framework for integrating Fully Homomorphic Encryption (FHE) with blockchain applications项目地址: https://gitcode.com/GitHub_Trending/fh/fhevm

fhEVM 的 JavaScript SDK(sdk/js-sdk)在其既有底层userDecrypt函数之上,设计了一套面向用户解密场景的三层便利 API。本文以设计草案 sdk/js-sdk/notes/DRAFT-API-v1.md 为核心,结合仓库中真实的类型定义、底层实现与模块源码,系统讲解FhevmAccount(身份)、FhevmUserDecryptionPermit(授权)与FhevmWalletClient(绑定身份的解密客户端)三个新抽象的设计动机、内部实现与端到端用法。读完本文,你将掌握如何在fhevmClient之上组装"密钥自行托管、授权按次签发、解密随调随传"的完整用户解密链路。

三层架构:为什么在userDecrypt之上再做封装

草案开篇即明确了整体分层,底层能力保持不变,上层逐步收敛心智负担:

Layer 0 (unchanged): userDecrypt(fhevmClient, rawEIP712Params) Layer 1 (new): FhevmAccount, FhevmUserDecryptionPermit Layer 2 (new): FhevmWalletClient
  • Layer 0(原样保留):面向高级用法的裸userDecrypt函数,调用方需要自行组装 EIP-712 参数、持有原始 KMS 私钥、逐次手工签名授权,要求对协议细节有完整把握;
  • Layer 1:把"你是谁"(FhevmAccount)与"你能解什么"(FhevmUserDecryptionPermit)两个独立概念实体化,让身份与授权可被类型系统约束、可被工厂函数校验、可被复用;
  • Layer 2:把"身份 + 客户端"绑定成FhevmWalletClient,日常解密只需"传 permit + 传 handle 对",调用方不再接触任何 KMS 私钥与 EIP-712 细节。

草案同时强调,这些新层只是"convenience concepts"(便利抽象),它们最终全部委托给 Layer 0 的既有函数,不会在协议层引入任何新机制,因此对现有网络协议与后端 relayer 完全透明。

Layer 1 之FhevmAccount:FHE 世界中的身份

设计约束

FhevmAccount回答"在 FHE 世界中你是谁"这一问题:它把用户的以太坊地址与其 KMS 私钥配对成一个不可变对象。草案为其定义了四条硬性约束:

  1. KMS 私钥绝不自动生成——私钥必须由用户自己创建并显式传入,SDK 不负责托管或派生;
  2. 不可变——内部使用私有字段(#fields),仅通过只读 getter 暴露;
  3. kmsPrivateKey不出现在公开类型上——消费方代码只能读account.userAddress,永远无法触达私钥;
  4. 只能通过 SDK 的工厂函数创建——工厂负责输入校验,杜绝手工拼装出非法对象。

公开类型与私有实现

公开类型定义在core/types下(草案写作 core/types/fhevmAccount.ts,当前仓库的core/types目录按能力拆分为 fhevmClient.ts、coreFhevmClient.ts 等文件,读者可在 sdk/js-sdk/src/core/types 下找到对应声明):

type FhevmAccount = { readonly userAddress: ChecksummedAddress; };

kmsPrivateKey被有意从公开类型中移除。私有实现FhevmAccountImpl则沿用仓库中TkmsPrivateEncKeyMlKem512Impl的模式——Symbol 键 + token 校验的静态访问器(草案路径为core/actions/decrypt/user/FhevmAccount-p.ts):

const FHEVM_ACCOUNT_TOKEN = Symbol("FhevmAccount.token"); const GET_KMS_PRIVATE_KEY = Symbol("FhevmAccount.getKmsPrivateKey"); class FhevmAccountImpl implements FhevmAccount { readonly #kmsPrivateKey: TkmsPrivateKey; readonly #userAddress: ChecksummedAddress; constructor(parameters: { readonly kmsPrivateKey: TkmsPrivateKey; readonly userAddress: ChecksummedAddress; }) { this.#kmsPrivateKey = parameters.kmsPrivateKey; this.#userAddress = parameters.userAddress; } public get userAddress(): ChecksummedAddress { return this.#userAddress; } // Symbol-keyed — invisible to consumers, accessible only to SDK internals public static GET_KMS_PRIVATE_KEY: TkmsPrivateKey { if (token !== FHEVM_ACCOUNT_TOKEN) { throw new Error("Unauthorized"); } if (!(account instanceof FhevmAccountImpl)) { throw new Error("Unauthorized"); } return account.#kmsPrivateKey; } }

这种"Symbol 键 + token 双重校验"手法并非孤例:仓库中TkmsPrivateEncKeyMlKem512Impl同样通过私有 token(见 core/modules/decrypt/module/api-p.ts 中的PRIVATE_TKMS_LIB_TOKEN)隔离 WASM 原生对象。SDK 内部代码(如userDecryptFhevmWalletClient)通过下述方式取回私钥:

const kmsPrivateKey = FhevmAccountImplGET_KMS_PRIVATE_KEY;

外部调用方既看不到这个 Symbol,也没有 token,因而在类型层面与运行层面都无法读取私钥。

工厂函数与类型守卫

工厂与守卫放在FhevmAccount.ts(草案路径core/actions/decrypt/user/FhevmAccount.ts),完全遵循isDecryptionPermitinstanceof守卫模式:

import { FhevmAccountImpl } from "./FhevmAccount-p.js"; import { assertIsChecksummedAddress } from "../../../base/address.js"; import { isTkmsPrivateKey } from "../../../modules/tkms/module.js"; // new export needed // Type guard — guarantees value is a valid FhevmAccount created by the SDK function isFhevmAccount(value: unknown): value is FhevmAccount { return value instanceof FhevmAccountImpl; } // Factory — validates inputs, wraps into immutable FhevmAccountImpl function createFhevmAccount(parameters: { readonly kmsPrivateKey: TkmsPrivateKey; readonly userAddress: string; }): FhevmAccount { assertIsChecksummedAddress(parameters.userAddress, {}); if (!isTkmsPrivateKey(parameters.kmsPrivateKey)) { throw new Error("Invalid kmsPrivateKey: not a valid TkmsPrivateKey created by the SDK"); } return new FhevmAccountImpl({ kmsPrivateKey: parameters.kmsPrivateKey, userAddress: parameters.userAddress, // already validated as ChecksummedAddress }); }

要点:

  • createFhevmAccount接受kmsPrivateKey参数(因为私钥必须由用户提供),但创建完成后FhevmAccount不再暴露它;
  • 地址走assertIsChecksummedAddress校验(仓库中对应 core/base/address.ts 一类的地址断言工具),私钥走新的isTkmsPrivateKey守卫;
  • 它不依赖fhevmClient,是纯数据包装 + 输入校验,这也意味着它可以在没有网络、没有链上下文的环境中构造。

TkmsPrivateKey与新增守卫isTkmsPrivateKey

TkmsPrivateKey是一个带品牌(brand)标记的声明类型,真实载体是私有类TkmsPrivateEncKeyMlKem512Impl。仓库中 core/types/tkms-p.ts 的声明为:

export declare const TkmsPrivateKeyBrand: unique symbol; export type TkmsPrivateKey = { readonly [TkmsPrivateKeyBrand]: never; readonly tkmsVersion: TkmsVersion; free(): void; };

TkmsPrivateKey实例由 WASM 密钥生成路径产出:底层generateTkmsPrivateKey调用kmsLib.ml_kem_pke_keygen()并包装成TkmsPrivateEncKeyMlKem512Impl(见 core/modules/decrypt/module/api-p.ts 中generateTkmsPrivateKey的实现),密钥体系基于ML-KEM-512后量子加密算法。由于instanceof无法作用在带品牌的对象上,草案建议在core/modules/tkms/module.ts新增导出,沿用isDecryptionPermit的同一模式:

// in core/modules/tkms/module.ts export function isTkmsPrivateKey(value: unknown): value is TkmsPrivateKey { return value instanceof TkmsPrivateEncKeyMlKem512Impl; }

Layer 1 之FhevmUserDecryptionPermit:你可以解什么

授权语义

FhevmUserDecryptionPermit回答"你能解密什么":它是一份已签名的 EIP-712 消息,授权该账户对应的 KMS 公钥发起解密请求。其核心语义:

  • 纯数据、无方法——只是一个类型化对象;
  • 可复用——在有效性窗口内可跨多次解密调用重复使用;
  • 作用域受限——限定到具体合约地址(最多 10 个);
  • 限时——由startTimestamp + durationDays决定(最长 365 天);
  • 绑定密钥——通过 permit 内嵌的公钥绑定到某一把 KMS 私钥。
type FhevmUserDecryptionPermit = { readonly eip712: KmsUserDecryptEIP712; readonly signature: Bytes65Hex; readonly signerAddress: ChecksummedAddress; };

从仓库既有类型看,签名授权在协议演进中经历了 V1/V2 两个形态:core/types/signedDecryptionPermit.ts 定义了SignedDecryptionPermitV1(协议 v13 及以下,自签与委托是两种 EIP-712 形状)与SignedDecryptionPermitV2(协议 v14 及以上,统一为KmsUserDecryptEip712V2形状,signature放宽为BytesHex以承载 ERC-1271 签名 blob)。V2 消息体校验(core/kms/createKmsUserDecryptEip712V2.ts)要求userAddresspublicKeyallowedContracts(校验和地址数组)、startTimestampdurationSecondsextraData等字段齐全,且extraData必须携带 v2 或更新版本的编码。这些细节解释了草案中"permit 是自描述工件"的论断:permit 自身携带版本,路由时以 permit 版本为准,而非链上解析出的协议版本

工厂一:用钱包签名

signFhevmUserDecryptionPermit接受WalletSigner作为参数,签名器永远不被存储在任何结构中:

async function signFhevmUserDecryptionPermit( signer: WalletSigner, fhevmClient: FhevmClient, params: { account: FhevmAccount; contractAddresses: readonly string[]; durationDays: number; startTimestamp?: number; // defaults to now extraData?: string; // defaults to "0x" }, ): Promise<FhevmUserDecryptionPermit>;

内部执行三步:

  1. 通过fhevmClient.tkms.getTkmsPublicKeyHex()account.kmsPrivateKey派生publicKey
  2. fhevmClient.chain提取chainIdverifyingContractAddressDecryption(即解密验证合约地址);
  3. 在底层调用既有的signDecryptionPermit(signer, fhevmClient, ...)

仓库中的signDecryptionPermit动作(core/actions/base/signDecryptionPermit.ts)要求contractAddressesstartTimestampdurationSecondssignerAddresssignerNativeSigner)与transportKeyPair(端到端传输密钥对),并返回SignedDecryptionPermit。可见新工厂只是把"KMS 公钥 + 链信息 + 账户私钥"自动装配进了既有签名流程。

工厂二:从原始组件恢复

当 permit 已在别处签名完毕时,可用createFhevmUserDecryptionPermit从原始组件恢复:

function createFhevmUserDecryptionPermit( fhevmClient: FhevmClient, params: { signerAddress: string; eip712: KmsUserDecryptEIP712; signature: Bytes65Hex; }, ): Promise<FhevmUserDecryptionPermit>;

它在内部调用既有的createDecryptionPermit(fhevmClient, ...)校验签名有效性后才返回新对象,确保任何进入系统的高层 permit 都是真实可信的。

Layer 2 之FhevmWalletClient:绑定身份的解密入口

为什么不存储 permit

FhevmWalletClient是一个绑定了FhevmAccountFhevmClient

  • 持有FhevmClientFhevmAccount的引用;
  • 不存储任何WalletSigner
  • permit 按次传入,不存储——因为:
    • permit 是短暂的、限时的;
    • 不同的 permit 可覆盖不同的合约集合;
    • wallet client 的存活期可以比单个 permit 更长。
type FhevmWalletClient = { readonly account: FhevmAccount; userDecrypt(params: { permit: FhevmUserDecryptionPermit; handleContractPairs: ReadonlyArray<{ handle: FhevmHandle; contractAddress: ChecksummedAddress; }>; options?: RelayerFetchOptions; }): Promise<readonly DecryptedFhevmHandle[]>; };

工厂函数非常轻量,仅做绑定:

function createFhevmWalletClient( fhevmClient: FhevmClient, params: { account: FhevmAccount }, ): FhevmWalletClient;

userDecrypt如何映射到底层函数

FhevmWalletClient.userDecrypt不重复实现解密逻辑,而是把高层参数解构后转发给 Layer 0 的userDecrypt

// Inside FhevmWalletClient.userDecrypt(params): return userDecrypt(this.#fhevmClient, { tkmsPrivateKey: this.#account.kmsPrivateKey, handleContractPairs: params.handleContractPairs, userDecryptEIP712Signer: params.permit.signerAddress, userDecryptEIP712Message: { contractAddresses: params.permit.eip712.message.contractAddresses, startTimestamp: params.permit.eip712.message.startTimestamp, durationDays: params.permit.eip712.message.durationDays, extraData: params.permit.eip712.message.extraData, }, userDecryptEIP712Signature: params.permit.signature, options: params.options, });

注意this.#account.kmsPrivateKey一行——这正是FhevmAccountImpl的 Symbol 访问器在 SDK 内部的用武之地:上层无需也不应直接持有私钥,一切由封装代劳。

底层调用链印证

从仓库源码可以完整还原这条委托链(这也解释了为什么草案反复强调"既有函数原样保留"):

  1. 动作层:core/actions/decrypt/decryptValuesFromPairs.ts 对每一对handle/contractAddress做地址断言与 checksum 规范化,组装ownerAddress后交给 KMS 层;
  2. KMS 路由层:core/kms/decryptValuesFromPairs.ts 按permit 自身版本路由——version === 1fetchKmsSigncryptedSharesV1(对应 relayer 的v2/user-decrypt端点),否则走fetchKmsSigncryptedSharesV2(对应v3/user-decrypt端点);随后用transportKeyPair调用decryptKmsSigncryptedShares解出明文;
  3. 数据流:relayer 返回的KmsSigncryptedShares携带着经 KMS 后量子密钥签密的份额,SDK 本地使用传输密钥对解密重建TypedValue

也就是说,三层 API 只是在"权限/身份"语义上做了收敛,核心的签密份额获取与本地重建路径完全复用既有实现,协议兼容性因此得到保证。

端到端用法:完整代码示例

草案给出的完整示例覆盖了从初始化到多 permit 切换的全部 6 个步骤:

import { createEthersFhevmClient } from "@fhevm/sdk/ethers"; import { addRelayer, addTkms, createFhevmAccount, signFhevmUserDecryptionPermit, createFhevmWalletClient, } from "@fhevm/sdk"; import { mainnet } from "@fhevm/sdk/chains"; // --- Setup (existing) --- const fhevmClient = createEthersFhevmClient({ chain: mainnet, provider }); addRelayer(fhevmClient); await addTkms(fhevmClient); // --- New API --- // 1. Generate key (user manages this) const kmsPrivateKey = fhevmClient.tkms.generateTkmsPrivateKey(); // 2. Create account (pure data) const account = createFhevmAccount({ kmsPrivateKey, userAddress: "0xAbC1234...", }); // 3. Sign a permit (walletSigner is an argument, not stored) const permit = await signFhevmUserDecryptionPermit(walletSigner, fhevmClient, { account, contractAddresses: ["0xDef5678..."], durationDays: 1, }); // 4. Create wallet client (binds fhevmClient + account) const walletClient = createFhevmWalletClient(fhevmClient, { account }); // 5. Decrypt — permit passed per-call const results = await walletClient.userDecrypt({ permit, handleContractPairs: [ { handle: h1, contractAddress: "0xDef5678..." }, { handle: h2, contractAddress: "0xDef5678..." }, ], }); // 6. Same wallet client, different permit (different contracts, different validity) const permit2 = await signFhevmUserDecryptionPermit(walletSigner, fhevmClient, { account, contractAddresses: ["0xGhi9012..."], durationDays: 7, }); const results2 = await walletClient.userDecrypt({ permit: permit2, handleContractPairs: [{ handle: h3, contractAddress: "0xGhi9012..." }], });

关键要点逐条拆解:

  • 第 1 步:私钥生成由用户主动发起(generateTkmsPrivateKey),SDK 绝不自动生成——这是"用户自己管理 KMS 私钥"原则的直接体现;
  • 第 2 步createFhevmAccount是纯数据构造,此处私钥仅作为入参传入,随后即被封存于#kmsPrivateKey
  • 第 3 步:签名器以参数形式传入,签名完成后不残留任何引用;durationDays: 1表示 permit 有效期一天;
  • 第 4 步:wallet client 只绑定fhevmClient + account,不绑定 permit、不绑定 signer;
  • 第 5/6 步:同一个 wallet client 可以配合不同 permit 使用——不同合约集合、不同有效期,实现"一个客户端、多份授权"的灵活模型。这也正是 permit 按次传入而非存储的根本原因。

底层函数速查表(原样保留)

便利层最终都委托给下表这些既有函数,高级场景需要裸 EIP-712 控制时仍可直接调用:

函数文件
userDecrypt(fhevmClient, params)core/actions/decrypt/user/userDecrypt.ts
signDecryptionPermit(signer, fhevmClient, params)core/actions/base/signDecryptionPermit.ts
createDecryptionPermit(fhevmClient, params)core/actions/decrypt/user/createDecryptionPermit.ts

说明:草案中给出的部分文件路径(如core/actions/decrypt/user/目录)在当前仓库中尚未落地为最终实现(该 API 仍处于草稿设计阶段),上表将signDecryptionPermit映射到仓库中实际存在的 core/actions/base/signDecryptionPermit.ts;读者可结合 sdk/js-sdk/notes/ 目录下的 DRAFT-API-v2.md、DRAFT-API-FHEVM-ACCOUNT.md 等草案,跟踪该设计的后续演进。

设计小结

抽象回答的问题关键特征底层复用
FhevmAccount你是谁不可变、私钥不暴露、工厂校验、纯数据
FhevmUserDecryptionPermit你能解什么限时(≤365 天)、限合约(≤10 个)、可复用、已签名signDecryptionPermit/createDecryptionPermit
FhevmWalletClient日常怎么解绑定 client+account、permit 按次传入、不存 signeruserDecryptdecryptValuesFromPairsfetchKmsSigncryptedSharesV1/V2+decryptKmsSigncryptedShares

这套三层设计在安全边界(KMS 私钥通过 Symbol/token 隔离、signer 不落存储、permit 有有效期与合约范围)与易用性(每次调用只传 permit 与 handle 对)之间取得了平衡;而其"新层全部委托既有底层"的原则,保证了向后兼容与协议演进空间。

【免费下载链接】fhevmFHEVM, a full-stack framework for integrating Fully Homomorphic Encryption (FHE) with blockchain applications项目地址: https://gitcode.com/GitHub_Trending/fh/fhevm

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/13 4:41:55

AI教材生成工具:低查重与结构化内容的技术解析

1. AI教材生成工具的核心价值与行业痛点在教育行业深耕多年&#xff0c;我见证了无数教师和教材编写者被重复性工作折磨得焦头烂额。直到去年尝试了AI教材生成工具&#xff0c;才真正体会到技术革新带来的解放感。这类工具的核心价值在于&#xff1a;通过自然语言处理(NLP)和知…

作者头像 李华
网站建设 2026/9/13 4:39:32

Pump.fun深度解析:从meme发射台到加密资产发行基础设施的演进

2024年加密圈里最不缺的就是戏剧性&#xff0c;但真要说哪个产品能把“草根发币”这件事做到现象级&#xff0c;Pump.fun 绝对是绕不开的名字。它把 Solana 上发行 meme 币的门槛一脚踢到了谷底&#xff0c;过去发一个币要懂合约、要组池子、要找做市商&#xff0c;现在几美元、…

作者头像 李华
网站建设 2026/9/13 4:38:54

小爱音箱接入大模型:MiGPT 智能音箱改造完整指南

小爱音箱接入大模型:MiGPT 智能音箱改造完整指南 【免费下载链接】mi-gpt &#x1f3e0; 将小爱音箱接入 ChatGPT 和豆包&#xff0c;改造成你的专属语音助手。 项目地址: https://gitcode.com/GitHub_Trending/mi/mi-gpt 周六早上你迷迷糊糊喊了句"小爱同学,今天适…

作者头像 李华