|
| 1 | +# Tutorial 05: On-Chain Verifiable Oracle |
| 2 | + |
| 3 | +Build an oracle that signs price data with TEE-derived keys, verifiable on-chain. This tutorial demonstrates the **AppAuth contract** architecture that controls which TEEs can get keys from KMS. |
| 4 | + |
| 5 | +## Understanding AppAuth |
| 6 | + |
| 7 | +Every dstack app has an **AppAuth contract** on Base. When a TEE requests keys, KMS calls your AppAuth's `isAppAllowed()` to decide. |
| 8 | + |
| 9 | +``` |
| 10 | +┌─────────────────────────────────────────────────────────────┐ |
| 11 | +│ DstackKms Contract (Base) │ |
| 12 | +│ │ |
| 13 | +│ registerApp(address) ← registers your AppAuth │ |
| 14 | +│ isAppAllowed(bootInfo) → delegates to your contract │ |
| 15 | +└─────────────────────────────────────────────────────────────┘ |
| 16 | + │ |
| 17 | + │ calls IAppAuth(appId).isAppAllowed(bootInfo) |
| 18 | + ▼ |
| 19 | +┌─────────────────────────────────────────────────────────────┐ |
| 20 | +│ Your AppAuth Contract (DstackApp) │ |
| 21 | +│ │ |
| 22 | +│ owner: 0x... ← can add devices/hashes │ |
| 23 | +│ allowedDeviceIds[...] │ |
| 24 | +│ allowedComposeHashes[...] │ |
| 25 | +│ allowAnyDevice: true/false │ |
| 26 | +│ │ |
| 27 | +│ isAppAllowed(bootInfo) → checks whitelist │ |
| 28 | +└─────────────────────────────────────────────────────────────┘ |
| 29 | +``` |
| 30 | + |
| 31 | +**Key insight:** The private key you deploy with becomes the **owner** of the AppAuth contract. The owner can add devices and compose hashes to the whitelist. |
| 32 | + |
| 33 | +## Four Deployment Options |
| 34 | + |
| 35 | +### Option 1: Single Device (CLI Default) |
| 36 | + |
| 37 | +```bash |
| 38 | +phala deploy -n my-oracle -c docker-compose.yaml \ |
| 39 | + --kms-id kms-base-prod5 \ |
| 40 | + --private-key "$PRIVATE_KEY" |
| 41 | +``` |
| 42 | + |
| 43 | +Creates AppAuth with: |
| 44 | +- `owner` = address derived from PRIVATE_KEY |
| 45 | +- `allowAnyDevice = false` |
| 46 | +- `allowedDeviceIds[thisDevice] = true` |
| 47 | +- `allowedComposeHashes[thisHash] = true` |
| 48 | + |
| 49 | +**Result:** Only this device can get keys. Simple single-node deployment. |
| 50 | + |
| 51 | +### Option 2: Owner Adds Devices (Recommended for Multi-Node) |
| 52 | + |
| 53 | +Deploy first node, then the **owner** explicitly whitelists additional devices: |
| 54 | + |
| 55 | +```bash |
| 56 | +# Step 1: Deploy first node (owner = your address) |
| 57 | +phala deploy -n my-oracle -c docker-compose.yaml \ |
| 58 | + --kms-id kms-base-prod5 \ |
| 59 | + --private-key "$PRIVATE_KEY" |
| 60 | + |
| 61 | +# Step 2: Get device ID of second node (from Phala Cloud API) |
| 62 | +# Step 3: Owner calls addDevice() on the AppAuth contract |
| 63 | +cast send $APP_AUTH_ADDRESS "addDevice(bytes32)" $DEVICE_ID_2 \ |
| 64 | + --private-key "$PRIVATE_KEY" --rpc-url https://mainnet.base.org |
| 65 | + |
| 66 | +# Step 4: Deploy second node with same appId |
| 67 | +python3 deploy_replica.py |
| 68 | +``` |
| 69 | + |
| 70 | +**Result:** Controlled multi-node. Owner explicitly approves each device. |
| 71 | + |
| 72 | +### Option 3: allowAnyDevice (Permissive) |
| 73 | + |
| 74 | +Deploy with `allowAnyDevice=true` to let any TEE device get keys: |
| 75 | + |
| 76 | +```bash |
| 77 | +python3 deploy_with_contract.py # Sets allowAnyDevice=true |
| 78 | +``` |
| 79 | + |
| 80 | +The contract checks: |
| 81 | +```solidity |
| 82 | +function isAppAllowed(AppBootInfo calldata bootInfo) { |
| 83 | + if (!allowedComposeHashes[bootInfo.composeHash]) |
| 84 | + return (false, "Compose hash not allowed"); |
| 85 | + if (!allowAnyDevice && !allowedDeviceIds[bootInfo.deviceId]) |
| 86 | + return (false, "Device not allowed"); |
| 87 | + return (true, ""); |
| 88 | +} |
| 89 | +``` |
| 90 | + |
| 91 | +**Result:** Any device with a whitelisted composeHash can join. Less secure but simpler. |
| 92 | + |
| 93 | +### Option 4: Custom AppAuth Contract |
| 94 | + |
| 95 | +Deploy your own contract implementing `IAppAuth`: |
| 96 | + |
| 97 | +```solidity |
| 98 | +interface IAppAuth { |
| 99 | + function isAppAllowed(AppBootInfo calldata bootInfo) |
| 100 | + external view returns (bool isAllowed, string memory reason); |
| 101 | +} |
| 102 | +``` |
| 103 | + |
| 104 | +Then register it: |
| 105 | +```solidity |
| 106 | +DstackKms(KMS_ADDRESS).registerApp(yourContract); // Public function! |
| 107 | +``` |
| 108 | + |
| 109 | +**Examples:** |
| 110 | +- NFT-gated: Only NFT holders can run nodes |
| 111 | +- DAO-controlled: Compose hashes approved by vote |
| 112 | +- Time-locked: Deployments only during certain periods |
| 113 | +- Multi-sig: Multiple approvals required |
| 114 | + |
| 115 | +## DstackApp Owner Functions |
| 116 | + |
| 117 | +The standard AppAuth (`DstackApp`) gives the owner these functions: |
| 118 | + |
| 119 | +```solidity |
| 120 | +// Whitelist management |
| 121 | +function addDevice(bytes32 deviceId) external onlyOwner; |
| 122 | +function removeDevice(bytes32 deviceId) external onlyOwner; |
| 123 | +function addComposeHash(bytes32 composeHash) external onlyOwner; |
| 124 | +function removeComposeHash(bytes32 composeHash) external onlyOwner; |
| 125 | +
|
| 126 | +// Mode toggle |
| 127 | +function setAllowAnyDevice(bool allow) external onlyOwner; |
| 128 | +
|
| 129 | +// Upgrades |
| 130 | +function disableUpgrades() external onlyOwner; // Permanent! |
| 131 | +``` |
| 132 | + |
| 133 | +## Signature Chain |
| 134 | + |
| 135 | +Regardless of which option you use, the oracle proves its keys came from KMS: |
| 136 | + |
| 137 | +``` |
| 138 | +KMS Root (known on-chain) |
| 139 | + │ |
| 140 | + │ signs: "dstack-kms-issued:" + appId + appPubkey |
| 141 | + ▼ |
| 142 | +App Key (recovered from kmsSignature) |
| 143 | + │ |
| 144 | + │ signs: "ethereum:" + derivedPubkeyHex |
| 145 | + ▼ |
| 146 | +Derived Key → signs oracle messages |
| 147 | +``` |
| 148 | + |
| 149 | +## Quick Start (Local Simulator) |
| 150 | + |
| 151 | +```bash |
| 152 | +pip install -r requirements.txt |
| 153 | +phala simulator start |
| 154 | + |
| 155 | +docker compose build |
| 156 | +docker compose run --rm -p 8080:8080 \ |
| 157 | + -v ~/.phala-cloud/simulator/0.5.3/dstack.sock:/var/run/dstack.sock \ |
| 158 | + app |
| 159 | + |
| 160 | +# In another terminal |
| 161 | +python3 test_local.py |
| 162 | +``` |
| 163 | + |
| 164 | +## Files |
| 165 | + |
| 166 | +``` |
| 167 | +05-onchain-oracle/ |
| 168 | +├── docker-compose.yaml # Oracle app |
| 169 | +├── TeeOracle.sol # On-chain signature verification |
| 170 | +├── test_local.py # Test with simulator |
| 171 | +├── test_phalacloud.py # Test on Phala Cloud |
| 172 | +├── deploy_with_contract.py # Deploy with allowAnyDevice=true (Option 3) |
| 173 | +├── deploy_replica.py # Deploy replica using existing appId |
| 174 | +├── add_device.py # Add device to whitelist (Option 2) |
| 175 | +├── add_compose_hash.py # Add compose hash to whitelist |
| 176 | +├── requirements.txt |
| 177 | +├── NOTES.md # Troubleshooting & CLI limitations |
| 178 | +└── README.md |
| 179 | +``` |
| 180 | + |
| 181 | +## Contract Addresses |
| 182 | + |
| 183 | +| Contract | Address | |
| 184 | +|----------|---------| |
| 185 | +| DstackKms (Base) | `0x2f83172A49584C017F2B256F0FB2Dca14126Ba9C` | |
| 186 | +| KMS Root (Simulator) | `0x8f2cF602C9695b23130367ed78d8F557554de7C5` | |
| 187 | + |
| 188 | +## IAppAuth Interface |
| 189 | + |
| 190 | +From [dstack/kms/auth-eth/contracts/IAppAuth.sol](https://github.com/dstack-tee/dstack/blob/main/kms/auth-eth/contracts/IAppAuth.sol): |
| 191 | + |
| 192 | +```solidity |
| 193 | +struct AppBootInfo { |
| 194 | + address appId; |
| 195 | + bytes32 composeHash; |
| 196 | + address instanceId; |
| 197 | + bytes32 deviceId; |
| 198 | + bytes32 mrAggregated; |
| 199 | + bytes32 mrSystem; |
| 200 | + bytes32 osImageHash; |
| 201 | + string tcbStatus; |
| 202 | + string[] advisoryIds; |
| 203 | +} |
| 204 | +
|
| 205 | +function isAppAllowed(AppBootInfo calldata bootInfo) |
| 206 | + external view returns (bool isAllowed, string memory reason); |
| 207 | +``` |
| 208 | + |
| 209 | +## References |
| 210 | + |
| 211 | +- [DstackKms.sol](https://github.com/dstack-tee/dstack/blob/main/kms/auth-eth/contracts/DstackKms.sol) |
| 212 | +- [DstackApp.sol](https://github.com/dstack-tee/dstack/blob/main/kms/auth-eth/contracts/DstackApp.sol) |
| 213 | +- [NOTES.md](NOTES.md) |
0 commit comments