Skip to content

Commit 95a7979

Browse files
committed
Added light client example in tutorial
1 parent a83dfe8 commit 95a7979

2 files changed

Lines changed: 220 additions & 0 deletions

File tree

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
# Tutorial 06: Light Client Oracle
2+
3+
Read verified Ethereum state inside a TEE without trusting an RPC provider.
4+
5+
## What it does
6+
7+
```
8+
┌─────────────────────────────────────────────────────────────────┐
9+
│ TEE │
10+
│ │
11+
│ ┌─────────┐ ┌──────────┐ ┌─────────┐ │
12+
│ │ Helios │───▶│ oracle.py│───▶│ Proof │ │
13+
│ │ Light │ │ │ │ (JSON) │ │
14+
│ │ Client │ │ sign + │ │ │ │
15+
│ └────┬────┘ │ quote │ └─────────┘ │
16+
│ │ └──────────┘ │
17+
│ ▼ │
18+
│ Untrusted RPC │
19+
└───────┬──────────────────────────────────────────────────────────┘
20+
21+
22+
┌──────────────┐ ┌─────────────────────┐
23+
│ PublicNode │ │ beaconcha.in │
24+
│ (execution) │ │ (checkpoint sync) │
25+
└──────────────┘ └─────────────────────┘
26+
```
27+
28+
[Helios](https://github.com/a16z/helios) is an Ethereum light client that verifies block headers and state proofs. The TEE:
29+
30+
1. Syncs Helios using a beacon chain checkpoint
31+
2. Queries block data and contract state via verified light client
32+
3. Signs the claim with a KMS-derived key
33+
4. Gets a TDX quote binding `sha256(claim)` to `report_data`
34+
35+
## Why this matters
36+
37+
Unlike [05-onchain-oracle](../05-onchain-oracle) which fetches from off-chain APIs (CoinGecko), this reads directly from Ethereum state. Helios verifies state proofs internally, so you don't need to trust the RPC provider.
38+
39+
Use cases:
40+
- Attested `eth_call` results (token balances, contract state)
41+
- Cross-chain bridges that verify source chain state
42+
- Oracles for L2s that need L1 state proofs
43+
44+
## Run
45+
46+
```bash
47+
docker compose build
48+
docker compose run --rm app
49+
```
50+
51+
With a custom RPC (for `eth_call` state proofs):
52+
53+
```bash
54+
ETH_RPC_URL="https://mainnet.infura.io/v3/YOUR_KEY" docker compose run --rm -e ETH_RPC_URL app
55+
```
56+
57+
## Output
58+
59+
```json
60+
{
61+
"claim": {
62+
"type": "lightclient_attestation",
63+
"network": "mainnet",
64+
"checkpoint_epoch": 416537,
65+
"checkpoint_root": "0xbe1360...",
66+
"block_number": 21492847,
67+
"block_hash": "0xf180be...",
68+
"state_root": "0x811128...",
69+
"call": {
70+
"to": "0x6b175474e89094c44da98b954eedeac495271d0f",
71+
"data": "0x18160ddd",
72+
"result": "0x00000000...db77394bd15356c736ab846"
73+
}
74+
},
75+
"claimHash": "a1b2c3...",
76+
"signature": "...",
77+
"pubkey": "...",
78+
"quote": "BAACAQI..."
79+
}
80+
```
81+
82+
The example queries DAI's `totalSupply()` (`0x18160ddd`) but you can modify the contract call.
83+
84+
## Verification
85+
86+
Two things to verify:
87+
88+
1. **TDX quote** — proves this claim came from a TEE running this code
89+
→ See [01-attestation-oracle](../01-attestation-oracle) for `dcap-qvl` + `dstack-mr` verification
90+
91+
2. **Signature** — signed with KMS-derived key, verifiable on-chain
92+
→ See [05-onchain-oracle](../05-onchain-oracle) for signature chain verification
93+
94+
The `checkpoint_root` can be cross-checked against any beacon chain source (e.g., [beaconcha.in](https://beaconcha.in)).
95+
96+
## Limitations
97+
98+
- **State proofs** require an RPC with `eth_getProof` support. The free PublicNode RPC doesn't support this, so `eth_call` requires setting `ETH_RPC_URL` to Infura/Alchemy.
99+
- **Checkpoint trust**: Initial sync uses beaconcha.in's checkpoint service. The root is included in the claim for cross-verification.
100+
101+
## Files
102+
103+
```
104+
06-lightclient-oracle/
105+
├── docker-compose.yaml # Helios + oracle (self-contained)
106+
└── README.md
107+
```
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
services:
2+
app:
3+
configs:
4+
- source: run.sh
5+
target: /root/run.sh
6+
- source: oracle.py
7+
target: /root/oracle.py
8+
volumes:
9+
- /var/run/dstack.sock:/var/run/dstack.sock
10+
build:
11+
context: .
12+
dockerfile_inline: |
13+
FROM ubuntu:24.04@sha256:b59d21599a2b151e23eea5f6602f4af4d7d31c4e236d22bf0b62b86d2e386b8f
14+
RUN apt-get update && apt install -y curl python3 python3-pip
15+
RUN pip3 install --break-system-packages ecdsa requests dstack-sdk
16+
WORKDIR /root
17+
# Helios 0.11.0
18+
RUN curl -L 'https://github.com/a16z/helios/releases/download/0.11.0/helios_linux_amd64.tar.gz' | tar -xzC .
19+
CMD ["bash", "/root/run.sh"]
20+
platform: linux/amd64
21+
22+
configs:
23+
run.sh:
24+
content: |
25+
RPC=$${ETH_RPC_URL:-https://ethereum-rpc.publicnode.com}
26+
echo "Using execution RPC: $$RPC"
27+
28+
/root/helios ethereum \
29+
--network mainnet \
30+
--execution-rpc "$$RPC" \
31+
--fallback https://sync-mainnet.beaconcha.in \
32+
--rpc-bind-ip 0.0.0.0 &
33+
34+
echo "Waiting for Helios to sync..."
35+
for i in $$(seq 1 30); do
36+
if curl -s localhost:8545 -X POST -H "Content-Type: application/json" \
37+
-d '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}' | grep -q result; then
38+
echo "Helios synced!"
39+
break
40+
fi
41+
sleep 2
42+
done
43+
44+
python3 /root/oracle.py
45+
46+
oracle.py:
47+
content: |
48+
import json, hashlib, requests
49+
from ecdsa import SigningKey, NIST256p
50+
51+
HELIOS = "http://localhost:8545"
52+
CHECKPOINT_API = "https://sync-mainnet.beaconcha.in"
53+
DAI = "0x6b175474e89094c44da98b954eedeac495271d0f"
54+
55+
# Try to connect to TEE environment
56+
client = None
57+
try:
58+
from dstack_sdk import TappdClient
59+
client = TappdClient()
60+
key_result = client.derive_key("/oracle", "")
61+
sk = SigningKey.from_pem(key_result.key)
62+
print(f"TEE: using derived key (curve: {sk.curve.name})", flush=True)
63+
except Exception as e:
64+
print(f"WARNING: Not in TEE ({e}), using random key", flush=True)
65+
sk = SigningKey.generate(curve=NIST256p)
66+
client = None
67+
68+
pk = sk.get_verifying_key()
69+
70+
def rpc(method, params=[]):
71+
r = requests.post(HELIOS, json={"jsonrpc":"2.0","method":method,"params":params,"id":1})
72+
resp = r.json()
73+
if "error" in resp:
74+
raise Exception(resp["error"])
75+
return resp.get("result")
76+
77+
def get_checkpoint():
78+
r = requests.get(f"{CHECKPOINT_API}/checkpointz/v1/status", timeout=5)
79+
data = r.json()["data"]["finality"]["finalized"]
80+
return {"epoch": int(data["epoch"]), "root": data["root"]}
81+
82+
# Fetch verified data via Helios
83+
checkpoint = get_checkpoint()
84+
block_num = rpc("eth_blockNumber")
85+
block = rpc("eth_getBlockByNumber", [block_num, False])
86+
call_result = rpc("eth_call", [{"to": DAI, "data": "0x18160ddd", "gas": "0x100000"}, block_num])
87+
88+
claim = {
89+
"type": "lightclient_attestation",
90+
"network": "mainnet",
91+
"checkpoint_epoch": checkpoint["epoch"],
92+
"checkpoint_root": checkpoint["root"],
93+
"block_number": int(block_num, 16),
94+
"block_hash": block["hash"],
95+
"state_root": block["stateRoot"],
96+
"call": {"to": DAI, "data": "0x18160ddd", "result": call_result},
97+
}
98+
99+
claim_bytes = json.dumps(claim, sort_keys=True).encode()
100+
claim_hash = hashlib.sha256(claim_bytes).digest()
101+
sig = sk.sign_deterministic(claim_hash, hashfunc=hashlib.sha256)
102+
103+
quote = client.tdx_quote(claim_hash.hex()).quote if client else None
104+
105+
proof = {
106+
"claim": claim,
107+
"claimHash": claim_hash.hex(),
108+
"signature": sig.hex(),
109+
"pubkey": pk.to_string().hex(),
110+
"quote": quote,
111+
}
112+
113+
print(json.dumps(proof, indent=2))

0 commit comments

Comments
 (0)