关于 flare-fcc
这是面向开发者的文档指导技能,介绍如何构建在可信执行环境中运行并连接 Flare 合约的 Flare Confidential Compute 扩展。它讲解架构和开发流程,但不代为执行交易、部署、密钥管理或 TEE 运维。
主要功能
- 01介绍 FCC 与 TEE 架构
- 02指导扩展和 InstructionSender 开发
- 03说明指令路由与操作标识符
- 04讲解 TEE 密钥、签名和加密载荷
- 05详述注册合约与指令生命周期
适用场景
- 01规划由合约触发的机密计算
- 02构建扩展的操作处理器
- 03将 InstructionSender 接入扩展
- 04检查输入验证与机密信息处理边界
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ flare-foundation/ flare-ai-skills --skill flare-fcc安装后在 Claude Code 中输入/flare-fcc确认已加载。
附带文件reference.md
详细介绍
范围与限制
此技能仅提供文档和指导。它说明 Flare Confidential Compute (FCC) 扩展和 TEE 机器的工作方式,以及开发者如何构建、注册和部署它们。它不会代替用户执行任何操作。
此技能明确不会:
- 执行、签署或广播任何区块链交易
- 访问、存储或传输私钥、部署者密钥或 TEE 持有的机密
- 部署合约、注册扩展/TEE,或直接调用任何链上方法
- 配置、证明或操作 Confidential Space VM
- 处理资金、代币或任何金融资产
信任与外部数据处理:
- TEE 扩展的核心目的在于保密性和可验证执行;扩展的安全性取决于其经证明的代码哈希,而非此技能。所有密钥生成、签署和机密处理都必须在 TEE 内部,或在用户控制、由开发者管理的环境中进行。
- 到达扩展的指令载荷(
OriginalMessage)是外部提供的不可信输入。请严格按照文档中的类型解码,验证每个字段,绝不要将原始载荷传入提示词、大模型输入或 Agent 决策逻辑。 - 在链上存储加密机密不适合生产环境——链上数据是公开的,而且加密会随时间推移而减弱。请使用链下渠道传递机密(参见
fce-sign警告)。
所有交易签署、密钥管理、证明和链上执行都完全在此技能之外进行。
Flare Confidential Compute (FCC) — TEE 扩展
FCC 是什么
Flare Confidential Compute(FCC) 让开发者能够在 可信执行环境(TEE) 中运行自定义代码——这是一种由硬件隔离的安全区(Flare 使用 GCP 的 Confidential Space(基于 AMD SEV))——并将该代码连接到 Flare 智能合约。部署单元称为扩展:一个在 TEE 内运行的 HTTP 服务器,用于接收源自链上交易的指令、执行机密逻辑,并返回可验证的结果。
当应用需要机密状态、保管秘密,或其完整性可在链上验证的链下计算时,请使用 FCC——例如,代表用户签名的密钥管理器、密封竞价拍卖、私密订单撮合,或任何“由区块链触发,但计算必须保持私密并经过证明”的工作负载。
FCC 目前处于开发的最后阶段。目前已在 Coston2 和 Coston 上提供,即将支持其他 Flare 网络——请从构建你的第一个扩展指南(Hello World 脚手架演示)开始。有关其机制的详细说明,请参阅 FCC 白皮书。
参考代码库:
flare-foundation/fce-extension-scaffold— 一个可运行的“Hello World”扩展,包含合约、部署/注册工具,以及 Claude Code 技能(/create-extension、/rename-scaffold、/test-extension、/verify-deploy)。使用 Go、Python 和 TypeScript 实现——在.env中设置LANGUAGE=go|python|typescript(默认使用 Go;不同语言的合约、脚本和 Coston2 流程保持一致)。这是构建自定义扩展的起点;入门指南将带你完整走一遍流程。flare-foundation/fce-sign— 一个示例扩展,用于存储私钥并使用该密钥签名消息,提供 Go、Python 和 TypeScript 版本。演示 TEE 签名端口和可复现构建。链上秘密部分明确仅用于演示。flare-foundation/fce-weather-insurance— 一个完整的 FCC 应用,演示参数化降雨保险。投保人在链上购买保障;TEE 获取 OpenWeatherMap 数据并签署结算结果;任何人都可以调用settle()进行验证并支付赔款。支持公开保单和 ECIES 加密的私密保单。包含 Next.js dApp 前端。
指令生命周期
扩展只控制两部分:链上合约(步骤 1)和动作处理器(步骤 6)。中间的所有环节均由 TEE 基础设施处理。
1. User calls your InstructionSender contract (on-chain)
2. Contract routes through TeeExtensionRegistry.sendInstructions() → emits TeeInstructionsSent
3. TEE proxy picks up the instruction from the chain
4. TEE node fetches the instruction from the proxy
5. TEE node forwards it as POST /action to your extension server (inside the TEE)
6. Your extension decodes, validates, executes, and returns a result
7. TEE node returns the (optionally cosigned) result to the proxy
8. Caller polls the proxy for the result
链上构建模块
系统由两个协议合约(来自 flare-smart-contracts-v2)作为入口:
TeeExtensionRegistry— 扩展注册表,也是提交指令的唯一途径。主要接口:sendInstructions(address[] _teeIds, TeeInstructionParams _params) payable returns (bytes32 instructionId)— 唯一的入口点。TeeInstructionParams={ bytes32 opType; bytes32 opCommand; bytes message; address[] cosigners; uint64 cosignersThreshold; address claimBackAddress; }。nextPublicExtensionId()和getTeeExtensionInstructionsSender(uint256 extensionId)— 用于查找扩展的 ID。公开扩展 ID 从0x10000(65536)开始;低于该值的 ID 保留给系统扩展,因此setExtensionId()会从0x10000扫描到nextPublicExtensionId(),而不是从零开始。- **访问控制:**注册扩展时,必须将其绑定到一个 InstructionSender 地址。注册表会拒绝任何
msg.sender不是该地址的sendInstructions调用——无论是 EOA 还是其他合约都不行。
TeeMachineRegistry— 将扩展映射到为其提供服务的 TEE 机器。getRandomTeeIds(uint256 _extensionId, uint256 _count)会选取机器地址,以便将指令路由给它们(使用_count > 1可将一条指令分发给多个 TEE)。
在链上,这两个合约都由**一个 EIP-2535 diamond(FlareTeeManager)**承载——调用 diamond 地址;IFlareTeeManager 是聚合接口。上文提到的 TeeExtensionRegistry 接口属于 diamond 的 IExtensionManager facet;TeeMachineRegistry 的接口属于 IMachineManager。另外还有两个日常使用的重要 facet:IVerification(attestation/可用性检查请求、共同签名者、ChallengeExpired/InvalidAttestation 错误)和 IOperationFees(按指令查询费用——getOperationFee、calculateFeeByTeeIds)。IOwnerAllowlist 用于限制可注册扩展、机器或钱包项目所有者的对象。ITeeCommonErrors 包含多个 facet 共用的错误,包括上文已提到的错误(TooMany、ChallengeExpired、InvalidGovernanceHash)。完整 ABI 参考:FCC Reference(IFlareTeeManager、IExtensionManager、IMachineManager、IVerification、IOperationFees、IOwnerAllowlist、ITeeCommonErrors)。FlareTeeManager 在主网上部署后,应在运行时通过 FlareContractRegistry 解析其地址,而不是将地址硬编码;目前已部署在 Coston2、Coston 和 Songbird 上。
InstructionSender 合约
这是你的合约,也是唯一允许为你的扩展提交指令的地址。最低要求:
- 知道其扩展 ID — 用于查找提供服务的 TEE 机器。脚手架中的
setExtensionId()会在注册后扫描注册表(从第一个公共扩展 ID0x10000开始),并缓存该 ID(仅设置一次)。 - 在
TeeExtensionRegistry上调用sendInstructions,并提供至少一个teeId、opType/opCommandbytes32标识符、非空的message,以及(通常为空的)协签者。 - 设为
payable并转发msg.value— 注册表会收取每条指令的费用。 - 在注册前部署 — 注册时需要传入已部署的 InstructionSender 地址。
脚手架中的 HelloWorldInstructionSender 是现成模板:它定义了 bytes32 操作常量,并为每个操作提供一个 payable 发送函数(sendSayHello、sendSayGoodbye)。你也可以编写自定义的最简发送合约,以实现自定义访问控制、链上验证、多 TEE 路由、协签者工作流或批处理 — 注册表只关心已注册的地址是否使用有效参数调用 sendInstructions。
OPType / OPCommand 路由模型
合约与扩展代码通过两级标识符关联,这些标识符在三个层之间必须完全一致:
| 层 | 操作类型 | 命令 |
|---|---|---|
| Solidity | bytes32 OP_TYPE_GREETING = bytes32("GREETING") | bytes32 OP_COMMAND_SAY_HELLO = bytes32("SAY_HELLO") |
| Go 配置 | OPTypeGreeting = "GREETING" | OPCommandSayHello = "SAY_HELLO" |
| Go 路由器 | dataFixed.OPType == teeutils.ToHash(config.OPTypeGreeting) | df.OPCommand == teeutils.ToHash(config.OPCommandSayHello) |
OPType 用于选择操作组;OPCommand 用于在组内进一步路由。OPType 不匹配时会进入“unsupported op type”分支;OPCommand 不匹配时会进入“unsupported op command”分支。bytes32("...") 最多只能容纳 31 个字节 — 标识符应保持简短。
保留名称: OPType 中的 F_ 前缀保留给 Flare 系统操作(F_REG、F_WALLET、F_GET、F_POLICY、F_GOVERNANCE、F_XRP、F_BTC、F_FDC2)— 切勿为自己的 OPType 添加 F_ 前缀。它们的命令名称也同样保留(TEE_ATTESTATION、TEE_INFO、TEE_BACKUP、KEY_GENERATE、KEY_INFO、KEY_PROOF、KEY_DELETE、KEY_DIRECT_BACKUP、KEY_DIRECT_RESTORE、KEY_DATA_PROVIDER_RESTORE、INITIALIZE_POLICY、UPDATE_POLICY、SET_MACHINE_PATH_LIST、PAY、REISSUE、VRF、PROVE)— 在自定义的非 F_ OPType 下复用这些命令名称,会通过本地验证,但指令永远不会到达你的扩展(也不会报错),因此两者都应使用特定于业务领域的名称。
TEE 密钥与签名模型
每台 TEE 机器启动时都会生成一对身份密钥(基于 secp256k1 的 ECDSA,与 Ethereum 风格的密钥相匹配)。私钥部分永远不会离开 enclave。
- 公钥 — 可验证的身份;可以存储在链上,也可以通过代理的
/info端点提供。任何人都可以验证 ECDSA 签名,并向该公钥加密 ECIES 载荷。 - 私钥 — 仅限 enclave 使用。用于签名结果(ECDSA),以及解密发送给该机器的载荷(ECIES)。
ECDSA 用于证明来源(相当于在公开消息上盖章);ECIES 用于隐藏内容(相当于只有匹配的私钥才能打开的保险箱)。不要混淆两者——使用私钥加密是错误的做法。
机密数据的处理模式:**在链下加密至 TEE 的公钥,并且只在 enclave 内解密。**扩展程序本身不持有身份私钥——它会在本地调用 TEE 节点(签名端口上的 POST /decrypt,仅限 localhost),由节点使用 enclave 持有的密钥进行解密。加密可以隐藏字节,但不能代替下文的共识检查。
数据提供者和联签者决定 TEE 是否可以执行工作。只有在 Flare Systems Protocol 数据提供者中继指令,并使用超过当前签名策略权重 50% 的权重对其签名后,链上指令才会执行(该加权集合与 FTSO/FDC 使用的集合相同)——代理不能自行授权执行操作。对于敏感操作(付款、密钥管理),联签者会增加第二道独立的授权关卡:必须同时提供两类签署——提供者权重和联签者阈值,否则 TEE 不会执行。
有关完整机制,请参阅 TEE 公钥和私钥和数据提供者和联签者。
扩展程序(Go)——操作处理程序
在 TEE 内部,扩展程序是一个 HTTP 服务器。TEE 节点会通过 POST /action 发送每条指令。你需要实现 processAction,解析操作中的 instruction.DataFixed(包含 OPType、OPCommand 和原始 OriginalMessage),然后根据 OPType 进行分发,再根据 OPCommand 进行路由。
每个处理程序都遵循相同的4 步模式:
func (e *Extension) processSayHello(action teetypes.Action, df *instruction.DataFixed) teetypes.ActionResult {
// 1. DECODE the raw OriginalMessage (JSON here; use structs.DecodeTo for ABI-encoded)
var req types.SayHelloRequest
dec := json.NewDecoder(bytes.NewReader(df.OriginalMessage))
dec.DisallowUnknownFields()
if err := dec.Decode(&req); err != nil {
return buildResult(action, df, nil, 0, fmt.Errorf("decoding request: %w", err))
}
// 2. VALIDATE every field — this is untrusted external input
if req.Name == "" {
return buildResult(action, df, nil, 0, fmt.Errorf("name must not be empty"))
}
// 3. EXECUTE your confidential logic (guard shared state with the mutex)
e.mu.Lock()
e.greetingCount++
n := e.greetingCount
e.mu.Unlock()
// 4. BUILD the result: status 1 = success (data returned), status 0 = error (err logged)
data, _ := json.Marshal(types.SayHelloResponse{Greeting: "Hello, " + req.Name, GreetingNumber: n})
return buildResult(action, df, data, 1, nil)
}
开发者需要修改的文件(脚手架会用 ★ 标记这些文件,/rename-scaffold 可自动重命名 Hello World 占位内容):
go/internal/config/config.go—OPType/OPCommand字符串常量和版本go/pkg/types/types.go— 请求/响应/状态结构体go/internal/extension/extension.go— 路由分支和处理程序(主要的自定义位置)contracts/InstructionSender.sol— 对应的bytes32常量和发送函数tools/cmd/run-test/main.go— 端到端测试载荷和断言
编辑合约后,运行 ./scripts/generate-bindings.sh 重新生成 Go 绑定。
TEE 签名端口
需要使用 TEE 管理的密钥进行签名、证明或加密的扩展程序,会在扩展程序内部调用 TEE 的签名端口(例如 localhost:7701/SIGN_PORT,或某些文档中的 9090——请查看脚手架的配置)。fce-sign 正是通过这种方式对消息进行签名,而密钥永远不会离开 enclave。
fce-sign:私钥扩展程序示例
fce-sign 仓库演示了完整的 TEE 工作流:存储 ECIES 加密的私钥,并对任意消息签名。提供 Go、Python 和 TypeScript 版本(在 .env 中设置 LANGUAGE)。
Docker 服务架构
三个容器以 Docker 服务的形式运行:
extension-tee— 你的扩展程序代码。接收代理解码后的指令并返回结果。ext-proxy— 监控链上的新指令,将其转发给你的处理程序,并将结果提交回链上。redis— 代理使用的内存存储。
端口参考(fce-sign)
| 服务 | 容器端口 | 主机端口 |
|---|---|---|
| ext-proxy 内部 | 6663 | 6673 |
| ext-proxy 外部 | 6664 | 6674 |
| redis | 6379 | 6382 |
ngrok 将主机端口 6674(ext-proxy 外部)暴露到互联网。内部端口 6673 用于扩展容器与 Docker 内代理之间的通信。
在 Coston2 上部署 fce-sign(本地模拟 TEE)
前提条件:Docker Desktop、Foundry、Go、ngrok,以及已注资的 Coston2 钱包。
步骤 0 — 激活本地模拟模式:
./scripts/use-chain.sh local coston2 go # or python / typescript
设置 SIMULATED_TEE=true 和 LOCAL_MODE=false(真实 Coston2 链,模拟证明)。运行 ./scripts/use-chain.sh --list 查看所有选项。
步骤 1 — 配置部署者密钥,编辑 .env.local.coston2:
DEPLOYMENT_PRIVATE_KEY="<funded-coston2-private-key-hex-no-0x>"
INITIAL_OWNER="0x<your-address>"
编辑后重新运行 use-chain.sh,使 .env 获取更改。
步骤 2 — 预留公共代理 URL(在另一个终端中):post-build.sh、start-services.sh 和 test.sh 都会从 .env 读取 EXT_PROXY_URL,因此请在部署合约或启动 Docker 服务之前设置该值。
安全提示:
ngrok http 6674会将本地 ext-proxy 暴露到公网 — 端口 6674 会开放代理 HTTP API,任何拥有该 URL 的人都能调用它。ngrok 仅用于 Coston2 测试网,完成后请停止隧道。
ngrok http 6674
复制 ngrok Forwarding 行中的 HTTPS URL,将其设置为 .env.local.coston2 中的 EXT_PROXY_URL,然后重新运行 use-chain.sh。此时代理尚未运行 — 第 5 步启动 ext-proxy 后,ngrok 才会转发流量。ngrok 免费版会在重启后保持该 URL 不变,因此通常只需设置一次。
步骤 3 — 部署合约并注册扩展:
./scripts/pre-build.sh
编译 Solidity、部署 InstructionSender,并在链上注册扩展。将 EXTENSION_ID 和 INSTRUCTION_SENDER 写入 config/extension.env。
警告:
config/extension.env一旦存在,pre-build 就会拒绝再次运行。只有在确实要创建新扩展时才使用--force— 这会部署新的InstructionSender并注册新的扩展 ID;如果旧 TEE 机器仍以先前的扩展 ID 注册,就会导致MachineManager.TooMany()。
步骤 4 — 配置索引器数据库:
cp config/proxy/extension_proxy.coston2.docker.toml.example \
config/proxy/extension_proxy.coston2.docker.toml
编辑 [db] 配置块,填入 Coston2 索引器凭据(主机 34.38.42.208、端口 3306、数据库 indexer)。凭据可通过 支持渠道 或 @flare_network 申请获取。
步骤 5 — 启动扩展服务栈:
./scripts/start-services.sh
构建扩展镜像,然后启动 redis、ext-proxy 和 extension-tee。脚本本身会等待 EXT_PROXY_URL/info — 如果第 2 步启动的 ngrok 仍在运行,该检查会通过公共隧道访问本地代理。在本地等待代理就绪:until curl -sf http://localhost:6674/info >/dev/null 2>&1; do sleep 2; done。确认只有扩展代理在监听 6674:lsof -i :6674。
步骤 6 — 验证代理:
curl -s "$EXT_PROXY_URL/info" | jq '.machineData'
模拟 TEE:codeHash = 0x194844cf…,extensionId 与 config/extension.env 中的值一致,initialOwner 与你的地址一致。
步骤 7 — 注册 TEE 机器:
./scripts/post-build.sh
运行三个链上步骤:allow-tee-version(将代码哈希加入白名单)、set-governance(注册扩展的 TEE 治理签名者集合和阈值 — 除非你在 .env 中设置 GOVERNANCE_SIGNERS/GOVERNANCE_THRESHOLD,否则默认由部署者作为唯一签名者,阈值为 1),以及 register-tee -command rRap(注册机器、签发新的证明、运行 FTDC 检查,并将其提升至生产环境)。治理集合必须与 extension-tee 节点容器获得的配置一致,否则注册会因 InvalidGovernanceHash 而回滚。
步骤 8 — 运行端到端测试:
./scripts/test.sh
流程:setExtensionId() → 获取 TEE 公钥 → 使用 ECIES 加密测试私钥 → 在链上调用 updateKey → 等待处理完成 → 在链上调用 sign → 验证 ECDSA 签名是否匹配。
Python 和 TypeScript 处理器框架
Python 和 TypeScript 使用 Framework 类,按 (opType, opCommand) 对注册处理器:
def register(framework: Framework) -> None:
framework.handle(OP_TYPE_KEY, OP_COMMAND_UPDATE, handle_key_update)
framework.handle(OP_TYPE_KEY, OP_COMMAND_SIGN, handle_key_sign)
每个处理器都会接收十六进制编码的 originalMessage,并返回 (data, status, error):
- **
data:**以十六进制编码的返回数据,会写回链上;也可以是None status:0= 错误,1= 成功,>=2= 待处理- **
error:**处理器失败时的错误消息字符串
base/ 包(Python/TypeScript)提供 hex_to_bytes/bytes_to_hex 和 Framework HTTP 服务端。请勿修改 base/——它属于框架基础设施。
fce-sign 项目结构
sign/
├── contracts/ # Solidity contracts (shared)
├── config/
│ ├── extension.env # Generated by pre-build.sh
│ ├── coston2/deployed-addresses.json
│ └── proxy/ # ext-proxy TOML configs
├── scripts/ # use-chain.sh, pre-build.sh, start-services.sh, post-build.sh, test.sh
├── go/internal/extension/ # Go business logic (modify)
├── go/tools/cmd/ # Deploy CLIs (Go-only, shared across all languages)
├── python/app/ # Python business logic (modify)
├── python/base/ # Framework infrastructure (do not modify)
├── typescript/src/app/ # TypeScript business logic (modify)
├── typescript/src/base/ # Framework infrastructure (do not modify)
├── docker-compose.yaml
├── .env.example
└── .env.local.coston2
基于 fce-sign 构建自己的扩展
- 克隆仓库并选择一种语言。
- 在 Solidity 合约和处理器中定义
opType/opCommand常量。 - 使用你的常量和参数修改
contracts/InstructionSender.sol。 - 在
go/internal/extension/(Go)或app/(Python/TypeScript)中编写处理器。 - 按照上述部署步骤操作——无论使用哪种语言,
go/tools/部署 CLI 都可共用。
清理
停止整个栈:
./scripts/stop-services.sh
完全重置(从头开始):
./scripts/stop-services.sh
docker compose down --rmi local
rm -f .env config/extension.env config/proxy/extension_proxy.coston2.docker.toml
然后从步骤 0 重新开始。注意:链上状态(已部署的合约、已注册的扩展、TEE)无法重置——每次运行 pre-build.sh 都会部署新合约。
fce-sign 故障排查
- 代理无法启动 / 数据库同步错误——检查
docker compose logs ext-proxy;确认config/proxy/extension_proxy.coston2.docker.toml中的数据库凭据。 - 交易回滚——C2FLR 不足;使用 Coston2 水龙头。
MachineManager.TooMany()——config/extension.env中的扩展 ID 与链上 TEE 记录不匹配(通常发生在pre-build.sh --force之后)。请完全重置,或保留现有的extension.env,并仅重新运行post-build.sh+test.sh。Verification.ChallengeExpired——重新运行post-build.sh。InvalidGovernanceHash——set-governance使用的GOVERNANCE_SIGNERS/GOVERNANCE_THRESHOLD与 TEE 节点签名的治理哈希不匹配;默认仅由部署者管理时,请将两者都留空;或者确保.env与节点容器配置一致,然后重新运行post-build.sh。code hashes do not match——SIMULATED_TEE与容器MODE不一致;请使用SIMULATED_TEE=true和MODE=1(由 Docker Compose 注入)。- TEE 注册超时——尝试运行
docker compose restart ext-proxy;FDC 证明需要 Coston2 上有活跃的中继提供方。 - ngrok URL 已更改——更新
.env.local.coston2中的EXT_PROXY_URL,重新运行use-chain.sh;如有需要,重启 ngrok 隧道(ngrok http 6674),重启 Docker 栈,然后重新运行post-build.sh和test.sh。
fce-weather-insurance:参数化保险示例
fce-weather-insurance 仓库是一个完整的 FCC 应用:在 enclave 中使用 OpenWeatherMap 数据结算参数化降雨保险。它展示了 TEE 工作负载的一种模式:获取链下数据、对结果签名,并由链上合约验证该签名后再转移资金——这与任何由预言机支持的结算流程类似。请参阅 flare-foundation/fce-weather-insurance-x402-agent,了解如何使用 x402 payments 通过 AI Agent 向此扩展购买并结算保单。
流程
- 保单持有人为某个日期和地点购买降雨保障,并支付 ERC-20 保费(
payToken);合约从资金池流动性中预留赔付金额。 - 从
settlementUnlockAt开始(保障日期次日 00:00 UTC),keeper 调用requestSettlement,将WEATHER/SETTLE指令路由到扩展。 - 扩展在飞地内从 OpenWeatherMap 获取当天的降水量,并签署结算结果。
- 任何人都可以使用签名后的
ActionResult调用settle();合约会将结果哈希封装在一个域分隔载荷中,通过ecrecover恢复签名者,要求其与注册的teeAddress一致,并在达到降雨阈值时支付赔款。
OP 标识符(OP_TYPE_WEATHER = bytes32("WEATHER"))
| 命令 | 用途 |
|---|---|
FETCH | 返回某个城市当前天气的 JSON(用于测试 / dApp 显示)。 |
SETTLE | 获取保单的每日降雨量,并返回签名后的结算载荷。 |
BUY | 解密经过 ECIES 加密的私人保单,并返回经证明的条款。 |
一如既往,WeatherInsurance.sol 和 internal/config/config.go 中的常量必须完全一致。
公开保单与私人保单
- 公开购买(
buyPolicy):所有条款都在链上;调用者需先对保费调用approve。 - 私人购买(
buyPolicyPrivate):买方使用扩展的公钥对 ABI 编码的PrivateBuyParams进行 ECIES 加密;密文作为WEATHER/BUY指令发送。TEE 通过节点的/decrypt端点解密,并在飞地内以termsCommitment为键保存阈值;随后买方通过relayPrivateBuy完成购买(该函数会验证 TEE 签名)。结算前,链上只有承诺值。
TEE 签名验证
relayPrivateBuy 和 settle 都以相同方式验证 TEE 结果:重建节点签名的哈希并恢复签名者,要求其与 teeAddress 一致。
bytes32 resultHash = keccak256(abi.encodePacked(
keccak256(_resultData), _actionId, keccak256(bytes(_submissionTag)), _status));
// Domain-separated payload — must match go-flare-common signing.TEEActionResult
bytes32 payloadHash = keccak256(abi.encode(bytes32("TEE_ACTION_RESULT"), block.chainid, resultHash));
address signer = _recover(_ethSigned(payloadHash), _signature); // EIP-191 personal-sign
require(signer == teeAddress, "bad TEE signature");
节点使用 EIP-191 前缀对 keccak256(abi.encode(TEE_ACTION_RESULT_PREFIX, chainId, ActionResult.Hash())) 签名,而不是仅对 ActionResult.Hash() 签名;只接受成功的结果(status == 1)。使用原始的 resultHash 进行验证,无法通过当前 TEE 节点签名的验证。SettlementTime(一个库)会计算 settlementUnlockAt,即保障日期次日的 UTC 午夜。
部署说明(与 scaffold/fce-sign 不同)
- 使用
./scripts/use-chain.sh local coston2激活(无需语言参数——此应用使用 Go)。 - 在
.env.local.coston2中设置OPENWEATHERMAP_API_KEY和PAY_TOKEN。Coston2 上有一个模拟 ERC-20,地址为0x53192e788991AD96bC180249B15AefB94E597dD1;你也可以部署自己的代币。 - 先运行
./scripts/pre-build.sh,然后运行./scripts/extension-setup.sh进行部署(后者会对已部署的合约调用setPayToken)。其余步骤(在6674端口运行 ngrok、post-build.sh、证明、代码哈希列入白名单)与下方 scaffold 生命周期相同。
证明与可复现构建
TEE 的信任来自远程证明:Confidential Space VM 会测量正在运行的镜像并报告代码哈希。Flare 的数据提供方(FTDC)只接受来自代码哈希已针对该扩展加入链上白名单的 TEE 的结果。因此,构建具有关键的安全性影响:
MODE=0是生产环境的证明后端;MODE=1会生成 FTDC 不接受的模拟证明。对于测试网/主网,镜像必须内置MODE=0,且.env必须设置LOCAL_MODE=false/SIMULATED_TEE=false。- **可复现性:**设置
SOURCE_DATE_EPOCH(例如,最后一次提交的时间),以确保相同源码生成相同代码哈希。Go 路径可在不同机器间实现逐位可复现(单个静态二进制文件)。Python/TypeScript 可实现同一台机器上的确定性,但跨机器逐位可复现只能尽力而为(pip wheel /node_modules会嵌入主机路径)——在另一台机器上重新构建可能会更改代码哈希,并要求重新注册。fce-sign通过.env中的LANGUAGE=go|python|typescript选择语言。
部署生命周期(Coston / Coston2)
脚手架脚本会依次执行四个阶段(./scripts/full-setup.sh --chain coston2 --test 会一次性运行全部阶段;每个阶段也可以单独运行):
- 预构建(
pre-build.sh)——编译并部署InstructionSender,在TeeExtensionRegistry上注册扩展,并将EXTENSION_ID+INSTRUCTION_SENDER写入config/extension.env。重新运行时会复用现有的config/extension.env,而不是拒绝运行;只有在明确要创建新扩展时才传入--force。 - 启动服务(
docker compose up -d --build)——以容器形式运行redis、ext-proxy和extension-tee(你的代码)。本地设置LOCAL_MODE=true会跳过证明。 - 构建后(
post-build.sh)——allow-tee-version将代码哈希加入白名单,set-governance注册扩展的 TEE 治理签名者集合/阈值(除非设置了GOVERNANCE_SIGNERS/GOVERNANCE_THRESHOLD,否则默认为部署者且阈值为 1),然后register-tee在链上注册 TEE 机器。使用register-tee -command rRap,以便重新运行时发起新的证明质询(大写R),避免出现Verification.ChallengeExpired。 - 测试(
test.sh)——通过已部署的 TEE 发送指令并验证往返结果。
真实测试网还需要:已注资的部署者密钥(Coston2 水龙头)、可公开访问的代理 URL(任何转发到端口 6674 的 HTTPS 隧道,例如 ngrok 或 cloudflared)、代理的索引器数据库凭据,以及用于运行镜像的 GCP Confidential Space VM。通过向代理 /info 发送 curl 请求并确认 machineData 来验证部署:platform 以 0x4743505f414d445f534556…(GCP_AMD_SEV)开头,codeHash 是真实测量的哈希(而非模拟值 0x194844cf…),且 extensionId / initialOwner 与 config/extension.env 中的值相符。如果重新部署了 FlareTeeManager diamond,所有注册都会被清除——重新运行预构建以获取新的 EXTENSION_ID,让 VM 操作员使用新 ID 重启,然后重新运行构建后和测试。
常见故障模式
Verification.TeeNotFound——NORMAL_PROXY_URL指向了错误链的 FTDC 代理。Verification.ChallengeExpired—— 重新运行构建后;确保register-tee使用-command rRap。InvalidGovernanceHash——GOVERNANCE_SIGNERS/GOVERNANCE_THRESHOLD与 TEE 节点签署的治理哈希不匹配;留空以使用仅部署者的默认值,或者让两侧保持一致并重新运行构建后。code hashes do not match——SIMULATED_TEE与镜像的MODE不一致;两者都必须为“真实”(SIMULATED_TEE=false、MODE=0)。- ext-proxy 返回
connect: connection refused—— 到 Flare 索引器数据库的 VPN/路由连接中断。
深入排查(专用指南:FCC 故障排查)
大多数故障都是本地技术栈与当前链上部署不一致,或已注册的 URL 现已无法解析所致。在认定是网络问题之前,请先检查以下事项:
- TEE 始终无法进入生产状态(可用性检查持续返回
404、机器队列为空、getRandomTeeIds无返回结果、调用方因TooMany()回退):首先检查是否正在使用SIMULATED_TEE=true运行——模拟证明无法由真实的 Coston2 数据提供方共同签署,因此可用性检查始终不会进入投票流程,机器会永远停留在INITIALIZED,对它的每次轮询都会返回404。这是本地开发时的预期行为,并非错误;该机器永远不会在链上显示为PRODUCTION。除此之外,请通过脚手架的query-tee工具读取实际注册的信息——go run ./tools/cmd/query-tee -reg <FlareTeeManager address> <your TEE id>(从config/coston2/deployed-addresses.json获取注册表地址,而不要使用工具内置的默认值)。逐字符比较已注册的url与当前提供服务的 URL——快速隧道(ngrok/Cloudflare 快速隧道)每次重启都会生成新主机名,因此重启隧道会悄悄导致链上注册失效。请改用命名的 Cloudflare 隧道或预留的 ngrok 域名。query-tee返回空的teeId,意味着你连接的部署并非预期中的部署——拉取最新的main,从config/coston2/deployed-addresses.json重新获取所有地址,然后重新运行 pre-build/post-build(重新部署会清除现有注册)。 - 代理启动时发生 panic,或拒绝 TEE 响应(
hex string without 0x prefix、verifying response signature: invalid signature、error signing: could not sign):tee-node与tee-proxy版本不一致——FCC 仍在开发中,它们的线路格式会发生变化。切勿只升级其中一方;Go 模块固定版本和proxy/Dockerfile中的TEE_PROXY_VERSION必须同步更新。通常,从磁盘上的本地同级目录构建(而不是使用固定版本)会导致版本不一致。 - 有指令 ID 且没有其他错误,但指令始终无法传到扩展——你可能重复使用了保留的、以
F_开头的OPType,或在自定义OPType下使用了上文列出的保留命令名称;这会通过本地成对验证,不会报错,因此指令只会被静默丢弃。 - 索引器 DB 身份验证/连接失败(
Access denied for user 'indexer-reader'、连接 VPN 专用主机时超时)——主机/端口/数据库名称已在上文第 4 步中说明;缺少的只有只读用户名和密码,这些凭据会按请求者单独发放并定期轮换。请通过技术支持或 X申请当前凭据。社区聊天中分享的凭据比文档更容易过期,而且较旧的仓库文档可能指向 Coston(而非 Coston2)索引器;它使用不同的凭据并需要 VPN,Coston2 则不需要。 - Web2Json 验证器请求仅在访问 Weather Insurance 示例中的 OpenWeatherMap URL 时失败——在认定验证器有问题之前,请检查你的 OpenWeatherMap 套餐。Weather Insurance Extension 示例调用的端点需要付费的 OpenWeatherMap 套餐,因此免费套餐的密钥会在此处失败,即使同一个验证器访问其他 URL 时工作正常。
- 求助时请提供扩展 ID、TEE 机器地址、失败的指令 ID 和
query-tee输出——这些信息通常足以立即确定原因。
何时使用其他技能
- 读取 FTSO 价格馈送、FDC 证明、FAssets 铸造/赎回,或 Smart Accounts — 使用对应的专用技能。
- 通用网络信息(链 ID、RPC、测试币水龙头、区块浏览器)— 使用
flare-general。
当任务涉及机密/TEE 执行时,请使用此技能:构建或部署扩展、InstructionSender/注册表模式、证明和代码哈希白名单、基于 TEE 的参数化保险,或 fce-extension-scaffold / fce-sign / fce-weather-insurance 仓库。另请参阅 dev.flare.network/fcc,了解官方 Flare Developer Hub 的 FCC 文档。