|
| 1 | +# Tutorial 02: KMS and Signing |
| 2 | + |
| 3 | +Derive persistent keys that survive restarts and produce on-chain verifiable signatures. |
| 4 | + |
| 5 | +## The Problem |
| 6 | + |
| 7 | +TEE memory is wiped on restart. If your app generates a private key at startup, it gets a new key every time — breaking wallets, signatures, and any persistent identity. |
| 8 | + |
| 9 | +## The Solution: `getKey()` |
| 10 | + |
| 11 | +The dstack SDK's `getKey()` derives deterministic keys from KMS: |
| 12 | + |
| 13 | +```javascript |
| 14 | +import { DstackClient } from '@phala/dstack-sdk' |
| 15 | + |
| 16 | +const client = new DstackClient() |
| 17 | +const result = await client.getKey('/oracle', 'ethereum') |
| 18 | + |
| 19 | +// Derive an Ethereum wallet |
| 20 | +const privateKey = '0x' + Buffer.from(result.key).toString('hex').slice(0, 64) |
| 21 | +``` |
| 22 | + |
| 23 | +The derived key is: |
| 24 | +- **Deterministic**: Same path → same key, every restart |
| 25 | +- **Unique to your app**: Different apps (compose hashes) get different keys |
| 26 | +- **Verifiable**: Signature chain proves the key came from KMS |
| 27 | + |
| 28 | +## How It Works |
| 29 | + |
| 30 | +``` |
| 31 | +┌─────────────────────────────────────────────────────────────┐ |
| 32 | +│ KMS │ |
| 33 | +│ (runs in its own TEE, holds root keys) │ |
| 34 | +│ │ |
| 35 | +│ Root Key ──derives──▶ App Key ──derives──▶ Your Key │ |
| 36 | +│ │ │ │ │ |
| 37 | +│ │ │ └─ getKey('/oracle') |
| 38 | +│ │ └─ tied to appId (compose hash) |
| 39 | +│ └─ KMS root, known on-chain |
| 40 | +└─────────────────────────────────────────────────────────────┘ |
| 41 | +``` |
| 42 | + |
| 43 | +The KMS returns a **signature chain** proving derivation: |
| 44 | + |
| 45 | +```javascript |
| 46 | +const result = await client.getKey('/oracle', 'ethereum') |
| 47 | + |
| 48 | +result.key // Your derived key bytes |
| 49 | +result.signature_chain[0] // App signature: appKey signs derivedPubkey |
| 50 | +result.signature_chain[1] // KMS signature: kmsRoot signs appPubkey |
| 51 | +``` |
| 52 | + |
| 53 | +## On-Chain Verification |
| 54 | + |
| 55 | +The signature chain lets smart contracts verify a signature came from a TEE: |
| 56 | + |
| 57 | +``` |
| 58 | +KMS Root (known on-chain: 0x2f83172A...) |
| 59 | + │ |
| 60 | + │ signs: "dstack-kms-issued:" + appId + appPubkey |
| 61 | + ▼ |
| 62 | +App Key (recovered from kmsSignature) |
| 63 | + │ |
| 64 | + │ signs: "ethereum:" + derivedPubkeyHex |
| 65 | + ▼ |
| 66 | +Derived Key → signs your messages |
| 67 | +``` |
| 68 | + |
| 69 | +A contract can: |
| 70 | +1. Recover `appPubkey` from `kmsSignature` and verify it matches KMS root |
| 71 | +2. Recover `derivedPubkey` from `appSignature` |
| 72 | +3. Verify the message signature against `derivedPubkey` |
| 73 | + |
| 74 | +## Minimal Example |
| 75 | + |
| 76 | +```javascript |
| 77 | +import { DstackClient } from '@phala/dstack-sdk' |
| 78 | +import { privateKeyToAccount } from 'viem/accounts' |
| 79 | + |
| 80 | +const client = new DstackClient() |
| 81 | + |
| 82 | +// Derive a persistent wallet |
| 83 | +const result = await client.getKey('/wallet', 'ethereum') |
| 84 | +const privateKey = '0x' + Buffer.from(result.key).toString('hex').slice(0, 64) |
| 85 | +const account = privateKeyToAccount(privateKey) |
| 86 | + |
| 87 | +console.log('Address:', account.address) // Same every restart |
| 88 | + |
| 89 | +// Sign a message |
| 90 | +const signature = await account.signMessage({ message: 'hello' }) |
| 91 | +``` |
| 92 | + |
| 93 | +## Key Paths |
| 94 | + |
| 95 | +Use paths to organize multiple keys: |
| 96 | + |
| 97 | +```javascript |
| 98 | +await client.getKey('/wallet/main') // Main wallet |
| 99 | +await client.getKey('/wallet/fees') // Fee payer |
| 100 | +await client.getKey('/signing/oracle') // Oracle signatures |
| 101 | +``` |
| 102 | + |
| 103 | +## Try It |
| 104 | + |
| 105 | +```bash |
| 106 | +phala simulator start |
| 107 | + |
| 108 | +docker compose run --rm \ |
| 109 | + -v ~/.phala-cloud/simulator/0.5.3/dstack.sock:/var/run/dstack.sock \ |
| 110 | + app |
| 111 | +``` |
| 112 | + |
| 113 | +## Next Steps |
| 114 | + |
| 115 | +- [01-attestation](../01-attestation): Understand TDX quotes and verification |
| 116 | +- [04-onchain-oracle](../04-onchain-oracle): Full oracle with signature chain verification contract |
| 117 | + |
| 118 | +## References |
| 119 | + |
| 120 | +- [Key Management Protocol](https://docs.phala.network/dstack/design-documents/key-management-protocol) |
| 121 | +- [DstackKms contract](https://github.com/dstack-tee/dstack/blob/main/kms/auth-eth/contracts/DstackKms.sol) |
0 commit comments