@@ -8,6 +8,16 @@ Access TEE features from your Python application running inside dstack. Derive d
88pip install dstack-sdk
99```
1010
11+ Blockchain helpers are optional extras:
12+
13+ | Extra | Pulls in | Use when |
14+ | ---| ---| ---|
15+ | ` dstack-sdk[ethereum] ` | ` eth-account ` | You want ` to_account ` / ` to_account_secure ` for Ethereum signing |
16+ | ` dstack-sdk[solana] ` | ` solders ` | You want ` to_keypair ` / ` to_keypair_secure ` for Solana signing |
17+ | ` dstack-sdk[all] ` | both | You need both |
18+
19+ Aliases ` [eth] ` and ` [sol] ` are accepted for convenience.
20+
1121## Quick Start
1222
1323``` python
@@ -24,10 +34,11 @@ quote = client.get_quote(b'my-app-state')
2434print (quote.quote)
2535```
2636
27- The client automatically connects to ` /var/run/dstack.sock ` . For local development with the simulator, pass the endpoint explicitly :
37+ The client automatically connects to ` /var/run/dstack.sock ` . For local development with the simulator:
2838
2939``` python
3040client = DstackClient(' http://localhost:8090' )
41+ # or export DSTACK_SIMULATOR_ENDPOINT=http://localhost:8090
3142```
3243
3344## Core API
@@ -45,18 +56,19 @@ btc_key = client.get_key('wallet/bitcoin')
4556mainnet_key = client.get_key(' wallet/eth/mainnet' )
4657testnet_key = client.get_key(' wallet/eth/testnet' )
4758
48- # Different algorithm
59+ # Use a different signature algorithm (requires dstack OS >= 0.5.7)
4960ed_key = client.get_key(' signing/key' , algorithm = ' ed25519' )
5061```
5162
5263** Parameters:**
53- - ` path ` : Key derivation path (determines the key)
54- - ` purpose ` (optional): Included in signature chain message, does not affect the derived key
55- - ` algorithm ` (optional): ` 'secp256k1' ` (default) or ` 'ed25519' `
64+ - ` path ` (optional) : Key derivation path. Defaults to ` "" ` (root).
65+ - ` purpose ` (optional): Included in the signature chain message; does not affect the derived key.
66+ - ` algorithm ` (optional): ` 'secp256k1' ` (default) or ` 'ed25519' ` .
5667
5768** Returns:** ` GetKeyResponse `
5869- ` key ` : Hex-encoded private key
5970- ` signature_chain ` : Signatures proving the key was derived in a genuine TEE
71+ - ` decode_key() ` / ` decode_signature_chain() ` : Helpers that return ` bytes `
6072
6173### Generate Attestation Quotes
6274
@@ -71,12 +83,23 @@ print(rtmrs)
7183```
7284
7385** Parameters:**
74- - ` report_data ` : Exactly 64 bytes recommended. If shorter, pad with zeros. If longer, hash it first (e.g., SHA-256).
86+ - ` report_data ` : Up to 64 bytes ( ` bytes ` or ` str ` ). Shorter inputs are padded with zeros; longer inputs should be hashed first (e.g., SHA-256).
7587
7688** Returns:** ` GetQuoteResponse `
7789- ` quote ` : Hex-encoded TDX quote
7890- ` event_log ` : JSON string of measured events
79- - ` replay_rtmrs() ` : Method to compute RTMR values from event log
91+ - ` replay_rtmrs() ` : Method to compute RTMR values from the event log
92+ - ` decode_quote() ` / ` decode_event_log() ` : Helpers
93+
94+ ### Versioned Attestation
95+
96+ ` attest() ` returns a versioned attestation payload that newer verifier APIs can dispatch on without sniffing the quote format.
97+
98+ ``` python
99+ result = client.attest(b ' user:alice:nonce123' )
100+ print (result.attestation) # hex string
101+ print (result.decode_attestation()) # bytes
102+ ```
80103
81104### Get Instance Info
82105
@@ -85,15 +108,16 @@ info = client.info()
85108print (info.app_id)
86109print (info.instance_id)
87110print (info.tcb_info)
111+ print (info.cloud_vendor, info.cloud_product) # 0.5.7+
88112```
89113
90114** Returns:** ` InfoResponse `
91- - ` app_id ` : Application identifier
92- - ` instance_id ` : Instance identifier
93- - ` app_name ` : Application name
94- - ` tcb_info ` : TCB measurements (MRTD, RTMRs, event log)
115+ - ` app_id ` , ` instance_id ` , ` app_name ` , ` device_id `
116+ - ` tcb_info ` : TCB measurements (MRTD, RTMRs, event log, compose hash, ...)
95117- ` compose_hash ` : Hash of the app configuration
96118- ` app_cert ` : Application certificate (PEM)
119+ - ` key_provider_info ` : Key management configuration
120+ - ` cloud_vendor ` / ` cloud_product ` : Cloud provider strings (empty on older OS)
97121
98122### Generate TLS Certificates
99123
@@ -103,27 +127,35 @@ print(info.tcb_info)
103127tls = client.get_tls_key(
104128 subject = ' api.example.com' ,
105129 alt_names = [' localhost' ],
106- usage_ra_tls = True # Embed attestation in certificate
130+ usage_ra_tls = True , # Embed attestation in certificate
131+ # 0.5.7+ options below:
132+ not_before = 1700000000 , # seconds since UNIX epoch
133+ not_after = 1800000000 ,
134+ with_app_info = True ,
107135)
108-
109- print (tls.key) # PEM private key
110- print (tls.certificate_chain) # Certificate chain
136+ print (tls.key) # PEM private key
137+ print (tls.certificate_chain) # Certificate chain
111138```
112139
113140** Parameters:**
114- - ` subject ` (optional): Certificate common name (e.g., domain name)
115- - ` alt_names ` (optional): List of subject alternative names
116- - ` usage_ra_tls ` (optional): Embed TDX quote in certificate extension
117- - ` usage_server_auth ` (optional): Enable for server authentication (default: ` True ` )
118- - ` usage_client_auth ` (optional): Enable for client authentication (default: ` False ` )
141+ - ` subject ` (optional): Certificate Common Name (e.g., domain name)
142+ - ` alt_names ` (optional): Subject Alternative Names
143+ - ` usage_ra_tls ` (optional): Embed TDX quote in a certificate extension (default ` False ` )
144+ - ` usage_server_auth ` (optional): Enable for server authentication (default ` True ` )
145+ - ` usage_client_auth ` (optional): Enable for client authentication (default ` False ` )
146+ - ` not_before ` / ` not_after ` (optional, kw-only): Validity window in seconds since UNIX epoch. Requires dstack OS >= 0.5.7.
147+ - ` with_app_info ` (optional, kw-only): Embed app identity into the certificate. Requires dstack OS >= 0.5.7.
148+
149+ When any of the 0.5.7-only options is set, the SDK probes ` Version ` first and raises ` RuntimeError ` on older guest agents that lack it.
119150
120151** Returns:** ` GetTlsKeyResponse `
121152- ` key ` : PEM-encoded private key
122153- ` certificate_chain ` : List of PEM certificates
154+ - ` as_uint8array(max_length=None) ` : Returns the DER-encoded private key bytes (handy when feeding key material into low-level crypto libraries)
123155
124156### Sign and Verify
125157
126- Sign data using TEE-derived keys (not yet released) :
158+ Sign data using TEE-derived keys:
127159
128160``` python
129161result = client.sign(' ed25519' , b ' message to sign' )
@@ -137,21 +169,15 @@ print(valid.valid) # True
137169
138170** ` sign() ` Parameters:**
139171- ` algorithm ` : ` 'ed25519' ` , ` 'secp256k1' ` , or ` 'secp256k1_prehashed' `
140- - ` data ` : Data to sign (bytes or string)
172+ - ` data ` : Data to sign (` bytes ` or ` str ` ). For ` secp256k1_prehashed ` , must be a 32-byte digest.
141173
142174** ` sign() ` Returns:** ` SignResponse `
143175- ` signature ` : Hex-encoded signature
144176- ` public_key ` : Hex-encoded public key
145177- ` signature_chain ` : Signatures proving TEE origin
146178
147- ** ` verify() ` Parameters:**
148- - ` algorithm ` : Algorithm used for signing
149- - ` data ` : Original data
150- - ` signature ` : Signature to verify
151- - ` public_key ` : Public key to verify against
152-
153179** ` verify() ` Returns:** ` VerifyResponse `
154- - ` valid ` : Boolean indicating if signature is valid
180+ - ` valid ` : Boolean indicating if the signature is valid
155181
156182### Emit Events
157183
@@ -162,25 +188,28 @@ client.emit_event('config_loaded', 'production')
162188client.emit_event(' plugin_initialized' , ' auth-v2' )
163189```
164190
165- ** Parameters:**
166- - ` event ` : Event name (string identifier)
167- - ` payload ` : Event value (bytes or string)
191+ ### Diagnostics
192+
193+ ``` python
194+ client.version() # VersionResponse(version, rev) — raises on OS < 0.5.7
195+ client.is_reachable() # Quick connectivity probe; never raises
196+ ```
168197
169198## Async Client
170199
171- For async applications, use ` AsyncDstackClient ` :
200+ For async applications, use ` AsyncDstackClient ` . The API surface is identical, but every method is a coroutine :
172201
173202``` python
174- from dstack_sdk import AsyncDstackClient
175203import asyncio
204+ from dstack_sdk import AsyncDstackClient
176205
177206async def main ():
178207 client = AsyncDstackClient()
179208
180209 info = await client.info()
181210 key = await client.get_key(' wallet/eth' )
182211
183- # Concurrent operations
212+ # Run requests concurrently
184213 keys = await asyncio.gather(
185214 client.get_key(' user/alice' ),
186215 client.get_key(' user/bob' ),
@@ -189,79 +218,91 @@ async def main():
189218asyncio.run(main())
190219```
191220
221+ ` AsyncDstackClient ` accepts the same constructor as ` DstackClient ` plus ` use_sync_http: bool = False ` for callers that need to issue sync HTTP from within an async context.
222+
192223## Blockchain Integration
193224
194225### Ethereum
195226
196227``` python
197- from dstack_sdk.ethereum import to_account
228+ from dstack_sdk.ethereum import to_account_secure
198229
199230key = client.get_key(' wallet/ethereum' )
200- account = to_account (key)
231+ account = to_account_secure (key)
201232print (account.address)
202233```
203234
235+ ` to_account_secure(key) ` hashes the full key material with SHA-256 before deriving the Ethereum private key. The legacy ` to_account() ` is kept for backward compatibility but uses raw key bytes—prefer the secure variant for new code.
236+
204237### Solana
205238
206239``` python
207- from dstack_sdk.solana import to_keypair
240+ from dstack_sdk.solana import to_keypair_secure
208241
209242key = client.get_key(' wallet/solana' )
210- keypair = to_keypair(key)
211- print (keypair.public_key)
212- ```
213-
214- ## Development
215-
216- For local development without TDX hardware, use the simulator:
217-
218- ``` bash
219- git clone https://github.com/Dstack-TEE/dstack.git
220- cd dstack/sdk/simulator
221- ./build.sh
222- ./dstack-simulator
223- ```
224-
225- Then set the endpoint:
226-
227- ``` bash
228- export DSTACK_SIMULATOR_ENDPOINT=http://localhost:8090
243+ keypair = to_keypair_secure(key)
244+ print (keypair.pubkey())
229245```
230246
231- Run tests with PDM:
232-
233- ``` bash
234- pdm install -d
235- pdm run pytest -s
236- ```
247+ Same pattern: ` to_keypair_secure(key) ` SHA-256-hashes the key material; ` to_keypair() ` is the legacy raw-bytes variant.
237248
238249---
239250
240251## Deployment Utilities
241252
242253These utilities are for deployment scripts, not runtime SDK operations.
243254
244- ### Encrypt Environment Variables
255+ ### Encrypted Environment Variables
245256
246- Encrypt secrets before deploying to dstack :
257+ The KMS returns a fresh X25519 public key (with a secp256k1 signature) that you encrypt secrets against before submitting them with your deployment. Always verify the signer before trusting the key :
247258
248259``` python
249- from dstack_sdk import encrypt_env_vars, verify_env_encrypt_public_key, EnvVar
260+ from dstack_sdk import (
261+ encrypt_env_vars,
262+ verify_env_encrypt_public_key,
263+ verify_env_encrypt_public_key_legacy,
264+ EnvVar,
265+ )
250266
251- # Get and verify the KMS public key
252- # (obtain public_key and signature from KMS API)
253- kms_identity = verify_env_encrypt_public_key(public_key_bytes, signature_bytes, app_id)
254- if not kms_identity:
255- raise RuntimeError (' Invalid KMS key' )
267+ # `public_key`, `signature_v1`, `timestamp` come from KMS /GetAppEnvEncryptPubKey.
268+ signer = verify_env_encrypt_public_key(
269+ public_key = public_key_bytes,
270+ signature = signature_v1_bytes,
271+ app_id = app_id_hex,
272+ timestamp = timestamp,
273+ )
274+ if signer is None :
275+ # Fallback for older KMS builds that only emit the unprotected legacy
276+ # signature. Vulnerable to replay; warn loudly if you must use it.
277+ signer = verify_env_encrypt_public_key_legacy(
278+ public_key = public_key_bytes,
279+ signature = legacy_signature_bytes,
280+ app_id = app_id_hex,
281+ )
282+ if signer is None :
283+ raise RuntimeError (' invalid KMS env-encrypt public key' )
284+
285+ # Always compare the recovered signer against a known-good KMS signer
286+ # address, obtained out-of-band from the DstackKms contract or your
287+ # deployment configuration. Without this check, an attacker could sign
288+ # their own env-encrypt key and the verification above would still pass.
289+ EXPECTED_KMS_SIGNER = ' 0x...' # replace with your known KMS signer address
290+ if signer != EXPECTED_KMS_SIGNER :
291+ raise RuntimeError (
292+ f ' unexpected KMS signer: got { signer} , '
293+ f ' expected { EXPECTED_KMS_SIGNER } '
294+ )
256295
257- # Encrypt variables
258296env_vars = [
259297 EnvVar(key = ' DATABASE_URL' , value = ' postgresql://...' ),
260298 EnvVar(key = ' API_KEY' , value = ' secret' ),
261299]
262- encrypted = encrypt_env_vars(env_vars, public_key)
300+ encrypted = await encrypt_env_vars(env_vars, public_key_hex)
301+ # encrypt_env_vars_sync(...) is also available for non-async callers.
263302```
264303
304+ ` verify_env_encrypt_public_key ` returns the recovered compressed secp256k1 signer (` 0x ` -prefixed hex) on success, or ` None ` for any failure (bad length, expired/future timestamp, malformed ` app_id ` , invalid signature). The default ` max_age_seconds ` is 300; pass a larger value if your deployment workflow legitimately holds the response longer.
305+
265306### Calculate Compose Hash
266307
267308``` python
@@ -272,6 +313,43 @@ hash_value = get_compose_hash(app_compose_dict)
272313
273314---
274315
316+ ## Compatibility
317+
318+ | Feature | Required dstack OS |
319+ | ---| ---|
320+ | ` get_key ` , ` get_quote ` , ` get_tls_key ` (legacy fields), ` info ` (legacy fields) | 0.3+ |
321+ | ` emit_event ` | 0.5.0+ |
322+ | ` attest ` , ` sign ` / ` verify ` , ` is_reachable ` | 0.5.0+ (sign/verify require server build with the feature) |
323+ | ` version ` , ` algorithm='ed25519' ` on ` get_key ` , ` info.cloud_vendor ` / ` cloud_product ` , ` not_before ` / ` not_after ` / ` with_app_info ` on ` get_tls_key ` | 0.5.7+ |
324+ | ` verify_env_encrypt_public_key ` (signature_v1 with timestamp) | Requires KMS build that emits ` signature_v1 ` ; legacy variant remains available |
325+
326+ Calls that require 0.5.7-only fields probe the ` Version ` RPC first and raise a clear ` RuntimeError ` on older guest agents.
327+
328+ ## Development
329+
330+ For local development without TDX hardware, use the simulator:
331+
332+ ``` bash
333+ git clone https://github.com/Dstack-TEE/dstack.git
334+ cd dstack/sdk/simulator
335+ ./build.sh
336+ ./dstack-simulator
337+ ```
338+
339+ Then set the endpoint:
340+
341+ ``` bash
342+ export DSTACK_SIMULATOR_ENDPOINT=http://localhost:8090
343+ ```
344+
345+ Install dev dependencies and run tests with PDM:
346+
347+ ``` bash
348+ cd sdk/python
349+ make install
350+ make test
351+ ```
352+
275353## Migration from TappdClient
276354
277355Replace ` TappdClient ` with ` DstackClient ` :
@@ -288,7 +366,7 @@ client = DstackClient()
288366
289367Method changes:
290368- ` derive_key() ` → ` get_tls_key() ` for TLS certificates
291- - ` tdx_quote() ` → ` get_quote() `
369+ - ` tdx_quote() ` → ` get_quote() ` (raw data only, no hash algorithms)
292370- Socket path: ` /var/run/tappd.sock ` → ` /var/run/dstack.sock `
293371
294372## License
0 commit comments