An example TEE extension that stores a private key and signs messages with it.
Warning: This repo is for demonstration purposes only. Storing encrypted secrets on-chain is not advisable in production — on-chain data is public and encryption can be broken over time. A production extension should use off-chain channels for secret delivery.
This repo contains three implementations of the same extension. All three are
deployable to Coston/Coston2 — pick which one runs in the TEE by setting
LANGUAGE in .env.<chain>:
| Language | Directory | Dockerfile | Notes |
|---|---|---|---|
| Go | go/ |
Dockerfile |
Bit-for-bit reproducible across machines. |
| Python | python/ |
python/Dockerfile |
Same-machine reproducible; cross-machine is best-effort. |
| TypeScript | typescript/ |
typescript/Dockerfile |
Same-machine reproducible; cross-machine is best-effort. |
# .env.coston2
LANGUAGE=python # or go (default), or typescriptscripts/start-services.sh maps LANGUAGE to the right Dockerfile via
EXTENSION_DOCKERFILE, which docker-compose.yaml then uses for the
extension-tee build. The on-chain registration tooling under go/tools/
runs on the developer machine, never inside the TEE, and stays in Go regardless
of the language choice.
The cross-machine reproducibility gap on Python/TS comes from pip wheels and
node_modules trees that embed build-host paths and timestamps. The Go path
sidesteps this by compiling to a single static binary. See
docs/reproducibility.md for the full caveats — short version:
if a different machine rebuilds a Python/TS image, the attested code hash may
differ and the TEE will need to be re-registered.
| Language | Test command |
|---|---|
| Go | cd go && go test ./... |
| Python | cd python && python3 -m unittest discover -s tests -p 'test_*.py' |
| TypeScript | cd typescript && npm ci && npm test |
contracts/InstructionSender.sol is shared across all implementations. It
declares OP_TYPE_KEY = bytes32("KEY"), OP_COMMAND_UPDATE = bytes32("UPDATE")
and OP_COMMAND_SIGN = bytes32("SIGN") and exposes updateKey(bytes) and
sign(bytes) entry points that route through the Flare TEE Manager diamond.
The full testnet flow (Coston/Coston2 with a devops-hosted Confidential Space
VM) is documented in docs/deployment-steps.md. The
short version:
bash ./scripts/use-chain.sh coston2 # or coston
bash ./scripts/full-setup.sh --chain coston2 --testFor local development — simulated TEE in Docker, real chain, proxy exposed through a tunnel:
bash ./scripts/use-chain.sh local coston2
bash ./scripts/full-setup.sh --chain coston2 --testEach phase can also be run individually:
./scripts/pre-build.sh # 1. Deploy contract + register extension → config/<chain>/extension.env
./scripts/start-services.sh # 2. Docker compose up (redis + ext-proxy + extension-tee)
./scripts/post-build.sh # 3. Allow TEE version + register TEE machine on-chain
./scripts/test.sh # 4. End-to-end UPDATE/SIGN test against the running TEE
./scripts/stop-services.sh # Tear downTo build a hand-off image for a devops-hosted TEE (instead of the local stack),
use ./scripts/build-image.sh — it builds the LANGUAGE from .env, verifies
MODE=0, and saves a tar. See docs/deployment-steps.md.
- Docker Desktop (Linux containers) — for the local stack
- Go 1.25.1+ — for the deploy + registration tools in
go/tools/ - Foundry (
forge,cast,jq) — for Solidity compilation and contract bindings - Bash — Git Bash works on Windows
- No sibling repos needed —
tee-nodeandtee-proxyare fetched from the publicgithub.com/flare-foundationrepos at build time (Go modules pinned ingo.sum; the proxy imagegit clones them). A flat checkout of just this repo is enough. - A funded private key for the target chain. Set as
DEPLOYMENT_PRIVATE_KEYin.env.<chain>(no0xprefix). Fund atfaucet.flare.network. - For Coston/Coston2 deploys: a devops contact who'll run the TEE on a real
GCP Confidential Space VM. See
docs/deployment-steps.mdfor the full handoff.
.env is a per-chain file. scripts/use-chain.sh <chain> [--simulated] [--language <lang>]
creates anything a chain still needs (.env.<chain>, proxy toml, compose
override — never overwriting what's already there) and then activates it by
copying .env.<chain> over .env. Useful subcommands:
--list— chains with a.env.<chain>file-s/--status— the active chain, mode, language and which config files exist-v/--versions—tee-node/tee-proxypins in use vs. latest upstream-h/--help— full usage
All scripts then source .env automatically; --chain <name> on full-setup.sh,
start-services.sh, etc. overrides it for a single run.
--list— chains with a.env.<chain>file-s/--status— the active chain, mode, language and which config files exist-v/--versions—tee-node/tee-proxypins in use vs. latest upstream-h/--help— full usage
All scripts then source .env automatically; --chain <name> on full-setup.sh,
start-services.sh, etc. overrides it for a single run.
| Chain | .env.<chain> |
Addresses file | RPC |
|---|---|---|---|
| coston | .env.coston |
config/coston/deployed-addresses.json |
https://coston-api.flare.network/ext/C/rpc |
| coston2 | .env.coston2 |
config/coston2/deployed-addresses.json |
https://coston2-api.flare.network/ext/C/rpc |
songbird and flare are also recognized by the tooling, but nothing here has
ever deployed to them — each needs its own config/<chain>/deployed-addresses.json
(copied in from the tee deployment repo) before use-chain.sh <chain> will do
anything with it.
On coston/coston2, start-services.sh also syncs a shared Cloudflare tunnel
(docker-compose.cloudflared.yaml) so EXT_PROXY_URL doesn't need to be set by
hand in simulated mode; pass --tunnel to start it in live mode too, and
stop-services.sh --tunnel to tear it down (other extensions may be reusing the
same container, so it's left running by default).
songbird and flare are also recognized by the tooling, but nothing here has
ever deployed to them — each needs its own config/<chain>/deployed-addresses.json
(copied in from the tee deployment repo) before use-chain.sh <chain> will do
anything with it.
On coston/coston2, start-services.sh also syncs a shared Cloudflare tunnel
(docker-compose.cloudflared.yaml) so EXT_PROXY_URL doesn't need to be set by
hand in simulated mode; pass --tunnel to start it in live mode too, and
stop-services.sh --tunnel to tear it down (other extensions may be reusing the
same container, so it's left running by default).
pre-build.sh writes the new EXTENSION_ID and INSTRUCTION_SENDER to
config/<chain>/extension.env. Every subsequent script (start-services,
post-build, test) reads this file automatically — no manual .env edits
required. (The Coston2 extension deployed before this per-chain layout existed
still has its config at the flat config/extension.env — the tooling falls
back to it for coston2 until it's migrated to config/coston2/extension.env.)
The Go Dockerfile is bit-for-bit reproducible: same source + same
SOURCE_DATE_EPOCH yields an identical image on any host. The Python and
TypeScript Dockerfiles use the same apt snapshot + mtime normalization tricks
and reach same-machine determinism, but cross-machine bit-for-bit is not
guaranteed because of compiled pip wheels and varying node_modules trees.
See docs/reproducibility.md.
See docs/deployment-steps.md § Troubleshooting for the
full catalogue. Common
issues:
connect: connection refusedfrom ext-proxy — your indexer DB is unreachable. Check your indexer host/creds and network connectivity.Verification.TeeNotFound—NORMAL_PROXY_URLis pointed at the wrong chain's FTDC proxy.Verification.ChallengeExpired— re-runpost-build.sh; the patchedregister-teealready passes-command rRapfor fresh attestation.code hashes do not match—SIMULATED_TEEand the TEE'sMODEenv disagree. Both must point at "real" for testnet (SIMULATED_TEE=false,MODE=0).
| Doc | What it covers |
|---|---|
docs/ |
Full documentation index |
docs/getting-started.md |
Run the whole stack locally against Coston2 |
docs/deployment-steps.md |
Confidential Space deploy, platform traps, troubleshooting |
docs/reproducibility.md |
SOURCE_DATE_EPOCH and reproducible image builds |
go/ |
Go extension binary + deploy/registration tooling |
python/ |
Python extension (deployable; select with LANGUAGE=python) |
typescript/ |
TypeScript extension (deployable; select with LANGUAGE=typescript) |