The dstack SDK provides a Go client for secure communication with the dstack Trusted Execution Environment (TEE). This SDK enables applications to derive cryptographic keys, generate remote attestation quotes, and perform other security-critical operations within confidential computing environments.
go get github.com/Dstack-TEE/dstack/sdk/goThe dstack SDK enables secure communication with dstack Trusted Execution Environment (TEE) instances. dstack applications are defined using app-compose.json (based on the AppCompose structure) and deployed as containerized applications using Docker Compose.
dstack applications consist of:
- App Configuration:
app-compose.jsondefining app metadata, security settings, and Docker Compose content - Container Deployment: Docker Compose configuration embedded within the app definition
- TEE Integration: Access to TEE functionality via Unix socket (
/var/run/dstack.sock)
- Key Derivation: Deterministic key derivation for signing, encryption, and other application-specific secrets
- Remote Attestation: Versioned attestations providing cryptographic proof of execution environment, including GPU evidence
- TLS Certificate Management: Fresh certificate issuance with optional RA-TLS support for secure connections
- Deployment Security: Client-side encryption of sensitive environment variables ensuring secrets are only accessible to target TEE applications
- Blockchain Integration (legacy): v0-era adapters for Ethereum and Solana, not part of v1 — see Blockchain adapters
dstack 0.6.0 splits the guest agent API into two surfaces on the same socket, selected by URL path. The SDK mirrors both, and it is a transport mirror only: it does not translate between them.
| Client | Paths | Status |
|---|---|---|
DstackClient (= DstackClientV1) |
/v1/<Method> |
Current, and the default. Six methods: IssueCert, GetKey, Attest, AttestGpu, Info, Version |
DstackClientV0 |
/GetKey, equivalently /v0/GetKey |
Retiring. Frozen at v0.5.11, served unchanged for pre-0.6 clients, never extended |
client := dstack.NewDstackClient() // v1, the current API — use this
v0 := dstack.NewDstackClientV0() // frozen v0.5.11 API, legacyThe unsuffixed names mean v1: DstackClient is an alias for DstackClientV1,
and NewDstackClient returns a v1 client. The frozen surface remains available,
but only under its explicit V0 name.
What v1 changes:
GetTlsKeyis nowIssueCert— certificate issuance is the operation; the key was only ever a by-product.pathpluspurposecollapse into a singledomain, andalgorithmis required, with nok256alias and no default.AttestsubsumesGetQuote;Infois flat, with notcb_infoblob and noapp_cert.Sign,VerifyandEmitEventare gone. Sign and verify locally with a standard library, using the keyGetKeyreturns;EmitEventis gone because runtime RTMR3 events became system-owned.
⚠️ v1 derives different key material than v0.client.GetKey(ctx, "storage-encryption", "secp256k1")andv0.GetKey(ctx, "storage-encryption", "", "secp256k1")return unrelated keys. v1 derives under its own HKDF salt and binds the algorithm and a versioned context tag alongside the domain, so secp256k1 and ed25519 no longer share one 32-byte secret either. There is no compatibility mode and no way to reach a v0 key through v1. An application holding anything under a v0 key must migrate it deliberately: derive the v1 key, then re-key whatever the old one protected. Seedocs/guest-api-v1.mdfor the byte-level construction.Code that used the unsuffixed client for v0 calls fails loudly on upgrade rather than silently deriving different keys, because the v1 method signatures differ and
GetKeyrequiresalgorithmexplicitly. To stay on the frozen surface, switch toDstackClientV0.
To use the SDK, your Docker Compose configuration must bind-mount the dstack socket:
# docker-compose.yml
services:
your-app:
image: your-app-image
volumes:
- /var/run/dstack.sock:/var/run/dstack.sock # dstack OS 0.5.x and later
# For dstack OS 0.3.x compatibility (deprecated):
# - /var/run/tappd.sock:/var/run/tappd.sockFirst, ensure your dstack application is properly configured:
1. App Configuration (app-compose.json)
{
"manifest_version": 1,
"name": "my-secure-app",
"runner": "docker-compose",
"docker_compose_file": "services:\n app:\n build: .\n volumes:\n - /var/run/dstack.sock:/var/run/dstack.sock\n environment:\n - NODE_ENV=production",
"public_tcbinfo": true,
"kms_enabled": false,
"gateway_enabled": false
}Note: The docker_compose_file field contains the actual Docker Compose YAML content as a string, not a file path.
package main
import (
"context"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"log"
"time"
"github.com/Dstack-TEE/dstack/sdk/go/dstack"
)
func main() {
// Create client - automatically connects to /var/run/dstack.sock
client := dstack.NewDstackClient()
// For local development with simulator
// devClient := dstack.NewDstackClient(dstack.WithEndpoint("http://localhost:8090"))
ctx := context.Background()
// Get TEE instance information
info, err := client.Info(ctx)
if err != nil {
log.Fatal(err)
}
fmt.Printf("App ID: %x\n", info.AppID)
fmt.Printf("Instance ID: %x\n", info.InstanceID)
fmt.Println("App Name:", info.AppName)
fmt.Println("App Compose:", info.AppCompose)
// Derive deterministic keys for application-specific secrets
storageKey, err := client.GetKey(ctx, "storage-encryption", "secp256k1")
if err != nil {
log.Fatal(err)
}
fmt.Println("Derived key (32 bytes):", hex.EncodeToString(storageKey.Key)) // secp256k1 private key
fmt.Println("Public key:", hex.EncodeToString(storageKey.PublicKey))
fmt.Println("Signature chain links:", len(storageKey.SignatureChain)) // Authenticity proof
// Generate a remote attestation, bound to your own data
applicationData := map[string]interface{}{
"version": "1.0.0",
"timestamp": time.Now().Unix(),
"user_id": "alice",
}
jsonData, _ := json.Marshal(applicationData)
digest := sha256.Sum256(jsonData) // report data is at most 64 bytes
attestation, err := client.Attest(ctx, digest[:], false)
if err != nil {
log.Fatal(err)
}
fmt.Println("Attestation:", hex.EncodeToString(attestation.Attestation))
}- dstack OS 0.6.x and later: serves both
/v1/<Method>and the frozen v0 paths on/var/run/dstack.sock - dstack OS 0.5.x: serves the v0 paths only;
DstackClient(v1) gets a plain HTTP 404 - dstack OS 0.3.x:
/var/run/tappd.sock(deprecated but supported)
The SDK automatically detects the correct socket path, but you must ensure the appropriate volume binding in your Docker Compose configuration. Version() is the cheapest probe for whether an agent speaks v1 at all.
Issue fresh TLS certificates with optional Remote Attestation support. Important: IssueCert() generates a random key on each call — it is designed specifically for TLS/SSL scenarios where fresh keys are required. Use GetKey() when you need a stable, attestable key.
// Issue a certificate with different usage scenarios
cert, err := client.IssueCert(ctx,
dstack.WithCertSubject("my-secure-service"), // Certificate common name
dstack.WithCertAltNames([]string{"localhost", "127.0.0.1"}), // Additional valid domains/IPs
dstack.WithCertUsageRaTls(true), // Include remote attestation
dstack.WithCertUsageServerAuth(true), // Enable server authentication
dstack.WithCertUsageClientAuth(false), // Disable client authentication
)
if err != nil {
log.Fatal(err)
}
fmt.Println("Private Key (PEM):", cert.Key)
fmt.Println("Certificate Chain:", cert.CertificateChain)
// ⚠️ WARNING: Each call generates a different key
cert1, _ := client.IssueCert(ctx)
cert2, _ := client.IssueCert(ctx)
// cert1.Key != cert2.Key (always different!)You can validate SDK changes immediately from another Go project by using replace:
require github.com/Dstack-TEE/dstack/sdk/go v0.0.0
replace github.com/Dstack-TEE/dstack/sdk/go => ../dstack/sdk/goThen run your starter normally:
go mod tidy
go run .If your starter enables the v0-era blockchain routes, run with matching tags (see Blockchain adapters):
# ethereum only
go get github.com/ethereum/go-ethereum@v1.16.8
go run -tags ethereum .
# solana only
go run -tags solana .
# both
go run -tags "ethereum solana" .Important: This feature is specifically for deployment-time security, not runtime SDK operations.
The SDK provides end-to-end encryption capabilities for securely transmitting sensitive environment variables during dstack application deployment.
import (
"encoding/hex"
"fmt"
"log"
"github.com/Dstack-TEE/dstack/sdk/go/dstack"
)
// 1. Define sensitive environment variables
envVars := []dstack.EnvVar{
{Key: "DATABASE_URL", Value: "postgresql://user:pass@host:5432/db"},
{Key: "API_SECRET_KEY", Value: "your-secret-key"},
{Key: "JWT_PRIVATE_KEY", Value: "-----BEGIN PRIVATE KEY-----\n..."},
{Key: "BACKUP_SIGNING_SEED", Value: "hex-encoded seed..."},
}
// 2. Obtain encryption public key from KMS API (dstack-vmm or Phala Cloud).
// HTTP request implementation depends on your HTTP client.
kmsResponse := struct {
PublicKey string `json:"public_key"`
SignatureV1 string `json:"signature_v1"`
Timestamp uint64 `json:"timestamp"`
}{
// Fill these fields from /prpc/GetAppEnvEncryptPubKey?json.
}
// 3. Verify KMS API authenticity to prevent man-in-the-middle attacks
publicKeyBytes, _ := hex.DecodeString(kmsResponse.PublicKey)
signatureBytes, _ := hex.DecodeString(kmsResponse.SignatureV1)
// Prefer timestamped verification to prevent replay attacks.
kmsIdentity, err := dstack.VerifyEnvEncryptPublicKeyWithTimestamp(
publicKeyBytes,
signatureBytes,
"your-app-id-hex",
kmsResponse.Timestamp,
nil, // use default freshness policy (max age 300s)
)
if err != nil || kmsIdentity == nil {
log.Fatal("kms API provided untrusted encryption key")
}
expectedKMSIdentity := "0x03..." // From the DstackKms contract or deployment config
actualKMSIdentity := string(kmsIdentity)
if actualKMSIdentity != expectedKMSIdentity {
log.Fatalf("unexpected KMS identity: got %s", actualKMSIdentity)
}
fmt.Println("Verified KMS identity:", actualKMSIdentity)
// VerifyEnvEncryptPublicKey() is available only for explicit compatibility with
// older KMS builds. It does not provide timestamp replay protection.
// 4. Encrypt environment variables for secure deployment
encryptedData, err := dstack.EncryptEnvVars(envVars, kmsResponse.PublicKey)
if err != nil {
log.Fatal(err)
}
fmt.Println("Encrypted payload:", encryptedData)
// 5. Deploy with encrypted configuration
// deployDstackApp(..., encryptedData)The SDK implements secure key derivation using:
- Deterministic Generation: Keys are derived using HMAC-based Key Derivation Function (HKDF)
- Application Isolation: Different
app_idvalues derive different keys even with the same domain - Signature Verification: All derived keys include cryptographic proof of origin
- TEE Protection: Master keys never leave the secure enclave
// Each domain generates a unique, deterministic key
storageKey, _ := client.GetKey(ctx, "storage-encryption", "secp256k1")
authKey, _ := client.GetKey(ctx, "api-auth", "secp256k1")
// storageKey.Key != authKey.Key (guaranteed different)
sameStorageKey, _ := client.GetKey(ctx, "storage-encryption", "secp256k1")
// storageKey.Key == sameStorageKey.Key (guaranteed identical)
// The algorithm is bound into the derivation, so the two curves never share a
// secret — this is a second, unrelated key, not a reinterpretation of the first.
storageKeyEd25519, _ := client.GetKey(ctx, "storage-encryption", "ed25519")Derivation is flat: a/b is not a child of a. The / is a naming convention, nothing more, and two domains that share a prefix yield unrelated keys.
Attestations provide cryptographic proof of:
- Code Integrity: Measurement of loaded application code
- Data Integrity: Inclusion of application-specific data in the attestation
- Environment Authenticity: Verification of TEE platform and configuration
applicationState := map[string]interface{}{
"version": "1.0.0",
"config_hash": "sha256:...",
"timestamp": time.Now().Unix(),
}
stateData, _ := json.Marshal(applicationState)
digest := sha256.Sum256(stateData) // report data is 1-64 bytes
attestation, err := client.Attest(ctx, digest[:], false)
if err != nil {
log.Fatal(err)
}
// The attestation can be verified by external parties to confirm:
// 1. Application is running in a genuine TEE
// 2. Application code matches expected measurements
// 3. Application state is authentic and unmodifiedThe encryption scheme uses:
- X25519 ECDH: Elliptic curve key exchange for forward secrecy
- AES-256-GCM: Authenticated encryption with 256-bit keys
- Ephemeral Keys: New keypair generated for each encryption operation
- Authenticated Data: Prevents tampering and ensures integrity
For development without physical TDX hardware:
# Clone and build simulator
git clone https://github.com/Dstack-TEE/dstack.git
cd dstack/sdk/simulator
./build.sh
./dstack-simulator
# Set environment variable
export DSTACK_SIMULATOR_ENDPOINT=http://localhost:8090client := dstack.NewDstackClient()
// Version takes no arguments and touches nothing, so it is the cheapest probe
// for whether the agent is up and speaks v1.
if _, err := client.Version(context.Background()); err != nil {
log.Fatal("dstack v1 service is not reachable: ", err)
}The client automatically connects to /var/run/dstack.sock. For local development with the simulator:
client := dstack.NewDstackClient(dstack.WithEndpoint("http://localhost:8090"))Options: the same set applies to NewDstackClient and NewDstackClientV0.
WithEndpoint(endpoint string): Connection endpoint- Unix socket path (production):
/var/run/dstack.sock - HTTP/HTTPS URL (development):
http://localhost:8090 - Environment variable:
DSTACK_SIMULATOR_ENDPOINT
- Unix socket path (production):
WithLogger(logger *slog.Logger): Custom logger (default:slog.Default())
Production App Configuration:
The Docker Compose configuration is embedded in app-compose.json:
{
"manifest_version": 1,
"name": "production-app",
"runner": "docker-compose",
"docker_compose_file": "services:\n app:\n image: your-app\n volumes:\n - /var/run/dstack.sock:/var/run/dstack.sock\n environment:\n - NODE_ENV=production",
"public_tcbinfo": true
}Important: The docker_compose_file contains YAML content as a string, ensuring the volume binding for /var/run/dstack.sock is included.
The current API, and the default. DstackClient is an alias for
DstackClientV1; NewDstackClient and NewDstackClientV1 are the same
constructor, with the same options and the same endpoint resolution as the v0
client — the two surfaces share one socket and differ only in the URL path.
client := dstack.NewDstackClient()Protobuf bytes fields travel as lowercase hex on the wire and are exposed as
[]byte; the fields carrying JSON documents (AppCompose, VmConfig,
KeyProviderInfo) stay string.
Issues a certificate for this application. Options are WithCertSubject,
WithCertAltNames, WithCertUsageRaTls, WithCertUsageServerAuth,
WithCertUsageClientAuth, WithCertAppInfo, WithCertNotBefore,
WithCertNotAfter.
Returns: Key (PEM) and CertificateChain (PEM, leaf first).
The key is freshly generated on every call and is not derived from the app
identity — that is what GetKey is for. v0 called this GetTlsKey.
Derives an application key from (domain, algorithm).
domain: any byte string, including one containing:,/or NUL. Derivation is flat:a/bis not a child ofa, and two domains yield unrelated keys.algorithm:secp256k1ored25519. Required — there is no default and nok256alias, so a typo is an error rather than a key of the wrong type under a name you thought meant something else. An empty value is rejected client-side.
Returns: Key (32 bytes), PublicKey (33 bytes SEC1-compressed for
secp256k1, 32 raw bytes for ed25519), and a two-element SignatureChain.
key, err := client.GetKey(ctx, "backup-signing", "secp256k1")Use Cases:
- Stable service identity keys
- Application signing keys
- Encryption key seeds
- Any scenario requiring consistent, reproducible keys
Attest(ctx context.Context, reportData []byte, includeBoottimeGpuEvidence bool) (*AttestV1Response, error)
Produces a versioned attestation over reportData (1–64 bytes, zero-padded on
the right to 64). The sole CVM attestation entry point in v1: the attestation
already carries the TDX quote and the event log, so there is no GetQuote.
Setting includeBoottimeGpuEvidence also returns BoottimeGpuEvidence, the GPU
evidence nvattest recorded at boot, as []GpuEvidenceBundle — the same type
AttestGpu returns, so one parser serves both methods. Absence is the empty
slice, not a sentinel: it stays empty unless you asked for it and the guest has
boot-time output.
It is not bound to reportData: bind a bundle by replaying the runtime event
log and comparing sha256 of its Evidence against the evidence_sha256 field of
the measured gpu-attestation event. Evidence holds the exact bytes nvattest
wrote, byte for byte, and that exactness is what makes the comparison work —
re-serializing the JSON changes the digest.
att, err := client.Attest(ctx, reportData, true)
for _, bundle := range att.BoottimeGpuEvidence {
// bundle.Format == "nvidia-nvattest-boottime-json-v1"
digest := sha256.Sum256(bundle.Evidence)
// compare digest against the `gpu-attestation` event's evidence_sha256
}Collects GPU evidence now, against a nonce you choose. The nonce must be
exactly 32 bytes (checked client-side): SPDM fixes the evidence nonce at that
length and dstack passes it through verbatim, so you can compare it directly
against the eat_nonce claim rather than reversing a hash.
Returns: Bundles, each a GpuEvidenceBundle with Vendor, Format and
opaque Evidence bytes — the same type Attest returns in
BoottimeGpuEvidence. Format is what separates them: these carry
nvidia-nvattest-collect-evidence-json-v1, the boot-time record carries
nvidia-nvattest-boottime-json-v1, and a verifier for one does not appraise the
other.
This is evidence, not a verdict — select a verifier by vendor and format, then check the signature, certificate chain, measurements and embedded nonce. It still does not bind the GPU to this CVM.
Identity and configuration, in a flat shape: AppID, AppName, ComposeHash,
AppCompose, InstanceID, DeviceID, OsImageHash, MrAggregated,
VmConfig, KeyProviderInfo, CloudVendor, CloudProduct.
No TcbInfo and no AppCert. The measurement registers and the event log live
on the attestation Attest returns, which is the only place they are
quote-backed. Nothing here is evidence — it arrives over a local socket with no
quote behind it, so confirm the hashes against an attestation before relying on
them.
ComposeHash is sha256 over the exact AppCompose bytes. Do not parse and
re-serialize before hashing: key order, whitespace and unknown fields all change
the digest, and that digest is what gets whitelisted on chain.
Returns the agent Version and Rev. The cheapest probe for whether an agent
speaks v1 at all: an agent that predates v1 has no /v1 mount and answers with
a plain HTTP 404.
import "github.com/Dstack-TEE/dstack/sdk/go/dstack"
appCompose := dstack.AppCompose{
ManifestVersion: &[]int{1}[0],
Name: "my-app",
Runner: "docker-compose",
DockerComposeFile: "docker-compose.yml",
}
hash, err := dstack.GetComposeHash(appCompose)
if err != nil {
log.Fatal(err)
}
fmt.Println("Configuration hash:", hash)The SDK no longer ships local signature or signature-chain helpers, and v1 has
no Verify RPC. Verification needs no key material and no attestation, so the
guest agent is not the right place for it: its answer arrives over the socket
unattested, which is no better than checking the signature yourself. Sign and
verify locally with a standard Go crypto library, using the key GetKey returns.
docs/guest-api-v1.md is the normative specification for verifying a v1
chain. It pins the bytes: the length-prefixed claim encoding, the KDF, and the
step-by-step procedure a relying party follows. In outline, GetKey returns two
links —
[0] app root key signs keccak256(LP("dstack-guest-v1-key-claim") || LP(algorithm) || LP(domain) || LP(public_key))
[1] KMS root key signs keccak256("dstack-kms-issued" || ":" || app_id || app_root_pubkey)
— and the step that carries the security of all the others is the anchor: obtain
the KMS root public key from a source you trust independently of the agent being
checked, either the DstackKms contract's kmsInfo().k256Pubkey or a value
pinned out of band. An attacker who can answer your query for the anchor can also
mint a self-consistent chain, so reading it from the KMS you are checking proves
nothing. The same goes for app_id: use the one you registered on chain, not the
one Info() echoed back from the CVM you are verifying.
Verify the authenticity of encryption public keys provided by KMS APIs:
import (
"encoding/hex"
"fmt"
"log"
"github.com/Dstack-TEE/dstack/sdk/go/dstack"
)
// Example: Verify a KMS response from /prpc/GetAppEnvEncryptPubKey?json
kmsResponse := struct {
PublicKey string `json:"public_key"`
SignatureV1 string `json:"signature_v1"`
Timestamp uint64 `json:"timestamp"`
}{
// Fill these fields from the KMS API response.
}
publicKey, _ := hex.DecodeString(kmsResponse.PublicKey)
signature, _ := hex.DecodeString(kmsResponse.SignatureV1)
appID := "0000000000000000000000000000000000000000"
kmsIdentity, err := dstack.VerifyEnvEncryptPublicKeyWithTimestamp(publicKey, signature, appID, kmsResponse.Timestamp, nil)
if err != nil || kmsIdentity == nil {
log.Fatal("kms signature verification failed")
}
expectedKMSIdentity := "0x03..." // From the DstackKms contract or deployment config
actualKMSIdentity := string(kmsIdentity)
if actualKMSIdentity != expectedKMSIdentity {
log.Fatalf("unexpected KMS identity: got %s", actualKMSIdentity)
}
fmt.Println("Trusted KMS identity:", actualKMSIdentity)-
Key Management
- Use descriptive, unique domains for key derivation
- Never expose derived keys outside the TEE
- Implement proper access controls in your application
-
Remote Attestation
- Always verify attestations before trusting remote TEE instances
- Include application-specific data in
reportData - Validate RTMR measurements against expected values
-
TLS Configuration
- Enable RA-TLS for attestation-based authentication
- Use appropriate certificate validity periods
- Implement proper certificate validation
-
Error Handling
- Fail closed on security-critical cryptographic errors
- Log security events for monitoring
- Avoid fallback behavior that weakens verification or key isolation
For local development without TDX devices, you can use the simulator:
git clone https://github.com/Dstack-TEE/dstack.git
cd dstack/sdk/simulator
./build.sh
./dstack-simulator# Set environment variables and run tests
TAPPD_SIMULATOR_ENDPOINT=/path/to/simulator/tappd.sock \
DSTACK_SIMULATOR_ENDPOINT=/path/to/simulator/dstack.sock \
go test -v ./dstack ./tappdRun tests:
go test -v ./dstackEverything below this line describes the frozen v0.5.11 surface. It is retiring:
present so pre-0.6 applications keep working, never extended, and reachable only
under its explicit V0 name. New code should use
DstackClient, which is v1.
v0 := dstack.NewDstackClientV0()
info, _ := v0.Info(ctx) // AppID and friends are hex strings
key, _ := v0.GetKey(ctx, "wallet/ethereum", "mainnet", "secp256k1")
quote, _ := v0.GetQuote(ctx, reportData) // Intel TDX only
tlsKey, _ := v0.GetTlsKey(ctx, dstack.WithSubject("api.example.com"))
⚠️ v0 keys are not v1 keys. Moving a name fromDstackClientV0toDstackClientderives unrelated key material — see the warning under Two API versions. Code that used the unsuffixed client for v0 calls fails loudly on upgrade rather than silently deriving different keys, because the v1 method signatures differ andGetKeyrequiresalgorithmexplicitly. To stay on the frozen surface, switch toDstackClientV0.
Retrieves comprehensive information about the TEE instance.
Returns: InfoResponse
AppID: Unique application identifierInstanceID: Unique instance identifierAppName: Application name from configurationDeviceID: TEE device identifierTcbInfo: Trusted Computing Base informationMrtd: Measurement of TEE domainRtmr0-3: Runtime Measurement RegistersEventLog: Boot and runtime events
AppCert: Application certificate in PEM format
Derives deterministic private key material for wallets, signing, encryption, stable service identities, and other application-specific secrets.
Parameters:
path: Unique identifier for key derivation (e.g.,"wallet/ethereum","signing/solana")purpose: Included in the signature-chain message; does not affect the private key bytesalgorithm:"secp256k1"(default behavior),"k256"(alias), or"ed25519"
Returns: GetKeyResponse
Key: 32-byte private key material as a hex stringSignatureChain: Array of cryptographic signatures proving key authenticity
Key Characteristics:
- Deterministic: Same path always generates identical raw key material for the same app
- Isolated: Different paths produce cryptographically independent keys
- Blockchain-Ready: Use
secp256k1for Ethereum and Bitcoin-style signing; useed25519with a Solana-specific path for independent Solana keys - Verifiable: Signature chain proves key was derived inside genuine TEE
For compatibility, algorithm selects how the same derived 32-byte material is interpreted; it does not domain-separate the derivation. Use algorithm-specific paths when independent keys are required. v1 fixes this by binding algorithm and a versioned context tag into the KDF — and, for the same reason, a v1 key is never a v0 key.
// Examples of deterministic key derivation
ethWallet, _ := v0.GetKey(ctx, "wallet/ethereum", "mainnet", "secp256k1")
btcWallet, _ := v0.GetKey(ctx, "wallet/bitcoin", "mainnet", "secp256k1")
solWallet, _ := v0.GetKey(ctx, "wallet/solana", "mainnet", "ed25519")
// Same path always returns same key
key1, _ := v0.GetKey(ctx, "my-app/signing", "", "secp256k1")
key2, _ := v0.GetKey(ctx, "my-app/signing", "", "secp256k1")
// key1.Key == key2.Key (guaranteed identical)
// Different paths return different keys
userA, _ := v0.GetKey(ctx, "user/alice/wallet", "", "secp256k1")
userB, _ := v0.GetKey(ctx, "user/bob/wallet", "", "secp256k1")
// userA.Key != userB.Key (guaranteed different)Generates a TDX attestation quote containing the provided report data. Intel TDX
only; on any other platform it returns an error and you should call Attest()
instead.
Parameters:
reportData: Data to include in quote (max 64 bytes)
Returns: GetQuoteResponse
Quote: TDX quote as hex stringEventLog: JSON string of system events
Produces a versioned dstack attestation over reportData (at most 64 bytes),
covering every supported platform rather than Intel TDX alone.
Returns: AttestResponse
Attestation: the versioned attestation bytes
There is no GPU option on this surface. GPU attestation is v1 only — see
DstackClient.Attest and DstackClient.AttestGpu.
Signs a payload with the app signing key. algorithm is ed25519, secp256k1,
or secp256k1_prehashed (where data is already a 32-byte digest).
Verify(ctx context.Context, algorithm string, data, signature, publicKey []byte) (*VerifyResponse, error)
Asks the agent to check a signature, and reports the agent's verdict, not an
attested one. Frozen surface only: v1 has no Verify, because verification needs
no key material and no attestation, so the agent's answer arrives unattested and
is no better than checking the signature yourself. See docs/guest-api-v1.md for
how to verify a v1 chain.
Removed server-side in dstack 0.6.0. Runtime RTMR3 events became
system-owned, so an agent from 0.6.0 on answers this with an error, which the
client returns rather than swallowing — an application that believes it measured
something it did not is worse off than one that fails loudly. The method remains
so that pre-0.6 code still compiles. Bind application data through reportData
on Attest instead.
Generates a fresh, random TLS key pair with X.509 certificate for TLS/SSL connections. Important: This method generates different keys on each call - use GetKey() for deterministic keys. v1 calls this IssueCert.
Options: WithSubject, WithAltNames, WithUsageRaTls, WithUsageServerAuth, WithUsageClientAuth, WithNotBefore, WithNotAfter, WithAppInfo.
Returns: GetTlsKeyResponse
Key: Private key in PEM format (X.509/PKCS#8)CertificateChain: Certificate chain array
// Example 1: Standard HTTPS server certificate
serverCert, _ := v0.GetTlsKey(ctx,
dstack.WithSubject("api.example.com"),
dstack.WithAltNames([]string{"api.example.com", "www.api.example.com", "10.0.0.1"}),
dstack.WithUsageServerAuth(true),
)
// Example 2: Certificate with remote attestation (RA-TLS)
attestedCert, _ := v0.GetTlsKey(ctx,
dstack.WithSubject("secure-api.example.com"),
dstack.WithUsageRaTls(true), // Include TDX quote for remote verification
)
// ⚠️ Each call generates different keys (unlike GetKey)
cert1, _ := v0.GetTlsKey(ctx)
cert2, _ := v0.GetTlsKey(ctx)
// cert1.Key != cert2.Key (always different)Returns the guest-agent version. Available on dstack OS 0.5.7 and later; older
agents have no Version RPC and this returns an error.
Tests connectivity to the dstack service.
Returns: bool indicating service availability
The chain adapters are v0-era. ToEthereumAccount, ToEthereumAccountSecure,
ToSolanaKeypair and ToSolanaKeypairSecure accept the v0 *GetKeyResponse
(and, with a warning, *GetTlsKeyResponse), and that is the only shape they
take: v1 has no chain-related surface. GetKey returns key material, and what
an application builds out of those bytes is its own business.
keyResult, _ := v0.GetKey(ctx, "ethereum/main", "wallet", "secp256k1")
// Enhanced security with SHA256 hashing (recommended over ToEthereumAccount)
secureAccount, err := dstack.ToEthereumAccountSecure(keyResult)
if err != nil {
log.Fatal(err)
}
fmt.Println("Ethereum Address:", secureAccount.Address.Hex())By default, the Go SDK builds a core profile (attestation, key derivation, info, env encryption).
The adapters are split by tags:
ethereumtag:ToEthereumAccount()ToEthereumAccountSecure()
solanatag:ToSolanaKeypair()ToSolanaKeypairSecure()
# add optional dependency
go get github.com/ethereum/go-ethereum@v1.16.8
# build/test with ethereum helpers enabled
go build -tags ethereum ./...
go test -tags ethereum ./...# no extra dependency is required for solana helper APIs
go build -tags solana ./...
go test -tags solana ./...go get github.com/ethereum/go-ethereum@v1.16.8
go build -tags "ethereum solana" ./...
go test -tags "ethereum solana" ./...If you don't need blockchain helper APIs, do not use these tags and you won't pull optional helper imports.
TappdClient is deprecated and will be removed.
The legacy tappd client mixed two different use cases that v0 already separated:
GetKey(): Deterministic key derivation for application-specific secretsGetTlsKey(): Random TLS certificate generation for HTTPS/SSL
| Component | TappdClient (Old) | DstackClientV0 (New) | Status |
|---|---|---|---|
| Socket Path | /var/run/tappd.sock |
/var/run/dstack.sock |
✅ Updated |
| HTTP URL Format | http://localhost/prpc/Tappd.<Method> |
http://localhost/<Method> |
✅ Simplified |
| K256 Key Method | DeriveKey(...) |
GetKey(...) |
✅ Renamed |
| TLS Certificate Method | DeriveKey(...) |
GetTlsKey(...) |
✅ Separated |
| TDX Quote | TdxQuote(...) |
GetQuote(report_data) |
✅ Renamed |
Step 1: Update Imports and Client
// Before
import "github.com/Dstack-TEE/dstack/sdk/go/tappd"
tappdClient := tappd.NewTappdClient()
// After
import "github.com/Dstack-TEE/dstack/sdk/go/dstack"
v0 := dstack.NewDstackClientV0()The table above maps tappd onto the v0 method set, which is the smallest step
away from TappdClient. It is not the destination: new code should target
NewDstackClient (v1). See Two API versions for what
changes, including the warning that v1 derives different key material.
Step 2: Update Method Calls
// For deterministic application keys (most common)
// Before: TappdClient methods
keyResult, _ := tappdClient.DeriveKey(ctx, "wallet")
// After: DstackClientV0 methods
keyResult, _ := v0.GetKey(ctx, "wallet/ethereum", "ethereum", "secp256k1")
// For TLS certificates
// Before: DeriveKey with TLS options
tlsCert, _ := tappdClient.DeriveKeyWithSubjectAndAltNames(ctx, "api", "example.com", []string{"localhost"})
// After: GetTlsKey with proper options
tlsCert, _ := v0.GetTlsKey(ctx,
dstack.WithSubject("example.com"),
dstack.WithAltNames([]string{"localhost"}),
)-
Infrastructure Updates:
- Update Docker volume binding to
/var/run/dstack.sock - Change environment variables from
TAPPD_*toDSTACK_*
- Update Docker volume binding to
-
Client Code Updates:
- Replace
tappd.NewTappdClient()withdstack.NewDstackClientV0() - Replace
DeriveKey()calls with appropriate method:-
GetKey()for deterministic application keys -
GetTlsKey()for TLS certificates (random)
-
- Replace
TdxQuote()calls withGetQuote() - SECURITY CRITICAL: Update blockchain integration functions:
- Replace
ToEthereumAccount()withToEthereumAccountSecure()(Ethereum) - Replace
ToSolanaKeypair()withToSolanaKeypairSecure()(Solana)
- Replace
- Replace
-
Testing:
- Test that deterministic keys still work as expected
- Verify TLS certificate generation works
- Test quote generation with new interface
- Verify blockchain integrations work with secure functions
-
Then move on from v0: port to
DstackClient(v1), migrating any assets held under a v0 key deliberately — the derivations are unrelated.
Apache License 2.0