Access TEE features from your Python application running inside dstack. Derive deterministic keys, generate attestation quotes, create TLS certificates, and sign data—all backed by hardware security.
pip install dstack-sdkBlockchain helpers are optional extras:
| Extra | Pulls in | Use when |
|---|---|---|
dstack-sdk[ethereum] |
eth-account |
You want to_account / to_account_secure for Ethereum signing |
dstack-sdk[solana] |
solders |
You want to_keypair / to_keypair_secure for Solana signing |
dstack-sdk[all] |
both | You need both |
Aliases [eth] and [sol] are accepted for convenience.
from dstack_sdk import DstackClient
client = DstackClient()
# Derive a deterministic key for your wallet
key = client.get_key('wallet/eth')
print(key.key) # Same path always returns the same key
# Generate an attestation quote
quote = client.get_quote(b'my-app-state')
print(quote.quote)The client automatically connects to /var/run/dstack.sock. For local development with the simulator:
client = DstackClient('http://localhost:8090')
# or export DSTACK_SIMULATOR_ENDPOINT=http://localhost:8090get_key() derives deterministic keys bound to your application's identity (app_id). The same path always produces the same key for your app, but different apps get different keys even with the same path.
# Derive keys by path
eth_key = client.get_key('wallet/ethereum')
btc_key = client.get_key('wallet/bitcoin')
# Use path to separate keys
mainnet_key = client.get_key('wallet/eth/mainnet')
testnet_key = client.get_key('wallet/eth/testnet')
# Use a different signature algorithm (requires dstack OS >= 0.5.7)
ed_key = client.get_key('signing/key', algorithm='ed25519')Parameters:
path(optional): Key derivation path. Defaults to""(root).purpose(optional): Included in the signature chain message; does not affect the derived key.algorithm(optional):'secp256k1'(default) or'ed25519'.
Returns: GetKeyResponse
key: Hex-encoded private keysignature_chain: Signatures proving the key was derived in a genuine TEEdecode_key()/decode_signature_chain(): Helpers that returnbytes
get_quote() creates a TDX quote proving your code runs in a genuine TEE.
quote = client.get_quote(b'user:alice:nonce123')
# Replay RTMRs from the event log
rtmrs = quote.replay_rtmrs()
print(rtmrs)Parameters:
report_data: Up to 64 bytes (bytesorstr). Shorter inputs are padded with zeros; longer inputs should be hashed first (e.g., SHA-256).
Returns: GetQuoteResponse
quote: Hex-encoded TDX quoteevent_log: JSON string of measured eventsreplay_rtmrs(): Method to compute RTMR values from the event logdecode_quote()/decode_event_log(): Helpers
attest() returns a versioned attestation payload that newer verifier APIs can dispatch on without sniffing the quote format.
result = client.attest(b'user:alice:nonce123')
print(result.attestation) # hex string
print(result.decode_attestation()) # bytesinfo = client.info()
print(info.app_id)
print(info.instance_id)
print(info.tcb_info)
print(info.cloud_vendor, info.cloud_product) # 0.5.7+Returns: InfoResponse
app_id,instance_id,app_name,device_idtcb_info: TCB measurements (MRTD, RTMRs, event log, compose hash, ...)compose_hash: Hash of the app configurationapp_cert: Application certificate (PEM)key_provider_info: Key management configurationcloud_vendor/cloud_product: Cloud provider strings (empty on older OS)
get_tls_key() creates fresh TLS certificates. Unlike get_key(), each call generates a new random key.
tls = client.get_tls_key(
subject='api.example.com',
alt_names=['localhost'],
usage_ra_tls=True, # Embed attestation in certificate
# 0.5.7+ options below:
not_before=1700000000, # seconds since UNIX epoch
not_after=1800000000,
with_app_info=True,
)
print(tls.key) # PEM private key
print(tls.certificate_chain) # Certificate chainParameters:
subject(optional): Certificate Common Name (e.g., domain name)alt_names(optional): Subject Alternative Namesusage_ra_tls(optional): Embed TDX quote in a certificate extension (defaultFalse)usage_server_auth(optional): Enable for server authentication (defaultTrue)usage_client_auth(optional): Enable for client authentication (defaultFalse)not_before/not_after(optional, kw-only): Validity window in seconds since UNIX epoch. Requires dstack OS >= 0.5.7.with_app_info(optional, kw-only): Embed app identity into the certificate. Requires dstack OS >= 0.5.7.
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.
Returns: GetTlsKeyResponse
key: PEM-encoded private keycertificate_chain: List of PEM certificatesas_uint8array(max_length=None): Returns the DER-encoded private key bytes (handy when feeding key material into low-level crypto libraries)
Sign data using TEE-derived keys:
result = client.sign('ed25519', b'message to sign')
print(result.signature)
print(result.public_key)
# Verify the signature
valid = client.verify('ed25519', b'message to sign', result.signature, result.public_key)
print(valid.valid) # Truesign() Parameters:
algorithm:'ed25519','secp256k1', or'secp256k1_prehashed'data: Data to sign (bytesorstr). Forsecp256k1_prehashed, must be a 32-byte digest.
sign() Returns: SignResponse
signature: Hex-encoded signaturepublic_key: Hex-encoded public keysignature_chain: Signatures proving TEE origin
verify() Returns: VerifyResponse
valid: Boolean indicating if the signature is valid
Extend RTMR3 with custom measurements for your application's boot sequence (requires dstack OS 0.5.0+). These measurements are append-only and become part of the attestation record.
client.emit_event('config_loaded', 'production')
client.emit_event('plugin_initialized', 'auth-v2')client.version() # VersionResponse(version, rev) — raises on OS < 0.5.7
client.is_reachable() # Quick connectivity probe; never raisesFor async applications, use AsyncDstackClient. The API surface is identical, but every method is a coroutine:
import asyncio
from dstack_sdk import AsyncDstackClient
async def main():
client = AsyncDstackClient()
info = await client.info()
key = await client.get_key('wallet/eth')
# Run requests concurrently
keys = await asyncio.gather(
client.get_key('user/alice'),
client.get_key('user/bob'),
)
asyncio.run(main())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.
from dstack_sdk.ethereum import to_account_secure
key = client.get_key('wallet/ethereum')
account = to_account_secure(key)
print(account.address)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.
from dstack_sdk.solana import to_keypair_secure
key = client.get_key('wallet/solana')
keypair = to_keypair_secure(key)
print(keypair.pubkey())Same pattern: to_keypair_secure(key) SHA-256-hashes the key material; to_keypair() is the legacy raw-bytes variant.
These utilities are for deployment scripts, not runtime SDK operations.
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:
from dstack_sdk import (
encrypt_env_vars,
verify_env_encrypt_public_key,
verify_env_encrypt_public_key_legacy,
EnvVar,
)
# `public_key`, `signature_v1`, `timestamp` come from KMS /GetAppEnvEncryptPubKey.
signer = verify_env_encrypt_public_key(
public_key=public_key_bytes,
signature=signature_v1_bytes,
app_id=app_id_hex,
timestamp=timestamp,
)
if signer is None:
# Fallback for older KMS builds that only emit the unprotected legacy
# signature. Vulnerable to replay; warn loudly if you must use it.
signer = verify_env_encrypt_public_key_legacy(
public_key=public_key_bytes,
signature=legacy_signature_bytes,
app_id=app_id_hex,
)
if signer is None:
raise RuntimeError('invalid KMS env-encrypt public key')
# Always compare the recovered signer against a known-good KMS signer
# address, obtained out-of-band from the DstackKms contract or your
# deployment configuration. Without this check, an attacker could sign
# their own env-encrypt key and the verification above would still pass.
EXPECTED_KMS_SIGNER = '0x...' # replace with your known KMS signer address
if signer != EXPECTED_KMS_SIGNER:
raise RuntimeError(
f'unexpected KMS signer: got {signer}, '
f'expected {EXPECTED_KMS_SIGNER}'
)
env_vars = [
EnvVar(key='DATABASE_URL', value='postgresql://...'),
EnvVar(key='API_KEY', value='secret'),
]
encrypted = await encrypt_env_vars(env_vars, public_key_hex)
# encrypt_env_vars_sync(...) is also available for non-async callers.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.
from dstack_sdk import get_compose_hash
hash_value = get_compose_hash(app_compose_dict)| Feature | Required dstack OS |
|---|---|
get_key, get_quote, get_tls_key (legacy fields), info (legacy fields) |
0.3+ |
emit_event |
0.5.0+ |
attest, sign / verify, is_reachable |
0.5.0+ (sign/verify require server build with the feature) |
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+ |
verify_env_encrypt_public_key (signature_v1 with timestamp) |
Requires KMS build that emits signature_v1; legacy variant remains available |
Calls that require 0.5.7-only fields probe the Version RPC first and raise a clear RuntimeError on older guest agents.
For local development without TDX hardware, use the simulator:
git clone https://github.com/Dstack-TEE/dstack.git
cd dstack/sdk/simulator
./build.sh
./dstack-simulatorThen set the endpoint:
export DSTACK_SIMULATOR_ENDPOINT=http://localhost:8090Install dev dependencies and run tests with PDM:
cd sdk/python
make install
make testReplace TappdClient with DstackClient:
# Before
from dstack_sdk import TappdClient
client = TappdClient()
# After
from dstack_sdk import DstackClient
client = DstackClient()Method changes:
derive_key()→get_tls_key()for TLS certificatestdx_quote()→get_quote()(raw data only, no hash algorithms)- Socket path:
/var/run/tappd.sock→/var/run/dstack.sock
Apache License 2.0