diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 81e63758f..7bde54e0b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -50,7 +50,7 @@ jobs: launcher-nontee-check: name: "MPC Launcher non-tee check" - runs-on: warp-ubuntu-2404-x64-2x + runs-on: warp-ubuntu-2404-x64-8x timeout-minutes: 60 permissions: contents: read @@ -67,7 +67,7 @@ jobs: docker-launcher-build-and-verify: name: "Build MPC Launcher Docker image and verify" - runs-on: warp-ubuntu-2404-x64-2x + runs-on: warp-ubuntu-2404-x64-8x timeout-minutes: 60 permissions: contents: read @@ -78,10 +78,25 @@ jobs: with: persist-credentials: false - - name: Install skopeo + - name: Allow unprivileged user namespaces (needed by repro-env/podman on Ubuntu 24.04) + run: sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0 + + - name: Install build dependencies run: | sudo apt-get update - sudo apt-get install -y skopeo + sudo apt-get install -y skopeo liblzma-dev podman + + - name: Install repro-env + run: | + wget 'https://github.com/kpcyrd/repro-env/releases/download/v0.4.3/repro-env' + echo '2a00b21ac5e990e0c6a0ccbf3b91e34a073660d1f4553b5f3cda2b09cc4d4d8a repro-env' | sha256sum -c - + sudo install -m755 repro-env -t /usr/bin + + - name: Cache Rust dependencies + uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1 + with: + save-if: ${{ github.ref == 'refs/heads/main' }} + cache-provider: "warpbuild" - name: Build launcher docker image and verify its hash shell: bash @@ -467,13 +482,13 @@ jobs: run: | python3 -m venv tee_launcher/venv source tee_launcher/venv/bin/activate - cd tee_launcher + cd tee_launcher pip install -r requirements.txt - name: Run pytest run: | source tee_launcher/venv/bin/activate - cd tee_launcher + cd tee_launcher PYTHONPATH=. pytest -vsx fast-ci-checks: diff --git a/.github/workflows/docker_build_launcher.yml b/.github/workflows/docker_build_launcher.yml index 2cab91641..3b8e80da8 100644 --- a/.github/workflows/docker_build_launcher.yml +++ b/.github/workflows/docker_build_launcher.yml @@ -13,7 +13,7 @@ on: jobs: build-and-push-images: name: "Build and push Docker launcher image with commit hash" - runs-on: warp-ubuntu-2404-x64-2x + runs-on: warp-ubuntu-2404-x64-8x permissions: contents: read @@ -23,17 +23,32 @@ jobs: with: persist-credentials: false + - name: Allow unprivileged user namespaces (needed by repro-env/podman on Ubuntu 24.04) + run: sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0 + + - name: Install build dependencies + run: | + sudo apt-get update + sudo apt-get install -y skopeo liblzma-dev podman + + - name: Install repro-env + run: | + wget 'https://github.com/kpcyrd/repro-env/releases/download/v0.4.3/repro-env' + echo '2a00b21ac5e990e0c6a0ccbf3b91e34a073660d1f4553b5f3cda2b09cc4d4d8a repro-env' | sha256sum -c - + sudo install -m755 repro-env -t /usr/bin + + - name: Cache Rust dependencies + uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1 + with: + save-if: ${{ github.ref == 'refs/heads/main' }} + cache-provider: "warpbuild" + - name: Login to Docker Hub uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - - name: Install skopeo - run: | - sudo apt-get update - sudo apt-get install -y skopeo - - name: Build and push launcher image run: | export LAUNCHER_IMAGE_NAME=mpc-launcher diff --git a/Cargo.lock b/Cargo.lock index b401a82aa..253daadb2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10843,6 +10843,29 @@ dependencies = [ "tracing", ] +[[package]] +name = "tee-launcher" +version = "3.7.0" +dependencies = [ + "assert_matches", + "backon", + "clap", + "dstack-sdk", + "httpmock", + "launcher-interface", + "near-mpc-bounded-collections", + "reqwest 0.12.28", + "serde", + "serde_json", + "tempfile", + "thiserror 2.0.18", + "tokio", + "toml 1.0.6+spec-1.1.0", + "tracing", + "tracing-subscriber", + "url", +] + [[package]] name = "tempfile" version = "3.27.0" diff --git a/Cargo.toml b/Cargo.toml index 434b524c1..0046524e2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,6 +27,7 @@ members = [ "crates/primitives", "crates/tee-authority", "crates/tee-context", + "crates/tee-launcher", "crates/test-migration-contract", "crates/test-parallel-contract", "crates/test-port-allocator", diff --git a/crates/attestation/assets/tcb_info.json b/crates/attestation/assets/tcb_info.json index eb6dd57d8..3fa3fd841 100644 --- a/crates/attestation/assets/tcb_info.json +++ b/crates/attestation/assets/tcb_info.json @@ -3,11 +3,11 @@ "rtmr0": "e673be2f70beefb70b48a6109eed4715d7270d4683b3bf356fa25fafbf1aa76e39e9127e6e688ccda98bdab1d4d47f46", "rtmr1": "b598fde9491427341bc4683b75d10d3e36770af3a36a6954d8b6b7b22aa66358f13e1f172e51b7d6e6710d99a8d8532f", "rtmr2": "c812d42bfff1c75382e91a37c867ab117b97eb5e8d6797488928ea38e5fd38b5ed2f87d9613d392507f1c3af94657c93", - "rtmr3": "1df9222353d7c680e4692fd4e1c929cf55c3f02e0e5437c09da0ed4ec11473319cc7fba1404c3eef193aad3afbaee28b", + "rtmr3": "bfd1345bc9ecef80f380f6d619eb52893290312427237081b9765470109c597dffc98d4578588fbab210f2c97413c167", "os_image_hash": "7d47512fda31dc5a7318f72ae1869a3c76323981eea21fc30cafd0f79668642c", - "compose_hash": "05860c32c210b09b0a3957039f926bb3efafb00e11e6ce99cd7d823488fca8ef", + "compose_hash": "6c6037dc97767a0f37f4f21521a1c28a22922ff5a4478967948a94b6c7a7b591", "device_id": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", - "app_compose": "{\n \"manifest_version\": 2,\n \"name\": \"mpc-localnet-one-node-1774339569\",\n \"runner\": \"docker-compose\",\n \"docker_compose_file\": \"version: '3.8'\\n\\nservices:\\n launcher:\\n image: nearone/mpc-launcher@sha256:84c7537a2f84d3477eac2e5ef3ba0765b5d688f86096947eea4744ce25b27054\\n\\n container_name: launcher\\n\\n environment:\\n - PLATFORM=TEE\\n - DOCKER_CONTENT_TRUST=1\\n - DEFAULT_IMAGE_DIGEST=sha256:e2ef71c220158f9ee19a265d583647eedb4e0cd7ca37021fbf0ab34e3d214ed0\\n\\n volumes:\\n - /var/run/docker.sock:/var/run/docker.sock\\n - /var/run/dstack.sock:/var/run/dstack.sock\\n - /tapp:/tapp:ro\\n - shared-volume:/mnt/shared:ro\\n\\n security_opt:\\n - no-new-privileges:true\\n\\n read_only: true\\n\\n tmpfs:\\n - /tmp\\n\\nvolumes:\\n shared-volume:\\n name: shared-volume\\n\",\n \"kms_enabled\": false,\n \"gateway_enabled\": false,\n \"local_key_provider_enabled\": true,\n \"key_provider_id\": \"\",\n \"public_logs\": true,\n \"public_sysinfo\": true,\n \"allowed_envs\": [],\n \"no_instance_id\": true,\n \"secure_time\": false\n}", + "app_compose": "{\n \"manifest_version\": 2,\n \"name\": \"mpc-localnet-one-node-1774515652\",\n \"runner\": \"docker-compose\",\n \"docker_compose_file\": \"version: '3.8'\\n\\nservices:\\n launcher:\\n image: nearone/mpc-launcher@sha256:f0d8146ae705dad182f7e9601e6e97215be4cf94ce80b38fddb2df654020be49\\n\\n container_name: launcher\\n\\n environment:\\n - PLATFORM=TEE\\n - DOCKER_CONTENT_TRUST=1\\n - DEFAULT_IMAGE_DIGEST=sha256:6a5700fccbb3facddd1f3934f4976c4dcefc176c4aac28cd2fd035984b368980\\n\\n volumes:\\n - /var/run/docker.sock:/var/run/docker.sock\\n - /var/run/dstack.sock:/var/run/dstack.sock\\n - /tapp:/tapp:ro\\n - shared-volume:/mnt/shared:rw\\n\\n security_opt:\\n - no-new-privileges:true\\n\\n read_only: true\\n\\n tmpfs:\\n - /tmp\\n\\nvolumes:\\n shared-volume:\\n name: shared-volume\\n\",\n \"kms_enabled\": false,\n \"gateway_enabled\": false,\n \"local_key_provider_enabled\": true,\n \"key_provider_id\": \"\",\n \"public_logs\": true,\n \"public_sysinfo\": true,\n \"allowed_envs\": [],\n \"no_instance_id\": true,\n \"secure_time\": false\n}", "event_log": [ { "imr": 0, @@ -159,16 +159,16 @@ { "imr": 3, "event_type": 134217729, - "digest": "c24d38d25cce2d2f4def3765a97c348363f485d5c429c2c799d1596e1608a412a7edd654aeb494cecf3e37093b84bfe8", + "digest": "cdbd13eb98f8b2f83729abbd9a20cddd9053b9971802dda713c121babdb7d3dcb633f7506d8f99440f3f5d600afdf076", "event": "app-id", - "event_payload": "05860c32c210b09b0a3957039f926bb3efafb00e" + "event_payload": "6c6037dc97767a0f37f4f21521a1c28a22922ff5" }, { "imr": 3, "event_type": 134217729, - "digest": "e9a96dda81a262016807e428c4f8791681ae609c77df817f0a88fb63348174b91e2a9d09654dba5c569aa9b4623d82c8", + "digest": "e76932b68c12a11c217e235b5f16ce04eb70f0714501610986165f423b900974a05c0c8cc2d31aa6f7e9c986d945a168", "event": "compose-hash", - "event_payload": "05860c32c210b09b0a3957039f926bb3efafb00e11e6ce99cd7d823488fca8ef" + "event_payload": "6c6037dc97767a0f37f4f21521a1c28a22922ff5a4478967948a94b6c7a7b591" }, { "imr": 3, @@ -208,9 +208,9 @@ { "imr": 3, "event_type": 134217729, - "digest": "505922fbbd1de0732579ace623add381c1ef328ec64f73940ac12d3f9081163426126ba7c0a530241143bd3b8954a2d5", + "digest": "9aed81f5b1af85f768ef6873ed6f997f55f37de951cca18f5daa35890ab9e5573314d2e0cd188a6913dd4ab6f5455678", "event": "mpc-image-digest", - "event_payload": "e2ef71c220158f9ee19a265d583647eedb4e0cd7ca37021fbf0ab34e3d214ed0" + "event_payload": "6a5700fccbb3facddd1f3934f4976c4dcefc176c4aac28cd2fd035984b368980" } ] } diff --git a/crates/contract/assets/launcher_docker_compose.yaml.template b/crates/contract/assets/launcher_docker_compose.yaml.template index 612afb397..bcf258c4f 100644 --- a/crates/contract/assets/launcher_docker_compose.yaml.template +++ b/crates/contract/assets/launcher_docker_compose.yaml.template @@ -15,7 +15,7 @@ services: - /var/run/docker.sock:/var/run/docker.sock - /var/run/dstack.sock:/var/run/dstack.sock - /tapp:/tapp:ro - - shared-volume:/mnt/shared:ro + - shared-volume:/mnt/shared:rw security_opt: - no-new-privileges:true diff --git a/crates/mpc-attestation/src/attestation.rs b/crates/mpc-attestation/src/attestation.rs index c469ecd00..74ec7f916 100644 --- a/crates/mpc-attestation/src/attestation.rs +++ b/crates/mpc-attestation/src/attestation.rs @@ -147,6 +147,7 @@ impl Attestation { .get_single_event(MPC_IMAGE_HASH_EVENT)? .event_payload; + // TODO(#2478): decode raw bytes let mpc_image_hash_bytes: Vec = hex::decode(mpc_image_hash_payload) .map_err(|err| { VerificationError::Custom(format!( diff --git a/crates/tee-launcher/Cargo.toml b/crates/tee-launcher/Cargo.toml new file mode 100644 index 000000000..ffd07c290 --- /dev/null +++ b/crates/tee-launcher/Cargo.toml @@ -0,0 +1,42 @@ +[package] +name = "tee-launcher" +readme = "README.md" +version = { workspace = true } +license = { workspace = true } +edition = { workspace = true } + +[[bin]] +name = "tee-launcher" +path = "src/main.rs" + +[features] +external-services-tests = [] + +[dependencies] +backon = { workspace = true } +clap = { workspace = true } +dstack-sdk = { workspace = true } +launcher-interface = { workspace = true } +near-mpc-bounded-collections = { workspace = true } +# Pin reqwest 0.12 with bundled webpki-roots for reproducible builds. +# The workspace uses reqwest 0.13 which defaults to rustls-platform-verifier +# (loads CA certs from the system), but the launcher Docker image is a minimal +# container without system CA certs. Using rustls-tls bundles Mozilla's root +# certs into the binary, making TLS work without any system dependencies. +reqwest = { version = "0.12.28", default-features = false, features = ["rustls-tls", "json"] } +serde = { workspace = true } +serde_json = { workspace = true } +tempfile = { workspace = true } +thiserror = { workspace = true } +tokio = { workspace = true } +toml = { workspace = true } +tracing = { workspace = true } +tracing-subscriber = { workspace = true } +url = { workspace = true, features = ["serde"] } + +[dev-dependencies] +assert_matches = { workspace = true } +httpmock = { workspace = true } + +[lints] +workspace = true diff --git a/crates/tee-launcher/README.md b/crates/tee-launcher/README.md new file mode 100644 index 000000000..425d1fd0d --- /dev/null +++ b/crates/tee-launcher/README.md @@ -0,0 +1,110 @@ +# TEE Launcher (Rust) + +Secure launcher for initializing and attesting a Docker-based MPC node inside a TEE-enabled environment (e.g., Intel TDX via dstack). + +Replaces the previous Python launcher (`tee_launcher/launcher.py`). + +## What it does + +1. Loads a TOML configuration file from `/tapp/user_config` +2. Selects an approved MPC image hash (from on-disk approved list, override, or default) +3. Validates the image by resolving it through the Docker registry and pulling by digest +4. In TEE mode: extends RTMR3 by emitting the image digest to dstack +5. Writes the MPC node config to a shared volume +6. Launches the MPC container via `docker compose up -d` + +## CLI Arguments + +All arguments are read from environment variables (set via docker-compose `environment`): + +| Variable | Required | Description | +|----------|----------|-------------| +| `PLATFORM` | Yes | `TEE` or `NONTEE` | +| `DOCKER_CONTENT_TRUST` | Yes | Must be `1` | +| `DEFAULT_IMAGE_DIGEST` | Yes | Fallback `sha256:...` digest when the approved-hashes file is absent | + +## Configuration (TOML) + +The launcher reads its configuration from `/tapp/user_config` as a TOML file. This is a change from the previous Python launcher which used a `.env`-style file. + +```toml +[launcher_config] +image_tags = ["latest"] +image_name = "nearone/mpc-node" +registry = "registry.hub.docker.com" +rpc_request_timeout_secs = 10 +rpc_request_interval_secs = 1 +rpc_max_attempts = 20 +# Optional: force selection of a specific digest (must be in approved list) +# mpc_hash_override = "sha256:abcd..." +port_mappings = [ + { host = 11780, container = 11780 }, + { host = 2200, container = 2200 }, +] + +# Opaque MPC node configuration. +# The launcher does not interpret these fields — they are re-serialized +# to TOML and mounted into the container at /mnt/shared/mpc-config.toml +# for the MPC binary to consume via `start-with-config-file`. +[mpc_config] +# ... any fields the MPC node expects +``` + +### `[launcher_config]` + +| Field | Default | Description | +|-------|---------|-------------| +| `image_tags` | `["latest"]` | Comma-separated Docker image tags to search | +| `image_name` | `nearone/mpc-node` | Docker image name | +| `registry` | `registry.hub.docker.com` | Docker registry hostname | +| `rpc_request_timeout_secs` | `10` | Per-request timeout for registry API calls | +| `rpc_request_interval_secs` | `1` | Initial retry interval for registry API calls | +| `rpc_max_attempts` | `20` | Maximum registry API retry attempts | +| `mpc_hash_override` | (none) | Optional: force a specific `sha256:` digest (must appear in approved list) | + +| `port_mappings` | `[]` | Port mappings forwarded to the MPC container (`{ host, container }` pairs) | + +### `[mpc_config]` + +Arbitrary TOML table passed through to the MPC node. The launcher writes this verbatim to `/mnt/shared/mpc-config.toml`, which the container reads on startup. + +## Image Hash Selection + +Priority order: +1. If the approved hashes file (`/mnt/shared/image-digest.bin`) exists and `mpc_hash_override` is set: use the override (must be in the approved list) +2. If the approved hashes file exists: use the newest approved hash (first in list) +3. If the file is absent: fall back to `DEFAULT_IMAGE_DIGEST` + +## File Locations + +| Path | Description | +|------|-------------| +| `/tapp/user_config` | TOML configuration file | +| `/mnt/shared/image-digest.bin` | JSON file with approved image hashes (written by the MPC node) | +| `/mnt/shared/mpc-config.toml` | MPC node config (written by the launcher) | +| `/var/run/dstack.sock` | dstack unix socket (TEE mode only) | + +## Key Differences from the Python Launcher + +| Aspect | Python (`launcher.py`) | Rust (`tee-launcher`) | +|--------|----------------------|----------------------| +| Config format | `.env` key-value file | TOML | +| MPC node config | Environment variables passed to container | TOML file mounted into container | +| Container launch | `docker run` with flags | `docker compose up -d` with rendered template | +| RTMR3 extension | `curl` to unix socket | `dstack-sdk` native client | + +## Building + +```bash +cargo build -p tee-launcher --release +``` + +## Testing + +```bash +# Unit tests +cargo nextest run -p tee-launcher + +# Integration tests (requires network access and Docker Hub) +cargo nextest run -p tee-launcher --features integration-test +``` diff --git a/crates/tee-launcher/mpc-node-docker-compose.tee.template.yml b/crates/tee-launcher/mpc-node-docker-compose.tee.template.yml new file mode 100644 index 000000000..ef3c06fb4 --- /dev/null +++ b/crates/tee-launcher/mpc-node-docker-compose.tee.template.yml @@ -0,0 +1,21 @@ +services: + mpc-node: + image: "{{IMAGE_NAME}}@{{IMAGE}}" + container_name: "{{CONTAINER_NAME}}" + security_opt: + - no-new-privileges:true + ports: {{PORTS}} + environment: + - "DSTACK_ENDPOINT={{DSTACK_UNIX_SOCKET}}" + volumes: + - /tapp:/tapp:ro + - shared-volume:/mnt/shared + - mpc-data:/data + - "{{DSTACK_UNIX_SOCKET}}:{{DSTACK_UNIX_SOCKET}}" + command: ["/app/mpc-node", "start-with-config-file", "{{MPC_CONFIG_SHARED_PATH}}"] + +volumes: + shared-volume: + name: shared-volume + mpc-data: + name: mpc-data diff --git a/crates/tee-launcher/mpc-node-docker-compose.template.yml b/crates/tee-launcher/mpc-node-docker-compose.template.yml new file mode 100644 index 000000000..b6e3e5979 --- /dev/null +++ b/crates/tee-launcher/mpc-node-docker-compose.template.yml @@ -0,0 +1,18 @@ +services: + mpc-node: + image: "{{IMAGE_NAME}}@{{IMAGE}}" + container_name: "{{CONTAINER_NAME}}" + security_opt: + - no-new-privileges:true + ports: {{PORTS}} + volumes: + - /tapp:/tapp:ro + - shared-volume:/mnt/shared + - mpc-data:/data + command: ["/app/mpc-node", "start-with-config-file", "{{MPC_CONFIG_SHARED_PATH}}"] + +volumes: + shared-volume: + name: shared-volume + mpc-data: + name: mpc-data diff --git a/crates/tee-launcher/src/constants.rs b/crates/tee-launcher/src/constants.rs new file mode 100644 index 000000000..6a6b7623b --- /dev/null +++ b/crates/tee-launcher/src/constants.rs @@ -0,0 +1,8 @@ +pub(crate) const MPC_CONTAINER_NAME: &str = "mpc-node"; +pub(crate) const IMAGE_DIGEST_FILE: &str = "/mnt/shared/image-digest.bin"; +pub(crate) const DSTACK_UNIX_SOCKET: &str = "/var/run/dstack.sock"; +pub(crate) const DSTACK_USER_CONFIG_FILE: &str = "/tapp/user_config"; + +/// Path on the shared volume where the launcher writes the MPC config and the +/// MPC container reads it. Both containers mount `shared-volume` at `/mnt/shared`. +pub(crate) const MPC_CONFIG_SHARED_PATH: &str = "/mnt/shared/mpc-config.toml"; diff --git a/crates/tee-launcher/src/docker_types.rs b/crates/tee-launcher/src/docker_types.rs new file mode 100644 index 000000000..ac9321b3e --- /dev/null +++ b/crates/tee-launcher/src/docker_types.rs @@ -0,0 +1,150 @@ +use launcher_interface::types::DockerSha256Digest; +use serde::{Deserialize, Serialize}; + +/// Partial response +#[derive(Debug, Deserialize, Serialize)] +pub(crate) struct DockerTokenResponse { + pub(crate) token: String, +} + +/// Response from `GET /v2/{name}/manifests/{reference}`. +/// +/// The `mediaType` field determines the variant: +/// - OCI image index → multi-platform manifest with a list of platform entries +/// - Docker V2 / OCI manifest → single-platform manifest with a config digest +#[derive(Debug, Deserialize, Serialize)] +#[serde(tag = "mediaType")] +pub(crate) enum ManifestResponse { + /// Multi-platform manifest (OCI image index). + #[serde(rename = "application/vnd.oci.image.index.v1+json")] + ImageIndex { manifests: Vec }, + + /// Single-platform Docker V2 manifest. + #[serde(rename = "application/vnd.docker.distribution.manifest.v2+json")] + DockerV2 { config: ManifestConfig }, + + /// Single-platform OCI manifest. + #[serde(rename = "application/vnd.oci.image.manifest.v1+json")] + OciManifest { config: ManifestConfig }, +} + +#[derive(Debug, Deserialize, Serialize)] +pub(crate) struct ManifestEntry { + pub(crate) digest: String, + pub(crate) platform: ManifestPlatform, +} + +#[derive(Debug, Deserialize, Serialize, PartialEq, Eq)] +pub(crate) struct ManifestPlatform { + pub(crate) architecture: String, + pub(crate) os: String, +} + +#[derive(Debug, Deserialize, Serialize)] +pub(crate) struct ManifestConfig { + pub(crate) digest: DockerSha256Digest, +} + +#[cfg(test)] +mod tests { + use assert_matches::assert_matches; + + use super::*; + + fn sample_digest_str() -> String { + format!("sha256:{}", "ab".repeat(32)) + } + + #[test] + fn image_index_deserializes() { + // given + let json = serde_json::json!({ + "mediaType": "application/vnd.oci.image.index.v1+json", + "manifests": [ + { + "digest": "sha256:abc123", + "platform": { "architecture": "amd64", "os": "linux" } + }, + { + "digest": "sha256:def456", + "platform": { "architecture": "arm64", "os": "linux" } + } + ] + }); + + // when + let result = serde_json::from_value::(json); + + // then + assert_matches!(result, Ok(ManifestResponse::ImageIndex { manifests }) => { + assert_eq!(manifests.len(), 2); + assert_eq!(manifests[0].platform, ManifestPlatform { + architecture: "amd64".into(), + os: "linux".into(), + }); + }); + } + + #[test] + fn docker_v2_manifest_deserializes() { + // given + let json = serde_json::json!({ + "mediaType": "application/vnd.docker.distribution.manifest.v2+json", + "config": { "digest": sample_digest_str() } + }); + + // when + let result = serde_json::from_value::(json); + + // then + assert_matches!(result, Ok(ManifestResponse::DockerV2 { config }) => { + assert_eq!(config.digest.to_string(), sample_digest_str()); + }); + } + + #[test] + fn oci_manifest_deserializes() { + // given + let json = serde_json::json!({ + "mediaType": "application/vnd.oci.image.manifest.v1+json", + "config": { "digest": sample_digest_str() } + }); + + // when + let result = serde_json::from_value::(json); + + // then + assert_matches!(result, Ok(ManifestResponse::OciManifest { config }) => { + assert_eq!(config.digest.to_string(), sample_digest_str()); + }); + } + + #[test] + fn unknown_media_type_is_rejected() { + // given + let json = serde_json::json!({ + "mediaType": "application/vnd.unknown.format", + "config": { "digest": sample_digest_str() } + }); + + // when + let result = serde_json::from_value::(json); + + // then + assert_matches!(result, Err(_)); + } + + #[test] + fn docker_token_response_deserializes() { + // given + let json = serde_json::json!({ "token": "abc.def.ghi" }); + + // when + let result = serde_json::from_value::(json); + + // then + assert_matches!(result, Ok(resp) => { + assert_eq!(resp.token, "abc.def.ghi"); + }); + } +} diff --git a/crates/tee-launcher/src/error.rs b/crates/tee-launcher/src/error.rs new file mode 100644 index 000000000..0b3283e71 --- /dev/null +++ b/crates/tee-launcher/src/error.rs @@ -0,0 +1,88 @@ +use launcher_interface::types::DockerSha256Digest; +use thiserror::Error; + +#[derive(Error, Debug)] +pub(crate) enum LauncherError { + #[error("EmitEvent failed while extending RTMR3: {0}")] + DstackEmitEventFailed(String), + + #[error("MPC_HASH_OVERRIDE invalid: {0}")] + InvalidHashOverride(String), + + #[error("Image hash not found among tags")] + ImageHashNotFoundAmongTags, + + #[error("Failed to get auth token from registry: {0}")] + RegistryAuthFailed(String), + + #[error("docker run failed for validated hash")] + DockerRunFailed { + image_hash: DockerSha256Digest, + inner: std::io::Error, + }, + + #[error("docker run failed for validated hash")] + DockerRunFailedExitStatus { + image_hash: DockerSha256Digest, + output: String, + }, + + #[error("Failed to read {path}: {source}")] + FileRead { + path: String, + source: std::io::Error, + }, + + #[error("Failed to write {path}: {source}")] + FileWrite { + path: String, + source: std::io::Error, + }, + + #[error("Failed to create temp file: {0}")] + TempFileCreate(std::io::Error), + + #[error("Failed to parse {path}: {source}")] + JsonParse { + path: String, + source: serde_json::Error, + }, + + #[error("Failed to parse {path}: {source}")] + TomlParse { + path: String, + source: toml::de::Error, + }, + + #[error("HTTP error: {0}")] + Http(#[from] reqwest::Error), + + #[error("Registry response parse error: {0}")] + RegistryResponseParse(String), + + #[error("Invalid manifest URL: {0}")] + InvalidManifestUrl(String), + + #[error("User config contains reserved key [{0}] — remove it from mpc_node_config")] + ReservedConfigKey(String), + + #[error("The selected image failed digest validation: {0}")] + ImageDigestValidationFailed(#[from] ImageDigestValidationFailed), +} + +#[derive(Error, Debug)] +pub(crate) enum ImageDigestValidationFailed { + #[error("manifest digest lookup failed: {0}")] + ManifestDigestLookupFailed(String), + #[error("docker pull failed for {0}")] + DockerPullFailed(String), + #[error("docker inspect failed for {0}")] + DockerInspectFailed(String), + #[error( + "pulled image has mismatching image ID. pulled: {pulled_image_id}, expected: {expected_image_id}" + )] + PulledImageHasMismatchedDigest { + expected_image_id: DockerSha256Digest, + pulled_image_id: DockerSha256Digest, + }, +} diff --git a/crates/tee-launcher/src/main.rs b/crates/tee-launcher/src/main.rs new file mode 100644 index 000000000..dff322660 --- /dev/null +++ b/crates/tee-launcher/src/main.rs @@ -0,0 +1,1278 @@ +use std::io::Write; +use std::process::Command; +use std::{collections::VecDeque, time::Duration}; + +use backon::{ExponentialBuilder, Retryable}; +use clap::Parser; +use launcher_interface::types::{ + ApprovedHashes, DockerSha256Digest, TeeAuthorityConfig, TeeConfig, +}; +use launcher_interface::{DEFAULT_PHALA_TDX_QUOTE_UPLOAD_URL, MPC_IMAGE_HASH_EVENT}; + +use constants::*; +use docker_types::*; +use error::*; +use reqwest::header::{ACCEPT, AUTHORIZATION, HeaderMap, HeaderValue}; + +use types::*; +use url::Url; + +mod constants; +mod docker_types; +mod error; +mod types; + +const COMPOSE_TEMPLATE: &str = include_str!("../mpc-node-docker-compose.template.yml"); +const COMPOSE_TEE_TEMPLATE: &str = include_str!("../mpc-node-docker-compose.tee.template.yml"); + +const DOCKER_AUTH_ACCEPT_HEADER_VALUE: HeaderValue = + HeaderValue::from_static("application/vnd.docker.distribution.manifest.v2+json"); + +const DOCKER_CONTENT_DIGEST_HEADER: &str = "Docker-Content-Digest"; + +const AMD64: &str = "amd64"; +const LINUX: &str = "linux"; + +#[tokio::main] +async fn main() { + tracing_subscriber::fmt() + .with_env_filter( + tracing_subscriber::EnvFilter::from_default_env() + .add_directive(tracing::Level::INFO.into()), + ) + .init(); + + if let Err(e) = run().await { + tracing::error!("Error: {e}"); + std::process::exit(1); + } +} + +async fn run() -> Result<(), LauncherError> { + tracing::info!("start"); + + let args = CliArgs::parse(); + + tracing::info!(platform = ?args.platform, "starting launcher"); + + // Load dstack user config (TOML) + let config_contents = std::fs::read_to_string(DSTACK_USER_CONFIG_FILE).map_err(|source| { + LauncherError::FileRead { + path: DSTACK_USER_CONFIG_FILE.to_string(), + source, + } + })?; + + let config: Config = + toml::from_str(&config_contents).map_err(|source| LauncherError::TomlParse { + path: DSTACK_USER_CONFIG_FILE.to_string(), + source, + })?; + + let approved_hashes_file = std::fs::OpenOptions::new() + .read(true) + .write(false) + .open(IMAGE_DIGEST_FILE) + .map_err(|source| LauncherError::FileRead { + path: IMAGE_DIGEST_FILE.to_string(), + source, + }); + + let approved_hashes_on_disk: Option = match approved_hashes_file { + Err(err) => { + tracing::warn!( + ?err, + default_image_digest = ?args.default_image_digest, + "approved hashes file does not exist on disk, falling back to default digest" + ); + None + } + Ok(file) => { + let parsed: ApprovedHashes = + serde_json::from_reader(file).map_err(|source| LauncherError::JsonParse { + path: IMAGE_DIGEST_FILE.to_string(), + source, + })?; + Some(parsed) + } + }; + + let image_hash = select_image_hash( + approved_hashes_on_disk.as_ref(), + &args.default_image_digest, + config.launcher_config.mpc_hash_override.as_ref(), + )?; + + let manifest_digest = validate_image_hash(&config.launcher_config, image_hash.clone()).await?; + + let should_extend_rtmr_3 = args.platform == Platform::Tee; + + if should_extend_rtmr_3 { + let dstack_client = dstack_sdk::dstack_client::DstackClient::new(Some(DSTACK_UNIX_SOCKET)); + + // EmitEvent with the image digest + dstack_client + .emit_event( + MPC_IMAGE_HASH_EVENT.to_string(), + image_hash.as_ref().to_vec(), + ) + .await + .map_err(|e| LauncherError::DstackEmitEventFailed(e.to_string()))?; + } + + let mpc_binary_config_path = std::path::Path::new(MPC_CONFIG_SHARED_PATH); + + let tee_authority_config = match args.platform { + Platform::Tee => TeeAuthorityConfig::Dstack { + dstack_endpoint: DSTACK_UNIX_SOCKET.to_string(), + quote_upload_url: DEFAULT_PHALA_TDX_QUOTE_UPLOAD_URL.to_string(), + }, + Platform::NonTee => TeeAuthorityConfig::Local, + }; + + let tee_config = TeeConfig { + authority: tee_authority_config, + image_hash: image_hash.clone(), + latest_allowed_hash_file_path: IMAGE_DIGEST_FILE + .parse() + .expect("image digest file has a valid path"), + }; + + let mpc_node_config = intercept_node_config(config.mpc_node_config, &tee_config)?; + + let mpc_config_toml = + toml::to_string(&mpc_node_config).expect("re-serializing a toml::Table always succeeds"); + + std::fs::write(mpc_binary_config_path, mpc_config_toml.as_bytes()).map_err(|source| { + LauncherError::FileWrite { + path: mpc_binary_config_path.display().to_string(), + source, + } + })?; + + launch_mpc_container( + args.platform, + &manifest_digest, + &config.launcher_config.image_name, + &config.launcher_config.port_mappings, + )?; + + Ok(()) +} + +/// Inject launcher-controlled config section (`tee`) into the user-provided +/// MPC node config table. Returns an error if the user config already +/// contains the reserved key. +fn intercept_node_config( + mut node_config: toml::Table, + tee_config: &TeeConfig, +) -> Result { + insert_reserved( + &mut node_config, + "tee", + toml::Value::try_from(tee_config).expect("tee config serializes to TOML"), + )?; + Ok(node_config) +} + +/// Inject launcher-controlled config section (`tee`) into the user-provided +/// MPC node config table. Returns an error if the user config already +/// contains the reserved key. +/// Insert `value` under `key` in `table`, returning an error if the key +/// already exists. +fn insert_reserved( + table: &mut toml::Table, + key: &str, + value: toml::Value, +) -> Result<(), LauncherError> { + match table.entry(key) { + toml::map::Entry::Vacant(vacant) => { + vacant.insert(value); + Ok(()) + } + toml::map::Entry::Occupied(_) => Err(LauncherError::ReservedConfigKey(key.to_string())), + } +} + +/// Select which image hash to use, given the approved hashes file (if present), +/// a fallback default digest, and an optional user override. +/// +/// Selection rules: +/// - If the approved hashes file is absent → use `default_digest` +/// - If `override_hash` is set and appears in the approved list → use it +/// - If `override_hash` is set but NOT in the approved list → error +/// - Otherwise → use the newest approved hash (first in the list) +fn select_image_hash( + approved_hashes: Option<&ApprovedHashes>, + default_digest: &DockerSha256Digest, + override_hash: Option<&DockerSha256Digest>, +) -> Result { + let Some(approved) = approved_hashes else { + tracing::info!("no approved hashes file, using default digest"); + return Ok(default_digest.clone()); + }; + + if let Some(override_image) = override_hash { + tracing::info!(?override_image, "override mpc image hash provided"); + if !approved.approved_hashes.contains(override_image) { + return Err(LauncherError::InvalidHashOverride(format!( + "MPC_HASH_OVERRIDE={override_image} does not match any approved hash", + ))); + } + return Ok(override_image.clone()); + } + + let selected = approved.newest_approved_hash().clone(); + tracing::info!(?selected, "selected newest approved hash"); + Ok(selected) +} + +/// Provides the URLs needed to interact with a container registry. +trait RegistryInfo { + fn token_url(&self) -> String; + fn manifest_url(&self, tag: &str) -> Result; +} + +/// Production registry info for Docker Hub. +struct DockerRegistry { + registry_base_url: String, + image_name: String, +} + +impl DockerRegistry { + fn new(config: &LauncherConfig) -> Self { + Self { + registry_base_url: format!("https://{}", config.registry), + image_name: config.image_name.clone(), + } + } +} + +impl RegistryInfo for DockerRegistry { + // TODO(#2479): if we use a different registry, we need a different auth-endpoint + fn token_url(&self) -> String { + format!( + "https://auth.docker.io/token?service=registry.docker.io&scope=repository:{}:pull", + self.image_name, + ) + } + + fn manifest_url(&self, tag: &str) -> Result { + let url_string = format!( + "{}/v2/{}/manifests/{tag}", + self.registry_base_url, self.image_name + ); + + url_string + .parse() + .map_err(|_| LauncherError::InvalidManifestUrl(url_string)) + } +} + +async fn get_manifest_digest( + registry: &dyn RegistryInfo, + config: &LauncherConfig, + expected_image_digest: &DockerSha256Digest, +) -> Result { + let mut tags: VecDeque = config.image_tags.iter().cloned().collect(); + + let reqwest_client = reqwest::Client::new(); + + // We need an authorization token to fetch manifests. + let token_url = registry.token_url(); + + let token_request_response = reqwest_client + .get(token_url) + .send() + .await + .map_err(|e| LauncherError::RegistryAuthFailed(e.to_string()))?; + + let status = token_request_response.status(); + if !status.is_success() { + return Err(LauncherError::RegistryAuthFailed(format!( + "token request returned non-success status: {status}" + ))); + } + + let token_response: DockerTokenResponse = token_request_response + .json() + .await + .map_err(|e| LauncherError::RegistryAuthFailed(e.to_string()))?; + + while let Some(tag) = tags.pop_front() { + let manifest_url = registry.manifest_url(&tag)?; + + let authorization_value: HeaderValue = format!("Bearer {}", token_response.token) + .parse() + .expect("bearer token received from docker auth is a valid header value"); + + let headers = HeaderMap::from_iter([ + (ACCEPT, DOCKER_AUTH_ACCEPT_HEADER_VALUE), + (AUTHORIZATION, authorization_value), + ]); + + let request_timeout = Duration::from_secs(config.rpc_request_timeout_secs); + let backoff = ExponentialBuilder::default() + .with_min_delay(Duration::from_secs(config.rpc_request_interval_secs)) + .with_factor(1.5) + .with_max_delay(Duration::from_secs(60)) + .with_max_times(config.rpc_max_attempts as usize); + + let request_future = || async { + reqwest_client + .get(manifest_url.clone()) + .headers(headers.clone()) + .timeout(request_timeout) + .send() + .await? + .error_for_status() + }; + + let request_with_retry_future = request_future + .retry(backoff) + .when(|_: &reqwest::Error| true) + .notify(|err, dur| { + tracing::warn!( + ?manifest_url, + ?dur, + ?err, + "failed to fetch manifest, retrying" + ); + }); + + let Ok(resp) = request_with_retry_future.await else { + tracing::warn!( + ?manifest_url, + "exceeded max RPC attempts. \ + Will continue in the hopes of finding the matching image hash among remaining tags" + ); + continue; + }; + + let response_headers = resp.headers().clone(); + let manifest: ManifestResponse = resp + .json() + .await + .map_err(|e| LauncherError::RegistryResponseParse(e.to_string()))?; + + match manifest { + ManifestResponse::ImageIndex { manifests } => { + let platform_digests: Vec<_> = manifests + .iter() + .filter(|m| m.platform.architecture == AMD64 && m.platform.os == LINUX) + .map(|m| m.digest.as_str()) + .collect(); + tracing::info!( + ?tag, + ?platform_digests, + "received multi-platform image index, queuing amd64/linux manifests" + ); + manifests + .into_iter() + .filter(|manifest| { + manifest.platform.architecture == AMD64 && manifest.platform.os == LINUX + }) + .for_each(|manifest| tags.push_back(manifest.digest)); + } + ManifestResponse::DockerV2 { config } | ManifestResponse::OciManifest { config } => { + if config.digest != *expected_image_digest { + tracing::warn!( + ?tag, + actual_config_digest = %config.digest, + expected_config_digest = %expected_image_digest, + "config digest mismatch, skipping tag" + ); + continue; + } + + let Some(content_digest) = response_headers + .get(DOCKER_CONTENT_DIGEST_HEADER) + .and_then(|v| v.to_str().ok()) + .map(|s| s.to_string()) + else { + tracing::warn!( + ?tag, + "manifest matched but Docker-Content-Digest header missing, skipping" + ); + continue; + }; + + tracing::info!( + ?tag, + %content_digest, + "config digest matched, resolved manifest digest" + ); + return content_digest.parse().map_err(|_| { + LauncherError::RegistryResponseParse(format!( + "failed to parse manifest digest: {}", + content_digest + )) + }); + } + } + } + + tracing::error!( + ?expected_image_digest, + tags = ?config.image_tags, + "no tag produced a manifest with matching config digest" + ); + Err(LauncherError::ImageHashNotFoundAmongTags) +} + +/// Returns if the given image digest is valid (pull + manifest + digest match). +/// Does NOT extend RTMR3 and does NOT run the container. +async fn validate_image_hash( + launcher_config: &LauncherConfig, + image_hash: DockerSha256Digest, +) -> Result { + let registry = DockerRegistry::new(launcher_config); + let manifest_digest = get_manifest_digest(®istry, launcher_config, &image_hash) + .await + .map_err(|e| ImageDigestValidationFailed::ManifestDigestLookupFailed(e.to_string()))?; + let image_name = &launcher_config.image_name; + + let name_and_digest = format!("{image_name}@{manifest_digest}"); + + // Pull + let pull = Command::new("docker") + .args(["pull", &name_and_digest]) + .output() + .map_err(|e| ImageDigestValidationFailed::DockerPullFailed(e.to_string()))?; + + let pull_failed = !pull.status.success(); + if pull_failed { + return Err(ImageDigestValidationFailed::DockerPullFailed( + "docker pull terminated with unsuccessful status".to_string(), + )); + } + + // Verify that the pulled image ID matches the expected config digest. + // `docker inspect .ID` returns the image ID, which equals the config digest + // (i.e. the sha256 of the image config blob). + let inspect = Command::new("docker") + .args([ + "image", + "inspect", + "--format", + "{{index .ID}}", + &name_and_digest, + ]) + .output() + .map_err(|e| ImageDigestValidationFailed::DockerInspectFailed(e.to_string()))?; + + let docker_inspect_failed = !inspect.status.success(); + if docker_inspect_failed { + return Err(ImageDigestValidationFailed::DockerInspectFailed( + "docker inspect terminated with unsuccessful status".to_string(), + )); + } + + let pulled_image_id: DockerSha256Digest = String::from_utf8_lossy(&inspect.stdout) + .trim() + .to_string() + .parse() + .expect("is valid digest"); + + if pulled_image_id != image_hash { + return Err( + ImageDigestValidationFailed::PulledImageHasMismatchedDigest { + pulled_image_id, + expected_image_id: image_hash, + }, + ); + } + + Ok(manifest_digest) +} + +fn render_compose_file( + platform: Platform, + port_mappings: &[PortMapping], + image_name: &str, + manifest_digest: &DockerSha256Digest, +) -> Result { + let template = match platform { + Platform::Tee => COMPOSE_TEE_TEMPLATE, + Platform::NonTee => COMPOSE_TEMPLATE, + }; + + let ports: Vec = port_mappings + .iter() + .map(PortMapping::docker_compose_value) + .collect(); + let ports_json = serde_json::to_string(&ports).expect("port list is serializable"); + + let rendered = template + .replace("{{IMAGE_NAME}}", image_name) + .replace("{{IMAGE}}", &manifest_digest.to_string()) + .replace("{{CONTAINER_NAME}}", MPC_CONTAINER_NAME) + .replace("{{MPC_CONFIG_SHARED_PATH}}", MPC_CONFIG_SHARED_PATH) + .replace("{{DSTACK_UNIX_SOCKET}}", DSTACK_UNIX_SOCKET) + .replace("{{PORTS}}", &ports_json); + + tracing::info!(compose = %rendered, "rendered docker-compose file"); + + let mut file = tempfile::NamedTempFile::new().map_err(LauncherError::TempFileCreate)?; + file.write_all(rendered.as_bytes()) + .map_err(|source| LauncherError::FileWrite { + path: file.path().display().to_string(), + source, + })?; + + Ok(file) +} + +fn launch_mpc_container( + platform: Platform, + manifest_digest: &DockerSha256Digest, + image_name: &str, + port_mappings: &[PortMapping], +) -> Result<(), LauncherError> { + tracing::info!(?manifest_digest, "launching MPC node"); + + let compose_file = render_compose_file(platform, port_mappings, image_name, manifest_digest)?; + let compose_path = compose_file.path().display().to_string(); + + // Remove any existing container from a previous run (by name, independent of compose file) + let _ = Command::new("docker") + .args(["rm", "-f", MPC_CONTAINER_NAME]) + .output(); + + let run_output = Command::new("docker") + .args(["compose", "-f", &compose_path, "up", "-d"]) + .output() + .map_err(|inner| LauncherError::DockerRunFailed { + image_hash: manifest_digest.clone(), + inner, + })?; + + if !run_output.status.success() { + let stderr = String::from_utf8_lossy(&run_output.stderr); + let stdout = String::from_utf8_lossy(&run_output.stdout); + tracing::error!(%stderr, %stdout, "docker compose up failed"); + return Err(LauncherError::DockerRunFailedExitStatus { + image_hash: manifest_digest.clone(), + output: stderr.into_owned(), + }); + } + + tracing::info!("MPC launched successfully."); + Ok(()) +} + +#[cfg(test)] +mod tests { + use std::num::NonZeroU16; + + use crate::{ + RegistryInfo, constants::*, error::LauncherError, get_manifest_digest, + intercept_node_config, render_compose_file, select_image_hash, types::*, + }; + + use assert_matches::assert_matches; + use httpmock::prelude::*; + use launcher_interface::types::{ + ApprovedHashes, DockerSha256Digest, TeeAuthorityConfig, TeeConfig, + }; + use near_mpc_bounded_collections::NonEmptyVec; + + const SAMPLE_IMAGE_NAME: &str = "nearone/mpc-node"; + + fn render( + platform: Platform, + port_mappings: &[PortMapping], + digest: &DockerSha256Digest, + ) -> String { + let file = render_compose_file(platform, port_mappings, SAMPLE_IMAGE_NAME, digest).unwrap(); + std::fs::read_to_string(file.path()).unwrap() + } + + fn digest(hex_char: char) -> DockerSha256Digest { + format!( + "sha256:{}", + std::iter::repeat_n(hex_char, 64).collect::() + ) + .parse() + .unwrap() + } + + fn sample_digest() -> DockerSha256Digest { + digest('a') + } + + fn approved_file(hashes: Vec) -> ApprovedHashes { + ApprovedHashes { + approved_hashes: NonEmptyVec::from_vec(hashes).unwrap(), + } + } + + struct MockRegistry { + base_url: String, + image_name: String, + } + + impl RegistryInfo for MockRegistry { + fn token_url(&self) -> String { + format!("{}/token", self.base_url) + } + + fn manifest_url(&self, tag: &str) -> Result { + let raw = format!("{}/v2/{}/manifests/{tag}", self.base_url, self.image_name); + raw.parse() + .map_err(|_| crate::error::LauncherError::InvalidManifestUrl(raw)) + } + } + + fn mock_launcher_config(tag: &str) -> LauncherConfig { + LauncherConfig { + image_tags: near_mpc_bounded_collections::NonEmptyVec::from_vec(vec![tag.into()]) + .unwrap(), + image_name: "test/image".into(), + registry: "unused".into(), + rpc_request_timeout_secs: 5, + rpc_request_interval_secs: 1, + rpc_max_attempts: 1, + mpc_hash_override: None, + port_mappings: vec![], + } + } + + fn mock_registry(server: &MockServer) -> MockRegistry { + MockRegistry { + base_url: server.base_url(), + image_name: "test/image".into(), + } + } + + fn empty_port_mappings() -> Vec { + vec![] + } + + fn port_mappings_with_port() -> Vec { + vec![PortMapping { + host: NonZeroU16::new(11780).unwrap(), + container: NonZeroU16::new(11780).unwrap(), + }] + } + + #[test] + fn tee_mode_includes_dstack_env_and_volume() { + // given + let port_mappings = empty_port_mappings(); + let digest = sample_digest(); + + // when + let rendered = render(Platform::Tee, &port_mappings, &digest); + + // then + assert!(rendered.contains(&format!("DSTACK_ENDPOINT={DSTACK_UNIX_SOCKET}"))); + assert!(rendered.contains(&format!("{DSTACK_UNIX_SOCKET}:{DSTACK_UNIX_SOCKET}"))); + } + + #[test] + fn nontee_mode_excludes_dstack() { + // given + let port_mappings = empty_port_mappings(); + let digest = sample_digest(); + + // when + let rendered = render(Platform::NonTee, &port_mappings, &digest); + + // then + assert!(!rendered.contains("DSTACK_ENDPOINT")); + assert!(!rendered.contains(DSTACK_UNIX_SOCKET)); + } + + #[test] + fn includes_security_opts_and_required_volumes() { + // given + let port_mappings = empty_port_mappings(); + let digest = sample_digest(); + + // when + let rendered = render(Platform::NonTee, &port_mappings, &digest); + + // then + assert!(rendered.contains("no-new-privileges:true")); + assert!(rendered.contains("/tapp:/tapp:ro")); + assert!(rendered.contains("shared-volume:/mnt/shared")); + assert!(rendered.contains("mpc-data:/data")); + assert!(rendered.contains(&format!("container_name: \"{MPC_CONTAINER_NAME}\""))); + } + + #[test] + fn mounts_config_file_read_only() { + // given + let port_mappings = empty_port_mappings(); + let digest = sample_digest(); + + // when + let rendered = render(Platform::NonTee, &port_mappings, &digest); + + // then — config is on the shared volume, referenced in the command + assert!(rendered.contains(MPC_CONFIG_SHARED_PATH)); + } + + #[test] + fn includes_start_with_config_file_command() { + // given + let port_mappings = empty_port_mappings(); + let digest = sample_digest(); + + // when + let rendered = render(Platform::NonTee, &port_mappings, &digest); + + // then + assert!(rendered.contains("/app/mpc-node")); + assert!(rendered.contains(MPC_CONFIG_SHARED_PATH)); + } + + #[test] + fn image_is_set() { + // given + let port_mappings = empty_port_mappings(); + let digest = sample_digest(); + + // when + let rendered = render(Platform::NonTee, &port_mappings, &digest); + + // then + assert!(rendered.contains(&format!("image: \"{SAMPLE_IMAGE_NAME}@{digest}\""))); + } + + #[test] + fn includes_ports() { + // given + let port_mappings = port_mappings_with_port(); + let digest = sample_digest(); + + // when + let rendered = render(Platform::NonTee, &port_mappings, &digest); + + // then + assert!(rendered.contains("11780:11780")); + } + + #[test] + fn no_env_section_in_nontee_mode() { + // given + let port_mappings = empty_port_mappings(); + let digest = sample_digest(); + + // when + let rendered = render(Platform::NonTee, &port_mappings, &digest); + + // then + assert!(!rendered.contains("environment:")); + } + + fn sample_tee_config() -> TeeConfig { + TeeConfig { + authority: TeeAuthorityConfig::Dstack { + dstack_endpoint: "/var/run/dstack.sock".to_string(), + quote_upload_url: "https://example.com/quote".to_string(), + }, + image_hash: sample_digest(), + latest_allowed_hash_file_path: "/mnt/shared/image-digest.bin".into(), + } + } + + #[test] + fn intercept_config_injects_tee_config() { + // given + let config: toml::Table = toml::from_str(r#"home_dir = "/data""#).unwrap(); + + // when + let result = intercept_node_config(config, &sample_tee_config()).unwrap(); + + // then + assert!(result.contains_key("tee")); + assert_eq!(result["home_dir"].as_str(), Some("/data")); + } + + #[test] + fn intercept_config_rejects_user_provided_tee_key() { + // given + let config: toml::Table = toml::from_str( + r#"[tee] +type = "Local" +"#, + ) + .unwrap(); + + // when + let result = intercept_node_config(config, &sample_tee_config()); + + // then + assert_matches!(result, Err(LauncherError::ReservedConfigKey(key)) => { + assert_eq!(key, "tee"); + }); + } + + #[test] + fn intercept_config_empty_table_gets_tee_key() { + // given + let config = toml::Table::new(); + + // when + let result = intercept_node_config(config, &sample_tee_config()).unwrap(); + + // then + assert!(result.contains_key("tee")); + assert_eq!(result.len(), 1); + } + + #[test] + fn intercept_config_preserves_all_existing_keys() { + // given + let config: toml::Table = toml::from_str( + r#" +home_dir = "/data" +port = 8080 +[nested] +key = "value" +"#, + ) + .unwrap(); + + // when + let result = intercept_node_config(config, &sample_tee_config()).unwrap(); + + // then + assert_eq!(result["home_dir"].as_str(), Some("/data")); + assert_eq!(result["port"].as_integer(), Some(8080)); + assert_eq!(result["nested"]["key"].as_str(), Some("value")); + assert!(result.contains_key("tee")); + } + + #[test] + fn intercept_config_dstack_tee_config_serializes_correctly() { + // given + let config = toml::Table::new(); + let tee = TeeConfig { + authority: TeeAuthorityConfig::Dstack { + dstack_endpoint: "/my/socket".to_string(), + quote_upload_url: "https://example.com".to_string(), + }, + image_hash: sample_digest(), + latest_allowed_hash_file_path: "/mnt/shared/image-digest.bin".into(), + }; + + // when + let result = intercept_node_config(config, &tee).unwrap(); + + // then + let tee_table = result["tee"].as_table().unwrap(); + let authority = tee_table["authority"].as_table().unwrap(); + assert_eq!(authority["dstack_endpoint"].as_str(), Some("/my/socket")); + assert_eq!( + authority["quote_upload_url"].as_str(), + Some("https://example.com") + ); + } + + #[test] + fn intercept_config_local_tee_config_serializes_correctly() { + // given + let config = toml::Table::new(); + let tee = TeeConfig { + authority: TeeAuthorityConfig::Local, + image_hash: sample_digest(), + latest_allowed_hash_file_path: "/mnt/shared/image-digest.bin".into(), + }; + + // when + let result = intercept_node_config(config, &tee).unwrap(); + + // then — Local variant is a unit variant; just verify the key exists + assert!(result.contains_key("tee")); + // re-serialize the whole thing to verify it round-trips + let toml_str = toml::to_string(&result).unwrap(); + assert!(toml_str.contains("tee")); + } + + #[test] + fn intercept_config_image_config_contains_expected_fields() { + // given + let config = toml::Table::new(); + let tee = TeeConfig { + authority: TeeAuthorityConfig::Dstack { + dstack_endpoint: "/var/run/dstack.sock".to_string(), + quote_upload_url: "https://example.com/quote".to_string(), + }, + image_hash: digest('b'), + latest_allowed_hash_file_path: "/some/path".into(), + }; + + // when + let result = intercept_node_config(config, &tee).unwrap(); + + // then + let tee_table = result["tee"].as_table().unwrap(); + assert!(tee_table["image_hash"].as_str().unwrap().contains("bbbb")); + assert_eq!( + tee_table["latest_allowed_hash_file_path"].as_str(), + Some("/some/path") + ); + } + + #[test] + fn intercept_config_output_re_serializes_to_valid_toml() { + // given + let config: toml::Table = toml::from_str(r#"home_dir = "/data""#).unwrap(); + + // when + let result = intercept_node_config(config, &sample_tee_config()).unwrap(); + let toml_str = toml::to_string(&result).unwrap(); + + // then — the output can be parsed back + let reparsed: toml::Table = toml::from_str(&toml_str).unwrap(); + assert!(reparsed.contains_key("tee")); + assert_eq!(reparsed["home_dir"].as_str(), Some("/data")); + } + + #[test] + fn intercept_config_tee_as_non_table_value_is_rejected() { + // given — tee exists but as a string, not a table + let config: toml::Table = toml::from_str(r#"tee = "sneaky""#).unwrap(); + + // when + let result = intercept_node_config(config, &sample_tee_config()); + + // then — any occupied entry is rejected regardless of value type + assert_matches!(result, Err(LauncherError::ReservedConfigKey(key)) => { + assert_eq!(key, "tee"); + }); + } + + // --- select_image_hash --- + + #[test] + fn select_hash_override_present_and_in_approved_list() { + // given + let override_digest = digest('b'); + let approved = approved_file(vec![digest('c'), override_digest.clone(), digest('d')]); + + // when + let result = select_image_hash(Some(&approved), &digest('f'), Some(&override_digest)); + + // then + assert_matches!(result, Ok(selected) => { + assert_eq!(selected, override_digest); + }); + } + + #[test] + fn select_hash_override_not_in_approved_list() { + // given + let override_digest = digest('b'); + let approved = approved_file(vec![digest('c'), digest('d')]); + + // when + let result = select_image_hash(Some(&approved), &digest('f'), Some(&override_digest)); + + // then + assert_matches!(result, Err(LauncherError::InvalidHashOverride(_))); + } + + #[test] + fn select_hash_no_override_picks_newest() { + // given - first entry is "newest" + let newest = digest('a'); + let approved = approved_file(vec![newest.clone(), digest('b'), digest('c')]); + + // when + let result = select_image_hash(Some(&approved), &digest('f'), None); + + // then + assert_matches!(result, Ok(selected) => { + assert_eq!(selected, newest); + }); + } + + #[test] + fn select_hash_missing_file_falls_back_to_default() { + // given + let default = digest('d'); + + // when + let result = select_image_hash(None, &default, None); + + // then + assert_matches!(result, Ok(selected) => { + assert_eq!(selected, default); + }); + } + + #[test] + fn select_hash_missing_file_ignores_override() { + // given - override is set but file is missing, so default wins + let default = digest('d'); + let override_digest = digest('b'); + + // when + let result = select_image_hash(None, &default, Some(&override_digest)); + + // then + assert_matches!(result, Ok(selected) => { + assert_eq!(selected, default); + }); + } + + // --- approved_hashes JSON key alignment --- + + #[test] + fn approved_hashes_json_key_is_approved_hashes() { + // given - the JSON field name must match between launcher and MPC node + let file = approved_file(vec![sample_digest()]); + + // when + let json = serde_json::to_value(&file).unwrap(); + + // then + assert!(json.get("approved_hashes").is_some()); + } + + #[tokio::test] + async fn get_manifest_digest_resolves_docker_v2() { + // given + let server = MockServer::start(); + let expected_image_digest = sample_digest(); + let manifest_digest = digest('b'); + + server.mock(|when, then| { + when.method(GET).path("/token"); + then.status(200) + .json_body(serde_json::json!({ "token": "test-token" })); + }); + + let manifest_body = serde_json::json!({ + "mediaType": "application/vnd.docker.distribution.manifest.v2+json", + "config": { "digest": expected_image_digest.to_string() } + }); + + server.mock(|when, then| { + when.method(GET).path("/v2/test/image/manifests/v1.0"); + then.status(200) + .header("Docker-Content-Digest", manifest_digest.to_string()) + .json_body(manifest_body); + }); + + let registry = mock_registry(&server); + let config = mock_launcher_config("v1.0"); + + // when + let result = get_manifest_digest(®istry, &config, &expected_image_digest).await; + + // then + assert_matches!(result, Ok(d) => { + assert_eq!(d, manifest_digest); + }); + } + + #[tokio::test] + async fn get_manifest_digest_follows_image_index_to_amd64_manifest() { + // given + let server = MockServer::start(); + let expected_image_digest = sample_digest(); + let manifest_digest = digest('c'); + let amd64_ref = "sha256:amd64ref"; + + server.mock(|when, then| { + when.method(GET).path("/token"); + then.status(200) + .json_body(serde_json::json!({ "token": "test-token" })); + }); + + // First request: image index pointing to amd64 manifest + server.mock(|when, then| { + when.method(GET).path("/v2/test/image/manifests/latest"); + then.status(200).json_body(serde_json::json!({ + "mediaType": "application/vnd.oci.image.index.v1+json", + "manifests": [ + { + "digest": amd64_ref, + "platform": { "architecture": "amd64", "os": "linux" } + }, + { + "digest": "sha256:armref", + "platform": { "architecture": "arm64", "os": "linux" } + } + ] + })); + }); + + // Second request: the resolved amd64 manifest + server.mock(|when, then| { + when.method(GET) + .path(format!("/v2/test/image/manifests/{amd64_ref}")); + then.status(200) + .header("Docker-Content-Digest", manifest_digest.to_string()) + .json_body(serde_json::json!({ + "mediaType": "application/vnd.docker.distribution.manifest.v2+json", + "config": { "digest": expected_image_digest.to_string() } + })); + }); + + let registry = mock_registry(&server); + let config = mock_launcher_config("latest"); + + // when + let result = get_manifest_digest(®istry, &config, &expected_image_digest).await; + + // then + assert_matches!(result, Ok(d) => { + assert_eq!(d, manifest_digest); + }); + } + + #[tokio::test] + async fn get_manifest_digest_skips_mismatched_config_digest() { + // given + let server = MockServer::start(); + let expected_image_digest = sample_digest(); + let wrong_digest = digest('f'); + + server.mock(|when, then| { + when.method(GET).path("/token"); + then.status(200) + .json_body(serde_json::json!({ "token": "test-token" })); + }); + + server.mock(|when, then| { + when.method(GET).path("/v2/test/image/manifests/v1.0"); + then.status(200) + .header("Docker-Content-Digest", "sha256:doesntmatter") + .json_body(serde_json::json!({ + "mediaType": "application/vnd.docker.distribution.manifest.v2+json", + "config": { "digest": wrong_digest.to_string() } + })); + }); + + let registry = mock_registry(&server); + let config = mock_launcher_config("v1.0"); + + // when + let result = get_manifest_digest(®istry, &config, &expected_image_digest).await; + + // then + assert_matches!(result, Err(LauncherError::ImageHashNotFoundAmongTags)); + } + + #[tokio::test] + async fn get_manifest_digest_errors_on_auth_failure() { + // given + let server = MockServer::start(); + + server.mock(|when, then| { + when.method(GET).path("/token"); + then.status(403); + }); + + let registry = mock_registry(&server); + let config = mock_launcher_config("v1.0"); + + // when + let result = get_manifest_digest(®istry, &config, &sample_digest()).await; + + // then + assert_matches!(result, Err(LauncherError::RegistryAuthFailed(_))); + } + + #[tokio::test] + async fn get_manifest_digest_missing_content_digest_header_skips_tag() { + // given + let server = MockServer::start(); + let expected_image_digest = sample_digest(); + + server.mock(|when, then| { + when.method(GET).path("/token"); + then.status(200) + .json_body(serde_json::json!({ "token": "test-token" })); + }); + + // Manifest matches but no Docker-Content-Digest header + server.mock(|when, then| { + when.method(GET).path("/v2/test/image/manifests/v1.0"); + then.status(200).json_body(serde_json::json!({ + "mediaType": "application/vnd.docker.distribution.manifest.v2+json", + "config": { "digest": expected_image_digest.to_string() } + })); + }); + + let registry = mock_registry(&server); + let config = mock_launcher_config("v1.0"); + + // when + let result = get_manifest_digest(®istry, &config, &expected_image_digest).await; + + // then - tag is skipped, no more tags → error + assert_matches!(result, Err(LauncherError::ImageHashNotFoundAmongTags)); + } +} + +/// Tests requiring network access and Docker Hub. +#[cfg(all(test, feature = "external-services-tests"))] +mod integration_tests { + use super::*; + #[cfg(target_os = "linux")] + use assert_matches::assert_matches; + + // # Dockerfile + // FROM alpine@sha256:765942a4039992336de8dd5db680586e1a206607dd06170ff0a37267a9e01958 + // CMD ["true"] + // TODO: Look into reusing this image, as its small and will be faster on CI + + const TEST_DIGEST: &str = + "sha256:f2472280c437efc00fa25a030a24990ae16c4fbec0d74914e178473ce4d57372"; + const TEST_TAG: &str = "83b52da4e2270c688cdd30da04f6b9d3565f25bb"; + const TEST_IMAGE_NAME: &str = "nearone/testing"; + const TEST_REGISTRY: &str = "registry.hub.docker.com"; + + fn test_launcher_config() -> LauncherConfig { + LauncherConfig { + image_tags: near_mpc_bounded_collections::NonEmptyVec::from_vec(vec![TEST_TAG.into()]) + .unwrap(), + image_name: TEST_IMAGE_NAME.into(), + registry: TEST_REGISTRY.into(), + rpc_request_timeout_secs: 10, + rpc_request_interval_secs: 1, + rpc_max_attempts: 20, + mpc_hash_override: None, + port_mappings: vec![], + } + } + + #[tokio::test] + async fn get_manifest_digest_resolves_known_image() { + // given + let config = test_launcher_config(); + let expected_digest: DockerSha256Digest = TEST_DIGEST.parse().unwrap(); + + // when + let registry = DockerRegistry::new(&config); + let result = get_manifest_digest(®istry, &config, &expected_digest).await; + + // then + assert!(result.is_ok(), "get_manifest_digest failed: {result:?}"); + } + + // `validate_image_hash` compares the output of `docker inspect .ID` against + // the expected config digest. On native Linux, `.ID` returns the config digest + // (sha256 of the image config blob), but on macOS, Docker Desktop's containerd + // image store returns the manifest digest instead, causing a spurious mismatch. + #[cfg(target_os = "linux")] + #[tokio::test] + async fn validate_image_hash_succeeds_for_known_image() { + // given + let config = test_launcher_config(); + let expected_digest: DockerSha256Digest = TEST_DIGEST.parse().unwrap(); + + // when + let result = validate_image_hash(&config, expected_digest).await; + + // then + assert_matches!(result, Ok(_)); + } +} diff --git a/crates/tee-launcher/src/types.rs b/crates/tee-launcher/src/types.rs new file mode 100644 index 000000000..0f122f4b7 --- /dev/null +++ b/crates/tee-launcher/src/types.rs @@ -0,0 +1,288 @@ +use std::net::Ipv4Addr; +use std::num::NonZeroU16; + +use launcher_interface::types::DockerSha256Digest; +use url::Host; + +use clap::{Parser, ValueEnum}; +use near_mpc_bounded_collections::NonEmptyVec; +use serde::{Deserialize, Serialize}; + +/// CLI arguments parsed from environment variables via clap. +#[derive(Parser, Debug)] +#[command(name = "tee-launcher")] +pub(crate) struct CliArgs { + /// Platform mode: TEE or NONTEE + #[arg(long, env = "PLATFORM")] + pub(crate) platform: Platform, + + #[arg(long, env = "DOCKER_CONTENT_TRUST")] + // ensure that `docker_content_trust` is enabled. + docker_content_trust: DockerContentTrust, + + /// Fallback image digest when the approved-hashes file is absent + #[arg(long, env = "DEFAULT_IMAGE_DIGEST")] + pub(crate) default_image_digest: DockerSha256Digest, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum)] +enum DockerContentTrust { + #[value(name = "1")] + Enabled, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum)] +pub(crate) enum Platform { + #[value(name = "TEE")] + Tee, + #[value(name = "NONTEE")] + NonTee, +} + +/// Typed representation of the dstack user config file (`/tapp/user_config`). +#[derive(Debug, Clone, Serialize, Deserialize)] +pub(crate) struct Config { + pub(crate) launcher_config: LauncherConfig, + /// Opaque MPC node configuration table. + /// The launcher does not interpret these fields — they are re-serialized + /// to a TOML string, written to a file on disk, and mounted into the + /// container for the MPC binary to consume via `start-with-config-file`. + pub(crate) mpc_node_config: toml::Table, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub(crate) struct LauncherConfig { + /// Docker image tags to search (from `MPC_IMAGE_TAGS`, comma-separated). + pub(crate) image_tags: NonEmptyVec, + /// Docker image name (from `MPC_IMAGE_NAME`). + pub(crate) image_name: String, + /// Docker registry (from `MPC_REGISTRY`). + pub(crate) registry: String, + /// Per-request timeout for registry RPC calls (from `RPC_REQUEST_TIMEOUT_SECS`). + pub(crate) rpc_request_timeout_secs: u64, + /// Delay between registry RPC retries (from `RPC_REQUEST_INTERVAL_SECS`). + pub(crate) rpc_request_interval_secs: u64, + /// Maximum registry RPC attempts (from `RPC_MAX_ATTEMPTS`). + pub(crate) rpc_max_attempts: u32, + /// Optional hash override that bypasses registry lookup (from `MPC_HASH_OVERRIDE`). + pub(crate) mpc_hash_override: Option, + pub(crate) port_mappings: Vec, +} + +/// A `--add-host` entry: `hostname:IPv4`. +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub(crate) struct HostEntry { + pub(crate) hostname: Host, + pub(crate) ip: Ipv4Addr, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub(crate) struct PortMapping { + pub(crate) host: NonZeroU16, + pub(crate) container: NonZeroU16, +} + +impl PortMapping { + /// Returns e.g. `"11780:11780"` for use in docker-compose port lists. + pub(crate) fn docker_compose_value(&self) -> String { + format!("{}:{}", self.host, self.container) + } +} + +#[cfg(test)] +mod tests { + use assert_matches::assert_matches; + use std::net::Ipv4Addr; + use std::num::NonZeroU16; + + use super::*; + + // --- HostEntry deserialization --- + + #[test] + fn host_entry_valid_deserialization() { + // given + let json = serde_json::json!({"hostname": {"Domain": "node.local"}, "ip": "192.168.1.1"}); + + // when + let result = serde_json::from_value::(json); + + // then + assert_matches!(result, Ok(entry) => { + assert_eq!(entry.ip, Ipv4Addr::new(192, 168, 1, 1)); + }); + } + + #[test] + fn host_entry_rejects_invalid_ip() { + // given + let json = serde_json::json!({"hostname": {"Domain": "node.local"}, "ip": "not-an-ip"}); + + // when + let result = serde_json::from_value::(json); + + // then + assert_matches!(result, Err(_)); + } + + #[test] + fn host_entry_rejects_plain_string_as_hostname() { + // given - url::Host requires tagged variant, plain string is rejected + let json = serde_json::json!({"hostname": "node.local", "ip": "192.168.1.1"}); + + // when + let result = serde_json::from_value::(json); + + // then + assert_matches!(result, Err(_)); + } + + #[test] + fn host_entry_rejects_injection_string_as_hostname() { + // given + let json = serde_json::json!({"hostname": "--env LD_PRELOAD=hack.so", "ip": "192.168.1.1"}); + + // when + let result = serde_json::from_value::(json); + + // then + assert_matches!(result, Err(_)); + } + + // --- PortMapping deserialization --- + + #[test] + fn port_mapping_valid_deserialization() { + // given + let json = serde_json::json!({"host": 11780, "container": 11780}); + + // when + let result = serde_json::from_value::(json); + + // then + assert_matches!(result, Ok(_)); + } + + #[test] + fn port_mapping_rejects_zero_port() { + // given + let json = serde_json::json!({"host": 0, "container": 11780}); + + // when + let result = serde_json::from_value::(json); + + // then + assert_matches!(result, Err(_)); + } + + #[test] + fn port_mapping_rejects_out_of_range_port() { + // given + let json = serde_json::json!({"host": 65536, "container": 11780}); + + // when + let result = serde_json::from_value::(json); + + // then + assert_matches!(result, Err(_)); + } + + // --- docker_compose_value output format --- + + #[test] + fn port_mapping_docker_compose_value() { + // given + let mapping = PortMapping { + host: NonZeroU16::new(11780).unwrap(), + container: NonZeroU16::new(11780).unwrap(), + }; + + // when + let value = mapping.docker_compose_value(); + + // then + assert_eq!(value, "11780:11780"); + } + + // --- Config full deserialization (TOML) --- + + #[test] + fn config_deserializes_valid_toml() { + // given + let toml_str = r#" +[launcher_config] +image_tags = ["tag1"] +image_name = "nearone/mpc-node" +registry = "registry.hub.docker.com" +rpc_request_timeout_secs = 10 +rpc_request_interval_secs = 1 +rpc_max_attempts = 20 + +port_mappings = [{ host = 11780, container = 11780 }] + +[mpc_node_config] +home_dir = "/data" +some_opaque_field = true +"#; + + // when + let result = toml::from_str::(toml_str); + + // then + assert_matches!(result, Ok(config) => { + assert_eq!(config.launcher_config.image_name, "nearone/mpc-node"); + assert_eq!(config.mpc_node_config["home_dir"].as_str(), Some("/data")); + assert_eq!(config.mpc_node_config["some_opaque_field"].as_bool(), Some(true)); + }); + } + + #[test] + fn config_mpc_config_round_trips_to_toml_string() { + // given + let toml_str = r#" +[launcher_config] +image_tags = ["tag1"] +image_name = "nearone/mpc-node" +registry = "registry.hub.docker.com" +rpc_request_timeout_secs = 10 +rpc_request_interval_secs = 1 +rpc_max_attempts = 20 + +port_mappings = [{ host = 11780, container = 11780 }] + +[mpc_node_config] +home_dir = "/data" +arbitrary_key = "arbitrary_value" +"#; + let config: Config = toml::from_str(toml_str).unwrap(); + + // when — re-serialize the opaque table (what the launcher writes to disk) + let serialized = toml::to_string(&config.mpc_node_config).unwrap(); + + // then + assert!(serialized.contains("home_dir")); + assert!(serialized.contains("arbitrary_key")); + } + + #[test] + fn config_rejects_missing_required_field() { + // given - mpc_config is missing + let toml_str = r#" +[launcher_config] +image_tags = ["tag1"] +image_name = "nearone/mpc-node" +registry = "registry.hub.docker.com" +rpc_request_timeout_secs = 10 +rpc_request_interval_secs = 1 +rpc_max_attempts = 20 + +port_mappings = [] +"#; + + // when + let result = toml::from_str::(toml_str); + + // then + assert_matches!(result, Err(_)); + } +} diff --git a/crates/test-utils/assets/README.md b/crates/test-utils/assets/README.md index 8af63652f..7f4b63000 100644 --- a/crates/test-utils/assets/README.md +++ b/crates/test-utils/assets/README.md @@ -1,7 +1,6 @@ # Updating Test Assets -Updating test assets is needed when updating launcher code (or when updating other measured components). See [updating-launcher-internal-guide.md](../../../docs/updating-launcher-internal-guide.md) - +Updating test assets is needed when updating launcher code (or when updating other measured components). See [updating-launcher-internal-guide.md](../../../docs/updating-launcher-internal-guide.md) To update the test asset files, fetch `/public_data` from the MPC node’s public HTTP endpoint and save the response to a JSON file. @@ -12,20 +11,18 @@ Example: curl http://:/public_data -o public_data.json ``` - See [single-node-readme.md](../../../localnet/tee/scripts/single-node-readme.md) -for an automation script that will launch a TEE MPC node, collect the attestation, and save the public data to a temp directory (path printed by the script). - +for automation script that will launch a TEE MPC node, collect the attestation, and save the public data into /tmp/%user/public_data.json ## Steps -1. Change into the `crates/test-utils/assets` directory: +1. Change into the `crates/test_utils/assets` directory: ```shell - cd crates/test-utils/assets + cd crates/test_utils/assets ``` -2. Copy the `public_data.json` file into this directory. +2. Copy the `public_data.json` file into this directory. Keeping the original file allows future developers to trace the test vectors back to their source. 3. Run the asset extraction script: @@ -47,38 +44,4 @@ This will regenerate the following files: All files will be written into the specified output directory. -4. Update `VALID_ATTESTATION_TIMESTAMP` in `crates/test-utils/src/attestation.rs` to a Unix timestamp after the date when the measurements were taken. This ensures that the tests will consider the measurements valid. - -5. Update `crates/attestation/assets/tcb_info.json` — copy the newly generated `tcb_info.json` - there as well, since unit tests in the `attestation` crate use it for deserialization tests. - This is optional — the tests only verify parsing, not measurement values — but keeping it - in sync avoids confusion. - -6. Update the compiled-in measurements in `crates/mpc-attestation/assets/`: - - `tcb_info_dev.json` — replace with the `tcb_info.json` from a **dev** image attestation - - `tcb_info.json` — replace with the `tcb_info.json` from a **release** (non-dev) image attestation - - These are compiled into the contract and node binary via the `include_measurements!` macro. - You need attestation data from **both** release and dev images — run the single-node script - twice with `OS_IMAGE=dstack-` and `OS_IMAGE=dstack-dev-`. - - **Why this matters:** These measurements are seeded as the default allowed OS measurements - when the contract is deployed or migrated (see `default_measurements()` in - `mpc-attestation/src/attestation.rs`). If they are stale, nodes running a newer OS image - will fail attestation until operators vote in the correct measurements. - - > **Note:** This hardcoded seeding is a bootstrap mechanism. After release 3.8, measurements - > will be managed entirely through on-chain voting (`vote_add_os_measurement`), and these - > files will no longer need to be kept in sync with the deployed OS image. - -## Tests that depend on these assets - -After updating assets, these tests should pass: - -```shell -cargo test -p mpc-contract test_submit_participant_info_succeeds_with_valid_dstack_attestation -cargo test -p mpc-contract test_tee_attestation_fails_with_invalid_tls_key -cargo test -p mpc-contract test_submit_participant_info_fails_without_approved_mpc_hash -cargo test -p mpc-contract test_verify_tee_triggers_resharing_and_kickout_on_expired_attestation -cargo test -p test-utils -``` +In addition, look for the `VALID_ATTESTATION_TIMESTAMP` constant in `crates/test-utils/src/attestation.rs` and update it to a Unix timestamp that is after the date when the measurements were taken. This ensures that the tests will consider the measurements valid. diff --git a/crates/test-utils/assets/app_compose.json b/crates/test-utils/assets/app_compose.json index 5d2d22afa..90cae116e 100644 --- a/crates/test-utils/assets/app_compose.json +++ b/crates/test-utils/assets/app_compose.json @@ -1,8 +1,8 @@ { "manifest_version": 2, - "name": "mpc-localnet-one-node-1774339569", + "name": "mpc-localnet-one-node-1774515652", "runner": "docker-compose", - "docker_compose_file": "version: '3.8'\n\nservices:\n launcher:\n image: nearone/mpc-launcher@sha256:84c7537a2f84d3477eac2e5ef3ba0765b5d688f86096947eea4744ce25b27054\n\n container_name: launcher\n\n environment:\n - PLATFORM=TEE\n - DOCKER_CONTENT_TRUST=1\n - DEFAULT_IMAGE_DIGEST=sha256:e2ef71c220158f9ee19a265d583647eedb4e0cd7ca37021fbf0ab34e3d214ed0\n\n volumes:\n - /var/run/docker.sock:/var/run/docker.sock\n - /var/run/dstack.sock:/var/run/dstack.sock\n - /tapp:/tapp:ro\n - shared-volume:/mnt/shared:ro\n\n security_opt:\n - no-new-privileges:true\n\n read_only: true\n\n tmpfs:\n - /tmp\n\nvolumes:\n shared-volume:\n name: shared-volume\n", + "docker_compose_file": "version: '3.8'\n\nservices:\n launcher:\n image: nearone/mpc-launcher@sha256:f0d8146ae705dad182f7e9601e6e97215be4cf94ce80b38fddb2df654020be49\n\n container_name: launcher\n\n environment:\n - PLATFORM=TEE\n - DOCKER_CONTENT_TRUST=1\n - DEFAULT_IMAGE_DIGEST=sha256:6a5700fccbb3facddd1f3934f4976c4dcefc176c4aac28cd2fd035984b368980\n\n volumes:\n - /var/run/docker.sock:/var/run/docker.sock\n - /var/run/dstack.sock:/var/run/dstack.sock\n - /tapp:/tapp:ro\n - shared-volume:/mnt/shared:rw\n\n security_opt:\n - no-new-privileges:true\n\n read_only: true\n\n tmpfs:\n - /tmp\n\nvolumes:\n shared-volume:\n name: shared-volume\n", "kms_enabled": false, "gateway_enabled": false, "local_key_provider_enabled": true, diff --git a/crates/test-utils/assets/collateral.json b/crates/test-utils/assets/collateral.json index 20d3f3c09..8d649602c 100644 --- a/crates/test-utils/assets/collateral.json +++ b/crates/test-utils/assets/collateral.json @@ -1,11 +1,11 @@ { "pck_crl_issuer_chain": "-----BEGIN CERTIFICATE-----\nMIICljCCAj2gAwIBAgIVAJVvXc29G+HpQEnJ1PQzzgFXC95UMAoGCCqGSM49BAMC\nMGgxGjAYBgNVBAMMEUludGVsIFNHWCBSb290IENBMRowGAYDVQQKDBFJbnRlbCBD\nb3Jwb3JhdGlvbjEUMBIGA1UEBwwLU2FudGEgQ2xhcmExCzAJBgNVBAgMAkNBMQsw\nCQYDVQQGEwJVUzAeFw0xODA1MjExMDUwMTBaFw0zMzA1MjExMDUwMTBaMHAxIjAg\nBgNVBAMMGUludGVsIFNHWCBQQ0sgUGxhdGZvcm0gQ0ExGjAYBgNVBAoMEUludGVs\nIENvcnBvcmF0aW9uMRQwEgYDVQQHDAtTYW50YSBDbGFyYTELMAkGA1UECAwCQ0Ex\nCzAJBgNVBAYTAlVTMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAENSB/7t21lXSO\n2Cuzpxw74eJB72EyDGgW5rXCtx2tVTLq6hKk6z+UiRZCnqR7psOvgqFeSxlmTlJl\neTmi2WYz3qOBuzCBuDAfBgNVHSMEGDAWgBQiZQzWWp00ifODtJVSv1AbOScGrDBS\nBgNVHR8ESzBJMEegRaBDhkFodHRwczovL2NlcnRpZmljYXRlcy50cnVzdGVkc2Vy\ndmljZXMuaW50ZWwuY29tL0ludGVsU0dYUm9vdENBLmRlcjAdBgNVHQ4EFgQUlW9d\nzb0b4elAScnU9DPOAVcL3lQwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYB\nAf8CAQAwCgYIKoZIzj0EAwIDRwAwRAIgXsVki0w+i6VYGW3UF/22uaXe0YJDj1Ue\nnA+TjD1ai5cCICYb1SAmD5xkfTVpvo4UoyiSYxrDWLmUR4CI9NKyfPN+\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIICjzCCAjSgAwIBAgIUImUM1lqdNInzg7SVUr9QGzknBqwwCgYIKoZIzj0EAwIw\naDEaMBgGA1UEAwwRSW50ZWwgU0dYIFJvb3QgQ0ExGjAYBgNVBAoMEUludGVsIENv\ncnBvcmF0aW9uMRQwEgYDVQQHDAtTYW50YSBDbGFyYTELMAkGA1UECAwCQ0ExCzAJ\nBgNVBAYTAlVTMB4XDTE4MDUyMTEwNDUxMFoXDTQ5MTIzMTIzNTk1OVowaDEaMBgG\nA1UEAwwRSW50ZWwgU0dYIFJvb3QgQ0ExGjAYBgNVBAoMEUludGVsIENvcnBvcmF0\naW9uMRQwEgYDVQQHDAtTYW50YSBDbGFyYTELMAkGA1UECAwCQ0ExCzAJBgNVBAYT\nAlVTMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEC6nEwMDIYZOj/iPWsCzaEKi7\n1OiOSLRFhWGjbnBVJfVnkY4u3IjkDYYL0MxO4mqsyYjlBalTVYxFP2sJBK5zlKOB\nuzCBuDAfBgNVHSMEGDAWgBQiZQzWWp00ifODtJVSv1AbOScGrDBSBgNVHR8ESzBJ\nMEegRaBDhkFodHRwczovL2NlcnRpZmljYXRlcy50cnVzdGVkc2VydmljZXMuaW50\nZWwuY29tL0ludGVsU0dYUm9vdENBLmRlcjAdBgNVHQ4EFgQUImUM1lqdNInzg7SV\nUr9QGzknBqwwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQEwCgYI\nKoZIzj0EAwIDSQAwRgIhAOW/5QkR+S9CiSDcNoowLuPRLsWGf/Yi7GSX94BgwTwg\nAiEA4J0lrHoMs+Xo5o/sX6O9QWxHRAvZUGOdRQ7cvqRXaqI=\n-----END CERTIFICATE-----\n", "root_ca_crl": "308201223081c8020101300a06082a8648ce3d0403023068311a301806035504030c11496e74656c2053475820526f6f74204341311a3018060355040a0c11496e74656c20436f72706f726174696f6e3114301206035504070c0b53616e746120436c617261310b300906035504080c024341310b3009060355040613025553170d3236303232363133303430305a170d3237303232363133303430305aa02f302d300a0603551d140403020101301f0603551d2304183016801422650cd65a9d3489f383b49552bf501b392706ac300a06082a8648ce3d0403020349003046022100c252ed59c795ba2b11496a4a99758bb8cbc380a1ebbb0865be69f2c4b38bb6400221009a7d8b03602a9ee2d62322d759166d6933d24d9dfa01ab3fde4520691d715bd7", - "pck_crl": "30820d1630820cbd020101300a06082a8648ce3d04030230703122302006035504030c19496e74656c205347582050434b20506c6174666f726d204341311a3018060355040a0c11496e74656c20436f72706f726174696f6e3114301206035504070c0b53616e746120436c617261310b300906035504080c024341310b3009060355040613025553170d3236303332333039303332315a170d3236303432323039303332315a30820be9303302146fc34e5023e728923435d61aa4b83c618166ad35170d3236303332333039303332315a300c300a0603551d1504030a01013034021500efae6e9715fca13b87e333e8261ed6d990a926ad170d3236303332333039303332315a300c300a0603551d1504030a01013034021500fd608648629cba73078b4d492f4b3ea741ad08cd170d3236303332333039303332315a300c300a0603551d1504030a010130340215008af924184e1d5afddd73c3d63a12f5e8b5737e56170d3236303332333039303332315a300c300a0603551d1504030a01013034021500b1257978cfa9ccdd0759abf8c5ca72fae3a78a9b170d3236303332333039303332315a300c300a0603551d1504030a01013033021474fea614a972be0e2843f2059835811ed872f9b3170d3236303332333039303332315a300c300a0603551d1504030a01013034021500f9c4ef56b3ab48d577e108baedf4bf88014214b9170d3236303332333039303332315a300c300a0603551d1504030a010130330214071de0778f9e5fc4f2878f30d6b07c9a30e6b30b170d3236303332333039303332315a300c300a0603551d1504030a01013034021500cde2424f972cea94ff239937f4d80c25029dd60b170d3236303332333039303332315a300c300a0603551d1504030a0101303302146c3319e5109b64507d3cf1132ce00349ef527319170d3236303332333039303332315a300c300a0603551d1504030a01013034021500df08d756b66a7497f43b5bb58ada04d3f4f7a937170d3236303332333039303332315a300c300a0603551d1504030a01013033021428af485b6cf67e409a39d5cb5aee4598f7a8fa7b170d3236303332333039303332315a300c300a0603551d1504030a01013034021500fb8b2daec092cada8aa9bc4ff2f1c20d0346668c170d3236303332333039303332315a300c300a0603551d1504030a01013034021500cd4850ac52bdcc69a6a6f058c8bc57bbd0b5f864170d3236303332333039303332315a300c300a0603551d1504030a01013034021500994dd3666f5275fb805f95dd02bd50cb2679d8ad170d3236303332333039303332315a300c300a0603551d1504030a0101303302140702136900252274d9035eedf5457462fad0ef4c170d3236303332333039303332315a300c300a0603551d1504030a01013033021461f2bf73e39b4e04aa27d801bd73d24319b5bf80170d3236303332333039303332315a300c300a0603551d1504030a0101303302143992be851b96902eff38959e6c2eff1b0651a4b5170d3236303332333039303332315a300c300a0603551d1504030a0101303302140fda43a00b68ea79b7c2deaeac0b498bdfb2af90170d3236303332333039303332315a300c300a0603551d1504030a010130330214639f139a5040fdcff191e8a4fb1bf086ed603971170d3236303332333039303332315a300c300a0603551d1504030a01013034021500959d533f9249dc1e513544cdc830bf19b7f1f301170d3236303332333039303332315a300c300a0603551d1504030a0101303302147ae37748a9f912f4c63ba7ab07c593ce1d1d1181170d3236303332333039303332315a300c300a0603551d1504030a01013033021413884b33269938c195aa170fca75da177538df0b170d3236303332333039303332315a300c300a0603551d1504030a0101303402150085d3c9381b77a7e04d119c9e5ad6749ff3ffab87170d3236303332333039303332315a300c300a0603551d1504030a0101303402150093887ca4411e7a923bd1fed2819b2949f201b5b4170d3236303332333039303332315a300c300a0603551d1504030a0101303302142498dc6283930996fd8bf23a37acbe26a3bed457170d3236303332333039303332315a300c300a0603551d1504030a010130340215008a66f1a749488667689cc3903ac54c662b712e73170d3236303332333039303332315a300c300a0603551d1504030a01013034021500afc13610bdd36cb7985d106481a880d3a01fda07170d3236303332333039303332315a300c300a0603551d1504030a01013034021500efe04b2c33d036aac96ca673bf1e9a47b64d5cbb170d3236303332333039303332315a300c300a0603551d1504030a0101303402150083d9ac8d8bb509d1c6c809ad712e8430559ed7f3170d3236303332333039303332315a300c300a0603551d1504030a0101303302147931fd50b5071c1bbfc5b7b6ded8b45b9d8b8529170d3236303332333039303332315a300c300a0603551d1504030a0101303302141fa20e2970bde5d57f7b8ddf8339484e1f1d0823170d3236303332333039303332315a300c300a0603551d1504030a0101303302141e87b2c3b32d8d23e411cef34197b95af0c8adf5170d3236303332333039303332315a300c300a0603551d1504030a010130340215009afd2ee90a473550a167d996911437c7502d1f09170d3236303332333039303332315a300c300a0603551d1504030a0101303302144481b0f11728a13b696d3ea9c770a0b15ec58dda170d3236303332333039303332315a300c300a0603551d1504030a01013034021500a7859f57982ef0e67d37bc8ef2ef5ac835ff1aa9170d3236303332333039303332315a300c300a0603551d1504030a010130340215009d67753b81e47090aea763fbec4c4549bcdb9933170d3236303332333039303332315a300c300a0603551d1504030a01013033021434bfbb7a1d9c568147e118b614f7b76ed3ef68df170d3236303332333039303332315a300c300a0603551d1504030a0101303302142c3cc6fe9279db1516d5ce39f2a898cda5a175e1170d3236303332333039303332315a300c300a0603551d1504030a010130330214717948687509234be979e4b7dce6f31bef64b68c170d3236303332333039303332315a300c300a0603551d1504030a010130340215009d76ef2c39c136e8658b6e7396b1d7445a27631f170d3236303332333039303332315a300c300a0603551d1504030a01013034021500c3e025fca995f36f59b48467939e3e34e6361a6f170d3236303332333039303332315a300c300a0603551d1504030a010130340215008c5f6b3257da05b17429e2e61ba965d67330606a170d3236303332333039303332315a300c300a0603551d1504030a01013034021500a17c51722ec1e0c3278fe8bdf052059cbec4e648170d3236303332333039303332315a300c300a0603551d1504030a01013033021411c943b866fa04944e3057e5a67146596475a023170d3236303332333039303332315a300c300a0603551d1504030a01013034021500be6913785406155454a28885a515b3da5767d3a9170d3236303332333039303332315a300c300a0603551d1504030a0101303302140ac5ec91bd934c07b9ea41625e9cc09681002eb0170d3236303332333039303332315a300c300a0603551d1504030a0101303302146d51a0eabc1f9a1e9ddd5b36bdda1631ae6c182a170d3236303332333039303332315a300c300a0603551d1504030a01013034021500a52c5d71c4166b4fc0ded8b679951e5ee9193de5170d3236303332333039303332315a300c300a0603551d1504030a010130330214249779aedd85fcac93c8853516be5428c26b3bf8170d3236303332333039303332315a300c300a0603551d1504030a01013033021434ba4fd76bde5309210cf1dd1ffb494c638a9157170d3236303332333039303332315a300c300a0603551d1504030a010130330214043e04919daae13443248395094d2a2eacfc76fe170d3236303332333039303332315a300c300a0603551d1504030a01013033021447fc577d2d094cbdf270715ed6848a93855ad34b170d3236303332333039303332315a300c300a0603551d1504030a0101303302147d62a2f5e6f386e469653fffff045d0a8178e8e7170d3236303332333039303332315a300c300a0603551d1504030a01013034021500c4ed45fe026bb6a47eaec35ea80b7ef407ce062c170d3236303332333039303332315a300c300a0603551d1504030a01013034021500cf9831077a3ca4f1a2c56867bf55b18eccbeffd8170d3236303332333039303332315a300c300a0603551d1504030a0101303302146c2b81d7ea2e436720ce29f1d0b1ccb7a218600f170d3236303332333039303332315a300c300a0603551d1504030a0101a02f302d300a0603551d140403020101301f0603551d23041830168014956f5dcdbd1be1e94049c9d4f433ce01570bde54300a06082a8648ce3d0403020347003044022059471d848c82dffba25c398d9c0eba5ae84fd5becadc361e3b9aa29e685c5e9d02203292aaa14390b702045a652f2448ebe65dfd4217ae3b3ba3404549200993fe2b", + "pck_crl": "30820d1730820cbd020101300a06082a8648ce3d04030230703122302006035504030c19496e74656c205347582050434b20506c6174666f726d204341311a3018060355040a0c11496e74656c20436f72706f726174696f6e3114301206035504070c0b53616e746120436c617261310b300906035504080c024341310b3009060355040613025553170d3236303332353039303433335a170d3236303432343039303433335a30820be9303302146fc34e5023e728923435d61aa4b83c618166ad35170d3236303332353039303433335a300c300a0603551d1504030a01013034021500efae6e9715fca13b87e333e8261ed6d990a926ad170d3236303332353039303433335a300c300a0603551d1504030a01013034021500fd608648629cba73078b4d492f4b3ea741ad08cd170d3236303332353039303433335a300c300a0603551d1504030a010130340215008af924184e1d5afddd73c3d63a12f5e8b5737e56170d3236303332353039303433335a300c300a0603551d1504030a01013034021500b1257978cfa9ccdd0759abf8c5ca72fae3a78a9b170d3236303332353039303433335a300c300a0603551d1504030a01013033021474fea614a972be0e2843f2059835811ed872f9b3170d3236303332353039303433335a300c300a0603551d1504030a01013034021500f9c4ef56b3ab48d577e108baedf4bf88014214b9170d3236303332353039303433335a300c300a0603551d1504030a010130330214071de0778f9e5fc4f2878f30d6b07c9a30e6b30b170d3236303332353039303433335a300c300a0603551d1504030a01013034021500cde2424f972cea94ff239937f4d80c25029dd60b170d3236303332353039303433335a300c300a0603551d1504030a0101303302146c3319e5109b64507d3cf1132ce00349ef527319170d3236303332353039303433335a300c300a0603551d1504030a01013034021500df08d756b66a7497f43b5bb58ada04d3f4f7a937170d3236303332353039303433335a300c300a0603551d1504030a01013033021428af485b6cf67e409a39d5cb5aee4598f7a8fa7b170d3236303332353039303433335a300c300a0603551d1504030a01013034021500fb8b2daec092cada8aa9bc4ff2f1c20d0346668c170d3236303332353039303433335a300c300a0603551d1504030a01013034021500cd4850ac52bdcc69a6a6f058c8bc57bbd0b5f864170d3236303332353039303433335a300c300a0603551d1504030a01013034021500994dd3666f5275fb805f95dd02bd50cb2679d8ad170d3236303332353039303433335a300c300a0603551d1504030a0101303302140702136900252274d9035eedf5457462fad0ef4c170d3236303332353039303433335a300c300a0603551d1504030a01013033021461f2bf73e39b4e04aa27d801bd73d24319b5bf80170d3236303332353039303433335a300c300a0603551d1504030a0101303302143992be851b96902eff38959e6c2eff1b0651a4b5170d3236303332353039303433335a300c300a0603551d1504030a0101303302140fda43a00b68ea79b7c2deaeac0b498bdfb2af90170d3236303332353039303433335a300c300a0603551d1504030a010130330214639f139a5040fdcff191e8a4fb1bf086ed603971170d3236303332353039303433335a300c300a0603551d1504030a01013034021500959d533f9249dc1e513544cdc830bf19b7f1f301170d3236303332353039303433335a300c300a0603551d1504030a0101303302147ae37748a9f912f4c63ba7ab07c593ce1d1d1181170d3236303332353039303433335a300c300a0603551d1504030a01013033021413884b33269938c195aa170fca75da177538df0b170d3236303332353039303433335a300c300a0603551d1504030a0101303402150085d3c9381b77a7e04d119c9e5ad6749ff3ffab87170d3236303332353039303433335a300c300a0603551d1504030a0101303402150093887ca4411e7a923bd1fed2819b2949f201b5b4170d3236303332353039303433335a300c300a0603551d1504030a0101303302142498dc6283930996fd8bf23a37acbe26a3bed457170d3236303332353039303433335a300c300a0603551d1504030a010130340215008a66f1a749488667689cc3903ac54c662b712e73170d3236303332353039303433335a300c300a0603551d1504030a01013034021500afc13610bdd36cb7985d106481a880d3a01fda07170d3236303332353039303433335a300c300a0603551d1504030a01013034021500efe04b2c33d036aac96ca673bf1e9a47b64d5cbb170d3236303332353039303433335a300c300a0603551d1504030a0101303402150083d9ac8d8bb509d1c6c809ad712e8430559ed7f3170d3236303332353039303433335a300c300a0603551d1504030a0101303302147931fd50b5071c1bbfc5b7b6ded8b45b9d8b8529170d3236303332353039303433335a300c300a0603551d1504030a0101303302141fa20e2970bde5d57f7b8ddf8339484e1f1d0823170d3236303332353039303433335a300c300a0603551d1504030a0101303302141e87b2c3b32d8d23e411cef34197b95af0c8adf5170d3236303332353039303433335a300c300a0603551d1504030a010130340215009afd2ee90a473550a167d996911437c7502d1f09170d3236303332353039303433335a300c300a0603551d1504030a0101303302144481b0f11728a13b696d3ea9c770a0b15ec58dda170d3236303332353039303433335a300c300a0603551d1504030a01013034021500a7859f57982ef0e67d37bc8ef2ef5ac835ff1aa9170d3236303332353039303433335a300c300a0603551d1504030a010130340215009d67753b81e47090aea763fbec4c4549bcdb9933170d3236303332353039303433335a300c300a0603551d1504030a01013033021434bfbb7a1d9c568147e118b614f7b76ed3ef68df170d3236303332353039303433335a300c300a0603551d1504030a0101303302142c3cc6fe9279db1516d5ce39f2a898cda5a175e1170d3236303332353039303433335a300c300a0603551d1504030a010130330214717948687509234be979e4b7dce6f31bef64b68c170d3236303332353039303433335a300c300a0603551d1504030a010130340215009d76ef2c39c136e8658b6e7396b1d7445a27631f170d3236303332353039303433335a300c300a0603551d1504030a01013034021500c3e025fca995f36f59b48467939e3e34e6361a6f170d3236303332353039303433335a300c300a0603551d1504030a010130340215008c5f6b3257da05b17429e2e61ba965d67330606a170d3236303332353039303433335a300c300a0603551d1504030a01013034021500a17c51722ec1e0c3278fe8bdf052059cbec4e648170d3236303332353039303433335a300c300a0603551d1504030a01013033021411c943b866fa04944e3057e5a67146596475a023170d3236303332353039303433335a300c300a0603551d1504030a01013034021500be6913785406155454a28885a515b3da5767d3a9170d3236303332353039303433335a300c300a0603551d1504030a0101303302140ac5ec91bd934c07b9ea41625e9cc09681002eb0170d3236303332353039303433335a300c300a0603551d1504030a0101303302146d51a0eabc1f9a1e9ddd5b36bdda1631ae6c182a170d3236303332353039303433335a300c300a0603551d1504030a01013034021500a52c5d71c4166b4fc0ded8b679951e5ee9193de5170d3236303332353039303433335a300c300a0603551d1504030a010130330214249779aedd85fcac93c8853516be5428c26b3bf8170d3236303332353039303433335a300c300a0603551d1504030a01013033021434ba4fd76bde5309210cf1dd1ffb494c638a9157170d3236303332353039303433335a300c300a0603551d1504030a010130330214043e04919daae13443248395094d2a2eacfc76fe170d3236303332353039303433335a300c300a0603551d1504030a01013033021447fc577d2d094cbdf270715ed6848a93855ad34b170d3236303332353039303433335a300c300a0603551d1504030a0101303302147d62a2f5e6f386e469653fffff045d0a8178e8e7170d3236303332353039303433335a300c300a0603551d1504030a01013034021500c4ed45fe026bb6a47eaec35ea80b7ef407ce062c170d3236303332353039303433335a300c300a0603551d1504030a01013034021500cf9831077a3ca4f1a2c56867bf55b18eccbeffd8170d3236303332353039303433335a300c300a0603551d1504030a0101303302146c2b81d7ea2e436720ce29f1d0b1ccb7a218600f170d3236303332353039303433335a300c300a0603551d1504030a0101a02f302d300a0603551d140403020101301f0603551d23041830168014956f5dcdbd1be1e94049c9d4f433ce01570bde54300a06082a8648ce3d04030203480030450220436654da89e5124cb9e8e7298065f3754ddad212a31b8c89fbfd2c146326abbf022100f3eda6b4ea5f212063fdf434dcaa6a4b72e262729e070fba9d853ccfd00e8dac", "tcb_info_issuer_chain": "-----BEGIN CERTIFICATE-----\nMIICjTCCAjKgAwIBAgIUfjiC1ftVKUpASY5FhAPpFJG99FUwCgYIKoZIzj0EAwIw\naDEaMBgGA1UEAwwRSW50ZWwgU0dYIFJvb3QgQ0ExGjAYBgNVBAoMEUludGVsIENv\ncnBvcmF0aW9uMRQwEgYDVQQHDAtTYW50YSBDbGFyYTELMAkGA1UECAwCQ0ExCzAJ\nBgNVBAYTAlVTMB4XDTI1MDUwNjA5MjUwMFoXDTMyMDUwNjA5MjUwMFowbDEeMBwG\nA1UEAwwVSW50ZWwgU0dYIFRDQiBTaWduaW5nMRowGAYDVQQKDBFJbnRlbCBDb3Jw\nb3JhdGlvbjEUMBIGA1UEBwwLU2FudGEgQ2xhcmExCzAJBgNVBAgMAkNBMQswCQYD\nVQQGEwJVUzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABENFG8xzydWRfK92bmGv\nP+mAh91PEyV7Jh6FGJd5ndE9aBH7R3E4A7ubrlh/zN3C4xvpoouGlirMba+W2lju\nypajgbUwgbIwHwYDVR0jBBgwFoAUImUM1lqdNInzg7SVUr9QGzknBqwwUgYDVR0f\nBEswSTBHoEWgQ4ZBaHR0cHM6Ly9jZXJ0aWZpY2F0ZXMudHJ1c3RlZHNlcnZpY2Vz\nLmludGVsLmNvbS9JbnRlbFNHWFJvb3RDQS5kZXIwHQYDVR0OBBYEFH44gtX7VSlK\nQEmORYQD6RSRvfRVMA4GA1UdDwEB/wQEAwIGwDAMBgNVHRMBAf8EAjAAMAoGCCqG\nSM49BAMCA0kAMEYCIQDdmmRuAo3qCO8TC1IoJMITAoOEw4dlgEBHzSz1TuMSTAIh\nAKVTqOkt59+co0O3m3hC+v5Fb00FjYWcgeu3EijOULo5\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIICjzCCAjSgAwIBAgIUImUM1lqdNInzg7SVUr9QGzknBqwwCgYIKoZIzj0EAwIw\naDEaMBgGA1UEAwwRSW50ZWwgU0dYIFJvb3QgQ0ExGjAYBgNVBAoMEUludGVsIENv\ncnBvcmF0aW9uMRQwEgYDVQQHDAtTYW50YSBDbGFyYTELMAkGA1UECAwCQ0ExCzAJ\nBgNVBAYTAlVTMB4XDTE4MDUyMTEwNDUxMFoXDTQ5MTIzMTIzNTk1OVowaDEaMBgG\nA1UEAwwRSW50ZWwgU0dYIFJvb3QgQ0ExGjAYBgNVBAoMEUludGVsIENvcnBvcmF0\naW9uMRQwEgYDVQQHDAtTYW50YSBDbGFyYTELMAkGA1UECAwCQ0ExCzAJBgNVBAYT\nAlVTMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEC6nEwMDIYZOj/iPWsCzaEKi7\n1OiOSLRFhWGjbnBVJfVnkY4u3IjkDYYL0MxO4mqsyYjlBalTVYxFP2sJBK5zlKOB\nuzCBuDAfBgNVHSMEGDAWgBQiZQzWWp00ifODtJVSv1AbOScGrDBSBgNVHR8ESzBJ\nMEegRaBDhkFodHRwczovL2NlcnRpZmljYXRlcy50cnVzdGVkc2VydmljZXMuaW50\nZWwuY29tL0ludGVsU0dYUm9vdENBLmRlcjAdBgNVHQ4EFgQUImUM1lqdNInzg7SV\nUr9QGzknBqwwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQEwCgYI\nKoZIzj0EAwIDSQAwRgIhAOW/5QkR+S9CiSDcNoowLuPRLsWGf/Yi7GSX94BgwTwg\nAiEA4J0lrHoMs+Xo5o/sX6O9QWxHRAvZUGOdRQ7cvqRXaqI=\n-----END CERTIFICATE-----\n", - "tcb_info": "{\"id\":\"TDX\",\"version\":3,\"issueDate\":\"2026-03-23T08:57:57Z\",\"nextUpdate\":\"2026-04-22T08:57:57Z\",\"fmspc\":\"b0c06f000000\",\"pceId\":\"0000\",\"tcbType\":0,\"tcbEvaluationDataNumber\":18,\"tdxModule\":{\"mrsigner\":\"000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\",\"attributes\":\"0000000000000000\",\"attributesMask\":\"FFFFFFFFFFFFFFFF\"},\"tdxModuleIdentities\":[{\"id\":\"TDX_03\",\"mrsigner\":\"000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\",\"attributes\":\"0000000000000000\",\"attributesMask\":\"FFFFFFFFFFFFFFFF\",\"tcbLevels\":[{\"tcb\":{\"isvsvn\":3},\"tcbDate\":\"2024-11-13T00:00:00Z\",\"tcbStatus\":\"UpToDate\"}]},{\"id\":\"TDX_01\",\"mrsigner\":\"000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\",\"attributes\":\"0000000000000000\",\"attributesMask\":\"FFFFFFFFFFFFFFFF\",\"tcbLevels\":[{\"tcb\":{\"isvsvn\":6},\"tcbDate\":\"2024-11-13T00:00:00Z\",\"tcbStatus\":\"UpToDate\"},{\"tcb\":{\"isvsvn\":4},\"tcbDate\":\"2024-03-13T00:00:00Z\",\"tcbStatus\":\"OutOfDate\",\"advisoryIDs\":[\"INTEL-SA-01036\",\"INTEL-SA-01099\"]},{\"tcb\":{\"isvsvn\":2},\"tcbDate\":\"2023-08-09T00:00:00Z\",\"tcbStatus\":\"OutOfDate\",\"advisoryIDs\":[\"INTEL-SA-01036\",\"INTEL-SA-01099\"]}]}],\"tcbLevels\":[{\"tcb\":{\"sgxtcbcomponents\":[{\"svn\":3,\"category\":\"BIOS\",\"type\":\"Early Microcode Update\"},{\"svn\":3,\"category\":\"OS/VMM\",\"type\":\"SGX Late Microcode Update\"},{\"svn\":2,\"category\":\"OS/VMM\",\"type\":\"TXT SINIT\"},{\"svn\":2,\"category\":\"BIOS\"},{\"svn\":4,\"category\":\"BIOS\"},{\"svn\":1,\"category\":\"BIOS\"},{\"svn\":0},{\"svn\":5,\"category\":\"OS/VMM\",\"type\":\"SEAMLDR ACM\"},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0}],\"pcesvn\":11,\"tdxtcbcomponents\":[{\"svn\":5,\"category\":\"OS/VMM\",\"type\":\"TDX Module\"},{\"svn\":0,\"category\":\"OS/VMM\",\"type\":\"TDX Module\"},{\"svn\":3,\"category\":\"OS/VMM\",\"type\":\"TDX Late Microcode Update\"},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0}]},\"tcbDate\":\"2024-11-13T00:00:00Z\",\"tcbStatus\":\"UpToDate\"},{\"tcb\":{\"sgxtcbcomponents\":[{\"svn\":2,\"category\":\"BIOS\",\"type\":\"Early Microcode Update\"},{\"svn\":2,\"category\":\"OS/VMM\",\"type\":\"SGX Late Microcode Update\"},{\"svn\":2,\"category\":\"OS/VMM\",\"type\":\"TXT SINIT\"},{\"svn\":2,\"category\":\"BIOS\"},{\"svn\":3,\"category\":\"BIOS\"},{\"svn\":1,\"category\":\"BIOS\"},{\"svn\":0},{\"svn\":5,\"category\":\"OS/VMM\",\"type\":\"SEAMLDR ACM\"},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0}],\"pcesvn\":11,\"tdxtcbcomponents\":[{\"svn\":5,\"category\":\"OS/VMM\",\"type\":\"TDX Module\"},{\"svn\":0,\"category\":\"OS/VMM\",\"type\":\"TDX Module\"},{\"svn\":2,\"category\":\"OS/VMM\",\"type\":\"TDX Late Microcode Update\"},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0}]},\"tcbDate\":\"2024-03-13T00:00:00Z\",\"tcbStatus\":\"OutOfDate\",\"advisoryIDs\":[\"INTEL-SA-01036\",\"INTEL-SA-01079\",\"INTEL-SA-01099\",\"INTEL-SA-01103\",\"INTEL-SA-01111\"]},{\"tcb\":{\"sgxtcbcomponents\":[{\"svn\":2,\"category\":\"BIOS\",\"type\":\"Early Microcode Update\"},{\"svn\":2,\"category\":\"OS/VMM\",\"type\":\"SGX Late Microcode Update\"},{\"svn\":2,\"category\":\"OS/VMM\",\"type\":\"TXT SINIT\"},{\"svn\":2,\"category\":\"BIOS\"},{\"svn\":3,\"category\":\"BIOS\"},{\"svn\":1,\"category\":\"BIOS\"},{\"svn\":0},{\"svn\":5,\"category\":\"OS/VMM\",\"type\":\"SEAMLDR ACM\"},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0}],\"pcesvn\":5,\"tdxtcbcomponents\":[{\"svn\":5,\"category\":\"OS/VMM\",\"type\":\"TDX Module\"},{\"svn\":0,\"category\":\"OS/VMM\",\"type\":\"TDX Module\"},{\"svn\":2,\"category\":\"OS/VMM\",\"type\":\"TDX Late Microcode Update\"},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0}]},\"tcbDate\":\"2018-01-04T00:00:00Z\",\"tcbStatus\":\"OutOfDate\",\"advisoryIDs\":[\"INTEL-SA-00106\",\"INTEL-SA-00115\",\"INTEL-SA-00135\",\"INTEL-SA-00203\",\"INTEL-SA-00220\",\"INTEL-SA-00233\",\"INTEL-SA-00270\",\"INTEL-SA-00293\",\"INTEL-SA-00320\",\"INTEL-SA-00329\",\"INTEL-SA-00381\",\"INTEL-SA-00389\",\"INTEL-SA-00477\",\"INTEL-SA-00837\",\"INTEL-SA-01036\",\"INTEL-SA-01079\",\"INTEL-SA-01099\",\"INTEL-SA-01103\",\"INTEL-SA-01111\"]}]}", - "tcb_info_signature": "7ae741fc33b2ee32c6a86151c82911e54e330efe32ccedfaa579ce5bcbfbaf22628d4b07c0659c9e63774367a7bee9b3ce7979d9ba4a27b184266ecebb64233b", + "tcb_info": "{\"id\":\"TDX\",\"version\":3,\"issueDate\":\"2026-03-25T09:09:57Z\",\"nextUpdate\":\"2026-04-24T09:09:57Z\",\"fmspc\":\"b0c06f000000\",\"pceId\":\"0000\",\"tcbType\":0,\"tcbEvaluationDataNumber\":18,\"tdxModule\":{\"mrsigner\":\"000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\",\"attributes\":\"0000000000000000\",\"attributesMask\":\"FFFFFFFFFFFFFFFF\"},\"tdxModuleIdentities\":[{\"id\":\"TDX_03\",\"mrsigner\":\"000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\",\"attributes\":\"0000000000000000\",\"attributesMask\":\"FFFFFFFFFFFFFFFF\",\"tcbLevels\":[{\"tcb\":{\"isvsvn\":3},\"tcbDate\":\"2024-11-13T00:00:00Z\",\"tcbStatus\":\"UpToDate\"}]},{\"id\":\"TDX_01\",\"mrsigner\":\"000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\",\"attributes\":\"0000000000000000\",\"attributesMask\":\"FFFFFFFFFFFFFFFF\",\"tcbLevels\":[{\"tcb\":{\"isvsvn\":6},\"tcbDate\":\"2024-11-13T00:00:00Z\",\"tcbStatus\":\"UpToDate\"},{\"tcb\":{\"isvsvn\":4},\"tcbDate\":\"2024-03-13T00:00:00Z\",\"tcbStatus\":\"OutOfDate\",\"advisoryIDs\":[\"INTEL-SA-01036\",\"INTEL-SA-01099\"]},{\"tcb\":{\"isvsvn\":2},\"tcbDate\":\"2023-08-09T00:00:00Z\",\"tcbStatus\":\"OutOfDate\",\"advisoryIDs\":[\"INTEL-SA-01036\",\"INTEL-SA-01099\"]}]}],\"tcbLevels\":[{\"tcb\":{\"sgxtcbcomponents\":[{\"svn\":3,\"category\":\"BIOS\",\"type\":\"Early Microcode Update\"},{\"svn\":3,\"category\":\"OS/VMM\",\"type\":\"SGX Late Microcode Update\"},{\"svn\":2,\"category\":\"OS/VMM\",\"type\":\"TXT SINIT\"},{\"svn\":2,\"category\":\"BIOS\"},{\"svn\":4,\"category\":\"BIOS\"},{\"svn\":1,\"category\":\"BIOS\"},{\"svn\":0},{\"svn\":5,\"category\":\"OS/VMM\",\"type\":\"SEAMLDR ACM\"},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0}],\"pcesvn\":11,\"tdxtcbcomponents\":[{\"svn\":5,\"category\":\"OS/VMM\",\"type\":\"TDX Module\"},{\"svn\":0,\"category\":\"OS/VMM\",\"type\":\"TDX Module\"},{\"svn\":3,\"category\":\"OS/VMM\",\"type\":\"TDX Late Microcode Update\"},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0}]},\"tcbDate\":\"2024-11-13T00:00:00Z\",\"tcbStatus\":\"UpToDate\"},{\"tcb\":{\"sgxtcbcomponents\":[{\"svn\":2,\"category\":\"BIOS\",\"type\":\"Early Microcode Update\"},{\"svn\":2,\"category\":\"OS/VMM\",\"type\":\"SGX Late Microcode Update\"},{\"svn\":2,\"category\":\"OS/VMM\",\"type\":\"TXT SINIT\"},{\"svn\":2,\"category\":\"BIOS\"},{\"svn\":3,\"category\":\"BIOS\"},{\"svn\":1,\"category\":\"BIOS\"},{\"svn\":0},{\"svn\":5,\"category\":\"OS/VMM\",\"type\":\"SEAMLDR ACM\"},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0}],\"pcesvn\":11,\"tdxtcbcomponents\":[{\"svn\":5,\"category\":\"OS/VMM\",\"type\":\"TDX Module\"},{\"svn\":0,\"category\":\"OS/VMM\",\"type\":\"TDX Module\"},{\"svn\":2,\"category\":\"OS/VMM\",\"type\":\"TDX Late Microcode Update\"},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0}]},\"tcbDate\":\"2024-03-13T00:00:00Z\",\"tcbStatus\":\"OutOfDate\",\"advisoryIDs\":[\"INTEL-SA-01036\",\"INTEL-SA-01079\",\"INTEL-SA-01099\",\"INTEL-SA-01103\",\"INTEL-SA-01111\"]},{\"tcb\":{\"sgxtcbcomponents\":[{\"svn\":2,\"category\":\"BIOS\",\"type\":\"Early Microcode Update\"},{\"svn\":2,\"category\":\"OS/VMM\",\"type\":\"SGX Late Microcode Update\"},{\"svn\":2,\"category\":\"OS/VMM\",\"type\":\"TXT SINIT\"},{\"svn\":2,\"category\":\"BIOS\"},{\"svn\":3,\"category\":\"BIOS\"},{\"svn\":1,\"category\":\"BIOS\"},{\"svn\":0},{\"svn\":5,\"category\":\"OS/VMM\",\"type\":\"SEAMLDR ACM\"},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0}],\"pcesvn\":5,\"tdxtcbcomponents\":[{\"svn\":5,\"category\":\"OS/VMM\",\"type\":\"TDX Module\"},{\"svn\":0,\"category\":\"OS/VMM\",\"type\":\"TDX Module\"},{\"svn\":2,\"category\":\"OS/VMM\",\"type\":\"TDX Late Microcode Update\"},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0}]},\"tcbDate\":\"2018-01-04T00:00:00Z\",\"tcbStatus\":\"OutOfDate\",\"advisoryIDs\":[\"INTEL-SA-00106\",\"INTEL-SA-00115\",\"INTEL-SA-00135\",\"INTEL-SA-00203\",\"INTEL-SA-00220\",\"INTEL-SA-00233\",\"INTEL-SA-00270\",\"INTEL-SA-00293\",\"INTEL-SA-00320\",\"INTEL-SA-00329\",\"INTEL-SA-00381\",\"INTEL-SA-00389\",\"INTEL-SA-00477\",\"INTEL-SA-00837\",\"INTEL-SA-01036\",\"INTEL-SA-01079\",\"INTEL-SA-01099\",\"INTEL-SA-01103\",\"INTEL-SA-01111\"]}]}", + "tcb_info_signature": "5d9174f57476a3e58ff896fe676e6f2fcc8ebc3e89e9bc78a145ae611af1d02139c630e15e36dc9083bb891dd85c51a55e6a28bb35fb469c0e767864613046bc", "qe_identity_issuer_chain": "-----BEGIN CERTIFICATE-----\nMIICjTCCAjKgAwIBAgIUfjiC1ftVKUpASY5FhAPpFJG99FUwCgYIKoZIzj0EAwIw\naDEaMBgGA1UEAwwRSW50ZWwgU0dYIFJvb3QgQ0ExGjAYBgNVBAoMEUludGVsIENv\ncnBvcmF0aW9uMRQwEgYDVQQHDAtTYW50YSBDbGFyYTELMAkGA1UECAwCQ0ExCzAJ\nBgNVBAYTAlVTMB4XDTI1MDUwNjA5MjUwMFoXDTMyMDUwNjA5MjUwMFowbDEeMBwG\nA1UEAwwVSW50ZWwgU0dYIFRDQiBTaWduaW5nMRowGAYDVQQKDBFJbnRlbCBDb3Jw\nb3JhdGlvbjEUMBIGA1UEBwwLU2FudGEgQ2xhcmExCzAJBgNVBAgMAkNBMQswCQYD\nVQQGEwJVUzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABENFG8xzydWRfK92bmGv\nP+mAh91PEyV7Jh6FGJd5ndE9aBH7R3E4A7ubrlh/zN3C4xvpoouGlirMba+W2lju\nypajgbUwgbIwHwYDVR0jBBgwFoAUImUM1lqdNInzg7SVUr9QGzknBqwwUgYDVR0f\nBEswSTBHoEWgQ4ZBaHR0cHM6Ly9jZXJ0aWZpY2F0ZXMudHJ1c3RlZHNlcnZpY2Vz\nLmludGVsLmNvbS9JbnRlbFNHWFJvb3RDQS5kZXIwHQYDVR0OBBYEFH44gtX7VSlK\nQEmORYQD6RSRvfRVMA4GA1UdDwEB/wQEAwIGwDAMBgNVHRMBAf8EAjAAMAoGCCqG\nSM49BAMCA0kAMEYCIQDdmmRuAo3qCO8TC1IoJMITAoOEw4dlgEBHzSz1TuMSTAIh\nAKVTqOkt59+co0O3m3hC+v5Fb00FjYWcgeu3EijOULo5\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIICjzCCAjSgAwIBAgIUImUM1lqdNInzg7SVUr9QGzknBqwwCgYIKoZIzj0EAwIw\naDEaMBgGA1UEAwwRSW50ZWwgU0dYIFJvb3QgQ0ExGjAYBgNVBAoMEUludGVsIENv\ncnBvcmF0aW9uMRQwEgYDVQQHDAtTYW50YSBDbGFyYTELMAkGA1UECAwCQ0ExCzAJ\nBgNVBAYTAlVTMB4XDTE4MDUyMTEwNDUxMFoXDTQ5MTIzMTIzNTk1OVowaDEaMBgG\nA1UEAwwRSW50ZWwgU0dYIFJvb3QgQ0ExGjAYBgNVBAoMEUludGVsIENvcnBvcmF0\naW9uMRQwEgYDVQQHDAtTYW50YSBDbGFyYTELMAkGA1UECAwCQ0ExCzAJBgNVBAYT\nAlVTMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEC6nEwMDIYZOj/iPWsCzaEKi7\n1OiOSLRFhWGjbnBVJfVnkY4u3IjkDYYL0MxO4mqsyYjlBalTVYxFP2sJBK5zlKOB\nuzCBuDAfBgNVHSMEGDAWgBQiZQzWWp00ifODtJVSv1AbOScGrDBSBgNVHR8ESzBJ\nMEegRaBDhkFodHRwczovL2NlcnRpZmljYXRlcy50cnVzdGVkc2VydmljZXMuaW50\nZWwuY29tL0ludGVsU0dYUm9vdENBLmRlcjAdBgNVHQ4EFgQUImUM1lqdNInzg7SV\nUr9QGzknBqwwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQEwCgYI\nKoZIzj0EAwIDSQAwRgIhAOW/5QkR+S9CiSDcNoowLuPRLsWGf/Yi7GSX94BgwTwg\nAiEA4J0lrHoMs+Xo5o/sX6O9QWxHRAvZUGOdRQ7cvqRXaqI=\n-----END CERTIFICATE-----\n", - "qe_identity": "{\"id\":\"TD_QE\",\"version\":2,\"issueDate\":\"2026-03-23T09:17:59Z\",\"nextUpdate\":\"2026-04-22T09:17:59Z\",\"tcbEvaluationDataNumber\":18,\"miscselect\":\"00000000\",\"miscselectMask\":\"FFFFFFFF\",\"attributes\":\"11000000000000000000000000000000\",\"attributesMask\":\"FBFFFFFFFFFFFFFF0000000000000000\",\"mrsigner\":\"DC9E2A7C6F948F17474E34A7FC43ED030F7C1563F1BABDDF6340C82E0E54A8C5\",\"isvprodid\":2,\"tcbLevels\":[{\"tcb\":{\"isvsvn\":4},\"tcbDate\":\"2024-11-13T00:00:00Z\",\"tcbStatus\":\"UpToDate\"}]}", - "qe_identity_signature": "b481f03e03b44389f71fdf7a123dad2d5c75bd18e18155c775ddafe64b12dc29457075c0c84fe1155444bf940a34059774c2d3d26c8e600d591bf054a091b19a" + "qe_identity": "{\"id\":\"TD_QE\",\"version\":2,\"issueDate\":\"2026-03-25T09:18:51Z\",\"nextUpdate\":\"2026-04-24T09:18:51Z\",\"tcbEvaluationDataNumber\":18,\"miscselect\":\"00000000\",\"miscselectMask\":\"FFFFFFFF\",\"attributes\":\"11000000000000000000000000000000\",\"attributesMask\":\"FBFFFFFFFFFFFFFF0000000000000000\",\"mrsigner\":\"DC9E2A7C6F948F17474E34A7FC43ED030F7C1563F1BABDDF6340C82E0E54A8C5\",\"isvprodid\":2,\"tcbLevels\":[{\"tcb\":{\"isvsvn\":4},\"tcbDate\":\"2024-11-13T00:00:00Z\",\"tcbStatus\":\"UpToDate\"}]}", + "qe_identity_signature": "3794544b24b5880595ec5d59681a549e8c86263b2ef4d9ebd5ff3418a5ae87b562d12a59921b5ce4abda7d2b830f55e7285e22090ee706634ea1391351de5925" } diff --git a/crates/test-utils/assets/launcher_image_compose.yaml b/crates/test-utils/assets/launcher_image_compose.yaml index b415cd02e..55391067b 100644 --- a/crates/test-utils/assets/launcher_image_compose.yaml +++ b/crates/test-utils/assets/launcher_image_compose.yaml @@ -2,20 +2,20 @@ version: '3.8' services: launcher: - image: nearone/mpc-launcher@sha256:84c7537a2f84d3477eac2e5ef3ba0765b5d688f86096947eea4744ce25b27054 + image: nearone/mpc-launcher@sha256:f0d8146ae705dad182f7e9601e6e97215be4cf94ce80b38fddb2df654020be49 container_name: launcher environment: - PLATFORM=TEE - DOCKER_CONTENT_TRUST=1 - - DEFAULT_IMAGE_DIGEST=sha256:e2ef71c220158f9ee19a265d583647eedb4e0cd7ca37021fbf0ab34e3d214ed0 + - DEFAULT_IMAGE_DIGEST=sha256:6a5700fccbb3facddd1f3934f4976c4dcefc176c4aac28cd2fd035984b368980 volumes: - /var/run/docker.sock:/var/run/docker.sock - /var/run/dstack.sock:/var/run/dstack.sock - /tapp:/tapp:ro - - shared-volume:/mnt/shared:ro + - shared-volume:/mnt/shared:rw security_opt: - no-new-privileges:true diff --git a/crates/test-utils/assets/mpc_image_digest.txt b/crates/test-utils/assets/mpc_image_digest.txt index 1912dab8c..ea6e0fda2 100644 --- a/crates/test-utils/assets/mpc_image_digest.txt +++ b/crates/test-utils/assets/mpc_image_digest.txt @@ -1 +1 @@ -e2ef71c220158f9ee19a265d583647eedb4e0cd7ca37021fbf0ab34e3d214ed0 \ No newline at end of file +6a5700fccbb3facddd1f3934f4976c4dcefc176c4aac28cd2fd035984b368980 \ No newline at end of file diff --git a/crates/test-utils/assets/near_account_public_key.pub b/crates/test-utils/assets/near_account_public_key.pub index 6fddf49d1..82a4788b4 100644 --- a/crates/test-utils/assets/near_account_public_key.pub +++ b/crates/test-utils/assets/near_account_public_key.pub @@ -1 +1 @@ -ed25519:D5e5A8qCw5HYhxcy7bYiEmucaZh7xyJS1mHrZrFmxscb \ No newline at end of file +ed25519:FZ52K44wLHeBJ3zNrzkZ1TBpEsqe6TgtPXSXbVU9gpUm \ No newline at end of file diff --git a/crates/test-utils/assets/near_p2p_public_key.pub b/crates/test-utils/assets/near_p2p_public_key.pub index 42523308a..5d52af45c 100644 --- a/crates/test-utils/assets/near_p2p_public_key.pub +++ b/crates/test-utils/assets/near_p2p_public_key.pub @@ -1 +1 @@ -ed25519:3JEZTKnzYm9JkwCxCF32SzhCW9wkxQQywBK3UW5SpkdQ \ No newline at end of file +ed25519:DTuiEgmZwkEFBhBrKzQ8ooGRPZPwBeZznR7d4fg6yX5f \ No newline at end of file diff --git a/crates/test-utils/assets/public_data.json b/crates/test-utils/assets/public_data.json index 91e783c03..b7ea79e73 100644 --- a/crates/test-utils/assets/public_data.json +++ b/crates/test-utils/assets/public_data.json @@ -1,31 +1,31 @@ { - "near_signer_public_key": "ed25519:D5e5A8qCw5HYhxcy7bYiEmucaZh7xyJS1mHrZrFmxscb", - "near_p2p_public_key": "ed25519:3JEZTKnzYm9JkwCxCF32SzhCW9wkxQQywBK3UW5SpkdQ", - "near_responder_public_keys": ["ed25519:4NG1RXHdZ2NVHQ6uvvryHDE93DNRmyRuKCJqAPfwTqry", "ed25519:9MMyDg64bBpRQXvQfyFAQUVL6q8uE8E1feaBLVdQBz2r", "ed25519:6hQafWFPo1LwQHwnp4JKpgNXxLyFFt3w7h3mJ177ZKXi", "ed25519:JC71kjXjqGtzGqsWqFJGB61iHWPu4DUxT3oiiicTTLd7", "ed25519:AdSmEyeVwRBoaA17nLjbMCwHtw2GueUUEtPsWnA8eZMc", "ed25519:DCB3VHHML4LruHJi8p5DMmcnzDwXQotfxPi2u2GQcJ8N", "ed25519:CfCp92TRGMwLVaLXTp4gNh1ob6ETzvy7CFJzxE8ZhMM2", "ed25519:3FPs67mnPSpnZWM9p6vgDwxWgdzUmAzJBTaZePyPzLaV", "ed25519:BdgYKfqF9roT4sV7n4ZVgjaAtU3kThDNRHD9XX4roNbg", "ed25519:4oJmLWR1SjqnHevhwFYmGyj4ruGD6xAWwTVfz9uV2sWc", "ed25519:8omQHaJP6CA1Fvsj6kyWZkqwky6bFgZMmYAKbPXQ8TK8", "ed25519:FWuD26ZMQEqNxZvSq66AUjpRuv4fhYpEe5Ukh6JPezG9", "ed25519:6mnUhyEVVwm1Hg4jN1848qNizqt2PxZCfRjezBTwTa6G", "ed25519:7Do6gVZ1TnYwMwcVkLajjeT3UcFJihGA5AQ3vcxNnZGJ", "ed25519:3UKWCAsSPbUSSKx62WC5vfJQCwZ7sxqcaAsjPUdsttg2", "ed25519:oGHtLUa1jj2D6zybWD6Nuha5hm6rSZQYWQKrUNT4Au3", "ed25519:AgqUsbBwQU7YPy5yLNF4qddhNVdYX2nvMvKo8gsgXA8u", "ed25519:BveRvq1g8pkhyxM5JEHeSMGftie6e8VQ8s6g6zj6irYg", "ed25519:7KBuG94e4ttEFbssh717kPwhgwgmpKTVXyAUVKcQxv3r", "ed25519:2xWnrpfayvoQwC3qkimQUDxsNwyeRZQrSAotdFLVhZAD", "ed25519:7iHJqpUjjktgQM2Tc1SvpCFHHMVg9k9zy149zNVK52Hz", "ed25519:9rVzF7mvpqRufd5L6jkcqtHGSca37gDSESWaiQj164rg", "ed25519:EmwUeAVDKuXZxbXFio5BLvufnPoZUBfunsDRoDrQZ4gs", "ed25519:39DyuBwZUh6pP4UvpMMyqSDVkQnZzY74qCCEDZx4DCDd", "ed25519:DZtuzM61M39Tv1TAcNJFEZYzFkVqRRm9UcgFCZK9Bxbw", "ed25519:qJCucYVEUCHkEUvu1D1xaKRzjwwqggHAo45vMijugEJ", "ed25519:6sFGiuL3MnoLSBLTxdJF9W6BrWhvT9dN2Kawoh9K9Hi3", "ed25519:4cdmFgt5cFfGPZHmiD73te46BZthFwAEsao4nuMMz6Ti", "ed25519:Fd6Uv7SN4GpnYfFf1WHzKG7U5eEyrxBj9UZRB6cDuy6w", "ed25519:itnDWdxPALLEGJpSiHMSw9q3Yk7rrKk9GTJMdu5zQqQ", "ed25519:9rhd7wmjMwYamt6PR3CSH8izpzNRna3PTMpp1e2GbBZj", "ed25519:84idZJTRAiLdST1WiANDWpJjMXzZKQGZDfgvqLX3vjip", "ed25519:Fv48YAhJCZmrNh3Ao2SHjvVHsLqKQbaH4BeUHCZYv7ah", "ed25519:Bz7kRwYzqaMi9bhXuNco4WigU7nUzgRnvqsvD5jVwe9J", "ed25519:FqUC66nEunPC5wwrYWDXN2vF5jSMGgjJcEXjrL7f2gmm", "ed25519:995fzERYvZVBKUHsWC8Vc8S8xwTQFg9TDnReaoqJqLPj", "ed25519:HnuixSSrhahi6yNwRwJQTunP2q6EM9yH8HeJB7AhgUUA", "ed25519:WzgJNUS9JdWm1Msx5AZgjXgVvrRdyJeSPP7bPZVmHtS", "ed25519:GFfSvbVWE598ZMyAnezqaYVbAkjYwMUfmLkgW4ZsBB6f", "ed25519:D1U95zPGaTx8G7pU4zrhJTFJfriFBrTFB93nXpiNjH9V", "ed25519:DJUfFq4B2oa7hV573nan8crp6u9rReyBLnCoD8zYXJDM", "ed25519:F1EQccWw5KcQZuMV8sSUJNyxicjsBWxJPcKSYoLFupKu", "ed25519:GVnAw8pPFayRurq3P14CCK1YqXpQCHWYzzzzn7JDQruy", "ed25519:FjAsu165GiV8unMMLw3wGDacQcRKqJYQhhPA5x6AJg9V", "ed25519:H24941YUDARGvwZxrVjfWij2St4Ed8sATWCxRG1LwvhH", "ed25519:Fyra2wJ6adCzQHF75nLwePNmewAXEkkg6kYz1aUNoanN", "ed25519:91ZDM4WKBoNKLJZVEitHzWmejzjd14cM7aWxvdnRKiTU", "ed25519:FKhvLB4vgmx2e5Z4xpbJ81KQQGgvgqeehH5EgY8wzeqz", "ed25519:7Pd582DakJsRpHMizmiMkXa9B9NiSEPz9K4hTSEZvd7x", "ed25519:3JGTfVU2KaY8aMbCiDLzrnYqLmZ1Q121cwaHrjTY1H5n"], + "near_signer_public_key": "ed25519:FZ52K44wLHeBJ3zNrzkZ1TBpEsqe6TgtPXSXbVU9gpUm", + "near_p2p_public_key": "ed25519:DTuiEgmZwkEFBhBrKzQ8ooGRPZPwBeZznR7d4fg6yX5f", + "near_responder_public_keys": ["ed25519:Yc6FBRk8KAc9jKuHqTbP1jgme9jMaMwZvyarTYAgQmN"], "tee_participant_info": { "Dstack": { - "quote": [4, 0, 2, 0, 129, 0, 0, 0, 0, 0, 0, 0, 147, 154, 114, 51, 247, 156, 76, 169, 148, 10, 13, 179, 149, 127, 6, 7, 61, 153, 138, 108, 16, 87, 107, 253, 246, 246, 237, 142, 155, 133, 233, 50, 0, 0, 0, 0, 11, 1, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 123, 240, 99, 40, 14, 148, 251, 5, 31, 93, 215, 177, 252, 89, 206, 154, 172, 66, 187, 150, 29, 248, 212, 75, 112, 156, 155, 15, 248, 122, 123, 77, 246, 72, 101, 123, 166, 209, 24, 149, 137, 254, 171, 29, 90, 60, 154, 157, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 231, 2, 6, 0, 0, 0, 0, 0, 240, 109, 253, 166, 220, 225, 207, 144, 77, 78, 43, 171, 29, 195, 112, 99, 76, 249, 92, 239, 162, 206, 178, 222, 46, 238, 18, 124, 147, 130, 105, 128, 144, 215, 164, 161, 62, 20, 197, 54, 236, 108, 156, 60, 143, 168, 112, 119, 1, 5, 134, 12, 50, 194, 16, 176, 155, 10, 57, 87, 3, 159, 146, 107, 179, 239, 175, 176, 14, 17, 230, 206, 153, 205, 125, 130, 52, 136, 252, 168, 239, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 230, 115, 190, 47, 112, 190, 239, 183, 11, 72, 166, 16, 158, 237, 71, 21, 215, 39, 13, 70, 131, 179, 191, 53, 111, 162, 95, 175, 191, 26, 167, 110, 57, 233, 18, 126, 110, 104, 140, 205, 169, 139, 218, 177, 212, 212, 127, 70, 181, 152, 253, 233, 73, 20, 39, 52, 27, 196, 104, 59, 117, 209, 13, 62, 54, 119, 10, 243, 163, 106, 105, 84, 216, 182, 183, 178, 42, 166, 99, 88, 241, 62, 31, 23, 46, 81, 183, 214, 230, 113, 13, 153, 168, 216, 83, 47, 200, 18, 212, 43, 255, 241, 199, 83, 130, 233, 26, 55, 200, 103, 171, 17, 123, 151, 235, 94, 141, 103, 151, 72, 137, 40, 234, 56, 229, 253, 56, 181, 237, 47, 135, 217, 97, 61, 57, 37, 7, 241, 195, 175, 148, 101, 124, 147, 29, 249, 34, 35, 83, 215, 198, 128, 228, 105, 47, 212, 225, 201, 41, 207, 85, 195, 240, 46, 14, 84, 55, 192, 157, 160, 237, 78, 193, 20, 115, 49, 156, 199, 251, 161, 64, 76, 62, 239, 25, 58, 173, 58, 251, 174, 226, 139, 0, 1, 57, 22, 80, 247, 216, 170, 100, 64, 68, 54, 220, 49, 221, 50, 67, 1, 219, 232, 8, 48, 11, 49, 93, 180, 228, 250, 85, 157, 19, 250, 135, 77, 104, 59, 246, 53, 0, 220, 45, 247, 12, 251, 53, 182, 88, 195, 212, 238, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 204, 16, 0, 0, 27, 150, 161, 23, 33, 229, 84, 54, 73, 160, 81, 255, 112, 135, 32, 10, 45, 194, 222, 160, 153, 75, 23, 9, 159, 106, 44, 19, 128, 167, 119, 97, 164, 60, 240, 160, 221, 184, 18, 157, 229, 201, 119, 219, 251, 198, 228, 128, 157, 75, 43, 183, 215, 70, 161, 246, 191, 94, 0, 251, 47, 184, 88, 209, 142, 188, 151, 224, 189, 139, 21, 116, 87, 28, 157, 151, 225, 226, 250, 218, 147, 80, 231, 144, 252, 223, 62, 103, 176, 31, 51, 101, 181, 44, 82, 180, 72, 148, 151, 10, 88, 144, 81, 87, 230, 135, 174, 102, 165, 143, 241, 229, 60, 148, 151, 208, 187, 151, 100, 64, 82, 171, 52, 1, 40, 143, 31, 160, 6, 0, 70, 16, 0, 0, 4, 4, 25, 27, 4, 255, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 21, 0, 0, 0, 0, 0, 0, 0, 231, 0, 0, 0, 0, 0, 0, 0, 229, 163, 167, 181, 216, 48, 194, 149, 59, 152, 83, 76, 108, 89, 163, 163, 79, 220, 52, 233, 51, 247, 245, 137, 143, 10, 133, 207, 8, 132, 107, 202, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 220, 158, 42, 124, 111, 148, 143, 23, 71, 78, 52, 167, 252, 67, 237, 3, 15, 124, 21, 99, 241, 186, 189, 223, 99, 64, 200, 46, 14, 84, 168, 197, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 215, 98, 66, 167, 203, 62, 30, 139, 2, 195, 192, 57, 51, 40, 206, 207, 77, 159, 207, 61, 144, 77, 52, 130, 175, 199, 184, 139, 101, 54, 6, 152, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 55, 140, 184, 159, 16, 129, 112, 22, 170, 116, 15, 65, 194, 34, 25, 176, 21, 215, 45, 175, 225, 178, 25, 101, 154, 225, 248, 220, 253, 70, 99, 28, 30, 23, 98, 194, 123, 112, 75, 247, 216, 167, 77, 160, 167, 148, 175, 255, 112, 188, 66, 226, 79, 146, 195, 234, 106, 115, 163, 75, 32, 230, 83, 174, 32, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 5, 0, 94, 14, 0, 0, 45, 45, 45, 45, 45, 66, 69, 71, 73, 78, 32, 67, 69, 82, 84, 73, 70, 73, 67, 65, 84, 69, 45, 45, 45, 45, 45, 10, 77, 73, 73, 69, 56, 84, 67, 67, 66, 74, 97, 103, 65, 119, 73, 66, 65, 103, 73, 85, 102, 50, 83, 98, 121, 119, 107, 77, 86, 84, 74, 75, 85, 53, 55, 47, 74, 119, 66, 112, 56, 69, 100, 104, 80, 48, 52, 119, 67, 103, 89, 73, 75, 111, 90, 73, 122, 106, 48, 69, 65, 119, 73, 119, 10, 99, 68, 69, 105, 77, 67, 65, 71, 65, 49, 85, 69, 65, 119, 119, 90, 83, 87, 53, 48, 90, 87, 119, 103, 85, 48, 100, 89, 73, 70, 66, 68, 83, 121, 66, 81, 98, 71, 70, 48, 90, 109, 57, 121, 98, 83, 66, 68, 81, 84, 69, 97, 77, 66, 103, 71, 65, 49, 85, 69, 67, 103, 119, 82, 10, 83, 87, 53, 48, 90, 87, 119, 103, 81, 50, 57, 121, 99, 71, 57, 121, 89, 88, 82, 112, 98, 50, 52, 120, 70, 68, 65, 83, 66, 103, 78, 86, 66, 65, 99, 77, 67, 49, 78, 104, 98, 110, 82, 104, 73, 69, 78, 115, 89, 88, 74, 104, 77, 81, 115, 119, 67, 81, 89, 68, 86, 81, 81, 73, 10, 68, 65, 74, 68, 81, 84, 69, 76, 77, 65, 107, 71, 65, 49, 85, 69, 66, 104, 77, 67, 86, 86, 77, 119, 72, 104, 99, 78, 77, 106, 85, 120, 77, 84, 65, 50, 77, 68, 99, 122, 78, 122, 77, 48, 87, 104, 99, 78, 77, 122, 73, 120, 77, 84, 65, 50, 77, 68, 99, 122, 78, 122, 77, 48, 10, 87, 106, 66, 119, 77, 83, 73, 119, 73, 65, 89, 68, 86, 81, 81, 68, 68, 66, 108, 74, 98, 110, 82, 108, 98, 67, 66, 84, 82, 49, 103, 103, 85, 69, 78, 76, 73, 69, 78, 108, 99, 110, 82, 112, 90, 109, 108, 106, 89, 88, 82, 108, 77, 82, 111, 119, 71, 65, 89, 68, 86, 81, 81, 75, 10, 68, 66, 70, 74, 98, 110, 82, 108, 98, 67, 66, 68, 98, 51, 74, 119, 98, 51, 74, 104, 100, 71, 108, 118, 98, 106, 69, 85, 77, 66, 73, 71, 65, 49, 85, 69, 66, 119, 119, 76, 85, 50, 70, 117, 100, 71, 69, 103, 81, 50, 120, 104, 99, 109, 69, 120, 67, 122, 65, 74, 66, 103, 78, 86, 10, 66, 65, 103, 77, 65, 107, 78, 66, 77, 81, 115, 119, 67, 81, 89, 68, 86, 81, 81, 71, 69, 119, 74, 86, 85, 122, 66, 90, 77, 66, 77, 71, 66, 121, 113, 71, 83, 77, 52, 57, 65, 103, 69, 71, 67, 67, 113, 71, 83, 77, 52, 57, 65, 119, 69, 72, 65, 48, 73, 65, 66, 71, 112, 118, 10, 48, 89, 117, 89, 114, 113, 65, 117, 83, 75, 66, 122, 75, 108, 117, 98, 54, 109, 76, 43, 114, 118, 102, 68, 53, 65, 106, 89, 79, 51, 81, 78, 103, 102, 87, 122, 116, 103, 52, 101, 109, 49, 69, 71, 66, 86, 107, 71, 108, 87, 118, 100, 117, 66, 48, 88, 81, 83, 69, 47, 115, 120, 71, 68, 10, 109, 83, 118, 75, 111, 57, 116, 51, 67, 114, 79, 80, 67, 52, 83, 85, 54, 88, 54, 106, 103, 103, 77, 77, 77, 73, 73, 68, 67, 68, 65, 102, 66, 103, 78, 86, 72, 83, 77, 69, 71, 68, 65, 87, 103, 66, 83, 86, 98, 49, 51, 78, 118, 82, 118, 104, 54, 85, 66, 74, 121, 100, 84, 48, 10, 77, 56, 52, 66, 86, 119, 118, 101, 86, 68, 66, 114, 66, 103, 78, 86, 72, 82, 56, 69, 90, 68, 66, 105, 77, 71, 67, 103, 88, 113, 66, 99, 104, 108, 112, 111, 100, 72, 82, 119, 99, 122, 111, 118, 76, 50, 70, 119, 97, 83, 53, 48, 99, 110, 86, 122, 100, 71, 86, 107, 99, 50, 86, 121, 10, 100, 109, 108, 106, 90, 88, 77, 117, 97, 87, 53, 48, 90, 87, 119, 117, 89, 50, 57, 116, 76, 51, 78, 110, 101, 67, 57, 106, 90, 88, 74, 48, 97, 87, 90, 112, 89, 50, 70, 48, 97, 87, 57, 117, 76, 51, 89, 48, 76, 51, 66, 106, 97, 50, 78, 121, 98, 68, 57, 106, 89, 84, 49, 119, 10, 98, 71, 70, 48, 90, 109, 57, 121, 98, 83, 90, 108, 98, 109, 78, 118, 90, 71, 108, 117, 90, 122, 49, 107, 90, 88, 73, 119, 72, 81, 89, 68, 86, 82, 48, 79, 66, 66, 89, 69, 70, 71, 51, 110, 54, 83, 43, 75, 120, 78, 54, 116, 43, 72, 73, 56, 71, 112, 57, 54, 80, 107, 117, 90, 10, 105, 87, 115, 90, 77, 65, 52, 71, 65, 49, 85, 100, 68, 119, 69, 66, 47, 119, 81, 69, 65, 119, 73, 71, 119, 68, 65, 77, 66, 103, 78, 86, 72, 82, 77, 66, 65, 102, 56, 69, 65, 106, 65, 65, 77, 73, 73, 67, 79, 81, 89, 74, 75, 111, 90, 73, 104, 118, 104, 78, 65, 81, 48, 66, 10, 66, 73, 73, 67, 75, 106, 67, 67, 65, 105, 89, 119, 72, 103, 89, 75, 75, 111, 90, 73, 104, 118, 104, 78, 65, 81, 48, 66, 65, 81, 81, 81, 48, 103, 106, 102, 115, 81, 65, 106, 82, 113, 52, 98, 116, 79, 56, 113, 80, 65, 86, 83, 107, 106, 67, 67, 65, 87, 77, 71, 67, 105, 113, 71, 10, 83, 73, 98, 52, 84, 81, 69, 78, 65, 81, 73, 119, 103, 103, 70, 84, 77, 66, 65, 71, 67, 121, 113, 71, 83, 73, 98, 52, 84, 81, 69, 78, 65, 81, 73, 66, 65, 103, 69, 69, 77, 66, 65, 71, 67, 121, 113, 71, 83, 73, 98, 52, 84, 81, 69, 78, 65, 81, 73, 67, 65, 103, 69, 69, 10, 77, 66, 65, 71, 67, 121, 113, 71, 83, 73, 98, 52, 84, 81, 69, 78, 65, 81, 73, 68, 65, 103, 69, 67, 77, 66, 65, 71, 67, 121, 113, 71, 83, 73, 98, 52, 84, 81, 69, 78, 65, 81, 73, 69, 65, 103, 69, 67, 77, 66, 65, 71, 67, 121, 113, 71, 83, 73, 98, 52, 84, 81, 69, 78, 10, 65, 81, 73, 70, 65, 103, 69, 69, 77, 66, 65, 71, 67, 121, 113, 71, 83, 73, 98, 52, 84, 81, 69, 78, 65, 81, 73, 71, 65, 103, 69, 66, 77, 66, 65, 71, 67, 121, 113, 71, 83, 73, 98, 52, 84, 81, 69, 78, 65, 81, 73, 72, 65, 103, 69, 65, 77, 66, 65, 71, 67, 121, 113, 71, 10, 83, 73, 98, 52, 84, 81, 69, 78, 65, 81, 73, 73, 65, 103, 69, 70, 77, 66, 65, 71, 67, 121, 113, 71, 83, 73, 98, 52, 84, 81, 69, 78, 65, 81, 73, 74, 65, 103, 69, 65, 77, 66, 65, 71, 67, 121, 113, 71, 83, 73, 98, 52, 84, 81, 69, 78, 65, 81, 73, 75, 65, 103, 69, 65, 10, 77, 66, 65, 71, 67, 121, 113, 71, 83, 73, 98, 52, 84, 81, 69, 78, 65, 81, 73, 76, 65, 103, 69, 65, 77, 66, 65, 71, 67, 121, 113, 71, 83, 73, 98, 52, 84, 81, 69, 78, 65, 81, 73, 77, 65, 103, 69, 65, 77, 66, 65, 71, 67, 121, 113, 71, 83, 73, 98, 52, 84, 81, 69, 78, 10, 65, 81, 73, 78, 65, 103, 69, 65, 77, 66, 65, 71, 67, 121, 113, 71, 83, 73, 98, 52, 84, 81, 69, 78, 65, 81, 73, 79, 65, 103, 69, 65, 77, 66, 65, 71, 67, 121, 113, 71, 83, 73, 98, 52, 84, 81, 69, 78, 65, 81, 73, 80, 65, 103, 69, 65, 77, 66, 65, 71, 67, 121, 113, 71, 10, 83, 73, 98, 52, 84, 81, 69, 78, 65, 81, 73, 81, 65, 103, 69, 65, 77, 66, 65, 71, 67, 121, 113, 71, 83, 73, 98, 52, 84, 81, 69, 78, 65, 81, 73, 82, 65, 103, 69, 76, 77, 66, 56, 71, 67, 121, 113, 71, 83, 73, 98, 52, 84, 81, 69, 78, 65, 81, 73, 83, 66, 66, 65, 69, 10, 66, 65, 73, 67, 66, 65, 69, 65, 66, 81, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 77, 66, 65, 71, 67, 105, 113, 71, 83, 73, 98, 52, 84, 81, 69, 78, 65, 81, 77, 69, 65, 103, 65, 65, 77, 66, 81, 71, 67, 105, 113, 71, 83, 73, 98, 52, 84, 81, 69, 78, 65, 81, 81, 69, 10, 66, 114, 68, 65, 98, 119, 65, 65, 65, 68, 65, 80, 66, 103, 111, 113, 104, 107, 105, 71, 43, 69, 48, 66, 68, 81, 69, 70, 67, 103, 69, 66, 77, 66, 52, 71, 67, 105, 113, 71, 83, 73, 98, 52, 84, 81, 69, 78, 65, 81, 89, 69, 69, 68, 97, 57, 104, 116, 65, 56, 65, 74, 47, 90, 10, 50, 70, 109, 97, 76, 53, 74, 113, 47, 75, 69, 119, 82, 65, 89, 75, 75, 111, 90, 73, 104, 118, 104, 78, 65, 81, 48, 66, 66, 122, 65, 50, 77, 66, 65, 71, 67, 121, 113, 71, 83, 73, 98, 52, 84, 81, 69, 78, 65, 81, 99, 66, 65, 81, 72, 47, 77, 66, 65, 71, 67, 121, 113, 71, 10, 83, 73, 98, 52, 84, 81, 69, 78, 65, 81, 99, 67, 65, 81, 72, 47, 77, 66, 65, 71, 67, 121, 113, 71, 83, 73, 98, 52, 84, 81, 69, 78, 65, 81, 99, 68, 65, 81, 72, 47, 77, 65, 111, 71, 67, 67, 113, 71, 83, 77, 52, 57, 66, 65, 77, 67, 65, 48, 107, 65, 77, 69, 89, 67, 10, 73, 81, 67, 70, 71, 49, 89, 65, 98, 51, 101, 88, 70, 116, 101, 56, 53, 51, 67, 108, 86, 66, 110, 104, 108, 67, 102, 68, 121, 99, 53, 55, 50, 90, 88, 69, 113, 97, 120, 52, 85, 99, 99, 83, 97, 119, 73, 104, 65, 79, 110, 48, 86, 78, 75, 84, 90, 109, 65, 120, 85, 70, 52, 110, 10, 119, 82, 107, 83, 70, 104, 52, 113, 70, 74, 51, 97, 85, 108, 122, 70, 111, 80, 81, 84, 51, 120, 73, 102, 55, 107, 70, 68, 10, 45, 45, 45, 45, 45, 69, 78, 68, 32, 67, 69, 82, 84, 73, 70, 73, 67, 65, 84, 69, 45, 45, 45, 45, 45, 10, 45, 45, 45, 45, 45, 66, 69, 71, 73, 78, 32, 67, 69, 82, 84, 73, 70, 73, 67, 65, 84, 69, 45, 45, 45, 45, 45, 10, 77, 73, 73, 67, 108, 106, 67, 67, 65, 106, 50, 103, 65, 119, 73, 66, 65, 103, 73, 86, 65, 74, 86, 118, 88, 99, 50, 57, 71, 43, 72, 112, 81, 69, 110, 74, 49, 80, 81, 122, 122, 103, 70, 88, 67, 57, 53, 85, 77, 65, 111, 71, 67, 67, 113, 71, 83, 77, 52, 57, 66, 65, 77, 67, 10, 77, 71, 103, 120, 71, 106, 65, 89, 66, 103, 78, 86, 66, 65, 77, 77, 69, 85, 108, 117, 100, 71, 86, 115, 73, 70, 78, 72, 87, 67, 66, 83, 98, 50, 57, 48, 73, 69, 78, 66, 77, 82, 111, 119, 71, 65, 89, 68, 86, 81, 81, 75, 68, 66, 70, 74, 98, 110, 82, 108, 98, 67, 66, 68, 10, 98, 51, 74, 119, 98, 51, 74, 104, 100, 71, 108, 118, 98, 106, 69, 85, 77, 66, 73, 71, 65, 49, 85, 69, 66, 119, 119, 76, 85, 50, 70, 117, 100, 71, 69, 103, 81, 50, 120, 104, 99, 109, 69, 120, 67, 122, 65, 74, 66, 103, 78, 86, 66, 65, 103, 77, 65, 107, 78, 66, 77, 81, 115, 119, 10, 67, 81, 89, 68, 86, 81, 81, 71, 69, 119, 74, 86, 85, 122, 65, 101, 70, 119, 48, 120, 79, 68, 65, 49, 77, 106, 69, 120, 77, 68, 85, 119, 77, 84, 66, 97, 70, 119, 48, 122, 77, 122, 65, 49, 77, 106, 69, 120, 77, 68, 85, 119, 77, 84, 66, 97, 77, 72, 65, 120, 73, 106, 65, 103, 10, 66, 103, 78, 86, 66, 65, 77, 77, 71, 85, 108, 117, 100, 71, 86, 115, 73, 70, 78, 72, 87, 67, 66, 81, 81, 48, 115, 103, 85, 71, 120, 104, 100, 71, 90, 118, 99, 109, 48, 103, 81, 48, 69, 120, 71, 106, 65, 89, 66, 103, 78, 86, 66, 65, 111, 77, 69, 85, 108, 117, 100, 71, 86, 115, 10, 73, 69, 78, 118, 99, 110, 66, 118, 99, 109, 70, 48, 97, 87, 57, 117, 77, 82, 81, 119, 69, 103, 89, 68, 86, 81, 81, 72, 68, 65, 116, 84, 89, 87, 53, 48, 89, 83, 66, 68, 98, 71, 70, 121, 89, 84, 69, 76, 77, 65, 107, 71, 65, 49, 85, 69, 67, 65, 119, 67, 81, 48, 69, 120, 10, 67, 122, 65, 74, 66, 103, 78, 86, 66, 65, 89, 84, 65, 108, 86, 84, 77, 70, 107, 119, 69, 119, 89, 72, 75, 111, 90, 73, 122, 106, 48, 67, 65, 81, 89, 73, 75, 111, 90, 73, 122, 106, 48, 68, 65, 81, 99, 68, 81, 103, 65, 69, 78, 83, 66, 47, 55, 116, 50, 49, 108, 88, 83, 79, 10, 50, 67, 117, 122, 112, 120, 119, 55, 52, 101, 74, 66, 55, 50, 69, 121, 68, 71, 103, 87, 53, 114, 88, 67, 116, 120, 50, 116, 86, 84, 76, 113, 54, 104, 75, 107, 54, 122, 43, 85, 105, 82, 90, 67, 110, 113, 82, 55, 112, 115, 79, 118, 103, 113, 70, 101, 83, 120, 108, 109, 84, 108, 74, 108, 10, 101, 84, 109, 105, 50, 87, 89, 122, 51, 113, 79, 66, 117, 122, 67, 66, 117, 68, 65, 102, 66, 103, 78, 86, 72, 83, 77, 69, 71, 68, 65, 87, 103, 66, 81, 105, 90, 81, 122, 87, 87, 112, 48, 48, 105, 102, 79, 68, 116, 74, 86, 83, 118, 49, 65, 98, 79, 83, 99, 71, 114, 68, 66, 83, 10, 66, 103, 78, 86, 72, 82, 56, 69, 83, 122, 66, 74, 77, 69, 101, 103, 82, 97, 66, 68, 104, 107, 70, 111, 100, 72, 82, 119, 99, 122, 111, 118, 76, 50, 78, 108, 99, 110, 82, 112, 90, 109, 108, 106, 89, 88, 82, 108, 99, 121, 53, 48, 99, 110, 86, 122, 100, 71, 86, 107, 99, 50, 86, 121, 10, 100, 109, 108, 106, 90, 88, 77, 117, 97, 87, 53, 48, 90, 87, 119, 117, 89, 50, 57, 116, 76, 48, 108, 117, 100, 71, 86, 115, 85, 48, 100, 89, 85, 109, 57, 118, 100, 69, 78, 66, 76, 109, 82, 108, 99, 106, 65, 100, 66, 103, 78, 86, 72, 81, 52, 69, 70, 103, 81, 85, 108, 87, 57, 100, 10, 122, 98, 48, 98, 52, 101, 108, 65, 83, 99, 110, 85, 57, 68, 80, 79, 65, 86, 99, 76, 51, 108, 81, 119, 68, 103, 89, 68, 86, 82, 48, 80, 65, 81, 72, 47, 66, 65, 81, 68, 65, 103, 69, 71, 77, 66, 73, 71, 65, 49, 85, 100, 69, 119, 69, 66, 47, 119, 81, 73, 77, 65, 89, 66, 10, 65, 102, 56, 67, 65, 81, 65, 119, 67, 103, 89, 73, 75, 111, 90, 73, 122, 106, 48, 69, 65, 119, 73, 68, 82, 119, 65, 119, 82, 65, 73, 103, 88, 115, 86, 107, 105, 48, 119, 43, 105, 54, 86, 89, 71, 87, 51, 85, 70, 47, 50, 50, 117, 97, 88, 101, 48, 89, 74, 68, 106, 49, 85, 101, 10, 110, 65, 43, 84, 106, 68, 49, 97, 105, 53, 99, 67, 73, 67, 89, 98, 49, 83, 65, 109, 68, 53, 120, 107, 102, 84, 86, 112, 118, 111, 52, 85, 111, 121, 105, 83, 89, 120, 114, 68, 87, 76, 109, 85, 82, 52, 67, 73, 57, 78, 75, 121, 102, 80, 78, 43, 10, 45, 45, 45, 45, 45, 69, 78, 68, 32, 67, 69, 82, 84, 73, 70, 73, 67, 65, 84, 69, 45, 45, 45, 45, 45, 10, 45, 45, 45, 45, 45, 66, 69, 71, 73, 78, 32, 67, 69, 82, 84, 73, 70, 73, 67, 65, 84, 69, 45, 45, 45, 45, 45, 10, 77, 73, 73, 67, 106, 122, 67, 67, 65, 106, 83, 103, 65, 119, 73, 66, 65, 103, 73, 85, 73, 109, 85, 77, 49, 108, 113, 100, 78, 73, 110, 122, 103, 55, 83, 86, 85, 114, 57, 81, 71, 122, 107, 110, 66, 113, 119, 119, 67, 103, 89, 73, 75, 111, 90, 73, 122, 106, 48, 69, 65, 119, 73, 119, 10, 97, 68, 69, 97, 77, 66, 103, 71, 65, 49, 85, 69, 65, 119, 119, 82, 83, 87, 53, 48, 90, 87, 119, 103, 85, 48, 100, 89, 73, 70, 74, 118, 98, 51, 81, 103, 81, 48, 69, 120, 71, 106, 65, 89, 66, 103, 78, 86, 66, 65, 111, 77, 69, 85, 108, 117, 100, 71, 86, 115, 73, 69, 78, 118, 10, 99, 110, 66, 118, 99, 109, 70, 48, 97, 87, 57, 117, 77, 82, 81, 119, 69, 103, 89, 68, 86, 81, 81, 72, 68, 65, 116, 84, 89, 87, 53, 48, 89, 83, 66, 68, 98, 71, 70, 121, 89, 84, 69, 76, 77, 65, 107, 71, 65, 49, 85, 69, 67, 65, 119, 67, 81, 48, 69, 120, 67, 122, 65, 74, 10, 66, 103, 78, 86, 66, 65, 89, 84, 65, 108, 86, 84, 77, 66, 52, 88, 68, 84, 69, 52, 77, 68, 85, 121, 77, 84, 69, 119, 78, 68, 85, 120, 77, 70, 111, 88, 68, 84, 81, 53, 77, 84, 73, 122, 77, 84, 73, 122, 78, 84, 107, 49, 79, 86, 111, 119, 97, 68, 69, 97, 77, 66, 103, 71, 10, 65, 49, 85, 69, 65, 119, 119, 82, 83, 87, 53, 48, 90, 87, 119, 103, 85, 48, 100, 89, 73, 70, 74, 118, 98, 51, 81, 103, 81, 48, 69, 120, 71, 106, 65, 89, 66, 103, 78, 86, 66, 65, 111, 77, 69, 85, 108, 117, 100, 71, 86, 115, 73, 69, 78, 118, 99, 110, 66, 118, 99, 109, 70, 48, 10, 97, 87, 57, 117, 77, 82, 81, 119, 69, 103, 89, 68, 86, 81, 81, 72, 68, 65, 116, 84, 89, 87, 53, 48, 89, 83, 66, 68, 98, 71, 70, 121, 89, 84, 69, 76, 77, 65, 107, 71, 65, 49, 85, 69, 67, 65, 119, 67, 81, 48, 69, 120, 67, 122, 65, 74, 66, 103, 78, 86, 66, 65, 89, 84, 10, 65, 108, 86, 84, 77, 70, 107, 119, 69, 119, 89, 72, 75, 111, 90, 73, 122, 106, 48, 67, 65, 81, 89, 73, 75, 111, 90, 73, 122, 106, 48, 68, 65, 81, 99, 68, 81, 103, 65, 69, 67, 54, 110, 69, 119, 77, 68, 73, 89, 90, 79, 106, 47, 105, 80, 87, 115, 67, 122, 97, 69, 75, 105, 55, 10, 49, 79, 105, 79, 83, 76, 82, 70, 104, 87, 71, 106, 98, 110, 66, 86, 74, 102, 86, 110, 107, 89, 52, 117, 51, 73, 106, 107, 68, 89, 89, 76, 48, 77, 120, 79, 52, 109, 113, 115, 121, 89, 106, 108, 66, 97, 108, 84, 86, 89, 120, 70, 80, 50, 115, 74, 66, 75, 53, 122, 108, 75, 79, 66, 10, 117, 122, 67, 66, 117, 68, 65, 102, 66, 103, 78, 86, 72, 83, 77, 69, 71, 68, 65, 87, 103, 66, 81, 105, 90, 81, 122, 87, 87, 112, 48, 48, 105, 102, 79, 68, 116, 74, 86, 83, 118, 49, 65, 98, 79, 83, 99, 71, 114, 68, 66, 83, 66, 103, 78, 86, 72, 82, 56, 69, 83, 122, 66, 74, 10, 77, 69, 101, 103, 82, 97, 66, 68, 104, 107, 70, 111, 100, 72, 82, 119, 99, 122, 111, 118, 76, 50, 78, 108, 99, 110, 82, 112, 90, 109, 108, 106, 89, 88, 82, 108, 99, 121, 53, 48, 99, 110, 86, 122, 100, 71, 86, 107, 99, 50, 86, 121, 100, 109, 108, 106, 90, 88, 77, 117, 97, 87, 53, 48, 10, 90, 87, 119, 117, 89, 50, 57, 116, 76, 48, 108, 117, 100, 71, 86, 115, 85, 48, 100, 89, 85, 109, 57, 118, 100, 69, 78, 66, 76, 109, 82, 108, 99, 106, 65, 100, 66, 103, 78, 86, 72, 81, 52, 69, 70, 103, 81, 85, 73, 109, 85, 77, 49, 108, 113, 100, 78, 73, 110, 122, 103, 55, 83, 86, 10, 85, 114, 57, 81, 71, 122, 107, 110, 66, 113, 119, 119, 68, 103, 89, 68, 86, 82, 48, 80, 65, 81, 72, 47, 66, 65, 81, 68, 65, 103, 69, 71, 77, 66, 73, 71, 65, 49, 85, 100, 69, 119, 69, 66, 47, 119, 81, 73, 77, 65, 89, 66, 65, 102, 56, 67, 65, 81, 69, 119, 67, 103, 89, 73, 10, 75, 111, 90, 73, 122, 106, 48, 69, 65, 119, 73, 68, 83, 81, 65, 119, 82, 103, 73, 104, 65, 79, 87, 47, 53, 81, 107, 82, 43, 83, 57, 67, 105, 83, 68, 99, 78, 111, 111, 119, 76, 117, 80, 82, 76, 115, 87, 71, 102, 47, 89, 105, 55, 71, 83, 88, 57, 52, 66, 103, 119, 84, 119, 103, 10, 65, 105, 69, 65, 52, 74, 48, 108, 114, 72, 111, 77, 115, 43, 88, 111, 53, 111, 47, 115, 88, 54, 79, 57, 81, 87, 120, 72, 82, 65, 118, 90, 85, 71, 79, 100, 82, 81, 55, 99, 118, 113, 82, 88, 97, 113, 73, 61, 10, 45, 45, 45, 45, 45, 69, 78, 68, 32, 67, 69, 82, 84, 73, 70, 73, 67, 65, 84, 69, 45, 45, 45, 45, 45, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + "quote": [4, 0, 2, 0, 129, 0, 0, 0, 0, 0, 0, 0, 147, 154, 114, 51, 247, 156, 76, 169, 148, 10, 13, 179, 149, 127, 6, 7, 61, 153, 138, 108, 16, 87, 107, 253, 246, 246, 237, 142, 155, 133, 233, 50, 0, 0, 0, 0, 11, 1, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 123, 240, 99, 40, 14, 148, 251, 5, 31, 93, 215, 177, 252, 89, 206, 154, 172, 66, 187, 150, 29, 248, 212, 75, 112, 156, 155, 15, 248, 122, 123, 77, 246, 72, 101, 123, 166, 209, 24, 149, 137, 254, 171, 29, 90, 60, 154, 157, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 231, 2, 6, 0, 0, 0, 0, 0, 240, 109, 253, 166, 220, 225, 207, 144, 77, 78, 43, 171, 29, 195, 112, 99, 76, 249, 92, 239, 162, 206, 178, 222, 46, 238, 18, 124, 147, 130, 105, 128, 144, 215, 164, 161, 62, 20, 197, 54, 236, 108, 156, 60, 143, 168, 112, 119, 1, 108, 96, 55, 220, 151, 118, 122, 15, 55, 244, 242, 21, 33, 161, 194, 138, 34, 146, 47, 245, 164, 71, 137, 103, 148, 138, 148, 182, 199, 167, 181, 145, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 230, 115, 190, 47, 112, 190, 239, 183, 11, 72, 166, 16, 158, 237, 71, 21, 215, 39, 13, 70, 131, 179, 191, 53, 111, 162, 95, 175, 191, 26, 167, 110, 57, 233, 18, 126, 110, 104, 140, 205, 169, 139, 218, 177, 212, 212, 127, 70, 181, 152, 253, 233, 73, 20, 39, 52, 27, 196, 104, 59, 117, 209, 13, 62, 54, 119, 10, 243, 163, 106, 105, 84, 216, 182, 183, 178, 42, 166, 99, 88, 241, 62, 31, 23, 46, 81, 183, 214, 230, 113, 13, 153, 168, 216, 83, 47, 200, 18, 212, 43, 255, 241, 199, 83, 130, 233, 26, 55, 200, 103, 171, 17, 123, 151, 235, 94, 141, 103, 151, 72, 137, 40, 234, 56, 229, 253, 56, 181, 237, 47, 135, 217, 97, 61, 57, 37, 7, 241, 195, 175, 148, 101, 124, 147, 191, 209, 52, 91, 201, 236, 239, 128, 243, 128, 246, 214, 25, 235, 82, 137, 50, 144, 49, 36, 39, 35, 112, 129, 185, 118, 84, 112, 16, 156, 89, 125, 255, 201, 141, 69, 120, 88, 143, 186, 178, 16, 242, 201, 116, 19, 193, 103, 0, 1, 225, 54, 47, 82, 46, 167, 98, 107, 53, 88, 77, 20, 225, 20, 20, 11, 241, 205, 0, 151, 168, 2, 212, 236, 75, 111, 229, 21, 92, 99, 130, 22, 179, 191, 23, 141, 101, 150, 56, 32, 138, 209, 156, 158, 191, 75, 33, 230, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 204, 16, 0, 0, 220, 8, 78, 19, 149, 230, 23, 145, 248, 199, 226, 120, 77, 133, 123, 218, 76, 10, 193, 178, 70, 101, 24, 218, 144, 32, 68, 87, 142, 241, 105, 5, 57, 43, 51, 250, 76, 82, 76, 59, 191, 172, 71, 74, 127, 89, 234, 221, 234, 87, 227, 180, 54, 227, 172, 13, 41, 88, 142, 114, 21, 157, 72, 199, 142, 188, 151, 224, 189, 139, 21, 116, 87, 28, 157, 151, 225, 226, 250, 218, 147, 80, 231, 144, 252, 223, 62, 103, 176, 31, 51, 101, 181, 44, 82, 180, 72, 148, 151, 10, 88, 144, 81, 87, 230, 135, 174, 102, 165, 143, 241, 229, 60, 148, 151, 208, 187, 151, 100, 64, 82, 171, 52, 1, 40, 143, 31, 160, 6, 0, 70, 16, 0, 0, 4, 4, 25, 27, 4, 255, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 21, 0, 0, 0, 0, 0, 0, 0, 231, 0, 0, 0, 0, 0, 0, 0, 229, 163, 167, 181, 216, 48, 194, 149, 59, 152, 83, 76, 108, 89, 163, 163, 79, 220, 52, 233, 51, 247, 245, 137, 143, 10, 133, 207, 8, 132, 107, 202, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 220, 158, 42, 124, 111, 148, 143, 23, 71, 78, 52, 167, 252, 67, 237, 3, 15, 124, 21, 99, 241, 186, 189, 223, 99, 64, 200, 46, 14, 84, 168, 197, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 215, 98, 66, 167, 203, 62, 30, 139, 2, 195, 192, 57, 51, 40, 206, 207, 77, 159, 207, 61, 144, 77, 52, 130, 175, 199, 184, 139, 101, 54, 6, 152, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 1, 121, 119, 53, 12, 242, 234, 97, 244, 139, 210, 240, 161, 221, 87, 50, 53, 22, 62, 187, 93, 234, 174, 243, 37, 140, 227, 92, 65, 153, 44, 120, 88, 139, 241, 175, 11, 63, 135, 233, 6, 77, 16, 70, 191, 18, 226, 109, 1, 37, 18, 132, 34, 76, 170, 41, 121, 69, 128, 18, 239, 72, 153, 32, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 5, 0, 94, 14, 0, 0, 45, 45, 45, 45, 45, 66, 69, 71, 73, 78, 32, 67, 69, 82, 84, 73, 70, 73, 67, 65, 84, 69, 45, 45, 45, 45, 45, 10, 77, 73, 73, 69, 56, 84, 67, 67, 66, 74, 97, 103, 65, 119, 73, 66, 65, 103, 73, 85, 102, 50, 83, 98, 121, 119, 107, 77, 86, 84, 74, 75, 85, 53, 55, 47, 74, 119, 66, 112, 56, 69, 100, 104, 80, 48, 52, 119, 67, 103, 89, 73, 75, 111, 90, 73, 122, 106, 48, 69, 65, 119, 73, 119, 10, 99, 68, 69, 105, 77, 67, 65, 71, 65, 49, 85, 69, 65, 119, 119, 90, 83, 87, 53, 48, 90, 87, 119, 103, 85, 48, 100, 89, 73, 70, 66, 68, 83, 121, 66, 81, 98, 71, 70, 48, 90, 109, 57, 121, 98, 83, 66, 68, 81, 84, 69, 97, 77, 66, 103, 71, 65, 49, 85, 69, 67, 103, 119, 82, 10, 83, 87, 53, 48, 90, 87, 119, 103, 81, 50, 57, 121, 99, 71, 57, 121, 89, 88, 82, 112, 98, 50, 52, 120, 70, 68, 65, 83, 66, 103, 78, 86, 66, 65, 99, 77, 67, 49, 78, 104, 98, 110, 82, 104, 73, 69, 78, 115, 89, 88, 74, 104, 77, 81, 115, 119, 67, 81, 89, 68, 86, 81, 81, 73, 10, 68, 65, 74, 68, 81, 84, 69, 76, 77, 65, 107, 71, 65, 49, 85, 69, 66, 104, 77, 67, 86, 86, 77, 119, 72, 104, 99, 78, 77, 106, 85, 120, 77, 84, 65, 50, 77, 68, 99, 122, 78, 122, 77, 48, 87, 104, 99, 78, 77, 122, 73, 120, 77, 84, 65, 50, 77, 68, 99, 122, 78, 122, 77, 48, 10, 87, 106, 66, 119, 77, 83, 73, 119, 73, 65, 89, 68, 86, 81, 81, 68, 68, 66, 108, 74, 98, 110, 82, 108, 98, 67, 66, 84, 82, 49, 103, 103, 85, 69, 78, 76, 73, 69, 78, 108, 99, 110, 82, 112, 90, 109, 108, 106, 89, 88, 82, 108, 77, 82, 111, 119, 71, 65, 89, 68, 86, 81, 81, 75, 10, 68, 66, 70, 74, 98, 110, 82, 108, 98, 67, 66, 68, 98, 51, 74, 119, 98, 51, 74, 104, 100, 71, 108, 118, 98, 106, 69, 85, 77, 66, 73, 71, 65, 49, 85, 69, 66, 119, 119, 76, 85, 50, 70, 117, 100, 71, 69, 103, 81, 50, 120, 104, 99, 109, 69, 120, 67, 122, 65, 74, 66, 103, 78, 86, 10, 66, 65, 103, 77, 65, 107, 78, 66, 77, 81, 115, 119, 67, 81, 89, 68, 86, 81, 81, 71, 69, 119, 74, 86, 85, 122, 66, 90, 77, 66, 77, 71, 66, 121, 113, 71, 83, 77, 52, 57, 65, 103, 69, 71, 67, 67, 113, 71, 83, 77, 52, 57, 65, 119, 69, 72, 65, 48, 73, 65, 66, 71, 112, 118, 10, 48, 89, 117, 89, 114, 113, 65, 117, 83, 75, 66, 122, 75, 108, 117, 98, 54, 109, 76, 43, 114, 118, 102, 68, 53, 65, 106, 89, 79, 51, 81, 78, 103, 102, 87, 122, 116, 103, 52, 101, 109, 49, 69, 71, 66, 86, 107, 71, 108, 87, 118, 100, 117, 66, 48, 88, 81, 83, 69, 47, 115, 120, 71, 68, 10, 109, 83, 118, 75, 111, 57, 116, 51, 67, 114, 79, 80, 67, 52, 83, 85, 54, 88, 54, 106, 103, 103, 77, 77, 77, 73, 73, 68, 67, 68, 65, 102, 66, 103, 78, 86, 72, 83, 77, 69, 71, 68, 65, 87, 103, 66, 83, 86, 98, 49, 51, 78, 118, 82, 118, 104, 54, 85, 66, 74, 121, 100, 84, 48, 10, 77, 56, 52, 66, 86, 119, 118, 101, 86, 68, 66, 114, 66, 103, 78, 86, 72, 82, 56, 69, 90, 68, 66, 105, 77, 71, 67, 103, 88, 113, 66, 99, 104, 108, 112, 111, 100, 72, 82, 119, 99, 122, 111, 118, 76, 50, 70, 119, 97, 83, 53, 48, 99, 110, 86, 122, 100, 71, 86, 107, 99, 50, 86, 121, 10, 100, 109, 108, 106, 90, 88, 77, 117, 97, 87, 53, 48, 90, 87, 119, 117, 89, 50, 57, 116, 76, 51, 78, 110, 101, 67, 57, 106, 90, 88, 74, 48, 97, 87, 90, 112, 89, 50, 70, 48, 97, 87, 57, 117, 76, 51, 89, 48, 76, 51, 66, 106, 97, 50, 78, 121, 98, 68, 57, 106, 89, 84, 49, 119, 10, 98, 71, 70, 48, 90, 109, 57, 121, 98, 83, 90, 108, 98, 109, 78, 118, 90, 71, 108, 117, 90, 122, 49, 107, 90, 88, 73, 119, 72, 81, 89, 68, 86, 82, 48, 79, 66, 66, 89, 69, 70, 71, 51, 110, 54, 83, 43, 75, 120, 78, 54, 116, 43, 72, 73, 56, 71, 112, 57, 54, 80, 107, 117, 90, 10, 105, 87, 115, 90, 77, 65, 52, 71, 65, 49, 85, 100, 68, 119, 69, 66, 47, 119, 81, 69, 65, 119, 73, 71, 119, 68, 65, 77, 66, 103, 78, 86, 72, 82, 77, 66, 65, 102, 56, 69, 65, 106, 65, 65, 77, 73, 73, 67, 79, 81, 89, 74, 75, 111, 90, 73, 104, 118, 104, 78, 65, 81, 48, 66, 10, 66, 73, 73, 67, 75, 106, 67, 67, 65, 105, 89, 119, 72, 103, 89, 75, 75, 111, 90, 73, 104, 118, 104, 78, 65, 81, 48, 66, 65, 81, 81, 81, 48, 103, 106, 102, 115, 81, 65, 106, 82, 113, 52, 98, 116, 79, 56, 113, 80, 65, 86, 83, 107, 106, 67, 67, 65, 87, 77, 71, 67, 105, 113, 71, 10, 83, 73, 98, 52, 84, 81, 69, 78, 65, 81, 73, 119, 103, 103, 70, 84, 77, 66, 65, 71, 67, 121, 113, 71, 83, 73, 98, 52, 84, 81, 69, 78, 65, 81, 73, 66, 65, 103, 69, 69, 77, 66, 65, 71, 67, 121, 113, 71, 83, 73, 98, 52, 84, 81, 69, 78, 65, 81, 73, 67, 65, 103, 69, 69, 10, 77, 66, 65, 71, 67, 121, 113, 71, 83, 73, 98, 52, 84, 81, 69, 78, 65, 81, 73, 68, 65, 103, 69, 67, 77, 66, 65, 71, 67, 121, 113, 71, 83, 73, 98, 52, 84, 81, 69, 78, 65, 81, 73, 69, 65, 103, 69, 67, 77, 66, 65, 71, 67, 121, 113, 71, 83, 73, 98, 52, 84, 81, 69, 78, 10, 65, 81, 73, 70, 65, 103, 69, 69, 77, 66, 65, 71, 67, 121, 113, 71, 83, 73, 98, 52, 84, 81, 69, 78, 65, 81, 73, 71, 65, 103, 69, 66, 77, 66, 65, 71, 67, 121, 113, 71, 83, 73, 98, 52, 84, 81, 69, 78, 65, 81, 73, 72, 65, 103, 69, 65, 77, 66, 65, 71, 67, 121, 113, 71, 10, 83, 73, 98, 52, 84, 81, 69, 78, 65, 81, 73, 73, 65, 103, 69, 70, 77, 66, 65, 71, 67, 121, 113, 71, 83, 73, 98, 52, 84, 81, 69, 78, 65, 81, 73, 74, 65, 103, 69, 65, 77, 66, 65, 71, 67, 121, 113, 71, 83, 73, 98, 52, 84, 81, 69, 78, 65, 81, 73, 75, 65, 103, 69, 65, 10, 77, 66, 65, 71, 67, 121, 113, 71, 83, 73, 98, 52, 84, 81, 69, 78, 65, 81, 73, 76, 65, 103, 69, 65, 77, 66, 65, 71, 67, 121, 113, 71, 83, 73, 98, 52, 84, 81, 69, 78, 65, 81, 73, 77, 65, 103, 69, 65, 77, 66, 65, 71, 67, 121, 113, 71, 83, 73, 98, 52, 84, 81, 69, 78, 10, 65, 81, 73, 78, 65, 103, 69, 65, 77, 66, 65, 71, 67, 121, 113, 71, 83, 73, 98, 52, 84, 81, 69, 78, 65, 81, 73, 79, 65, 103, 69, 65, 77, 66, 65, 71, 67, 121, 113, 71, 83, 73, 98, 52, 84, 81, 69, 78, 65, 81, 73, 80, 65, 103, 69, 65, 77, 66, 65, 71, 67, 121, 113, 71, 10, 83, 73, 98, 52, 84, 81, 69, 78, 65, 81, 73, 81, 65, 103, 69, 65, 77, 66, 65, 71, 67, 121, 113, 71, 83, 73, 98, 52, 84, 81, 69, 78, 65, 81, 73, 82, 65, 103, 69, 76, 77, 66, 56, 71, 67, 121, 113, 71, 83, 73, 98, 52, 84, 81, 69, 78, 65, 81, 73, 83, 66, 66, 65, 69, 10, 66, 65, 73, 67, 66, 65, 69, 65, 66, 81, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 77, 66, 65, 71, 67, 105, 113, 71, 83, 73, 98, 52, 84, 81, 69, 78, 65, 81, 77, 69, 65, 103, 65, 65, 77, 66, 81, 71, 67, 105, 113, 71, 83, 73, 98, 52, 84, 81, 69, 78, 65, 81, 81, 69, 10, 66, 114, 68, 65, 98, 119, 65, 65, 65, 68, 65, 80, 66, 103, 111, 113, 104, 107, 105, 71, 43, 69, 48, 66, 68, 81, 69, 70, 67, 103, 69, 66, 77, 66, 52, 71, 67, 105, 113, 71, 83, 73, 98, 52, 84, 81, 69, 78, 65, 81, 89, 69, 69, 68, 97, 57, 104, 116, 65, 56, 65, 74, 47, 90, 10, 50, 70, 109, 97, 76, 53, 74, 113, 47, 75, 69, 119, 82, 65, 89, 75, 75, 111, 90, 73, 104, 118, 104, 78, 65, 81, 48, 66, 66, 122, 65, 50, 77, 66, 65, 71, 67, 121, 113, 71, 83, 73, 98, 52, 84, 81, 69, 78, 65, 81, 99, 66, 65, 81, 72, 47, 77, 66, 65, 71, 67, 121, 113, 71, 10, 83, 73, 98, 52, 84, 81, 69, 78, 65, 81, 99, 67, 65, 81, 72, 47, 77, 66, 65, 71, 67, 121, 113, 71, 83, 73, 98, 52, 84, 81, 69, 78, 65, 81, 99, 68, 65, 81, 72, 47, 77, 65, 111, 71, 67, 67, 113, 71, 83, 77, 52, 57, 66, 65, 77, 67, 65, 48, 107, 65, 77, 69, 89, 67, 10, 73, 81, 67, 70, 71, 49, 89, 65, 98, 51, 101, 88, 70, 116, 101, 56, 53, 51, 67, 108, 86, 66, 110, 104, 108, 67, 102, 68, 121, 99, 53, 55, 50, 90, 88, 69, 113, 97, 120, 52, 85, 99, 99, 83, 97, 119, 73, 104, 65, 79, 110, 48, 86, 78, 75, 84, 90, 109, 65, 120, 85, 70, 52, 110, 10, 119, 82, 107, 83, 70, 104, 52, 113, 70, 74, 51, 97, 85, 108, 122, 70, 111, 80, 81, 84, 51, 120, 73, 102, 55, 107, 70, 68, 10, 45, 45, 45, 45, 45, 69, 78, 68, 32, 67, 69, 82, 84, 73, 70, 73, 67, 65, 84, 69, 45, 45, 45, 45, 45, 10, 45, 45, 45, 45, 45, 66, 69, 71, 73, 78, 32, 67, 69, 82, 84, 73, 70, 73, 67, 65, 84, 69, 45, 45, 45, 45, 45, 10, 77, 73, 73, 67, 108, 106, 67, 67, 65, 106, 50, 103, 65, 119, 73, 66, 65, 103, 73, 86, 65, 74, 86, 118, 88, 99, 50, 57, 71, 43, 72, 112, 81, 69, 110, 74, 49, 80, 81, 122, 122, 103, 70, 88, 67, 57, 53, 85, 77, 65, 111, 71, 67, 67, 113, 71, 83, 77, 52, 57, 66, 65, 77, 67, 10, 77, 71, 103, 120, 71, 106, 65, 89, 66, 103, 78, 86, 66, 65, 77, 77, 69, 85, 108, 117, 100, 71, 86, 115, 73, 70, 78, 72, 87, 67, 66, 83, 98, 50, 57, 48, 73, 69, 78, 66, 77, 82, 111, 119, 71, 65, 89, 68, 86, 81, 81, 75, 68, 66, 70, 74, 98, 110, 82, 108, 98, 67, 66, 68, 10, 98, 51, 74, 119, 98, 51, 74, 104, 100, 71, 108, 118, 98, 106, 69, 85, 77, 66, 73, 71, 65, 49, 85, 69, 66, 119, 119, 76, 85, 50, 70, 117, 100, 71, 69, 103, 81, 50, 120, 104, 99, 109, 69, 120, 67, 122, 65, 74, 66, 103, 78, 86, 66, 65, 103, 77, 65, 107, 78, 66, 77, 81, 115, 119, 10, 67, 81, 89, 68, 86, 81, 81, 71, 69, 119, 74, 86, 85, 122, 65, 101, 70, 119, 48, 120, 79, 68, 65, 49, 77, 106, 69, 120, 77, 68, 85, 119, 77, 84, 66, 97, 70, 119, 48, 122, 77, 122, 65, 49, 77, 106, 69, 120, 77, 68, 85, 119, 77, 84, 66, 97, 77, 72, 65, 120, 73, 106, 65, 103, 10, 66, 103, 78, 86, 66, 65, 77, 77, 71, 85, 108, 117, 100, 71, 86, 115, 73, 70, 78, 72, 87, 67, 66, 81, 81, 48, 115, 103, 85, 71, 120, 104, 100, 71, 90, 118, 99, 109, 48, 103, 81, 48, 69, 120, 71, 106, 65, 89, 66, 103, 78, 86, 66, 65, 111, 77, 69, 85, 108, 117, 100, 71, 86, 115, 10, 73, 69, 78, 118, 99, 110, 66, 118, 99, 109, 70, 48, 97, 87, 57, 117, 77, 82, 81, 119, 69, 103, 89, 68, 86, 81, 81, 72, 68, 65, 116, 84, 89, 87, 53, 48, 89, 83, 66, 68, 98, 71, 70, 121, 89, 84, 69, 76, 77, 65, 107, 71, 65, 49, 85, 69, 67, 65, 119, 67, 81, 48, 69, 120, 10, 67, 122, 65, 74, 66, 103, 78, 86, 66, 65, 89, 84, 65, 108, 86, 84, 77, 70, 107, 119, 69, 119, 89, 72, 75, 111, 90, 73, 122, 106, 48, 67, 65, 81, 89, 73, 75, 111, 90, 73, 122, 106, 48, 68, 65, 81, 99, 68, 81, 103, 65, 69, 78, 83, 66, 47, 55, 116, 50, 49, 108, 88, 83, 79, 10, 50, 67, 117, 122, 112, 120, 119, 55, 52, 101, 74, 66, 55, 50, 69, 121, 68, 71, 103, 87, 53, 114, 88, 67, 116, 120, 50, 116, 86, 84, 76, 113, 54, 104, 75, 107, 54, 122, 43, 85, 105, 82, 90, 67, 110, 113, 82, 55, 112, 115, 79, 118, 103, 113, 70, 101, 83, 120, 108, 109, 84, 108, 74, 108, 10, 101, 84, 109, 105, 50, 87, 89, 122, 51, 113, 79, 66, 117, 122, 67, 66, 117, 68, 65, 102, 66, 103, 78, 86, 72, 83, 77, 69, 71, 68, 65, 87, 103, 66, 81, 105, 90, 81, 122, 87, 87, 112, 48, 48, 105, 102, 79, 68, 116, 74, 86, 83, 118, 49, 65, 98, 79, 83, 99, 71, 114, 68, 66, 83, 10, 66, 103, 78, 86, 72, 82, 56, 69, 83, 122, 66, 74, 77, 69, 101, 103, 82, 97, 66, 68, 104, 107, 70, 111, 100, 72, 82, 119, 99, 122, 111, 118, 76, 50, 78, 108, 99, 110, 82, 112, 90, 109, 108, 106, 89, 88, 82, 108, 99, 121, 53, 48, 99, 110, 86, 122, 100, 71, 86, 107, 99, 50, 86, 121, 10, 100, 109, 108, 106, 90, 88, 77, 117, 97, 87, 53, 48, 90, 87, 119, 117, 89, 50, 57, 116, 76, 48, 108, 117, 100, 71, 86, 115, 85, 48, 100, 89, 85, 109, 57, 118, 100, 69, 78, 66, 76, 109, 82, 108, 99, 106, 65, 100, 66, 103, 78, 86, 72, 81, 52, 69, 70, 103, 81, 85, 108, 87, 57, 100, 10, 122, 98, 48, 98, 52, 101, 108, 65, 83, 99, 110, 85, 57, 68, 80, 79, 65, 86, 99, 76, 51, 108, 81, 119, 68, 103, 89, 68, 86, 82, 48, 80, 65, 81, 72, 47, 66, 65, 81, 68, 65, 103, 69, 71, 77, 66, 73, 71, 65, 49, 85, 100, 69, 119, 69, 66, 47, 119, 81, 73, 77, 65, 89, 66, 10, 65, 102, 56, 67, 65, 81, 65, 119, 67, 103, 89, 73, 75, 111, 90, 73, 122, 106, 48, 69, 65, 119, 73, 68, 82, 119, 65, 119, 82, 65, 73, 103, 88, 115, 86, 107, 105, 48, 119, 43, 105, 54, 86, 89, 71, 87, 51, 85, 70, 47, 50, 50, 117, 97, 88, 101, 48, 89, 74, 68, 106, 49, 85, 101, 10, 110, 65, 43, 84, 106, 68, 49, 97, 105, 53, 99, 67, 73, 67, 89, 98, 49, 83, 65, 109, 68, 53, 120, 107, 102, 84, 86, 112, 118, 111, 52, 85, 111, 121, 105, 83, 89, 120, 114, 68, 87, 76, 109, 85, 82, 52, 67, 73, 57, 78, 75, 121, 102, 80, 78, 43, 10, 45, 45, 45, 45, 45, 69, 78, 68, 32, 67, 69, 82, 84, 73, 70, 73, 67, 65, 84, 69, 45, 45, 45, 45, 45, 10, 45, 45, 45, 45, 45, 66, 69, 71, 73, 78, 32, 67, 69, 82, 84, 73, 70, 73, 67, 65, 84, 69, 45, 45, 45, 45, 45, 10, 77, 73, 73, 67, 106, 122, 67, 67, 65, 106, 83, 103, 65, 119, 73, 66, 65, 103, 73, 85, 73, 109, 85, 77, 49, 108, 113, 100, 78, 73, 110, 122, 103, 55, 83, 86, 85, 114, 57, 81, 71, 122, 107, 110, 66, 113, 119, 119, 67, 103, 89, 73, 75, 111, 90, 73, 122, 106, 48, 69, 65, 119, 73, 119, 10, 97, 68, 69, 97, 77, 66, 103, 71, 65, 49, 85, 69, 65, 119, 119, 82, 83, 87, 53, 48, 90, 87, 119, 103, 85, 48, 100, 89, 73, 70, 74, 118, 98, 51, 81, 103, 81, 48, 69, 120, 71, 106, 65, 89, 66, 103, 78, 86, 66, 65, 111, 77, 69, 85, 108, 117, 100, 71, 86, 115, 73, 69, 78, 118, 10, 99, 110, 66, 118, 99, 109, 70, 48, 97, 87, 57, 117, 77, 82, 81, 119, 69, 103, 89, 68, 86, 81, 81, 72, 68, 65, 116, 84, 89, 87, 53, 48, 89, 83, 66, 68, 98, 71, 70, 121, 89, 84, 69, 76, 77, 65, 107, 71, 65, 49, 85, 69, 67, 65, 119, 67, 81, 48, 69, 120, 67, 122, 65, 74, 10, 66, 103, 78, 86, 66, 65, 89, 84, 65, 108, 86, 84, 77, 66, 52, 88, 68, 84, 69, 52, 77, 68, 85, 121, 77, 84, 69, 119, 78, 68, 85, 120, 77, 70, 111, 88, 68, 84, 81, 53, 77, 84, 73, 122, 77, 84, 73, 122, 78, 84, 107, 49, 79, 86, 111, 119, 97, 68, 69, 97, 77, 66, 103, 71, 10, 65, 49, 85, 69, 65, 119, 119, 82, 83, 87, 53, 48, 90, 87, 119, 103, 85, 48, 100, 89, 73, 70, 74, 118, 98, 51, 81, 103, 81, 48, 69, 120, 71, 106, 65, 89, 66, 103, 78, 86, 66, 65, 111, 77, 69, 85, 108, 117, 100, 71, 86, 115, 73, 69, 78, 118, 99, 110, 66, 118, 99, 109, 70, 48, 10, 97, 87, 57, 117, 77, 82, 81, 119, 69, 103, 89, 68, 86, 81, 81, 72, 68, 65, 116, 84, 89, 87, 53, 48, 89, 83, 66, 68, 98, 71, 70, 121, 89, 84, 69, 76, 77, 65, 107, 71, 65, 49, 85, 69, 67, 65, 119, 67, 81, 48, 69, 120, 67, 122, 65, 74, 66, 103, 78, 86, 66, 65, 89, 84, 10, 65, 108, 86, 84, 77, 70, 107, 119, 69, 119, 89, 72, 75, 111, 90, 73, 122, 106, 48, 67, 65, 81, 89, 73, 75, 111, 90, 73, 122, 106, 48, 68, 65, 81, 99, 68, 81, 103, 65, 69, 67, 54, 110, 69, 119, 77, 68, 73, 89, 90, 79, 106, 47, 105, 80, 87, 115, 67, 122, 97, 69, 75, 105, 55, 10, 49, 79, 105, 79, 83, 76, 82, 70, 104, 87, 71, 106, 98, 110, 66, 86, 74, 102, 86, 110, 107, 89, 52, 117, 51, 73, 106, 107, 68, 89, 89, 76, 48, 77, 120, 79, 52, 109, 113, 115, 121, 89, 106, 108, 66, 97, 108, 84, 86, 89, 120, 70, 80, 50, 115, 74, 66, 75, 53, 122, 108, 75, 79, 66, 10, 117, 122, 67, 66, 117, 68, 65, 102, 66, 103, 78, 86, 72, 83, 77, 69, 71, 68, 65, 87, 103, 66, 81, 105, 90, 81, 122, 87, 87, 112, 48, 48, 105, 102, 79, 68, 116, 74, 86, 83, 118, 49, 65, 98, 79, 83, 99, 71, 114, 68, 66, 83, 66, 103, 78, 86, 72, 82, 56, 69, 83, 122, 66, 74, 10, 77, 69, 101, 103, 82, 97, 66, 68, 104, 107, 70, 111, 100, 72, 82, 119, 99, 122, 111, 118, 76, 50, 78, 108, 99, 110, 82, 112, 90, 109, 108, 106, 89, 88, 82, 108, 99, 121, 53, 48, 99, 110, 86, 122, 100, 71, 86, 107, 99, 50, 86, 121, 100, 109, 108, 106, 90, 88, 77, 117, 97, 87, 53, 48, 10, 90, 87, 119, 117, 89, 50, 57, 116, 76, 48, 108, 117, 100, 71, 86, 115, 85, 48, 100, 89, 85, 109, 57, 118, 100, 69, 78, 66, 76, 109, 82, 108, 99, 106, 65, 100, 66, 103, 78, 86, 72, 81, 52, 69, 70, 103, 81, 85, 73, 109, 85, 77, 49, 108, 113, 100, 78, 73, 110, 122, 103, 55, 83, 86, 10, 85, 114, 57, 81, 71, 122, 107, 110, 66, 113, 119, 119, 68, 103, 89, 68, 86, 82, 48, 80, 65, 81, 72, 47, 66, 65, 81, 68, 65, 103, 69, 71, 77, 66, 73, 71, 65, 49, 85, 100, 69, 119, 69, 66, 47, 119, 81, 73, 77, 65, 89, 66, 65, 102, 56, 67, 65, 81, 69, 119, 67, 103, 89, 73, 10, 75, 111, 90, 73, 122, 106, 48, 69, 65, 119, 73, 68, 83, 81, 65, 119, 82, 103, 73, 104, 65, 79, 87, 47, 53, 81, 107, 82, 43, 83, 57, 67, 105, 83, 68, 99, 78, 111, 111, 119, 76, 117, 80, 82, 76, 115, 87, 71, 102, 47, 89, 105, 55, 71, 83, 88, 57, 52, 66, 103, 119, 84, 119, 103, 10, 65, 105, 69, 65, 52, 74, 48, 108, 114, 72, 111, 77, 115, 43, 88, 111, 53, 111, 47, 115, 88, 54, 79, 57, 81, 87, 120, 72, 82, 65, 118, 90, 85, 71, 79, 100, 82, 81, 55, 99, 118, 113, 82, 88, 97, 113, 73, 61, 10, 45, 45, 45, 45, 45, 69, 78, 68, 32, 67, 69, 82, 84, 73, 70, 73, 67, 65, 84, 69, 45, 45, 45, 45, 45, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], "collateral": { "pck_crl_issuer_chain": "-----BEGIN CERTIFICATE-----\nMIICljCCAj2gAwIBAgIVAJVvXc29G+HpQEnJ1PQzzgFXC95UMAoGCCqGSM49BAMC\nMGgxGjAYBgNVBAMMEUludGVsIFNHWCBSb290IENBMRowGAYDVQQKDBFJbnRlbCBD\nb3Jwb3JhdGlvbjEUMBIGA1UEBwwLU2FudGEgQ2xhcmExCzAJBgNVBAgMAkNBMQsw\nCQYDVQQGEwJVUzAeFw0xODA1MjExMDUwMTBaFw0zMzA1MjExMDUwMTBaMHAxIjAg\nBgNVBAMMGUludGVsIFNHWCBQQ0sgUGxhdGZvcm0gQ0ExGjAYBgNVBAoMEUludGVs\nIENvcnBvcmF0aW9uMRQwEgYDVQQHDAtTYW50YSBDbGFyYTELMAkGA1UECAwCQ0Ex\nCzAJBgNVBAYTAlVTMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAENSB/7t21lXSO\n2Cuzpxw74eJB72EyDGgW5rXCtx2tVTLq6hKk6z+UiRZCnqR7psOvgqFeSxlmTlJl\neTmi2WYz3qOBuzCBuDAfBgNVHSMEGDAWgBQiZQzWWp00ifODtJVSv1AbOScGrDBS\nBgNVHR8ESzBJMEegRaBDhkFodHRwczovL2NlcnRpZmljYXRlcy50cnVzdGVkc2Vy\ndmljZXMuaW50ZWwuY29tL0ludGVsU0dYUm9vdENBLmRlcjAdBgNVHQ4EFgQUlW9d\nzb0b4elAScnU9DPOAVcL3lQwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYB\nAf8CAQAwCgYIKoZIzj0EAwIDRwAwRAIgXsVki0w+i6VYGW3UF/22uaXe0YJDj1Ue\nnA+TjD1ai5cCICYb1SAmD5xkfTVpvo4UoyiSYxrDWLmUR4CI9NKyfPN+\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIICjzCCAjSgAwIBAgIUImUM1lqdNInzg7SVUr9QGzknBqwwCgYIKoZIzj0EAwIw\naDEaMBgGA1UEAwwRSW50ZWwgU0dYIFJvb3QgQ0ExGjAYBgNVBAoMEUludGVsIENv\ncnBvcmF0aW9uMRQwEgYDVQQHDAtTYW50YSBDbGFyYTELMAkGA1UECAwCQ0ExCzAJ\nBgNVBAYTAlVTMB4XDTE4MDUyMTEwNDUxMFoXDTQ5MTIzMTIzNTk1OVowaDEaMBgG\nA1UEAwwRSW50ZWwgU0dYIFJvb3QgQ0ExGjAYBgNVBAoMEUludGVsIENvcnBvcmF0\naW9uMRQwEgYDVQQHDAtTYW50YSBDbGFyYTELMAkGA1UECAwCQ0ExCzAJBgNVBAYT\nAlVTMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEC6nEwMDIYZOj/iPWsCzaEKi7\n1OiOSLRFhWGjbnBVJfVnkY4u3IjkDYYL0MxO4mqsyYjlBalTVYxFP2sJBK5zlKOB\nuzCBuDAfBgNVHSMEGDAWgBQiZQzWWp00ifODtJVSv1AbOScGrDBSBgNVHR8ESzBJ\nMEegRaBDhkFodHRwczovL2NlcnRpZmljYXRlcy50cnVzdGVkc2VydmljZXMuaW50\nZWwuY29tL0ludGVsU0dYUm9vdENBLmRlcjAdBgNVHQ4EFgQUImUM1lqdNInzg7SV\nUr9QGzknBqwwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQEwCgYI\nKoZIzj0EAwIDSQAwRgIhAOW/5QkR+S9CiSDcNoowLuPRLsWGf/Yi7GSX94BgwTwg\nAiEA4J0lrHoMs+Xo5o/sX6O9QWxHRAvZUGOdRQ7cvqRXaqI=\n-----END CERTIFICATE-----\n", "root_ca_crl": "308201223081c8020101300a06082a8648ce3d0403023068311a301806035504030c11496e74656c2053475820526f6f74204341311a3018060355040a0c11496e74656c20436f72706f726174696f6e3114301206035504070c0b53616e746120436c617261310b300906035504080c024341310b3009060355040613025553170d3236303232363133303430305a170d3237303232363133303430305aa02f302d300a0603551d140403020101301f0603551d2304183016801422650cd65a9d3489f383b49552bf501b392706ac300a06082a8648ce3d0403020349003046022100c252ed59c795ba2b11496a4a99758bb8cbc380a1ebbb0865be69f2c4b38bb6400221009a7d8b03602a9ee2d62322d759166d6933d24d9dfa01ab3fde4520691d715bd7", - "pck_crl": "30820d1630820cbd020101300a06082a8648ce3d04030230703122302006035504030c19496e74656c205347582050434b20506c6174666f726d204341311a3018060355040a0c11496e74656c20436f72706f726174696f6e3114301206035504070c0b53616e746120436c617261310b300906035504080c024341310b3009060355040613025553170d3236303332333039303332315a170d3236303432323039303332315a30820be9303302146fc34e5023e728923435d61aa4b83c618166ad35170d3236303332333039303332315a300c300a0603551d1504030a01013034021500efae6e9715fca13b87e333e8261ed6d990a926ad170d3236303332333039303332315a300c300a0603551d1504030a01013034021500fd608648629cba73078b4d492f4b3ea741ad08cd170d3236303332333039303332315a300c300a0603551d1504030a010130340215008af924184e1d5afddd73c3d63a12f5e8b5737e56170d3236303332333039303332315a300c300a0603551d1504030a01013034021500b1257978cfa9ccdd0759abf8c5ca72fae3a78a9b170d3236303332333039303332315a300c300a0603551d1504030a01013033021474fea614a972be0e2843f2059835811ed872f9b3170d3236303332333039303332315a300c300a0603551d1504030a01013034021500f9c4ef56b3ab48d577e108baedf4bf88014214b9170d3236303332333039303332315a300c300a0603551d1504030a010130330214071de0778f9e5fc4f2878f30d6b07c9a30e6b30b170d3236303332333039303332315a300c300a0603551d1504030a01013034021500cde2424f972cea94ff239937f4d80c25029dd60b170d3236303332333039303332315a300c300a0603551d1504030a0101303302146c3319e5109b64507d3cf1132ce00349ef527319170d3236303332333039303332315a300c300a0603551d1504030a01013034021500df08d756b66a7497f43b5bb58ada04d3f4f7a937170d3236303332333039303332315a300c300a0603551d1504030a01013033021428af485b6cf67e409a39d5cb5aee4598f7a8fa7b170d3236303332333039303332315a300c300a0603551d1504030a01013034021500fb8b2daec092cada8aa9bc4ff2f1c20d0346668c170d3236303332333039303332315a300c300a0603551d1504030a01013034021500cd4850ac52bdcc69a6a6f058c8bc57bbd0b5f864170d3236303332333039303332315a300c300a0603551d1504030a01013034021500994dd3666f5275fb805f95dd02bd50cb2679d8ad170d3236303332333039303332315a300c300a0603551d1504030a0101303302140702136900252274d9035eedf5457462fad0ef4c170d3236303332333039303332315a300c300a0603551d1504030a01013033021461f2bf73e39b4e04aa27d801bd73d24319b5bf80170d3236303332333039303332315a300c300a0603551d1504030a0101303302143992be851b96902eff38959e6c2eff1b0651a4b5170d3236303332333039303332315a300c300a0603551d1504030a0101303302140fda43a00b68ea79b7c2deaeac0b498bdfb2af90170d3236303332333039303332315a300c300a0603551d1504030a010130330214639f139a5040fdcff191e8a4fb1bf086ed603971170d3236303332333039303332315a300c300a0603551d1504030a01013034021500959d533f9249dc1e513544cdc830bf19b7f1f301170d3236303332333039303332315a300c300a0603551d1504030a0101303302147ae37748a9f912f4c63ba7ab07c593ce1d1d1181170d3236303332333039303332315a300c300a0603551d1504030a01013033021413884b33269938c195aa170fca75da177538df0b170d3236303332333039303332315a300c300a0603551d1504030a0101303402150085d3c9381b77a7e04d119c9e5ad6749ff3ffab87170d3236303332333039303332315a300c300a0603551d1504030a0101303402150093887ca4411e7a923bd1fed2819b2949f201b5b4170d3236303332333039303332315a300c300a0603551d1504030a0101303302142498dc6283930996fd8bf23a37acbe26a3bed457170d3236303332333039303332315a300c300a0603551d1504030a010130340215008a66f1a749488667689cc3903ac54c662b712e73170d3236303332333039303332315a300c300a0603551d1504030a01013034021500afc13610bdd36cb7985d106481a880d3a01fda07170d3236303332333039303332315a300c300a0603551d1504030a01013034021500efe04b2c33d036aac96ca673bf1e9a47b64d5cbb170d3236303332333039303332315a300c300a0603551d1504030a0101303402150083d9ac8d8bb509d1c6c809ad712e8430559ed7f3170d3236303332333039303332315a300c300a0603551d1504030a0101303302147931fd50b5071c1bbfc5b7b6ded8b45b9d8b8529170d3236303332333039303332315a300c300a0603551d1504030a0101303302141fa20e2970bde5d57f7b8ddf8339484e1f1d0823170d3236303332333039303332315a300c300a0603551d1504030a0101303302141e87b2c3b32d8d23e411cef34197b95af0c8adf5170d3236303332333039303332315a300c300a0603551d1504030a010130340215009afd2ee90a473550a167d996911437c7502d1f09170d3236303332333039303332315a300c300a0603551d1504030a0101303302144481b0f11728a13b696d3ea9c770a0b15ec58dda170d3236303332333039303332315a300c300a0603551d1504030a01013034021500a7859f57982ef0e67d37bc8ef2ef5ac835ff1aa9170d3236303332333039303332315a300c300a0603551d1504030a010130340215009d67753b81e47090aea763fbec4c4549bcdb9933170d3236303332333039303332315a300c300a0603551d1504030a01013033021434bfbb7a1d9c568147e118b614f7b76ed3ef68df170d3236303332333039303332315a300c300a0603551d1504030a0101303302142c3cc6fe9279db1516d5ce39f2a898cda5a175e1170d3236303332333039303332315a300c300a0603551d1504030a010130330214717948687509234be979e4b7dce6f31bef64b68c170d3236303332333039303332315a300c300a0603551d1504030a010130340215009d76ef2c39c136e8658b6e7396b1d7445a27631f170d3236303332333039303332315a300c300a0603551d1504030a01013034021500c3e025fca995f36f59b48467939e3e34e6361a6f170d3236303332333039303332315a300c300a0603551d1504030a010130340215008c5f6b3257da05b17429e2e61ba965d67330606a170d3236303332333039303332315a300c300a0603551d1504030a01013034021500a17c51722ec1e0c3278fe8bdf052059cbec4e648170d3236303332333039303332315a300c300a0603551d1504030a01013033021411c943b866fa04944e3057e5a67146596475a023170d3236303332333039303332315a300c300a0603551d1504030a01013034021500be6913785406155454a28885a515b3da5767d3a9170d3236303332333039303332315a300c300a0603551d1504030a0101303302140ac5ec91bd934c07b9ea41625e9cc09681002eb0170d3236303332333039303332315a300c300a0603551d1504030a0101303302146d51a0eabc1f9a1e9ddd5b36bdda1631ae6c182a170d3236303332333039303332315a300c300a0603551d1504030a01013034021500a52c5d71c4166b4fc0ded8b679951e5ee9193de5170d3236303332333039303332315a300c300a0603551d1504030a010130330214249779aedd85fcac93c8853516be5428c26b3bf8170d3236303332333039303332315a300c300a0603551d1504030a01013033021434ba4fd76bde5309210cf1dd1ffb494c638a9157170d3236303332333039303332315a300c300a0603551d1504030a010130330214043e04919daae13443248395094d2a2eacfc76fe170d3236303332333039303332315a300c300a0603551d1504030a01013033021447fc577d2d094cbdf270715ed6848a93855ad34b170d3236303332333039303332315a300c300a0603551d1504030a0101303302147d62a2f5e6f386e469653fffff045d0a8178e8e7170d3236303332333039303332315a300c300a0603551d1504030a01013034021500c4ed45fe026bb6a47eaec35ea80b7ef407ce062c170d3236303332333039303332315a300c300a0603551d1504030a01013034021500cf9831077a3ca4f1a2c56867bf55b18eccbeffd8170d3236303332333039303332315a300c300a0603551d1504030a0101303302146c2b81d7ea2e436720ce29f1d0b1ccb7a218600f170d3236303332333039303332315a300c300a0603551d1504030a0101a02f302d300a0603551d140403020101301f0603551d23041830168014956f5dcdbd1be1e94049c9d4f433ce01570bde54300a06082a8648ce3d0403020347003044022059471d848c82dffba25c398d9c0eba5ae84fd5becadc361e3b9aa29e685c5e9d02203292aaa14390b702045a652f2448ebe65dfd4217ae3b3ba3404549200993fe2b", + "pck_crl": "30820d1730820cbd020101300a06082a8648ce3d04030230703122302006035504030c19496e74656c205347582050434b20506c6174666f726d204341311a3018060355040a0c11496e74656c20436f72706f726174696f6e3114301206035504070c0b53616e746120436c617261310b300906035504080c024341310b3009060355040613025553170d3236303332353039303433335a170d3236303432343039303433335a30820be9303302146fc34e5023e728923435d61aa4b83c618166ad35170d3236303332353039303433335a300c300a0603551d1504030a01013034021500efae6e9715fca13b87e333e8261ed6d990a926ad170d3236303332353039303433335a300c300a0603551d1504030a01013034021500fd608648629cba73078b4d492f4b3ea741ad08cd170d3236303332353039303433335a300c300a0603551d1504030a010130340215008af924184e1d5afddd73c3d63a12f5e8b5737e56170d3236303332353039303433335a300c300a0603551d1504030a01013034021500b1257978cfa9ccdd0759abf8c5ca72fae3a78a9b170d3236303332353039303433335a300c300a0603551d1504030a01013033021474fea614a972be0e2843f2059835811ed872f9b3170d3236303332353039303433335a300c300a0603551d1504030a01013034021500f9c4ef56b3ab48d577e108baedf4bf88014214b9170d3236303332353039303433335a300c300a0603551d1504030a010130330214071de0778f9e5fc4f2878f30d6b07c9a30e6b30b170d3236303332353039303433335a300c300a0603551d1504030a01013034021500cde2424f972cea94ff239937f4d80c25029dd60b170d3236303332353039303433335a300c300a0603551d1504030a0101303302146c3319e5109b64507d3cf1132ce00349ef527319170d3236303332353039303433335a300c300a0603551d1504030a01013034021500df08d756b66a7497f43b5bb58ada04d3f4f7a937170d3236303332353039303433335a300c300a0603551d1504030a01013033021428af485b6cf67e409a39d5cb5aee4598f7a8fa7b170d3236303332353039303433335a300c300a0603551d1504030a01013034021500fb8b2daec092cada8aa9bc4ff2f1c20d0346668c170d3236303332353039303433335a300c300a0603551d1504030a01013034021500cd4850ac52bdcc69a6a6f058c8bc57bbd0b5f864170d3236303332353039303433335a300c300a0603551d1504030a01013034021500994dd3666f5275fb805f95dd02bd50cb2679d8ad170d3236303332353039303433335a300c300a0603551d1504030a0101303302140702136900252274d9035eedf5457462fad0ef4c170d3236303332353039303433335a300c300a0603551d1504030a01013033021461f2bf73e39b4e04aa27d801bd73d24319b5bf80170d3236303332353039303433335a300c300a0603551d1504030a0101303302143992be851b96902eff38959e6c2eff1b0651a4b5170d3236303332353039303433335a300c300a0603551d1504030a0101303302140fda43a00b68ea79b7c2deaeac0b498bdfb2af90170d3236303332353039303433335a300c300a0603551d1504030a010130330214639f139a5040fdcff191e8a4fb1bf086ed603971170d3236303332353039303433335a300c300a0603551d1504030a01013034021500959d533f9249dc1e513544cdc830bf19b7f1f301170d3236303332353039303433335a300c300a0603551d1504030a0101303302147ae37748a9f912f4c63ba7ab07c593ce1d1d1181170d3236303332353039303433335a300c300a0603551d1504030a01013033021413884b33269938c195aa170fca75da177538df0b170d3236303332353039303433335a300c300a0603551d1504030a0101303402150085d3c9381b77a7e04d119c9e5ad6749ff3ffab87170d3236303332353039303433335a300c300a0603551d1504030a0101303402150093887ca4411e7a923bd1fed2819b2949f201b5b4170d3236303332353039303433335a300c300a0603551d1504030a0101303302142498dc6283930996fd8bf23a37acbe26a3bed457170d3236303332353039303433335a300c300a0603551d1504030a010130340215008a66f1a749488667689cc3903ac54c662b712e73170d3236303332353039303433335a300c300a0603551d1504030a01013034021500afc13610bdd36cb7985d106481a880d3a01fda07170d3236303332353039303433335a300c300a0603551d1504030a01013034021500efe04b2c33d036aac96ca673bf1e9a47b64d5cbb170d3236303332353039303433335a300c300a0603551d1504030a0101303402150083d9ac8d8bb509d1c6c809ad712e8430559ed7f3170d3236303332353039303433335a300c300a0603551d1504030a0101303302147931fd50b5071c1bbfc5b7b6ded8b45b9d8b8529170d3236303332353039303433335a300c300a0603551d1504030a0101303302141fa20e2970bde5d57f7b8ddf8339484e1f1d0823170d3236303332353039303433335a300c300a0603551d1504030a0101303302141e87b2c3b32d8d23e411cef34197b95af0c8adf5170d3236303332353039303433335a300c300a0603551d1504030a010130340215009afd2ee90a473550a167d996911437c7502d1f09170d3236303332353039303433335a300c300a0603551d1504030a0101303302144481b0f11728a13b696d3ea9c770a0b15ec58dda170d3236303332353039303433335a300c300a0603551d1504030a01013034021500a7859f57982ef0e67d37bc8ef2ef5ac835ff1aa9170d3236303332353039303433335a300c300a0603551d1504030a010130340215009d67753b81e47090aea763fbec4c4549bcdb9933170d3236303332353039303433335a300c300a0603551d1504030a01013033021434bfbb7a1d9c568147e118b614f7b76ed3ef68df170d3236303332353039303433335a300c300a0603551d1504030a0101303302142c3cc6fe9279db1516d5ce39f2a898cda5a175e1170d3236303332353039303433335a300c300a0603551d1504030a010130330214717948687509234be979e4b7dce6f31bef64b68c170d3236303332353039303433335a300c300a0603551d1504030a010130340215009d76ef2c39c136e8658b6e7396b1d7445a27631f170d3236303332353039303433335a300c300a0603551d1504030a01013034021500c3e025fca995f36f59b48467939e3e34e6361a6f170d3236303332353039303433335a300c300a0603551d1504030a010130340215008c5f6b3257da05b17429e2e61ba965d67330606a170d3236303332353039303433335a300c300a0603551d1504030a01013034021500a17c51722ec1e0c3278fe8bdf052059cbec4e648170d3236303332353039303433335a300c300a0603551d1504030a01013033021411c943b866fa04944e3057e5a67146596475a023170d3236303332353039303433335a300c300a0603551d1504030a01013034021500be6913785406155454a28885a515b3da5767d3a9170d3236303332353039303433335a300c300a0603551d1504030a0101303302140ac5ec91bd934c07b9ea41625e9cc09681002eb0170d3236303332353039303433335a300c300a0603551d1504030a0101303302146d51a0eabc1f9a1e9ddd5b36bdda1631ae6c182a170d3236303332353039303433335a300c300a0603551d1504030a01013034021500a52c5d71c4166b4fc0ded8b679951e5ee9193de5170d3236303332353039303433335a300c300a0603551d1504030a010130330214249779aedd85fcac93c8853516be5428c26b3bf8170d3236303332353039303433335a300c300a0603551d1504030a01013033021434ba4fd76bde5309210cf1dd1ffb494c638a9157170d3236303332353039303433335a300c300a0603551d1504030a010130330214043e04919daae13443248395094d2a2eacfc76fe170d3236303332353039303433335a300c300a0603551d1504030a01013033021447fc577d2d094cbdf270715ed6848a93855ad34b170d3236303332353039303433335a300c300a0603551d1504030a0101303302147d62a2f5e6f386e469653fffff045d0a8178e8e7170d3236303332353039303433335a300c300a0603551d1504030a01013034021500c4ed45fe026bb6a47eaec35ea80b7ef407ce062c170d3236303332353039303433335a300c300a0603551d1504030a01013034021500cf9831077a3ca4f1a2c56867bf55b18eccbeffd8170d3236303332353039303433335a300c300a0603551d1504030a0101303302146c2b81d7ea2e436720ce29f1d0b1ccb7a218600f170d3236303332353039303433335a300c300a0603551d1504030a0101a02f302d300a0603551d140403020101301f0603551d23041830168014956f5dcdbd1be1e94049c9d4f433ce01570bde54300a06082a8648ce3d04030203480030450220436654da89e5124cb9e8e7298065f3754ddad212a31b8c89fbfd2c146326abbf022100f3eda6b4ea5f212063fdf434dcaa6a4b72e262729e070fba9d853ccfd00e8dac", "tcb_info_issuer_chain": "-----BEGIN CERTIFICATE-----\nMIICjTCCAjKgAwIBAgIUfjiC1ftVKUpASY5FhAPpFJG99FUwCgYIKoZIzj0EAwIw\naDEaMBgGA1UEAwwRSW50ZWwgU0dYIFJvb3QgQ0ExGjAYBgNVBAoMEUludGVsIENv\ncnBvcmF0aW9uMRQwEgYDVQQHDAtTYW50YSBDbGFyYTELMAkGA1UECAwCQ0ExCzAJ\nBgNVBAYTAlVTMB4XDTI1MDUwNjA5MjUwMFoXDTMyMDUwNjA5MjUwMFowbDEeMBwG\nA1UEAwwVSW50ZWwgU0dYIFRDQiBTaWduaW5nMRowGAYDVQQKDBFJbnRlbCBDb3Jw\nb3JhdGlvbjEUMBIGA1UEBwwLU2FudGEgQ2xhcmExCzAJBgNVBAgMAkNBMQswCQYD\nVQQGEwJVUzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABENFG8xzydWRfK92bmGv\nP+mAh91PEyV7Jh6FGJd5ndE9aBH7R3E4A7ubrlh/zN3C4xvpoouGlirMba+W2lju\nypajgbUwgbIwHwYDVR0jBBgwFoAUImUM1lqdNInzg7SVUr9QGzknBqwwUgYDVR0f\nBEswSTBHoEWgQ4ZBaHR0cHM6Ly9jZXJ0aWZpY2F0ZXMudHJ1c3RlZHNlcnZpY2Vz\nLmludGVsLmNvbS9JbnRlbFNHWFJvb3RDQS5kZXIwHQYDVR0OBBYEFH44gtX7VSlK\nQEmORYQD6RSRvfRVMA4GA1UdDwEB/wQEAwIGwDAMBgNVHRMBAf8EAjAAMAoGCCqG\nSM49BAMCA0kAMEYCIQDdmmRuAo3qCO8TC1IoJMITAoOEw4dlgEBHzSz1TuMSTAIh\nAKVTqOkt59+co0O3m3hC+v5Fb00FjYWcgeu3EijOULo5\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIICjzCCAjSgAwIBAgIUImUM1lqdNInzg7SVUr9QGzknBqwwCgYIKoZIzj0EAwIw\naDEaMBgGA1UEAwwRSW50ZWwgU0dYIFJvb3QgQ0ExGjAYBgNVBAoMEUludGVsIENv\ncnBvcmF0aW9uMRQwEgYDVQQHDAtTYW50YSBDbGFyYTELMAkGA1UECAwCQ0ExCzAJ\nBgNVBAYTAlVTMB4XDTE4MDUyMTEwNDUxMFoXDTQ5MTIzMTIzNTk1OVowaDEaMBgG\nA1UEAwwRSW50ZWwgU0dYIFJvb3QgQ0ExGjAYBgNVBAoMEUludGVsIENvcnBvcmF0\naW9uMRQwEgYDVQQHDAtTYW50YSBDbGFyYTELMAkGA1UECAwCQ0ExCzAJBgNVBAYT\nAlVTMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEC6nEwMDIYZOj/iPWsCzaEKi7\n1OiOSLRFhWGjbnBVJfVnkY4u3IjkDYYL0MxO4mqsyYjlBalTVYxFP2sJBK5zlKOB\nuzCBuDAfBgNVHSMEGDAWgBQiZQzWWp00ifODtJVSv1AbOScGrDBSBgNVHR8ESzBJ\nMEegRaBDhkFodHRwczovL2NlcnRpZmljYXRlcy50cnVzdGVkc2VydmljZXMuaW50\nZWwuY29tL0ludGVsU0dYUm9vdENBLmRlcjAdBgNVHQ4EFgQUImUM1lqdNInzg7SV\nUr9QGzknBqwwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQEwCgYI\nKoZIzj0EAwIDSQAwRgIhAOW/5QkR+S9CiSDcNoowLuPRLsWGf/Yi7GSX94BgwTwg\nAiEA4J0lrHoMs+Xo5o/sX6O9QWxHRAvZUGOdRQ7cvqRXaqI=\n-----END CERTIFICATE-----\n", - "tcb_info": "{\"id\":\"TDX\",\"version\":3,\"issueDate\":\"2026-03-23T08:57:57Z\",\"nextUpdate\":\"2026-04-22T08:57:57Z\",\"fmspc\":\"b0c06f000000\",\"pceId\":\"0000\",\"tcbType\":0,\"tcbEvaluationDataNumber\":18,\"tdxModule\":{\"mrsigner\":\"000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\",\"attributes\":\"0000000000000000\",\"attributesMask\":\"FFFFFFFFFFFFFFFF\"},\"tdxModuleIdentities\":[{\"id\":\"TDX_03\",\"mrsigner\":\"000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\",\"attributes\":\"0000000000000000\",\"attributesMask\":\"FFFFFFFFFFFFFFFF\",\"tcbLevels\":[{\"tcb\":{\"isvsvn\":3},\"tcbDate\":\"2024-11-13T00:00:00Z\",\"tcbStatus\":\"UpToDate\"}]},{\"id\":\"TDX_01\",\"mrsigner\":\"000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\",\"attributes\":\"0000000000000000\",\"attributesMask\":\"FFFFFFFFFFFFFFFF\",\"tcbLevels\":[{\"tcb\":{\"isvsvn\":6},\"tcbDate\":\"2024-11-13T00:00:00Z\",\"tcbStatus\":\"UpToDate\"},{\"tcb\":{\"isvsvn\":4},\"tcbDate\":\"2024-03-13T00:00:00Z\",\"tcbStatus\":\"OutOfDate\",\"advisoryIDs\":[\"INTEL-SA-01036\",\"INTEL-SA-01099\"]},{\"tcb\":{\"isvsvn\":2},\"tcbDate\":\"2023-08-09T00:00:00Z\",\"tcbStatus\":\"OutOfDate\",\"advisoryIDs\":[\"INTEL-SA-01036\",\"INTEL-SA-01099\"]}]}],\"tcbLevels\":[{\"tcb\":{\"sgxtcbcomponents\":[{\"svn\":3,\"category\":\"BIOS\",\"type\":\"Early Microcode Update\"},{\"svn\":3,\"category\":\"OS/VMM\",\"type\":\"SGX Late Microcode Update\"},{\"svn\":2,\"category\":\"OS/VMM\",\"type\":\"TXT SINIT\"},{\"svn\":2,\"category\":\"BIOS\"},{\"svn\":4,\"category\":\"BIOS\"},{\"svn\":1,\"category\":\"BIOS\"},{\"svn\":0},{\"svn\":5,\"category\":\"OS/VMM\",\"type\":\"SEAMLDR ACM\"},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0}],\"pcesvn\":11,\"tdxtcbcomponents\":[{\"svn\":5,\"category\":\"OS/VMM\",\"type\":\"TDX Module\"},{\"svn\":0,\"category\":\"OS/VMM\",\"type\":\"TDX Module\"},{\"svn\":3,\"category\":\"OS/VMM\",\"type\":\"TDX Late Microcode Update\"},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0}]},\"tcbDate\":\"2024-11-13T00:00:00Z\",\"tcbStatus\":\"UpToDate\"},{\"tcb\":{\"sgxtcbcomponents\":[{\"svn\":2,\"category\":\"BIOS\",\"type\":\"Early Microcode Update\"},{\"svn\":2,\"category\":\"OS/VMM\",\"type\":\"SGX Late Microcode Update\"},{\"svn\":2,\"category\":\"OS/VMM\",\"type\":\"TXT SINIT\"},{\"svn\":2,\"category\":\"BIOS\"},{\"svn\":3,\"category\":\"BIOS\"},{\"svn\":1,\"category\":\"BIOS\"},{\"svn\":0},{\"svn\":5,\"category\":\"OS/VMM\",\"type\":\"SEAMLDR ACM\"},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0}],\"pcesvn\":11,\"tdxtcbcomponents\":[{\"svn\":5,\"category\":\"OS/VMM\",\"type\":\"TDX Module\"},{\"svn\":0,\"category\":\"OS/VMM\",\"type\":\"TDX Module\"},{\"svn\":2,\"category\":\"OS/VMM\",\"type\":\"TDX Late Microcode Update\"},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0}]},\"tcbDate\":\"2024-03-13T00:00:00Z\",\"tcbStatus\":\"OutOfDate\",\"advisoryIDs\":[\"INTEL-SA-01036\",\"INTEL-SA-01079\",\"INTEL-SA-01099\",\"INTEL-SA-01103\",\"INTEL-SA-01111\"]},{\"tcb\":{\"sgxtcbcomponents\":[{\"svn\":2,\"category\":\"BIOS\",\"type\":\"Early Microcode Update\"},{\"svn\":2,\"category\":\"OS/VMM\",\"type\":\"SGX Late Microcode Update\"},{\"svn\":2,\"category\":\"OS/VMM\",\"type\":\"TXT SINIT\"},{\"svn\":2,\"category\":\"BIOS\"},{\"svn\":3,\"category\":\"BIOS\"},{\"svn\":1,\"category\":\"BIOS\"},{\"svn\":0},{\"svn\":5,\"category\":\"OS/VMM\",\"type\":\"SEAMLDR ACM\"},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0}],\"pcesvn\":5,\"tdxtcbcomponents\":[{\"svn\":5,\"category\":\"OS/VMM\",\"type\":\"TDX Module\"},{\"svn\":0,\"category\":\"OS/VMM\",\"type\":\"TDX Module\"},{\"svn\":2,\"category\":\"OS/VMM\",\"type\":\"TDX Late Microcode Update\"},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0}]},\"tcbDate\":\"2018-01-04T00:00:00Z\",\"tcbStatus\":\"OutOfDate\",\"advisoryIDs\":[\"INTEL-SA-00106\",\"INTEL-SA-00115\",\"INTEL-SA-00135\",\"INTEL-SA-00203\",\"INTEL-SA-00220\",\"INTEL-SA-00233\",\"INTEL-SA-00270\",\"INTEL-SA-00293\",\"INTEL-SA-00320\",\"INTEL-SA-00329\",\"INTEL-SA-00381\",\"INTEL-SA-00389\",\"INTEL-SA-00477\",\"INTEL-SA-00837\",\"INTEL-SA-01036\",\"INTEL-SA-01079\",\"INTEL-SA-01099\",\"INTEL-SA-01103\",\"INTEL-SA-01111\"]}]}", - "tcb_info_signature": "7ae741fc33b2ee32c6a86151c82911e54e330efe32ccedfaa579ce5bcbfbaf22628d4b07c0659c9e63774367a7bee9b3ce7979d9ba4a27b184266ecebb64233b", + "tcb_info": "{\"id\":\"TDX\",\"version\":3,\"issueDate\":\"2026-03-25T09:09:57Z\",\"nextUpdate\":\"2026-04-24T09:09:57Z\",\"fmspc\":\"b0c06f000000\",\"pceId\":\"0000\",\"tcbType\":0,\"tcbEvaluationDataNumber\":18,\"tdxModule\":{\"mrsigner\":\"000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\",\"attributes\":\"0000000000000000\",\"attributesMask\":\"FFFFFFFFFFFFFFFF\"},\"tdxModuleIdentities\":[{\"id\":\"TDX_03\",\"mrsigner\":\"000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\",\"attributes\":\"0000000000000000\",\"attributesMask\":\"FFFFFFFFFFFFFFFF\",\"tcbLevels\":[{\"tcb\":{\"isvsvn\":3},\"tcbDate\":\"2024-11-13T00:00:00Z\",\"tcbStatus\":\"UpToDate\"}]},{\"id\":\"TDX_01\",\"mrsigner\":\"000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\",\"attributes\":\"0000000000000000\",\"attributesMask\":\"FFFFFFFFFFFFFFFF\",\"tcbLevels\":[{\"tcb\":{\"isvsvn\":6},\"tcbDate\":\"2024-11-13T00:00:00Z\",\"tcbStatus\":\"UpToDate\"},{\"tcb\":{\"isvsvn\":4},\"tcbDate\":\"2024-03-13T00:00:00Z\",\"tcbStatus\":\"OutOfDate\",\"advisoryIDs\":[\"INTEL-SA-01036\",\"INTEL-SA-01099\"]},{\"tcb\":{\"isvsvn\":2},\"tcbDate\":\"2023-08-09T00:00:00Z\",\"tcbStatus\":\"OutOfDate\",\"advisoryIDs\":[\"INTEL-SA-01036\",\"INTEL-SA-01099\"]}]}],\"tcbLevels\":[{\"tcb\":{\"sgxtcbcomponents\":[{\"svn\":3,\"category\":\"BIOS\",\"type\":\"Early Microcode Update\"},{\"svn\":3,\"category\":\"OS/VMM\",\"type\":\"SGX Late Microcode Update\"},{\"svn\":2,\"category\":\"OS/VMM\",\"type\":\"TXT SINIT\"},{\"svn\":2,\"category\":\"BIOS\"},{\"svn\":4,\"category\":\"BIOS\"},{\"svn\":1,\"category\":\"BIOS\"},{\"svn\":0},{\"svn\":5,\"category\":\"OS/VMM\",\"type\":\"SEAMLDR ACM\"},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0}],\"pcesvn\":11,\"tdxtcbcomponents\":[{\"svn\":5,\"category\":\"OS/VMM\",\"type\":\"TDX Module\"},{\"svn\":0,\"category\":\"OS/VMM\",\"type\":\"TDX Module\"},{\"svn\":3,\"category\":\"OS/VMM\",\"type\":\"TDX Late Microcode Update\"},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0}]},\"tcbDate\":\"2024-11-13T00:00:00Z\",\"tcbStatus\":\"UpToDate\"},{\"tcb\":{\"sgxtcbcomponents\":[{\"svn\":2,\"category\":\"BIOS\",\"type\":\"Early Microcode Update\"},{\"svn\":2,\"category\":\"OS/VMM\",\"type\":\"SGX Late Microcode Update\"},{\"svn\":2,\"category\":\"OS/VMM\",\"type\":\"TXT SINIT\"},{\"svn\":2,\"category\":\"BIOS\"},{\"svn\":3,\"category\":\"BIOS\"},{\"svn\":1,\"category\":\"BIOS\"},{\"svn\":0},{\"svn\":5,\"category\":\"OS/VMM\",\"type\":\"SEAMLDR ACM\"},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0}],\"pcesvn\":11,\"tdxtcbcomponents\":[{\"svn\":5,\"category\":\"OS/VMM\",\"type\":\"TDX Module\"},{\"svn\":0,\"category\":\"OS/VMM\",\"type\":\"TDX Module\"},{\"svn\":2,\"category\":\"OS/VMM\",\"type\":\"TDX Late Microcode Update\"},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0}]},\"tcbDate\":\"2024-03-13T00:00:00Z\",\"tcbStatus\":\"OutOfDate\",\"advisoryIDs\":[\"INTEL-SA-01036\",\"INTEL-SA-01079\",\"INTEL-SA-01099\",\"INTEL-SA-01103\",\"INTEL-SA-01111\"]},{\"tcb\":{\"sgxtcbcomponents\":[{\"svn\":2,\"category\":\"BIOS\",\"type\":\"Early Microcode Update\"},{\"svn\":2,\"category\":\"OS/VMM\",\"type\":\"SGX Late Microcode Update\"},{\"svn\":2,\"category\":\"OS/VMM\",\"type\":\"TXT SINIT\"},{\"svn\":2,\"category\":\"BIOS\"},{\"svn\":3,\"category\":\"BIOS\"},{\"svn\":1,\"category\":\"BIOS\"},{\"svn\":0},{\"svn\":5,\"category\":\"OS/VMM\",\"type\":\"SEAMLDR ACM\"},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0}],\"pcesvn\":5,\"tdxtcbcomponents\":[{\"svn\":5,\"category\":\"OS/VMM\",\"type\":\"TDX Module\"},{\"svn\":0,\"category\":\"OS/VMM\",\"type\":\"TDX Module\"},{\"svn\":2,\"category\":\"OS/VMM\",\"type\":\"TDX Late Microcode Update\"},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0}]},\"tcbDate\":\"2018-01-04T00:00:00Z\",\"tcbStatus\":\"OutOfDate\",\"advisoryIDs\":[\"INTEL-SA-00106\",\"INTEL-SA-00115\",\"INTEL-SA-00135\",\"INTEL-SA-00203\",\"INTEL-SA-00220\",\"INTEL-SA-00233\",\"INTEL-SA-00270\",\"INTEL-SA-00293\",\"INTEL-SA-00320\",\"INTEL-SA-00329\",\"INTEL-SA-00381\",\"INTEL-SA-00389\",\"INTEL-SA-00477\",\"INTEL-SA-00837\",\"INTEL-SA-01036\",\"INTEL-SA-01079\",\"INTEL-SA-01099\",\"INTEL-SA-01103\",\"INTEL-SA-01111\"]}]}", + "tcb_info_signature": "5d9174f57476a3e58ff896fe676e6f2fcc8ebc3e89e9bc78a145ae611af1d02139c630e15e36dc9083bb891dd85c51a55e6a28bb35fb469c0e767864613046bc", "qe_identity_issuer_chain": "-----BEGIN CERTIFICATE-----\nMIICjTCCAjKgAwIBAgIUfjiC1ftVKUpASY5FhAPpFJG99FUwCgYIKoZIzj0EAwIw\naDEaMBgGA1UEAwwRSW50ZWwgU0dYIFJvb3QgQ0ExGjAYBgNVBAoMEUludGVsIENv\ncnBvcmF0aW9uMRQwEgYDVQQHDAtTYW50YSBDbGFyYTELMAkGA1UECAwCQ0ExCzAJ\nBgNVBAYTAlVTMB4XDTI1MDUwNjA5MjUwMFoXDTMyMDUwNjA5MjUwMFowbDEeMBwG\nA1UEAwwVSW50ZWwgU0dYIFRDQiBTaWduaW5nMRowGAYDVQQKDBFJbnRlbCBDb3Jw\nb3JhdGlvbjEUMBIGA1UEBwwLU2FudGEgQ2xhcmExCzAJBgNVBAgMAkNBMQswCQYD\nVQQGEwJVUzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABENFG8xzydWRfK92bmGv\nP+mAh91PEyV7Jh6FGJd5ndE9aBH7R3E4A7ubrlh/zN3C4xvpoouGlirMba+W2lju\nypajgbUwgbIwHwYDVR0jBBgwFoAUImUM1lqdNInzg7SVUr9QGzknBqwwUgYDVR0f\nBEswSTBHoEWgQ4ZBaHR0cHM6Ly9jZXJ0aWZpY2F0ZXMudHJ1c3RlZHNlcnZpY2Vz\nLmludGVsLmNvbS9JbnRlbFNHWFJvb3RDQS5kZXIwHQYDVR0OBBYEFH44gtX7VSlK\nQEmORYQD6RSRvfRVMA4GA1UdDwEB/wQEAwIGwDAMBgNVHRMBAf8EAjAAMAoGCCqG\nSM49BAMCA0kAMEYCIQDdmmRuAo3qCO8TC1IoJMITAoOEw4dlgEBHzSz1TuMSTAIh\nAKVTqOkt59+co0O3m3hC+v5Fb00FjYWcgeu3EijOULo5\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIICjzCCAjSgAwIBAgIUImUM1lqdNInzg7SVUr9QGzknBqwwCgYIKoZIzj0EAwIw\naDEaMBgGA1UEAwwRSW50ZWwgU0dYIFJvb3QgQ0ExGjAYBgNVBAoMEUludGVsIENv\ncnBvcmF0aW9uMRQwEgYDVQQHDAtTYW50YSBDbGFyYTELMAkGA1UECAwCQ0ExCzAJ\nBgNVBAYTAlVTMB4XDTE4MDUyMTEwNDUxMFoXDTQ5MTIzMTIzNTk1OVowaDEaMBgG\nA1UEAwwRSW50ZWwgU0dYIFJvb3QgQ0ExGjAYBgNVBAoMEUludGVsIENvcnBvcmF0\naW9uMRQwEgYDVQQHDAtTYW50YSBDbGFyYTELMAkGA1UECAwCQ0ExCzAJBgNVBAYT\nAlVTMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEC6nEwMDIYZOj/iPWsCzaEKi7\n1OiOSLRFhWGjbnBVJfVnkY4u3IjkDYYL0MxO4mqsyYjlBalTVYxFP2sJBK5zlKOB\nuzCBuDAfBgNVHSMEGDAWgBQiZQzWWp00ifODtJVSv1AbOScGrDBSBgNVHR8ESzBJ\nMEegRaBDhkFodHRwczovL2NlcnRpZmljYXRlcy50cnVzdGVkc2VydmljZXMuaW50\nZWwuY29tL0ludGVsU0dYUm9vdENBLmRlcjAdBgNVHQ4EFgQUImUM1lqdNInzg7SV\nUr9QGzknBqwwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQEwCgYI\nKoZIzj0EAwIDSQAwRgIhAOW/5QkR+S9CiSDcNoowLuPRLsWGf/Yi7GSX94BgwTwg\nAiEA4J0lrHoMs+Xo5o/sX6O9QWxHRAvZUGOdRQ7cvqRXaqI=\n-----END CERTIFICATE-----\n", - "qe_identity": "{\"id\":\"TD_QE\",\"version\":2,\"issueDate\":\"2026-03-23T09:17:59Z\",\"nextUpdate\":\"2026-04-22T09:17:59Z\",\"tcbEvaluationDataNumber\":18,\"miscselect\":\"00000000\",\"miscselectMask\":\"FFFFFFFF\",\"attributes\":\"11000000000000000000000000000000\",\"attributesMask\":\"FBFFFFFFFFFFFFFF0000000000000000\",\"mrsigner\":\"DC9E2A7C6F948F17474E34A7FC43ED030F7C1563F1BABDDF6340C82E0E54A8C5\",\"isvprodid\":2,\"tcbLevels\":[{\"tcb\":{\"isvsvn\":4},\"tcbDate\":\"2024-11-13T00:00:00Z\",\"tcbStatus\":\"UpToDate\"}]}", - "qe_identity_signature": "b481f03e03b44389f71fdf7a123dad2d5c75bd18e18155c775ddafe64b12dc29457075c0c84fe1155444bf940a34059774c2d3d26c8e600d591bf054a091b19a" + "qe_identity": "{\"id\":\"TD_QE\",\"version\":2,\"issueDate\":\"2026-03-25T09:18:51Z\",\"nextUpdate\":\"2026-04-24T09:18:51Z\",\"tcbEvaluationDataNumber\":18,\"miscselect\":\"00000000\",\"miscselectMask\":\"FFFFFFFF\",\"attributes\":\"11000000000000000000000000000000\",\"attributesMask\":\"FBFFFFFFFFFFFFFF0000000000000000\",\"mrsigner\":\"DC9E2A7C6F948F17474E34A7FC43ED030F7C1563F1BABDDF6340C82E0E54A8C5\",\"isvprodid\":2,\"tcbLevels\":[{\"tcb\":{\"isvsvn\":4},\"tcbDate\":\"2024-11-13T00:00:00Z\",\"tcbStatus\":\"UpToDate\"}]}", + "qe_identity_signature": "3794544b24b5880595ec5d59681a549e8c86263b2ef4d9ebd5ff3418a5ae87b562d12a59921b5ce4abda7d2b830f55e7285e22090ee706634ea1391351de5925" }, "tcb_info": { "mrtd": "f06dfda6dce1cf904d4e2bab1dc370634cf95cefa2ceb2de2eee127c9382698090d7a4a13e14c536ec6c9c3c8fa87077", "rtmr0": "e673be2f70beefb70b48a6109eed4715d7270d4683b3bf356fa25fafbf1aa76e39e9127e6e688ccda98bdab1d4d47f46", "rtmr1": "b598fde9491427341bc4683b75d10d3e36770af3a36a6954d8b6b7b22aa66358f13e1f172e51b7d6e6710d99a8d8532f", "rtmr2": "c812d42bfff1c75382e91a37c867ab117b97eb5e8d6797488928ea38e5fd38b5ed2f87d9613d392507f1c3af94657c93", - "rtmr3": "1df9222353d7c680e4692fd4e1c929cf55c3f02e0e5437c09da0ed4ec11473319cc7fba1404c3eef193aad3afbaee28b", + "rtmr3": "bfd1345bc9ecef80f380f6d619eb52893290312427237081b9765470109c597dffc98d4578588fbab210f2c97413c167", "os_image_hash": "7d47512fda31dc5a7318f72ae1869a3c76323981eea21fc30cafd0f79668642c", - "compose_hash": "05860c32c210b09b0a3957039f926bb3efafb00e11e6ce99cd7d823488fca8ef", + "compose_hash": "6c6037dc97767a0f37f4f21521a1c28a22922ff5a4478967948a94b6c7a7b591", "device_id": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", - "app_compose": "{\n \"manifest_version\": 2,\n \"name\": \"mpc-localnet-one-node-1774339569\",\n \"runner\": \"docker-compose\",\n \"docker_compose_file\": \"version: '3.8'\\n\\nservices:\\n launcher:\\n image: nearone/mpc-launcher@sha256:84c7537a2f84d3477eac2e5ef3ba0765b5d688f86096947eea4744ce25b27054\\n\\n container_name: launcher\\n\\n environment:\\n - PLATFORM=TEE\\n - DOCKER_CONTENT_TRUST=1\\n - DEFAULT_IMAGE_DIGEST=sha256:e2ef71c220158f9ee19a265d583647eedb4e0cd7ca37021fbf0ab34e3d214ed0\\n\\n volumes:\\n - /var/run/docker.sock:/var/run/docker.sock\\n - /var/run/dstack.sock:/var/run/dstack.sock\\n - /tapp:/tapp:ro\\n - shared-volume:/mnt/shared:ro\\n\\n security_opt:\\n - no-new-privileges:true\\n\\n read_only: true\\n\\n tmpfs:\\n - /tmp\\n\\nvolumes:\\n shared-volume:\\n name: shared-volume\\n\",\n \"kms_enabled\": false,\n \"gateway_enabled\": false,\n \"local_key_provider_enabled\": true,\n \"key_provider_id\": \"\",\n \"public_logs\": true,\n \"public_sysinfo\": true,\n \"allowed_envs\": [],\n \"no_instance_id\": true,\n \"secure_time\": false\n}", + "app_compose": "{\n \"manifest_version\": 2,\n \"name\": \"mpc-localnet-one-node-1774515652\",\n \"runner\": \"docker-compose\",\n \"docker_compose_file\": \"version: '3.8'\\n\\nservices:\\n launcher:\\n image: nearone/mpc-launcher@sha256:f0d8146ae705dad182f7e9601e6e97215be4cf94ce80b38fddb2df654020be49\\n\\n container_name: launcher\\n\\n environment:\\n - PLATFORM=TEE\\n - DOCKER_CONTENT_TRUST=1\\n - DEFAULT_IMAGE_DIGEST=sha256:6a5700fccbb3facddd1f3934f4976c4dcefc176c4aac28cd2fd035984b368980\\n\\n volumes:\\n - /var/run/docker.sock:/var/run/docker.sock\\n - /var/run/dstack.sock:/var/run/dstack.sock\\n - /tapp:/tapp:ro\\n - shared-volume:/mnt/shared:rw\\n\\n security_opt:\\n - no-new-privileges:true\\n\\n read_only: true\\n\\n tmpfs:\\n - /tmp\\n\\nvolumes:\\n shared-volume:\\n name: shared-volume\\n\",\n \"kms_enabled\": false,\n \"gateway_enabled\": false,\n \"local_key_provider_enabled\": true,\n \"key_provider_id\": \"\",\n \"public_logs\": true,\n \"public_sysinfo\": true,\n \"allowed_envs\": [],\n \"no_instance_id\": true,\n \"secure_time\": false\n}", "event_log": [ { "imr": 0, @@ -177,16 +177,16 @@ { "imr": 3, "event_type": 134217729, - "digest": "c24d38d25cce2d2f4def3765a97c348363f485d5c429c2c799d1596e1608a412a7edd654aeb494cecf3e37093b84bfe8", + "digest": "cdbd13eb98f8b2f83729abbd9a20cddd9053b9971802dda713c121babdb7d3dcb633f7506d8f99440f3f5d600afdf076", "event": "app-id", - "event_payload": "05860c32c210b09b0a3957039f926bb3efafb00e" + "event_payload": "6c6037dc97767a0f37f4f21521a1c28a22922ff5" }, { "imr": 3, "event_type": 134217729, - "digest": "e9a96dda81a262016807e428c4f8791681ae609c77df817f0a88fb63348174b91e2a9d09654dba5c569aa9b4623d82c8", + "digest": "e76932b68c12a11c217e235b5f16ce04eb70f0714501610986165f423b900974a05c0c8cc2d31aa6f7e9c986d945a168", "event": "compose-hash", - "event_payload": "05860c32c210b09b0a3957039f926bb3efafb00e11e6ce99cd7d823488fca8ef" + "event_payload": "6c6037dc97767a0f37f4f21521a1c28a22922ff5a4478967948a94b6c7a7b591" }, { "imr": 3, @@ -226,9 +226,9 @@ { "imr": 3, "event_type": 134217729, - "digest": "505922fbbd1de0732579ace623add381c1ef328ec64f73940ac12d3f9081163426126ba7c0a530241143bd3b8954a2d5", + "digest": "9aed81f5b1af85f768ef6873ed6f997f55f37de951cca18f5daa35890ab9e5573314d2e0cd188a6913dd4ab6f5455678", "event": "mpc-image-digest", - "event_payload": "e2ef71c220158f9ee19a265d583647eedb4e0cd7ca37021fbf0ab34e3d214ed0" + "event_payload": "6a5700fccbb3facddd1f3934f4976c4dcefc176c4aac28cd2fd035984b368980" } ] } diff --git a/crates/test-utils/assets/quote.json b/crates/test-utils/assets/quote.json index c77ffd430..274e929de 100644 --- a/crates/test-utils/assets/quote.json +++ b/crates/test-utils/assets/quote.json @@ -1 +1 @@ -[4,0,2,0,129,0,0,0,0,0,0,0,147,154,114,51,247,156,76,169,148,10,13,179,149,127,6,7,61,153,138,108,16,87,107,253,246,246,237,142,155,133,233,50,0,0,0,0,11,1,4,0,0,0,0,0,0,0,0,0,0,0,0,0,123,240,99,40,14,148,251,5,31,93,215,177,252,89,206,154,172,66,187,150,29,248,212,75,112,156,155,15,248,122,123,77,246,72,101,123,166,209,24,149,137,254,171,29,90,60,154,157,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,16,0,0,0,0,231,2,6,0,0,0,0,0,240,109,253,166,220,225,207,144,77,78,43,171,29,195,112,99,76,249,92,239,162,206,178,222,46,238,18,124,147,130,105,128,144,215,164,161,62,20,197,54,236,108,156,60,143,168,112,119,1,5,134,12,50,194,16,176,155,10,57,87,3,159,146,107,179,239,175,176,14,17,230,206,153,205,125,130,52,136,252,168,239,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,230,115,190,47,112,190,239,183,11,72,166,16,158,237,71,21,215,39,13,70,131,179,191,53,111,162,95,175,191,26,167,110,57,233,18,126,110,104,140,205,169,139,218,177,212,212,127,70,181,152,253,233,73,20,39,52,27,196,104,59,117,209,13,62,54,119,10,243,163,106,105,84,216,182,183,178,42,166,99,88,241,62,31,23,46,81,183,214,230,113,13,153,168,216,83,47,200,18,212,43,255,241,199,83,130,233,26,55,200,103,171,17,123,151,235,94,141,103,151,72,137,40,234,56,229,253,56,181,237,47,135,217,97,61,57,37,7,241,195,175,148,101,124,147,29,249,34,35,83,215,198,128,228,105,47,212,225,201,41,207,85,195,240,46,14,84,55,192,157,160,237,78,193,20,115,49,156,199,251,161,64,76,62,239,25,58,173,58,251,174,226,139,0,1,57,22,80,247,216,170,100,64,68,54,220,49,221,50,67,1,219,232,8,48,11,49,93,180,228,250,85,157,19,250,135,77,104,59,246,53,0,220,45,247,12,251,53,182,88,195,212,238,0,0,0,0,0,0,0,0,0,0,0,0,0,0,204,16,0,0,27,150,161,23,33,229,84,54,73,160,81,255,112,135,32,10,45,194,222,160,153,75,23,9,159,106,44,19,128,167,119,97,164,60,240,160,221,184,18,157,229,201,119,219,251,198,228,128,157,75,43,183,215,70,161,246,191,94,0,251,47,184,88,209,142,188,151,224,189,139,21,116,87,28,157,151,225,226,250,218,147,80,231,144,252,223,62,103,176,31,51,101,181,44,82,180,72,148,151,10,88,144,81,87,230,135,174,102,165,143,241,229,60,148,151,208,187,151,100,64,82,171,52,1,40,143,31,160,6,0,70,16,0,0,4,4,25,27,4,255,0,6,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,21,0,0,0,0,0,0,0,231,0,0,0,0,0,0,0,229,163,167,181,216,48,194,149,59,152,83,76,108,89,163,163,79,220,52,233,51,247,245,137,143,10,133,207,8,132,107,202,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,220,158,42,124,111,148,143,23,71,78,52,167,252,67,237,3,15,124,21,99,241,186,189,223,99,64,200,46,14,84,168,197,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,6,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,215,98,66,167,203,62,30,139,2,195,192,57,51,40,206,207,77,159,207,61,144,77,52,130,175,199,184,139,101,54,6,152,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,55,140,184,159,16,129,112,22,170,116,15,65,194,34,25,176,21,215,45,175,225,178,25,101,154,225,248,220,253,70,99,28,30,23,98,194,123,112,75,247,216,167,77,160,167,148,175,255,112,188,66,226,79,146,195,234,106,115,163,75,32,230,83,174,32,0,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,5,0,94,14,0,0,45,45,45,45,45,66,69,71,73,78,32,67,69,82,84,73,70,73,67,65,84,69,45,45,45,45,45,10,77,73,73,69,56,84,67,67,66,74,97,103,65,119,73,66,65,103,73,85,102,50,83,98,121,119,107,77,86,84,74,75,85,53,55,47,74,119,66,112,56,69,100,104,80,48,52,119,67,103,89,73,75,111,90,73,122,106,48,69,65,119,73,119,10,99,68,69,105,77,67,65,71,65,49,85,69,65,119,119,90,83,87,53,48,90,87,119,103,85,48,100,89,73,70,66,68,83,121,66,81,98,71,70,48,90,109,57,121,98,83,66,68,81,84,69,97,77,66,103,71,65,49,85,69,67,103,119,82,10,83,87,53,48,90,87,119,103,81,50,57,121,99,71,57,121,89,88,82,112,98,50,52,120,70,68,65,83,66,103,78,86,66,65,99,77,67,49,78,104,98,110,82,104,73,69,78,115,89,88,74,104,77,81,115,119,67,81,89,68,86,81,81,73,10,68,65,74,68,81,84,69,76,77,65,107,71,65,49,85,69,66,104,77,67,86,86,77,119,72,104,99,78,77,106,85,120,77,84,65,50,77,68,99,122,78,122,77,48,87,104,99,78,77,122,73,120,77,84,65,50,77,68,99,122,78,122,77,48,10,87,106,66,119,77,83,73,119,73,65,89,68,86,81,81,68,68,66,108,74,98,110,82,108,98,67,66,84,82,49,103,103,85,69,78,76,73,69,78,108,99,110,82,112,90,109,108,106,89,88,82,108,77,82,111,119,71,65,89,68,86,81,81,75,10,68,66,70,74,98,110,82,108,98,67,66,68,98,51,74,119,98,51,74,104,100,71,108,118,98,106,69,85,77,66,73,71,65,49,85,69,66,119,119,76,85,50,70,117,100,71,69,103,81,50,120,104,99,109,69,120,67,122,65,74,66,103,78,86,10,66,65,103,77,65,107,78,66,77,81,115,119,67,81,89,68,86,81,81,71,69,119,74,86,85,122,66,90,77,66,77,71,66,121,113,71,83,77,52,57,65,103,69,71,67,67,113,71,83,77,52,57,65,119,69,72,65,48,73,65,66,71,112,118,10,48,89,117,89,114,113,65,117,83,75,66,122,75,108,117,98,54,109,76,43,114,118,102,68,53,65,106,89,79,51,81,78,103,102,87,122,116,103,52,101,109,49,69,71,66,86,107,71,108,87,118,100,117,66,48,88,81,83,69,47,115,120,71,68,10,109,83,118,75,111,57,116,51,67,114,79,80,67,52,83,85,54,88,54,106,103,103,77,77,77,73,73,68,67,68,65,102,66,103,78,86,72,83,77,69,71,68,65,87,103,66,83,86,98,49,51,78,118,82,118,104,54,85,66,74,121,100,84,48,10,77,56,52,66,86,119,118,101,86,68,66,114,66,103,78,86,72,82,56,69,90,68,66,105,77,71,67,103,88,113,66,99,104,108,112,111,100,72,82,119,99,122,111,118,76,50,70,119,97,83,53,48,99,110,86,122,100,71,86,107,99,50,86,121,10,100,109,108,106,90,88,77,117,97,87,53,48,90,87,119,117,89,50,57,116,76,51,78,110,101,67,57,106,90,88,74,48,97,87,90,112,89,50,70,48,97,87,57,117,76,51,89,48,76,51,66,106,97,50,78,121,98,68,57,106,89,84,49,119,10,98,71,70,48,90,109,57,121,98,83,90,108,98,109,78,118,90,71,108,117,90,122,49,107,90,88,73,119,72,81,89,68,86,82,48,79,66,66,89,69,70,71,51,110,54,83,43,75,120,78,54,116,43,72,73,56,71,112,57,54,80,107,117,90,10,105,87,115,90,77,65,52,71,65,49,85,100,68,119,69,66,47,119,81,69,65,119,73,71,119,68,65,77,66,103,78,86,72,82,77,66,65,102,56,69,65,106,65,65,77,73,73,67,79,81,89,74,75,111,90,73,104,118,104,78,65,81,48,66,10,66,73,73,67,75,106,67,67,65,105,89,119,72,103,89,75,75,111,90,73,104,118,104,78,65,81,48,66,65,81,81,81,48,103,106,102,115,81,65,106,82,113,52,98,116,79,56,113,80,65,86,83,107,106,67,67,65,87,77,71,67,105,113,71,10,83,73,98,52,84,81,69,78,65,81,73,119,103,103,70,84,77,66,65,71,67,121,113,71,83,73,98,52,84,81,69,78,65,81,73,66,65,103,69,69,77,66,65,71,67,121,113,71,83,73,98,52,84,81,69,78,65,81,73,67,65,103,69,69,10,77,66,65,71,67,121,113,71,83,73,98,52,84,81,69,78,65,81,73,68,65,103,69,67,77,66,65,71,67,121,113,71,83,73,98,52,84,81,69,78,65,81,73,69,65,103,69,67,77,66,65,71,67,121,113,71,83,73,98,52,84,81,69,78,10,65,81,73,70,65,103,69,69,77,66,65,71,67,121,113,71,83,73,98,52,84,81,69,78,65,81,73,71,65,103,69,66,77,66,65,71,67,121,113,71,83,73,98,52,84,81,69,78,65,81,73,72,65,103,69,65,77,66,65,71,67,121,113,71,10,83,73,98,52,84,81,69,78,65,81,73,73,65,103,69,70,77,66,65,71,67,121,113,71,83,73,98,52,84,81,69,78,65,81,73,74,65,103,69,65,77,66,65,71,67,121,113,71,83,73,98,52,84,81,69,78,65,81,73,75,65,103,69,65,10,77,66,65,71,67,121,113,71,83,73,98,52,84,81,69,78,65,81,73,76,65,103,69,65,77,66,65,71,67,121,113,71,83,73,98,52,84,81,69,78,65,81,73,77,65,103,69,65,77,66,65,71,67,121,113,71,83,73,98,52,84,81,69,78,10,65,81,73,78,65,103,69,65,77,66,65,71,67,121,113,71,83,73,98,52,84,81,69,78,65,81,73,79,65,103,69,65,77,66,65,71,67,121,113,71,83,73,98,52,84,81,69,78,65,81,73,80,65,103,69,65,77,66,65,71,67,121,113,71,10,83,73,98,52,84,81,69,78,65,81,73,81,65,103,69,65,77,66,65,71,67,121,113,71,83,73,98,52,84,81,69,78,65,81,73,82,65,103,69,76,77,66,56,71,67,121,113,71,83,73,98,52,84,81,69,78,65,81,73,83,66,66,65,69,10,66,65,73,67,66,65,69,65,66,81,65,65,65,65,65,65,65,65,65,65,77,66,65,71,67,105,113,71,83,73,98,52,84,81,69,78,65,81,77,69,65,103,65,65,77,66,81,71,67,105,113,71,83,73,98,52,84,81,69,78,65,81,81,69,10,66,114,68,65,98,119,65,65,65,68,65,80,66,103,111,113,104,107,105,71,43,69,48,66,68,81,69,70,67,103,69,66,77,66,52,71,67,105,113,71,83,73,98,52,84,81,69,78,65,81,89,69,69,68,97,57,104,116,65,56,65,74,47,90,10,50,70,109,97,76,53,74,113,47,75,69,119,82,65,89,75,75,111,90,73,104,118,104,78,65,81,48,66,66,122,65,50,77,66,65,71,67,121,113,71,83,73,98,52,84,81,69,78,65,81,99,66,65,81,72,47,77,66,65,71,67,121,113,71,10,83,73,98,52,84,81,69,78,65,81,99,67,65,81,72,47,77,66,65,71,67,121,113,71,83,73,98,52,84,81,69,78,65,81,99,68,65,81,72,47,77,65,111,71,67,67,113,71,83,77,52,57,66,65,77,67,65,48,107,65,77,69,89,67,10,73,81,67,70,71,49,89,65,98,51,101,88,70,116,101,56,53,51,67,108,86,66,110,104,108,67,102,68,121,99,53,55,50,90,88,69,113,97,120,52,85,99,99,83,97,119,73,104,65,79,110,48,86,78,75,84,90,109,65,120,85,70,52,110,10,119,82,107,83,70,104,52,113,70,74,51,97,85,108,122,70,111,80,81,84,51,120,73,102,55,107,70,68,10,45,45,45,45,45,69,78,68,32,67,69,82,84,73,70,73,67,65,84,69,45,45,45,45,45,10,45,45,45,45,45,66,69,71,73,78,32,67,69,82,84,73,70,73,67,65,84,69,45,45,45,45,45,10,77,73,73,67,108,106,67,67,65,106,50,103,65,119,73,66,65,103,73,86,65,74,86,118,88,99,50,57,71,43,72,112,81,69,110,74,49,80,81,122,122,103,70,88,67,57,53,85,77,65,111,71,67,67,113,71,83,77,52,57,66,65,77,67,10,77,71,103,120,71,106,65,89,66,103,78,86,66,65,77,77,69,85,108,117,100,71,86,115,73,70,78,72,87,67,66,83,98,50,57,48,73,69,78,66,77,82,111,119,71,65,89,68,86,81,81,75,68,66,70,74,98,110,82,108,98,67,66,68,10,98,51,74,119,98,51,74,104,100,71,108,118,98,106,69,85,77,66,73,71,65,49,85,69,66,119,119,76,85,50,70,117,100,71,69,103,81,50,120,104,99,109,69,120,67,122,65,74,66,103,78,86,66,65,103,77,65,107,78,66,77,81,115,119,10,67,81,89,68,86,81,81,71,69,119,74,86,85,122,65,101,70,119,48,120,79,68,65,49,77,106,69,120,77,68,85,119,77,84,66,97,70,119,48,122,77,122,65,49,77,106,69,120,77,68,85,119,77,84,66,97,77,72,65,120,73,106,65,103,10,66,103,78,86,66,65,77,77,71,85,108,117,100,71,86,115,73,70,78,72,87,67,66,81,81,48,115,103,85,71,120,104,100,71,90,118,99,109,48,103,81,48,69,120,71,106,65,89,66,103,78,86,66,65,111,77,69,85,108,117,100,71,86,115,10,73,69,78,118,99,110,66,118,99,109,70,48,97,87,57,117,77,82,81,119,69,103,89,68,86,81,81,72,68,65,116,84,89,87,53,48,89,83,66,68,98,71,70,121,89,84,69,76,77,65,107,71,65,49,85,69,67,65,119,67,81,48,69,120,10,67,122,65,74,66,103,78,86,66,65,89,84,65,108,86,84,77,70,107,119,69,119,89,72,75,111,90,73,122,106,48,67,65,81,89,73,75,111,90,73,122,106,48,68,65,81,99,68,81,103,65,69,78,83,66,47,55,116,50,49,108,88,83,79,10,50,67,117,122,112,120,119,55,52,101,74,66,55,50,69,121,68,71,103,87,53,114,88,67,116,120,50,116,86,84,76,113,54,104,75,107,54,122,43,85,105,82,90,67,110,113,82,55,112,115,79,118,103,113,70,101,83,120,108,109,84,108,74,108,10,101,84,109,105,50,87,89,122,51,113,79,66,117,122,67,66,117,68,65,102,66,103,78,86,72,83,77,69,71,68,65,87,103,66,81,105,90,81,122,87,87,112,48,48,105,102,79,68,116,74,86,83,118,49,65,98,79,83,99,71,114,68,66,83,10,66,103,78,86,72,82,56,69,83,122,66,74,77,69,101,103,82,97,66,68,104,107,70,111,100,72,82,119,99,122,111,118,76,50,78,108,99,110,82,112,90,109,108,106,89,88,82,108,99,121,53,48,99,110,86,122,100,71,86,107,99,50,86,121,10,100,109,108,106,90,88,77,117,97,87,53,48,90,87,119,117,89,50,57,116,76,48,108,117,100,71,86,115,85,48,100,89,85,109,57,118,100,69,78,66,76,109,82,108,99,106,65,100,66,103,78,86,72,81,52,69,70,103,81,85,108,87,57,100,10,122,98,48,98,52,101,108,65,83,99,110,85,57,68,80,79,65,86,99,76,51,108,81,119,68,103,89,68,86,82,48,80,65,81,72,47,66,65,81,68,65,103,69,71,77,66,73,71,65,49,85,100,69,119,69,66,47,119,81,73,77,65,89,66,10,65,102,56,67,65,81,65,119,67,103,89,73,75,111,90,73,122,106,48,69,65,119,73,68,82,119,65,119,82,65,73,103,88,115,86,107,105,48,119,43,105,54,86,89,71,87,51,85,70,47,50,50,117,97,88,101,48,89,74,68,106,49,85,101,10,110,65,43,84,106,68,49,97,105,53,99,67,73,67,89,98,49,83,65,109,68,53,120,107,102,84,86,112,118,111,52,85,111,121,105,83,89,120,114,68,87,76,109,85,82,52,67,73,57,78,75,121,102,80,78,43,10,45,45,45,45,45,69,78,68,32,67,69,82,84,73,70,73,67,65,84,69,45,45,45,45,45,10,45,45,45,45,45,66,69,71,73,78,32,67,69,82,84,73,70,73,67,65,84,69,45,45,45,45,45,10,77,73,73,67,106,122,67,67,65,106,83,103,65,119,73,66,65,103,73,85,73,109,85,77,49,108,113,100,78,73,110,122,103,55,83,86,85,114,57,81,71,122,107,110,66,113,119,119,67,103,89,73,75,111,90,73,122,106,48,69,65,119,73,119,10,97,68,69,97,77,66,103,71,65,49,85,69,65,119,119,82,83,87,53,48,90,87,119,103,85,48,100,89,73,70,74,118,98,51,81,103,81,48,69,120,71,106,65,89,66,103,78,86,66,65,111,77,69,85,108,117,100,71,86,115,73,69,78,118,10,99,110,66,118,99,109,70,48,97,87,57,117,77,82,81,119,69,103,89,68,86,81,81,72,68,65,116,84,89,87,53,48,89,83,66,68,98,71,70,121,89,84,69,76,77,65,107,71,65,49,85,69,67,65,119,67,81,48,69,120,67,122,65,74,10,66,103,78,86,66,65,89,84,65,108,86,84,77,66,52,88,68,84,69,52,77,68,85,121,77,84,69,119,78,68,85,120,77,70,111,88,68,84,81,53,77,84,73,122,77,84,73,122,78,84,107,49,79,86,111,119,97,68,69,97,77,66,103,71,10,65,49,85,69,65,119,119,82,83,87,53,48,90,87,119,103,85,48,100,89,73,70,74,118,98,51,81,103,81,48,69,120,71,106,65,89,66,103,78,86,66,65,111,77,69,85,108,117,100,71,86,115,73,69,78,118,99,110,66,118,99,109,70,48,10,97,87,57,117,77,82,81,119,69,103,89,68,86,81,81,72,68,65,116,84,89,87,53,48,89,83,66,68,98,71,70,121,89,84,69,76,77,65,107,71,65,49,85,69,67,65,119,67,81,48,69,120,67,122,65,74,66,103,78,86,66,65,89,84,10,65,108,86,84,77,70,107,119,69,119,89,72,75,111,90,73,122,106,48,67,65,81,89,73,75,111,90,73,122,106,48,68,65,81,99,68,81,103,65,69,67,54,110,69,119,77,68,73,89,90,79,106,47,105,80,87,115,67,122,97,69,75,105,55,10,49,79,105,79,83,76,82,70,104,87,71,106,98,110,66,86,74,102,86,110,107,89,52,117,51,73,106,107,68,89,89,76,48,77,120,79,52,109,113,115,121,89,106,108,66,97,108,84,86,89,120,70,80,50,115,74,66,75,53,122,108,75,79,66,10,117,122,67,66,117,68,65,102,66,103,78,86,72,83,77,69,71,68,65,87,103,66,81,105,90,81,122,87,87,112,48,48,105,102,79,68,116,74,86,83,118,49,65,98,79,83,99,71,114,68,66,83,66,103,78,86,72,82,56,69,83,122,66,74,10,77,69,101,103,82,97,66,68,104,107,70,111,100,72,82,119,99,122,111,118,76,50,78,108,99,110,82,112,90,109,108,106,89,88,82,108,99,121,53,48,99,110,86,122,100,71,86,107,99,50,86,121,100,109,108,106,90,88,77,117,97,87,53,48,10,90,87,119,117,89,50,57,116,76,48,108,117,100,71,86,115,85,48,100,89,85,109,57,118,100,69,78,66,76,109,82,108,99,106,65,100,66,103,78,86,72,81,52,69,70,103,81,85,73,109,85,77,49,108,113,100,78,73,110,122,103,55,83,86,10,85,114,57,81,71,122,107,110,66,113,119,119,68,103,89,68,86,82,48,80,65,81,72,47,66,65,81,68,65,103,69,71,77,66,73,71,65,49,85,100,69,119,69,66,47,119,81,73,77,65,89,66,65,102,56,67,65,81,69,119,67,103,89,73,10,75,111,90,73,122,106,48,69,65,119,73,68,83,81,65,119,82,103,73,104,65,79,87,47,53,81,107,82,43,83,57,67,105,83,68,99,78,111,111,119,76,117,80,82,76,115,87,71,102,47,89,105,55,71,83,88,57,52,66,103,119,84,119,103,10,65,105,69,65,52,74,48,108,114,72,111,77,115,43,88,111,53,111,47,115,88,54,79,57,81,87,120,72,82,65,118,90,85,71,79,100,82,81,55,99,118,113,82,88,97,113,73,61,10,45,45,45,45,45,69,78,68,32,67,69,82,84,73,70,73,67,65,84,69,45,45,45,45,45,10,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0] +[4,0,2,0,129,0,0,0,0,0,0,0,147,154,114,51,247,156,76,169,148,10,13,179,149,127,6,7,61,153,138,108,16,87,107,253,246,246,237,142,155,133,233,50,0,0,0,0,11,1,4,0,0,0,0,0,0,0,0,0,0,0,0,0,123,240,99,40,14,148,251,5,31,93,215,177,252,89,206,154,172,66,187,150,29,248,212,75,112,156,155,15,248,122,123,77,246,72,101,123,166,209,24,149,137,254,171,29,90,60,154,157,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,16,0,0,0,0,231,2,6,0,0,0,0,0,240,109,253,166,220,225,207,144,77,78,43,171,29,195,112,99,76,249,92,239,162,206,178,222,46,238,18,124,147,130,105,128,144,215,164,161,62,20,197,54,236,108,156,60,143,168,112,119,1,108,96,55,220,151,118,122,15,55,244,242,21,33,161,194,138,34,146,47,245,164,71,137,103,148,138,148,182,199,167,181,145,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,230,115,190,47,112,190,239,183,11,72,166,16,158,237,71,21,215,39,13,70,131,179,191,53,111,162,95,175,191,26,167,110,57,233,18,126,110,104,140,205,169,139,218,177,212,212,127,70,181,152,253,233,73,20,39,52,27,196,104,59,117,209,13,62,54,119,10,243,163,106,105,84,216,182,183,178,42,166,99,88,241,62,31,23,46,81,183,214,230,113,13,153,168,216,83,47,200,18,212,43,255,241,199,83,130,233,26,55,200,103,171,17,123,151,235,94,141,103,151,72,137,40,234,56,229,253,56,181,237,47,135,217,97,61,57,37,7,241,195,175,148,101,124,147,191,209,52,91,201,236,239,128,243,128,246,214,25,235,82,137,50,144,49,36,39,35,112,129,185,118,84,112,16,156,89,125,255,201,141,69,120,88,143,186,178,16,242,201,116,19,193,103,0,1,225,54,47,82,46,167,98,107,53,88,77,20,225,20,20,11,241,205,0,151,168,2,212,236,75,111,229,21,92,99,130,22,179,191,23,141,101,150,56,32,138,209,156,158,191,75,33,230,0,0,0,0,0,0,0,0,0,0,0,0,0,0,204,16,0,0,220,8,78,19,149,230,23,145,248,199,226,120,77,133,123,218,76,10,193,178,70,101,24,218,144,32,68,87,142,241,105,5,57,43,51,250,76,82,76,59,191,172,71,74,127,89,234,221,234,87,227,180,54,227,172,13,41,88,142,114,21,157,72,199,142,188,151,224,189,139,21,116,87,28,157,151,225,226,250,218,147,80,231,144,252,223,62,103,176,31,51,101,181,44,82,180,72,148,151,10,88,144,81,87,230,135,174,102,165,143,241,229,60,148,151,208,187,151,100,64,82,171,52,1,40,143,31,160,6,0,70,16,0,0,4,4,25,27,4,255,0,6,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,21,0,0,0,0,0,0,0,231,0,0,0,0,0,0,0,229,163,167,181,216,48,194,149,59,152,83,76,108,89,163,163,79,220,52,233,51,247,245,137,143,10,133,207,8,132,107,202,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,220,158,42,124,111,148,143,23,71,78,52,167,252,67,237,3,15,124,21,99,241,186,189,223,99,64,200,46,14,84,168,197,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,6,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,215,98,66,167,203,62,30,139,2,195,192,57,51,40,206,207,77,159,207,61,144,77,52,130,175,199,184,139,101,54,6,152,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,10,1,121,119,53,12,242,234,97,244,139,210,240,161,221,87,50,53,22,62,187,93,234,174,243,37,140,227,92,65,153,44,120,88,139,241,175,11,63,135,233,6,77,16,70,191,18,226,109,1,37,18,132,34,76,170,41,121,69,128,18,239,72,153,32,0,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,5,0,94,14,0,0,45,45,45,45,45,66,69,71,73,78,32,67,69,82,84,73,70,73,67,65,84,69,45,45,45,45,45,10,77,73,73,69,56,84,67,67,66,74,97,103,65,119,73,66,65,103,73,85,102,50,83,98,121,119,107,77,86,84,74,75,85,53,55,47,74,119,66,112,56,69,100,104,80,48,52,119,67,103,89,73,75,111,90,73,122,106,48,69,65,119,73,119,10,99,68,69,105,77,67,65,71,65,49,85,69,65,119,119,90,83,87,53,48,90,87,119,103,85,48,100,89,73,70,66,68,83,121,66,81,98,71,70,48,90,109,57,121,98,83,66,68,81,84,69,97,77,66,103,71,65,49,85,69,67,103,119,82,10,83,87,53,48,90,87,119,103,81,50,57,121,99,71,57,121,89,88,82,112,98,50,52,120,70,68,65,83,66,103,78,86,66,65,99,77,67,49,78,104,98,110,82,104,73,69,78,115,89,88,74,104,77,81,115,119,67,81,89,68,86,81,81,73,10,68,65,74,68,81,84,69,76,77,65,107,71,65,49,85,69,66,104,77,67,86,86,77,119,72,104,99,78,77,106,85,120,77,84,65,50,77,68,99,122,78,122,77,48,87,104,99,78,77,122,73,120,77,84,65,50,77,68,99,122,78,122,77,48,10,87,106,66,119,77,83,73,119,73,65,89,68,86,81,81,68,68,66,108,74,98,110,82,108,98,67,66,84,82,49,103,103,85,69,78,76,73,69,78,108,99,110,82,112,90,109,108,106,89,88,82,108,77,82,111,119,71,65,89,68,86,81,81,75,10,68,66,70,74,98,110,82,108,98,67,66,68,98,51,74,119,98,51,74,104,100,71,108,118,98,106,69,85,77,66,73,71,65,49,85,69,66,119,119,76,85,50,70,117,100,71,69,103,81,50,120,104,99,109,69,120,67,122,65,74,66,103,78,86,10,66,65,103,77,65,107,78,66,77,81,115,119,67,81,89,68,86,81,81,71,69,119,74,86,85,122,66,90,77,66,77,71,66,121,113,71,83,77,52,57,65,103,69,71,67,67,113,71,83,77,52,57,65,119,69,72,65,48,73,65,66,71,112,118,10,48,89,117,89,114,113,65,117,83,75,66,122,75,108,117,98,54,109,76,43,114,118,102,68,53,65,106,89,79,51,81,78,103,102,87,122,116,103,52,101,109,49,69,71,66,86,107,71,108,87,118,100,117,66,48,88,81,83,69,47,115,120,71,68,10,109,83,118,75,111,57,116,51,67,114,79,80,67,52,83,85,54,88,54,106,103,103,77,77,77,73,73,68,67,68,65,102,66,103,78,86,72,83,77,69,71,68,65,87,103,66,83,86,98,49,51,78,118,82,118,104,54,85,66,74,121,100,84,48,10,77,56,52,66,86,119,118,101,86,68,66,114,66,103,78,86,72,82,56,69,90,68,66,105,77,71,67,103,88,113,66,99,104,108,112,111,100,72,82,119,99,122,111,118,76,50,70,119,97,83,53,48,99,110,86,122,100,71,86,107,99,50,86,121,10,100,109,108,106,90,88,77,117,97,87,53,48,90,87,119,117,89,50,57,116,76,51,78,110,101,67,57,106,90,88,74,48,97,87,90,112,89,50,70,48,97,87,57,117,76,51,89,48,76,51,66,106,97,50,78,121,98,68,57,106,89,84,49,119,10,98,71,70,48,90,109,57,121,98,83,90,108,98,109,78,118,90,71,108,117,90,122,49,107,90,88,73,119,72,81,89,68,86,82,48,79,66,66,89,69,70,71,51,110,54,83,43,75,120,78,54,116,43,72,73,56,71,112,57,54,80,107,117,90,10,105,87,115,90,77,65,52,71,65,49,85,100,68,119,69,66,47,119,81,69,65,119,73,71,119,68,65,77,66,103,78,86,72,82,77,66,65,102,56,69,65,106,65,65,77,73,73,67,79,81,89,74,75,111,90,73,104,118,104,78,65,81,48,66,10,66,73,73,67,75,106,67,67,65,105,89,119,72,103,89,75,75,111,90,73,104,118,104,78,65,81,48,66,65,81,81,81,48,103,106,102,115,81,65,106,82,113,52,98,116,79,56,113,80,65,86,83,107,106,67,67,65,87,77,71,67,105,113,71,10,83,73,98,52,84,81,69,78,65,81,73,119,103,103,70,84,77,66,65,71,67,121,113,71,83,73,98,52,84,81,69,78,65,81,73,66,65,103,69,69,77,66,65,71,67,121,113,71,83,73,98,52,84,81,69,78,65,81,73,67,65,103,69,69,10,77,66,65,71,67,121,113,71,83,73,98,52,84,81,69,78,65,81,73,68,65,103,69,67,77,66,65,71,67,121,113,71,83,73,98,52,84,81,69,78,65,81,73,69,65,103,69,67,77,66,65,71,67,121,113,71,83,73,98,52,84,81,69,78,10,65,81,73,70,65,103,69,69,77,66,65,71,67,121,113,71,83,73,98,52,84,81,69,78,65,81,73,71,65,103,69,66,77,66,65,71,67,121,113,71,83,73,98,52,84,81,69,78,65,81,73,72,65,103,69,65,77,66,65,71,67,121,113,71,10,83,73,98,52,84,81,69,78,65,81,73,73,65,103,69,70,77,66,65,71,67,121,113,71,83,73,98,52,84,81,69,78,65,81,73,74,65,103,69,65,77,66,65,71,67,121,113,71,83,73,98,52,84,81,69,78,65,81,73,75,65,103,69,65,10,77,66,65,71,67,121,113,71,83,73,98,52,84,81,69,78,65,81,73,76,65,103,69,65,77,66,65,71,67,121,113,71,83,73,98,52,84,81,69,78,65,81,73,77,65,103,69,65,77,66,65,71,67,121,113,71,83,73,98,52,84,81,69,78,10,65,81,73,78,65,103,69,65,77,66,65,71,67,121,113,71,83,73,98,52,84,81,69,78,65,81,73,79,65,103,69,65,77,66,65,71,67,121,113,71,83,73,98,52,84,81,69,78,65,81,73,80,65,103,69,65,77,66,65,71,67,121,113,71,10,83,73,98,52,84,81,69,78,65,81,73,81,65,103,69,65,77,66,65,71,67,121,113,71,83,73,98,52,84,81,69,78,65,81,73,82,65,103,69,76,77,66,56,71,67,121,113,71,83,73,98,52,84,81,69,78,65,81,73,83,66,66,65,69,10,66,65,73,67,66,65,69,65,66,81,65,65,65,65,65,65,65,65,65,65,77,66,65,71,67,105,113,71,83,73,98,52,84,81,69,78,65,81,77,69,65,103,65,65,77,66,81,71,67,105,113,71,83,73,98,52,84,81,69,78,65,81,81,69,10,66,114,68,65,98,119,65,65,65,68,65,80,66,103,111,113,104,107,105,71,43,69,48,66,68,81,69,70,67,103,69,66,77,66,52,71,67,105,113,71,83,73,98,52,84,81,69,78,65,81,89,69,69,68,97,57,104,116,65,56,65,74,47,90,10,50,70,109,97,76,53,74,113,47,75,69,119,82,65,89,75,75,111,90,73,104,118,104,78,65,81,48,66,66,122,65,50,77,66,65,71,67,121,113,71,83,73,98,52,84,81,69,78,65,81,99,66,65,81,72,47,77,66,65,71,67,121,113,71,10,83,73,98,52,84,81,69,78,65,81,99,67,65,81,72,47,77,66,65,71,67,121,113,71,83,73,98,52,84,81,69,78,65,81,99,68,65,81,72,47,77,65,111,71,67,67,113,71,83,77,52,57,66,65,77,67,65,48,107,65,77,69,89,67,10,73,81,67,70,71,49,89,65,98,51,101,88,70,116,101,56,53,51,67,108,86,66,110,104,108,67,102,68,121,99,53,55,50,90,88,69,113,97,120,52,85,99,99,83,97,119,73,104,65,79,110,48,86,78,75,84,90,109,65,120,85,70,52,110,10,119,82,107,83,70,104,52,113,70,74,51,97,85,108,122,70,111,80,81,84,51,120,73,102,55,107,70,68,10,45,45,45,45,45,69,78,68,32,67,69,82,84,73,70,73,67,65,84,69,45,45,45,45,45,10,45,45,45,45,45,66,69,71,73,78,32,67,69,82,84,73,70,73,67,65,84,69,45,45,45,45,45,10,77,73,73,67,108,106,67,67,65,106,50,103,65,119,73,66,65,103,73,86,65,74,86,118,88,99,50,57,71,43,72,112,81,69,110,74,49,80,81,122,122,103,70,88,67,57,53,85,77,65,111,71,67,67,113,71,83,77,52,57,66,65,77,67,10,77,71,103,120,71,106,65,89,66,103,78,86,66,65,77,77,69,85,108,117,100,71,86,115,73,70,78,72,87,67,66,83,98,50,57,48,73,69,78,66,77,82,111,119,71,65,89,68,86,81,81,75,68,66,70,74,98,110,82,108,98,67,66,68,10,98,51,74,119,98,51,74,104,100,71,108,118,98,106,69,85,77,66,73,71,65,49,85,69,66,119,119,76,85,50,70,117,100,71,69,103,81,50,120,104,99,109,69,120,67,122,65,74,66,103,78,86,66,65,103,77,65,107,78,66,77,81,115,119,10,67,81,89,68,86,81,81,71,69,119,74,86,85,122,65,101,70,119,48,120,79,68,65,49,77,106,69,120,77,68,85,119,77,84,66,97,70,119,48,122,77,122,65,49,77,106,69,120,77,68,85,119,77,84,66,97,77,72,65,120,73,106,65,103,10,66,103,78,86,66,65,77,77,71,85,108,117,100,71,86,115,73,70,78,72,87,67,66,81,81,48,115,103,85,71,120,104,100,71,90,118,99,109,48,103,81,48,69,120,71,106,65,89,66,103,78,86,66,65,111,77,69,85,108,117,100,71,86,115,10,73,69,78,118,99,110,66,118,99,109,70,48,97,87,57,117,77,82,81,119,69,103,89,68,86,81,81,72,68,65,116,84,89,87,53,48,89,83,66,68,98,71,70,121,89,84,69,76,77,65,107,71,65,49,85,69,67,65,119,67,81,48,69,120,10,67,122,65,74,66,103,78,86,66,65,89,84,65,108,86,84,77,70,107,119,69,119,89,72,75,111,90,73,122,106,48,67,65,81,89,73,75,111,90,73,122,106,48,68,65,81,99,68,81,103,65,69,78,83,66,47,55,116,50,49,108,88,83,79,10,50,67,117,122,112,120,119,55,52,101,74,66,55,50,69,121,68,71,103,87,53,114,88,67,116,120,50,116,86,84,76,113,54,104,75,107,54,122,43,85,105,82,90,67,110,113,82,55,112,115,79,118,103,113,70,101,83,120,108,109,84,108,74,108,10,101,84,109,105,50,87,89,122,51,113,79,66,117,122,67,66,117,68,65,102,66,103,78,86,72,83,77,69,71,68,65,87,103,66,81,105,90,81,122,87,87,112,48,48,105,102,79,68,116,74,86,83,118,49,65,98,79,83,99,71,114,68,66,83,10,66,103,78,86,72,82,56,69,83,122,66,74,77,69,101,103,82,97,66,68,104,107,70,111,100,72,82,119,99,122,111,118,76,50,78,108,99,110,82,112,90,109,108,106,89,88,82,108,99,121,53,48,99,110,86,122,100,71,86,107,99,50,86,121,10,100,109,108,106,90,88,77,117,97,87,53,48,90,87,119,117,89,50,57,116,76,48,108,117,100,71,86,115,85,48,100,89,85,109,57,118,100,69,78,66,76,109,82,108,99,106,65,100,66,103,78,86,72,81,52,69,70,103,81,85,108,87,57,100,10,122,98,48,98,52,101,108,65,83,99,110,85,57,68,80,79,65,86,99,76,51,108,81,119,68,103,89,68,86,82,48,80,65,81,72,47,66,65,81,68,65,103,69,71,77,66,73,71,65,49,85,100,69,119,69,66,47,119,81,73,77,65,89,66,10,65,102,56,67,65,81,65,119,67,103,89,73,75,111,90,73,122,106,48,69,65,119,73,68,82,119,65,119,82,65,73,103,88,115,86,107,105,48,119,43,105,54,86,89,71,87,51,85,70,47,50,50,117,97,88,101,48,89,74,68,106,49,85,101,10,110,65,43,84,106,68,49,97,105,53,99,67,73,67,89,98,49,83,65,109,68,53,120,107,102,84,86,112,118,111,52,85,111,121,105,83,89,120,114,68,87,76,109,85,82,52,67,73,57,78,75,121,102,80,78,43,10,45,45,45,45,45,69,78,68,32,67,69,82,84,73,70,73,67,65,84,69,45,45,45,45,45,10,45,45,45,45,45,66,69,71,73,78,32,67,69,82,84,73,70,73,67,65,84,69,45,45,45,45,45,10,77,73,73,67,106,122,67,67,65,106,83,103,65,119,73,66,65,103,73,85,73,109,85,77,49,108,113,100,78,73,110,122,103,55,83,86,85,114,57,81,71,122,107,110,66,113,119,119,67,103,89,73,75,111,90,73,122,106,48,69,65,119,73,119,10,97,68,69,97,77,66,103,71,65,49,85,69,65,119,119,82,83,87,53,48,90,87,119,103,85,48,100,89,73,70,74,118,98,51,81,103,81,48,69,120,71,106,65,89,66,103,78,86,66,65,111,77,69,85,108,117,100,71,86,115,73,69,78,118,10,99,110,66,118,99,109,70,48,97,87,57,117,77,82,81,119,69,103,89,68,86,81,81,72,68,65,116,84,89,87,53,48,89,83,66,68,98,71,70,121,89,84,69,76,77,65,107,71,65,49,85,69,67,65,119,67,81,48,69,120,67,122,65,74,10,66,103,78,86,66,65,89,84,65,108,86,84,77,66,52,88,68,84,69,52,77,68,85,121,77,84,69,119,78,68,85,120,77,70,111,88,68,84,81,53,77,84,73,122,77,84,73,122,78,84,107,49,79,86,111,119,97,68,69,97,77,66,103,71,10,65,49,85,69,65,119,119,82,83,87,53,48,90,87,119,103,85,48,100,89,73,70,74,118,98,51,81,103,81,48,69,120,71,106,65,89,66,103,78,86,66,65,111,77,69,85,108,117,100,71,86,115,73,69,78,118,99,110,66,118,99,109,70,48,10,97,87,57,117,77,82,81,119,69,103,89,68,86,81,81,72,68,65,116,84,89,87,53,48,89,83,66,68,98,71,70,121,89,84,69,76,77,65,107,71,65,49,85,69,67,65,119,67,81,48,69,120,67,122,65,74,66,103,78,86,66,65,89,84,10,65,108,86,84,77,70,107,119,69,119,89,72,75,111,90,73,122,106,48,67,65,81,89,73,75,111,90,73,122,106,48,68,65,81,99,68,81,103,65,69,67,54,110,69,119,77,68,73,89,90,79,106,47,105,80,87,115,67,122,97,69,75,105,55,10,49,79,105,79,83,76,82,70,104,87,71,106,98,110,66,86,74,102,86,110,107,89,52,117,51,73,106,107,68,89,89,76,48,77,120,79,52,109,113,115,121,89,106,108,66,97,108,84,86,89,120,70,80,50,115,74,66,75,53,122,108,75,79,66,10,117,122,67,66,117,68,65,102,66,103,78,86,72,83,77,69,71,68,65,87,103,66,81,105,90,81,122,87,87,112,48,48,105,102,79,68,116,74,86,83,118,49,65,98,79,83,99,71,114,68,66,83,66,103,78,86,72,82,56,69,83,122,66,74,10,77,69,101,103,82,97,66,68,104,107,70,111,100,72,82,119,99,122,111,118,76,50,78,108,99,110,82,112,90,109,108,106,89,88,82,108,99,121,53,48,99,110,86,122,100,71,86,107,99,50,86,121,100,109,108,106,90,88,77,117,97,87,53,48,10,90,87,119,117,89,50,57,116,76,48,108,117,100,71,86,115,85,48,100,89,85,109,57,118,100,69,78,66,76,109,82,108,99,106,65,100,66,103,78,86,72,81,52,69,70,103,81,85,73,109,85,77,49,108,113,100,78,73,110,122,103,55,83,86,10,85,114,57,81,71,122,107,110,66,113,119,119,68,103,89,68,86,82,48,80,65,81,72,47,66,65,81,68,65,103,69,71,77,66,73,71,65,49,85,100,69,119,69,66,47,119,81,73,77,65,89,66,65,102,56,67,65,81,69,119,67,103,89,73,10,75,111,90,73,122,106,48,69,65,119,73,68,83,81,65,119,82,103,73,104,65,79,87,47,53,81,107,82,43,83,57,67,105,83,68,99,78,111,111,119,76,117,80,82,76,115,87,71,102,47,89,105,55,71,83,88,57,52,66,103,119,84,119,103,10,65,105,69,65,52,74,48,108,114,72,111,77,115,43,88,111,53,111,47,115,88,54,79,57,81,87,120,72,82,65,118,90,85,71,79,100,82,81,55,99,118,113,82,88,97,113,73,61,10,45,45,45,45,45,69,78,68,32,67,69,82,84,73,70,73,67,65,84,69,45,45,45,45,45,10,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0] diff --git a/crates/test-utils/assets/tcb_info.json b/crates/test-utils/assets/tcb_info.json index eb6dd57d8..3fa3fd841 100644 --- a/crates/test-utils/assets/tcb_info.json +++ b/crates/test-utils/assets/tcb_info.json @@ -3,11 +3,11 @@ "rtmr0": "e673be2f70beefb70b48a6109eed4715d7270d4683b3bf356fa25fafbf1aa76e39e9127e6e688ccda98bdab1d4d47f46", "rtmr1": "b598fde9491427341bc4683b75d10d3e36770af3a36a6954d8b6b7b22aa66358f13e1f172e51b7d6e6710d99a8d8532f", "rtmr2": "c812d42bfff1c75382e91a37c867ab117b97eb5e8d6797488928ea38e5fd38b5ed2f87d9613d392507f1c3af94657c93", - "rtmr3": "1df9222353d7c680e4692fd4e1c929cf55c3f02e0e5437c09da0ed4ec11473319cc7fba1404c3eef193aad3afbaee28b", + "rtmr3": "bfd1345bc9ecef80f380f6d619eb52893290312427237081b9765470109c597dffc98d4578588fbab210f2c97413c167", "os_image_hash": "7d47512fda31dc5a7318f72ae1869a3c76323981eea21fc30cafd0f79668642c", - "compose_hash": "05860c32c210b09b0a3957039f926bb3efafb00e11e6ce99cd7d823488fca8ef", + "compose_hash": "6c6037dc97767a0f37f4f21521a1c28a22922ff5a4478967948a94b6c7a7b591", "device_id": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", - "app_compose": "{\n \"manifest_version\": 2,\n \"name\": \"mpc-localnet-one-node-1774339569\",\n \"runner\": \"docker-compose\",\n \"docker_compose_file\": \"version: '3.8'\\n\\nservices:\\n launcher:\\n image: nearone/mpc-launcher@sha256:84c7537a2f84d3477eac2e5ef3ba0765b5d688f86096947eea4744ce25b27054\\n\\n container_name: launcher\\n\\n environment:\\n - PLATFORM=TEE\\n - DOCKER_CONTENT_TRUST=1\\n - DEFAULT_IMAGE_DIGEST=sha256:e2ef71c220158f9ee19a265d583647eedb4e0cd7ca37021fbf0ab34e3d214ed0\\n\\n volumes:\\n - /var/run/docker.sock:/var/run/docker.sock\\n - /var/run/dstack.sock:/var/run/dstack.sock\\n - /tapp:/tapp:ro\\n - shared-volume:/mnt/shared:ro\\n\\n security_opt:\\n - no-new-privileges:true\\n\\n read_only: true\\n\\n tmpfs:\\n - /tmp\\n\\nvolumes:\\n shared-volume:\\n name: shared-volume\\n\",\n \"kms_enabled\": false,\n \"gateway_enabled\": false,\n \"local_key_provider_enabled\": true,\n \"key_provider_id\": \"\",\n \"public_logs\": true,\n \"public_sysinfo\": true,\n \"allowed_envs\": [],\n \"no_instance_id\": true,\n \"secure_time\": false\n}", + "app_compose": "{\n \"manifest_version\": 2,\n \"name\": \"mpc-localnet-one-node-1774515652\",\n \"runner\": \"docker-compose\",\n \"docker_compose_file\": \"version: '3.8'\\n\\nservices:\\n launcher:\\n image: nearone/mpc-launcher@sha256:f0d8146ae705dad182f7e9601e6e97215be4cf94ce80b38fddb2df654020be49\\n\\n container_name: launcher\\n\\n environment:\\n - PLATFORM=TEE\\n - DOCKER_CONTENT_TRUST=1\\n - DEFAULT_IMAGE_DIGEST=sha256:6a5700fccbb3facddd1f3934f4976c4dcefc176c4aac28cd2fd035984b368980\\n\\n volumes:\\n - /var/run/docker.sock:/var/run/docker.sock\\n - /var/run/dstack.sock:/var/run/dstack.sock\\n - /tapp:/tapp:ro\\n - shared-volume:/mnt/shared:rw\\n\\n security_opt:\\n - no-new-privileges:true\\n\\n read_only: true\\n\\n tmpfs:\\n - /tmp\\n\\nvolumes:\\n shared-volume:\\n name: shared-volume\\n\",\n \"kms_enabled\": false,\n \"gateway_enabled\": false,\n \"local_key_provider_enabled\": true,\n \"key_provider_id\": \"\",\n \"public_logs\": true,\n \"public_sysinfo\": true,\n \"allowed_envs\": [],\n \"no_instance_id\": true,\n \"secure_time\": false\n}", "event_log": [ { "imr": 0, @@ -159,16 +159,16 @@ { "imr": 3, "event_type": 134217729, - "digest": "c24d38d25cce2d2f4def3765a97c348363f485d5c429c2c799d1596e1608a412a7edd654aeb494cecf3e37093b84bfe8", + "digest": "cdbd13eb98f8b2f83729abbd9a20cddd9053b9971802dda713c121babdb7d3dcb633f7506d8f99440f3f5d600afdf076", "event": "app-id", - "event_payload": "05860c32c210b09b0a3957039f926bb3efafb00e" + "event_payload": "6c6037dc97767a0f37f4f21521a1c28a22922ff5" }, { "imr": 3, "event_type": 134217729, - "digest": "e9a96dda81a262016807e428c4f8791681ae609c77df817f0a88fb63348174b91e2a9d09654dba5c569aa9b4623d82c8", + "digest": "e76932b68c12a11c217e235b5f16ce04eb70f0714501610986165f423b900974a05c0c8cc2d31aa6f7e9c986d945a168", "event": "compose-hash", - "event_payload": "05860c32c210b09b0a3957039f926bb3efafb00e11e6ce99cd7d823488fca8ef" + "event_payload": "6c6037dc97767a0f37f4f21521a1c28a22922ff5a4478967948a94b6c7a7b591" }, { "imr": 3, @@ -208,9 +208,9 @@ { "imr": 3, "event_type": 134217729, - "digest": "505922fbbd1de0732579ace623add381c1ef328ec64f73940ac12d3f9081163426126ba7c0a530241143bd3b8954a2d5", + "digest": "9aed81f5b1af85f768ef6873ed6f997f55f37de951cca18f5daa35890ab9e5573314d2e0cd188a6913dd4ab6f5455678", "event": "mpc-image-digest", - "event_payload": "e2ef71c220158f9ee19a265d583647eedb4e0cd7ca37021fbf0ab34e3d214ed0" + "event_payload": "6a5700fccbb3facddd1f3934f4976c4dcefc176c4aac28cd2fd035984b368980" } ] } diff --git a/crates/test-utils/src/attestation.rs b/crates/test-utils/src/attestation.rs index 00b5ece59..fb8e832ab 100644 --- a/crates/test-utils/src/attestation.rs +++ b/crates/test-utils/src/attestation.rs @@ -18,10 +18,10 @@ pub const TEST_MPC_IMAGE_DIGEST_HEX: &str = include_str!("../assets/mpc_image_di pub const TEST_LAUNCHER_IMAGE_COMPOSE_STRING: &str = include_str!("../assets/launcher_image_compose.yaml"); -/// Unix time as of 2026/03/24, represents a date where +/// Unix time as of 2026/03/26, represents a date where /// the measurements stored in ../assets are valid. When these measurements are /// modified, this value should be updated as well -pub const VALID_ATTESTATION_TIMESTAMP: u64 = 1774340104; +pub const VALID_ATTESTATION_TIMESTAMP: u64 = 1774515730; pub fn launcher_compose_digest() -> LauncherDockerComposeHash { let digest: [u8; 32] = Sha256::digest(TEST_LAUNCHER_IMAGE_COMPOSE_STRING).into(); diff --git a/deployment/Dockerfile-launcher b/deployment/Dockerfile-launcher index 557e01c07..33a96871a 100644 --- a/deployment/Dockerfile-launcher +++ b/deployment/Dockerfile-launcher @@ -1,3 +1,9 @@ +FROM debian:bookworm-slim@sha256:acd98e6cfc42813a4db9ca54ed79b6f702830bfc2fa43a2c2e87517371d82edb AS download +ARG COMPOSE_VERSION=v2.37.0 +ARG COMPOSE_SHA256=e6e471b1e7bf0443592d3987dea6073f08db3e48ba0580199109aa7a44257e54 +ADD https://github.com/docker/compose/releases/download/${COMPOSE_VERSION}/docker-compose-linux-x86_64 /docker-compose +RUN echo "${COMPOSE_SHA256} /docker-compose" | sha256sum -c - + FROM debian:bookworm-slim@sha256:acd98e6cfc42813a4db9ca54ed79b6f702830bfc2fa43a2c2e87517371d82edb ENV DEBIAN_FRONTEND=noninteractive @@ -8,11 +14,12 @@ RUN \ --mount=type=bind,source=./deployment/repro-sources-list.sh,target=/usr/local/bin/repro-sources-list.sh \ repro-sources-list.sh && \ apt-get update && \ - apt-get install -y --no-install-recommends docker.io docker-compose curl jq python3 && \ + apt-get install -y --no-install-recommends docker.io && \ : "Clean up for improving reproducibility" && \ rm -rf /var/log/* /var/cache/ldconfig/aux-cache -COPY --chmod=0755 tee_launcher/launcher.py /scripts/ -ENV PATH="/scripts:${PATH}" +COPY --from=download --chmod=0755 /docker-compose /usr/local/lib/docker/cli-plugins/docker-compose + +COPY --chmod=0755 target/reproducible/tee-launcher /usr/local/bin/tee-launcher RUN mkdir -p /app-data && mkdir -p /mnt/shared -CMD ["python3", "/scripts/launcher.py"] +CMD ["tee-launcher"] diff --git a/deployment/build-images.sh b/deployment/build-images.sh index 933531a66..7e80e3407 100755 --- a/deployment/build-images.sh +++ b/deployment/build-images.sh @@ -2,7 +2,7 @@ # Script to reproducibly the docker images for the node and launcher # # Requirements: docker, docker-buildx, jq, git, find, touch -# Extra requirements if using --node: repro-env, podman +# Extra requirements if using --node or --launcher: repro-env, podman # # Usage: # ./deployment/build-images.sh [--node] [--launcher] [--push] @@ -61,7 +61,7 @@ require_cmds() { require_cmds docker jq git find touch -if $USE_NODE; then +if $USE_NODE || $USE_LAUNCHER; then require_cmds repro-env podman fi @@ -117,6 +117,8 @@ get_image_hash() { } if $USE_LAUNCHER; then + SOURCE_DATE_EPOCH=$SOURCE_DATE_EPOCH repro-env build --env SOURCE_DATE_EPOCH -- cargo build -p tee-launcher --profile reproducible --locked + launcher_binary_hash=$(sha256sum target/reproducible/tee-launcher | cut -d' ' -f1) build_reproducible_image $LAUNCHER_IMAGE_NAME $DOCKERFILE_LAUNCHER launcher_image_hash=$(get_image_hash $LAUNCHER_IMAGE_NAME) fi @@ -182,5 +184,6 @@ if $USE_NODE_GCP; then echo "node gcp docker image hash: $node_gcp_image_hash" fi if $USE_LAUNCHER; then + echo "launcher binary hash: $launcher_binary_hash" echo "launcher docker image hash: $launcher_image_hash" fi diff --git a/deployment/cvm-deployment/configs/kms.env b/deployment/cvm-deployment/configs/kms.env new file mode 100644 index 000000000..167c2c52c --- /dev/null +++ b/deployment/cvm-deployment/configs/kms.env @@ -0,0 +1,48 @@ +# Required environment variables for Launcher / MPC app deployment. +# Please uncomment and set values for the following variables: + +APP_NAME="launcher_test_app" + +# The URL of the dstack-vmm RPC service used to deploy the KMS app +#VMM_RPC=unix:../../../build/vmm.sock +VMM_RPC=http://127.0.0.1:16000 + +# The type of sealing key to used by the VM (SGX,KMS) +# KMS should only be used for development and SGX for production. +SEALING_KEY_TYPE=KMS + +# Port on the host machine to connect to the dstack guest agent +EXTERNAL_DSTACK_AGENT_PORT=127.0.0.1:9206 + +# SSH ports (only for dev images) +EXTERNAL_SSH_PORT=127.0.0.1:9207 + +# External MPC ports (host machine) +EXTERNAL_MPC_PUBLIC_DEBUG_PORT=0.0.0.0:8080 +EXTERNAL_MPC_LOCAL_DEBUG_PORT=127.0.0.1:3030 +EXTERNAL_MPC_DECENTRALIZED_STATE_SYNC=0.0.0.0:24567 +EXTERNAL_MPC_MAIN_PORT=0.0.0.0:80 + +# Internal MPC ports (inside CVM) +INTERNAL_MPC_PUBLIC_DEBUG_PORT=8080 +INTERNAL_MPC_LOCAL_DEBUG_PORT=3030 +INTERNAL_MPC_DECENTRALIZED_STATE_SYNC=24567 +INTERNAL_MPC_MAIN_PORT=80 + + + + +# The Dstack OS image name use for the CVM + +# production OS image +#OS_IMAGE=dstack-0.5.2 + +# development OS image +OS_IMAGE=dstack-dev-0.5.2 + +# Path of the launcher docker_compose_file +DOCKER_COMPOSE_FILE_PATH=launcher_docker_compose.yaml +# Path of the user_config file +USER_CONFIG_FILE_PATH=user-config.conf +# for testing use a smaller disk size 128G +DISK=500G \ No newline at end of file diff --git a/deployment/cvm-deployment/configs/sgx.env b/deployment/cvm-deployment/configs/sgx.env new file mode 100644 index 000000000..9e9e27e81 --- /dev/null +++ b/deployment/cvm-deployment/configs/sgx.env @@ -0,0 +1,47 @@ +# Required environment variables for Launcher / MPC app deployment. +# Please uncomment and set values for the following variables: + +APP_NAME="launcher_test_app" + +# The URL of the dstack-vmm RPC service used to deploy the KMS app +#VMM_RPC=unix:../../../build/vmm.sock +VMM_RPC=http://127.0.0.1:11100 + + +# The type of sealing key to used by the VM (SGX,KMS) +# KMS should only be used for development and SGX for production. +SEALING_KEY_TYPE=SGX + +# Port on the host machine to connect to the dstack guest agent +EXTERNAL_DSTACK_AGENT_PORT=127.0.0.1:9208 + +# SSH ports (only for dev images) +EXTERNAL_SSH_PORT=127.0.0.1:9207 + +# External MPC ports (host machine) +EXTERNAL_MPC_PUBLIC_DEBUG_PORT=0.0.0.0:8989 +EXTERNAL_MPC_LOCAL_DEBUG_PORT=127.0.0.1:3030 +EXTERNAL_MPC_DECENTRALIZED_STATE_SYNC=0.0.0.0:24567 +EXTERNAL_MPC_MAIN_PORT=0.0.0.0:80 + +# Internal MPC ports (inside CVM) +INTERNAL_MPC_PUBLIC_DEBUG_PORT=8080 +INTERNAL_MPC_LOCAL_DEBUG_PORT=3030 +INTERNAL_MPC_DECENTRALIZED_STATE_SYNC=24567 +INTERNAL_MPC_MAIN_PORT=80 + +# The dstack OS image name use for the CVM + +# production OS image +#OS_IMAGE=dstack-0.5.2 + +# development OS image +OS_IMAGE=dstack-dev-0.5.2 + +# Path of the launcher docker_compose_file +DOCKER_COMPOSE_FILE_PATH=launcher_docker_compose.yaml +# Path of the user_config file +USER_CONFIG_FILE_PATH=user-config.conf + +# for testing use a smaller disk size 128G +DISK=500G \ No newline at end of file diff --git a/deployment/cvm-deployment/default.env b/deployment/cvm-deployment/default.env new file mode 100644 index 000000000..9eb15569c --- /dev/null +++ b/deployment/cvm-deployment/default.env @@ -0,0 +1,49 @@ +# Required environment variables for Launcher / MPC app deployment. +# Please uncomment and set values for the following variables: + +APP_NAME="launcher_test_app" + +# VMM_RPC -The RPC of the dstack-vmm RPC service used to deploy CVMs +# Can either be a unix socket or an http address. (make sure this is the same configuration as in the dstack-vmm service.toml) + +#VMM_RPC=unix:../../../build/vmm.sock + +VMM_RPC=http://127.0.0.1:10000 + +# The type of sealing key to used by the VM (SGX,KMS) +# KMS should only be used for development and SGX for production. +SEALING_KEY_TYPE=KMS + +# Port on the host machine to connect to the dstack guest agent +EXTERNAL_DSTACK_AGENT_PORT=127.0.0.1:9208 + + +EXTERNAL_SSH_PORT=127.0.0.1:9209 + +# External MPC ports (host machine) +EXTERNAL_MPC_PUBLIC_DEBUG_PORT=0.0.0.0:8989 +EXTERNAL_MPC_LOCAL_DEBUG_PORT=127.0.0.1:3030 +EXTERNAL_MPC_DECENTRALIZED_STATE_SYNC=0.0.0.0:24567 +EXTERNAL_MPC_MAIN_PORT=0.0.0.0:80 + +# Internal MPC ports (inside CVM) +INTERNAL_MPC_PUBLIC_DEBUG_PORT=8080 +INTERNAL_MPC_LOCAL_DEBUG_PORT=3030 +INTERNAL_MPC_DECENTRALIZED_STATE_SYNC=24567 +INTERNAL_MPC_MAIN_PORT=80 + +# The dstack OS image name use for the CVM + +# production OS image +#OS_IMAGE=dstack-0.5.2 + +# development OS image +OS_IMAGE=dstack-dev-0.5.2 + +# Path of the launcher docker_compose_file +DOCKER_COMPOSE_FILE_PATH=launcher_docker_compose.yaml +# Path of the user_config file +USER_CONFIG_FILE_PATH=user-config.conf + +# for testing use a smaller disk size 128G +DISK=500G \ No newline at end of file diff --git a/deployment/cvm-deployment/deploy-launcher-guide.md b/deployment/cvm-deployment/deploy-launcher-guide.md new file mode 100644 index 000000000..01af0ccf4 --- /dev/null +++ b/deployment/cvm-deployment/deploy-launcher-guide.md @@ -0,0 +1,154 @@ +# 🛠 `deploy-launcher.sh` – Dstack VM Deployment Script + +This script automates the deployment of a **Dstack Launcher VM** (`launcher_test_app`) using a templated Docker Compose file and the Dstack VMM CLI. + +> **Note:** This script must be run **from within the server that hosts the VMs** (i.e., where `dstack-vmm` is running). + +It: + +- Loads deployment parameters from a `.env` file (defaults to `default.env`) +- Loads Docker Compose file and user\_config files +- Generates an `app-compose.json` configuration +- Deploys and starts CVM via `vmm-cli` + +--- + +## 📦 Requirements + +- A working Dstack setup and Dstack VMM service (`vmm-server`) accessible at `$VMM_RPC`. +See Phala's [setup guide](https://github.com/Dstack-TEE/dstack). +Also review specific MPC configuration in [running-an-mpc-node-in-tdx-external-guide.md](https://github.com/near/mpc/blob/main/docs/running-an-mpc-node-in-tdx-external-guide.md#mpc-node-setup-and-deployment) +- Python 3.6 or higher installed +- Required Python packages (cryptography, eth_keys, eth_utils) +- `vmm-cli.py` should be located under $basePath/vmm/src/vmm-cli.py +- Docker Compose template (`$DOCKER_COMPOSE_FILE_PATH`) +- Deployment configuration in `*.env` file +- user-config.conf file +- See full CLI documentation here: [vmm-cli-user-guide.md](https://github.com/Dstack-TEE/dstack/blob/master/docs/vmm-cli-user-guide.md) + +--- + +## 📂 Expected Files + +Ensure the following files are present in the working directory before running the script: + +- `default.env` – default environment configuration +- `$DOCKER_COMPOSE_FILE_PATH` – e.g. `launcher_docker_compose.yaml` +- `$USER_CONFIG_FILE_PATH` – e.g. `user-config.conf` + +You can also use the example `.env` files under `tee_deployment/configs/`: + +- `configs/kms.env` +- `configs/sgx.env` + +--- + +## 🚀 How to Use + +1. **Make the script executable** (if not already): + + ```bash + chmod +x deploy-launcher.sh + ``` + +2. **Run the script**, optionally specifying any of the following: + + ```bash + ./deploy-launcher.sh \ + --env-file tee_deployment/configs/sgx.env \ + --base-path /project \ + --python-exec /project/.venv/bin/python + ``` + + Or use just the `.env` override: + + ```bash + ./deploy-launcher.sh --env-file tee_deployment/configs/sgx.env + ``` + + Or use all defaults (`default.env`, default paths): + + ```bash + ./deploy-launcher.sh + ``` + +3. **Follow the prompt** to confirm deployment. + +--- + +## 🔧 Available Options + +| Option | Description | +| --------------------- | ------------------------------------------------------------------------- | +| `--env-file`, `-e` | Path to a `.env` file with deployment parameters (default: `default.env`) | +| `--base-path`, `-b` | Path to the parent directory containing the vmm folder . For example, if your Dstack installation is in /project/meta-dstack/dstack/vmm, then you should set --base-path /project/meta-dstack/dstack/ | +| `--python-exec`, `-p` | Path to the Python executable to use (default: under base path) | + +--- + +## 💡 Examples + +```bash +# Use KMS config from configs directory +./deploy-launcher.sh --env-file tee_deployment/configs/kms.env + +# Use SGX config +./deploy-launcher.sh --env-file tee_deployment/configs/sgx.env + +# Override Python path only +./deploy-launcher.sh --python-exec python3 + +# Override both base path (folder above meta-dstack) and Python path +./deploy-launcher.sh \ + --base-path /home/barak/project \ + --python-exec python3 +``` + +--- + +## 📄 `.env` File Format + +Make sure to create and fill in a `.env` file. Example (`default.env`): + +```env +APP_NAME=launcher_test_app +VMM_RPC=http://127.0.0.1:16000 + +# Sealing key type (KMS for deployment, SGX for production) +SEALING_KEY_TYPE=KMS + +# Port on the host machine to connect to the dstack guest agent +EXTERNAL_DSTACK_AGENT_PORT=127.0.0.1:9206 + +# SSH port on the host +EXTERNAL_SSH_PORT=127.0.0.1:9207 + +# External MPC ports (host machine) +EXTERNAL_MPC_PUBLIC_DEBUG_PORT=0.0.0.0:8080 +EXTERNAL_MPC_LOCAL_DEBUG_PORT=127.0.0.1:3030 +EXTERNAL_MPC_DECENTRALIZED_STATE_SYNC=0.0.0.0:24567 +EXTERNAL_MPC_MAIN_PORT=0.0.0.0:80 + +# Internal MPC ports (inside CVM) +INTERNAL_MPC_PUBLIC_DEBUG_PORT=8080 +INTERNAL_MPC_LOCAL_DEBUG_PORT=3030 +INTERNAL_MPC_DECENTRALIZED_STATE_SYNC=24567 +INTERNAL_MPC_MAIN_PORT=80 + +# OS image +OS_IMAGE=dstack-dev-0.5.2 + +# Path of the launcher docker_compose_file +DOCKER_COMPOSE_FILE_PATH=launcher_docker_compose.yaml +# Path of the user_config file +USER_CONFIG_FILE_PATH=user-config.conf + +# Resource configuration (defaults shown): +VCPU=8 # do not change since this is measured in the contract +MEMORY=64G # do not change since this is measured in the contract +DISK=128G # can change +``` + +--- + +Based on: [Original Dstack deploy script](https://github.com/Dstack-TEE/dstack/blob/be9d0476a63e937eda4c13659547a25088393394/kms/dstack-app/deploy-to-vmm.sh) diff --git a/deployment/cvm-deployment/deploy-launcher.sh b/deployment/cvm-deployment/deploy-launcher.sh new file mode 100755 index 000000000..0be5bf276 --- /dev/null +++ b/deployment/cvm-deployment/deploy-launcher.sh @@ -0,0 +1,232 @@ +#!/bin/bash + +# Deploys a new launcher_test_app VM to dstack-vmm using a templated Docker Compose file. +# Loads environment variables from a .env file, generates app-compose.json, and runs deployment. +# Based on: https://github.com/Dstack-TEE/dstack/blob/be9d0476a63e937eda4c13659547a25088393394/kms/dstack-app/deploy-to-vmm.sh + +check_ports_in_use() { + PORT_VARS=" + EXTERNAL_DSTACK_AGENT_PORT + EXTERNAL_SSH_PORT + EXTERNAL_MPC_PUBLIC_DEBUG_PORT + EXTERNAL_MPC_LOCAL_DEBUG_PORT + EXTERNAL_MPC_DECENTRALIZED_STATE_SYNC + EXTERNAL_MPC_MAIN_PORT + EXTERNAL_MPC_FUTURE_PORT + " + + if ! command -v ss >/dev/null 2>&1; then + echo "⚠️ WARNING: could not check port conflict. please install ss" + exit 1 + fi + + any_in_use=0 + + # Only IPv4 listeners (-4). Local address is column 4 (e.g. 0.0.0.0:13002 or 51.68.219.11:13002) + addrs="$(ss -H -4 -ltn 2>/dev/null | awk '{print $4}')" + + for var in $PORT_VARS; do + val=$(eval echo \$$var) + [ -z "$val" ] && continue + + ip="${val%%:*}" + port="${val##*:}" + + echo "Checking $var ($ip:$port)..." + + conflict=0 + + if [[ "$ip" == "0.0.0.0" ]]; then + # Binding to all IPv4 addresses conflicts with ANY existing listener on that port. + if echo "$addrs" | grep -Eq ":$port$"; then + conflict=1 + fi + else + # Binding to a specific IPv4 address conflicts if: + # 1) someone already bound 0.0.0.0:port, OR + # 2) someone already bound that exact ip:port. + if echo "$addrs" | grep -Eq "0\.0\.0\.0:$port$|${ip//./\\.}:$port$"; then + conflict=1 + fi + fi + + if [ $conflict -eq 1 ]; then + echo " -> ❌ CONFLICT" + any_in_use=1 + else + echo " -> ✅ free" + fi + done + + if [ $any_in_use -eq 1 ]; then + echo "❌ One or more required IPv4 IP:ports conflict. Aborting." + exit 1 + else + echo "✅ All required IPv4 IP:ports are free." + fi +} + + +# Default .env path +ENV_FILE="default.env" + +# Parse optional arguments +while [[ "$#" -gt 0 ]]; do + case "$1" in + -e|--env-file) + ENV_FILE="$2" + shift 2 + ;; + -b|--base-path) + basePath="$2" + shift 2 + ;; + -p|--python-exec) + pythonExec="$2" + shift 2 + ;; + -y|--yes) + AUTO_YES=1 + shift 1 + ;; + *) + echo "Unknown option: $1" + echo "Usage: $0 [--env-file ] [--base-path ] [--python-exec ]" + exit 1 + ;; + esac +done + +# Check if .env file exists +if [ -f "$ENV_FILE" ]; then + echo "Loading environment variables from $ENV_FILE..." + set -a + source "$ENV_FILE" + set +a +else + echo "Creating template $ENV_FILE..." + cat >"$ENV_FILE" < "/tmp/$USER/frodo.conf" +envsubst '${MACHINE_IP}' < deployment/localnet/tee/frodo.toml > "/tmp/$USER/frodo.toml" ``` ```bash -envsubst '${MACHINE_IP}' < deployment/localnet/tee/sam.conf > "/tmp/$USER/sam.conf" +envsubst '${MACHINE_IP}' < deployment/localnet/tee/sam.toml > "/tmp/$USER/sam.toml" ``` #### 5. Start the Frodo MPC Node diff --git a/docs/using-the-launcher-in-nontee-setup.md b/docs/using-the-launcher-in-nontee-setup.md new file mode 100644 index 000000000..a5d66bb41 --- /dev/null +++ b/docs/using-the-launcher-in-nontee-setup.md @@ -0,0 +1,147 @@ +# Running the MPC Launcher in Non-TEE Mode + +This guide describes the **high-level steps** required to run the MPC launcher **outside of a TEE**, using the same launcher and MPC Docker images as in TEE deployments. + +The goal is to allow developers and operators to exercise the **real production launcher flow** (image selection, validation, container launch, upgrades) **without relying on TEE / dstack infrastructure**. + +--- + +## Overview + +In non-TEE mode: +- The launcher runs with `PLATFORM=NONTEE` +- No dstack socket or attestation is used +- RTMR extensions are skipped +- Image hash validation and upgrade logic remain unchanged +- The MPC container is launched with DSTACK_ENDPOINT set to dstack.sock + +This provides maximum parity with production while keeping the setup simple. + +--- + +## Prerequisites + +- Docker installed and running +- Docker Compose (classic `docker-compose` or plugin `docker compose`) +- Network access to pull Docker images +- A valid MPC Docker image digest + +--- + +## Files Used + +- **Launcher Docker Compose (non-TEE)** + Example: + `./launcher_docker_compose_nontee.yaml` + +- **User configuration file** + Example: + `./user-config.conf` (mounted into the launcher container) + +--- + +## Step-by-Step Instructions + +### 1. Prepare the non-TEE docker-compose file + +Create or use a non-TEE launcher compose file with the following properties: + +- Set `PLATFORM=NONTEE` +- Mount `/var/run/docker.sock` +- Do **not** mount `/var/run/dstack.sock` +- Mount the user config file at `/tapp/user_config` +- Provide persistent volumes for shared state and MPC data + +See example: +``` +./tee_launcher/launcher_docker_compose_nontee.yaml +``` + +--- + +### 2. Prepare the user configuration file + +Create a `user-config.conf` file containing: +- MPC image selection parameters (`MPC_IMAGE_NAME`, `MPC_IMAGE_TAGS`, `MPC_REGISTRY`) +- MPC runtime configuration (account ID, contract ID, network, logging, etc.) +- Optional port mappings using `PORTS=` + +This file is read by the launcher and passed (safely) into the MPC container. + +--- + +### 3. Start the launcher + +From the directory containing the non-TEE compose file: + +```bash +docker-compose -f launcher_docker_compose_nontee.yaml up -d +``` + +--- + +### 4. Monitor launcher logs + +```bash +docker logs -f launcher +``` + +You should see: +- `Launcher platform: NONTEE` +- Image hash selection and validation +- `PLATFORM=NONTEE → skipping RTMR3 extension` +- A `docker run ...` command launching `mpc-node` + +--- + +### 5. Verify the MPC container + +Check that the MPC container is running: + +```bash +docker ps +``` + +Inspect the MPC container to confirm: +- No `DSTACK_ENDPOINT` environment variable exists +- No dstack socket is mounted +- Expected ports are published + +Example: +```bash +docker inspect mpc-node +``` + +--- + + + +--- + +## Stopping and Restarting + +Stop and remove containers: +```bash +docker-compose -f launcher_docker_compose_nontee.yaml down +``` + +Stop and remove containers **and volumes** (full reset): +```bash +docker-compose -f launcher_docker_compose_nontee.yaml down -v +``` + +Restart: +```bash +docker-compose -f launcher_docker_compose_nontee.yaml up -d +``` + +--- + +## Notes + +- Non-TEE mode is intended for **testing, development, and debugging** +- Security-sensitive behavior for TEE deployments remains unchanged +- The same launcher image and MPC image hashes are used in both modes + +--- + diff --git a/flake.nix b/flake.nix index da3d9bf7e..7f223d4c2 100644 --- a/flake.nix +++ b/flake.nix @@ -179,14 +179,14 @@ strictDeps = true; packages = - dockerTools ++ - llvmTools ++ - rustTools ++ - cargoTools ++ - pythonTools ++ - nearTools ++ - miscTools ++ - buildLibs; + dockerTools + ++ llvmTools + ++ rustTools + ++ cargoTools + ++ pythonTools + ++ nearTools + ++ miscTools + ++ buildLibs; env = envCommon // envDarwin; diff --git a/localnet/tee/scripts/rust-launcher/deploy-tee-localnet.sh b/localnet/tee/scripts/rust-launcher/deploy-tee-localnet.sh new file mode 100644 index 000000000..53c3d29e0 --- /dev/null +++ b/localnet/tee/scripts/rust-launcher/deploy-tee-localnet.sh @@ -0,0 +1,1656 @@ +#!/usr/bin/env bash +set -euo pipefail +export NEAR_CLI_DISABLE_SPINNER=1 + +# NEAR network selection +# - For testnet: NEAR_NETWORK_CONFIG=testnet (default) +# - For localnet: export NEAR_NETWORK_CONFIG=mpc-localnet (matches your near-cli config name) +NEAR_NETWORK_CONFIG="${NEAR_NETWORK_CONFIG:-testnet}" +# Used by curl-based RPC helpers (balance/bootnodes). Override for localnet if needed. +: "${NEAR_RPC_URL:?Must set NEAR_RPC_URL (e.g. http://127.0.0.1:3030 for localnet)}" +# MPC environment label used by templates / node config +# Default: "testnet" when NEAR_NETWORK_CONFIG=testnet, otherwise "localnet" +MPC_ENV="${MPC_ENV:-mpc-localnet}" + + + + +### ========================= +### Fully-automated, resumable MPC testnet TEE deploy (with subaccounts) +### - Avoids faucet 429 by allowing a funded FUNDER_ACCOUNT to create/top-up ROOT +### - Subaccounts are ALWAYS created by ROOT (required by NEAR permission model) +### - Contract created as mpc., nodes as node{i}. +### - Default funding supports contract (~16 NEAR) + up to 10 nodes (0.3 NEAR ea) + buffer +### - Resume logic + per-phase ENTER prompts +### - NEW: scale/extend network with vote_new_parameters (add nodes) +### +### NOTE on RESUME: +### - RESUME=1 is intended for reruns: it reuses existing artifacts and "auto" phase selection. +### - RESUME=0 primarily affects auto phase selection; artifacts may still exist on disk and +### some steps may still skip based on those artifacts unless FORCE_* is set. +### ========================= + +### Required inputs +: "${MPC_NETWORK_BASE_NAME:?Must set MPC_NETWORK_BASE_NAME (e.g. export MPC_NETWORK_BASE_NAME=barak-test)}" +: "${N:?Must set N (e.g. export N=10)}" +: "${BASE_PATH:?Must set BASE_PATH to dstack base path (contains vmm/src/vmm-cli.py)}" +: "${MACHINE_IP:?Must set MACHINE_IP (external IP for localnet node comms)}" + +: "${MPC_IMAGE_TAGS:?Must set MPC_IMAGE_TAGS (e.g. export MPC_IMAGE_TAGS=3.3.0)}" + +# If set, use this funded testnet account instead of faucet to create/top-up the ROOT account. +# Example: export FUNDER_ACCOUNT=barak_tee_test1.testnet +FUNDER_ACCOUNT="${FUNDER_ACCOUNT:-}" +# Optional: private key for FUNDER_ACCOUNT (useful for localnet where funder may not be in keychain) +FUNDER_PRIVATE_KEY="${FUNDER_PRIVATE_KEY:-}" + +# How much balance to ensure on ROOT (used for creating contract+nodes subaccounts) +# Default supports ~16 NEAR contract + 10 * 0.3 NEAR nodes + ~1 NEAR buffer => 20 NEAR +ROOT_INITIAL_BALANCE="${ROOT_INITIAL_BALANCE:-20 NEAR}" + +### Optional controls +ACCOUNT_MODE="${ACCOUNT_MODE:-subaccounts}" # subaccounts|faucet (faucet is fallback only) + +# Initial balances (for subaccounts mode) +CONTRACT_INITIAL_BALANCE="${CONTRACT_INITIAL_BALANCE:-20 NEAR}" +NODE_INITIAL_BALANCE="${NODE_INITIAL_BALANCE:-3.0 NEAR}" + +# How many nodes to fund for, even if N is smaller (so you can scale later without re-funding root) +MAX_NODES_TO_FUND="${MAX_NODES_TO_FUND:-10}" + +# Faucet retry/backoff (for root creation if FUNDER_ACCOUNT is not set) +FAUCET_MAX_RETRIES="${FAUCET_MAX_RETRIES:-8}" +FAUCET_BACKOFF_BASE_SEC="${FAUCET_BACKOFF_BASE_SEC:-10}" + +# Resume behavior +RESUME="${RESUME:-1}" +FORCE_REDEPLOY="${FORCE_REDEPLOY:-0}" +FORCE_RECOLLECT="${FORCE_RECOLLECT:-0}" +FORCE_REINIT_ARGS="${FORCE_REINIT_ARGS:-0}" + +# Phase gating +START_FROM_PHASE="${START_FROM_PHASE:-auto}" # auto recommended +STOP_AFTER_PHASE="${STOP_AFTER_PHASE:-}" + +# Pause between phases +NO_PAUSE="${NO_PAUSE:-0}" + +# --- Scale / add nodes controls --- +# Set ADD_NODES>0 (or set NEW_TOTAL_N) and run phase near_vote_new_parameters (add nodes) +ADD_NODES="${ADD_NODES:-0}" # how many NEW nodes to add +NEW_TOTAL_N="${NEW_TOTAL_N:-}" # optional: absolute total count after scaling +NEW_THRESHOLD_OVERRIDE="${NEW_THRESHOLD_OVERRIDE:-}" # optional: override threshold for new proposal + +# If set, reuse existing network name (and NEAR accounts) +if [ -n "${REUSE_NETWORK_NAME:-}" ]; then + RAND_SUFFIX="(reused)" + MPC_NETWORK_NAME="${REUSE_NETWORK_NAME}" +else + RAND_SUFFIX="$(printf '%04x' $((RANDOM % 65536)))" + MPC_NETWORK_NAME="${MPC_NETWORK_BASE_NAME}-${RAND_SUFFIX}" +fi + +### Constants / defaults +# Host profile: alice | bob +HOST_PROFILE="${HOST_PROFILE:-bob}" + +case "$HOST_PROFILE" in + alice) + IP_PREFIX="51.68.219." + IP_START_OCTET=1 + ;; + bob) + IP_PREFIX="5.196.36." + IP_START_OCTET=113 + ;; + *) + err "Unknown HOST_PROFILE: $HOST_PROFILE (supported: alice | bob)" + exit 1 + ;; +esac + +# Optional per-node IP override (format: "5=5.196.36.113 6=5.196.36.114 ...") +NODE_IP_OVERRIDES="${NODE_IP_OVERRIDES:-}" + +SSH_BASE=1220 +AGENT_BASE=18090 +PUBLIC_DATA_BASE=18081 +LOCAL_DEBUG_BASE=3031 + +STATE_SYNC_PORT=24567 +MAIN_PORT=80 +FUTURE_PORT=13001 +FUTURE_BASE_PORT="${FUTURE_BASE_PORT:-13001}" # host-side per-node future/N2N port base +future_port_for_i() { echo $((FUTURE_BASE_PORT + $1)); } + +INTERNAL_PUBLIC_DEBUG_PORT=8080 +INTERNAL_LOCAL_DEBUG_PORT=3030 +INTERNAL_STATE_SYNC_PORT=24567 +INTERNAL_MAIN_PORT=80 +INTERNAL_FUTURE_PORT=13001 + +OS_IMAGE="${OS_IMAGE:-dstack-dev-0.5.8}" +SEALING_KEY_TYPE="${SEALING_KEY_TYPE:-SGX}" +VMM_RPC="${VMM_RPC:-http://127.0.0.1:10000}" + +# Repo-relative paths (assumes you're running from repo root) +REPO_ROOT="$(pwd)" +TEE_LAUNCHER_DIR="$REPO_ROOT/deployment/cvm-deployment" +COMPOSE_YAML="$TEE_LAUNCHER_DIR/launcher_docker_compose.yaml" +ADD_DOMAIN_JSON="$REPO_ROOT/docs/localnet/args/add_domain.json" + +MODE="${MODE:-testnet}" # testnet|localnet + +# templates live here (UPDATED for move to localnet/tee/scripts) +ENV_TPL="$REPO_ROOT/localnet/tee/scripts/node.env.tpl" +if [ "$MODE" = "localnet" ]; then + CONF_TPL="$REPO_ROOT/localnet/tee/scripts/rust-launcher/node.conf.localnet.toml.tpl" +else + CONF_TPL="$REPO_ROOT/localnet/tee/scripts/node.conf.tpl" +fi + +# Convert comma-separated "host:container" port string to TOML inline table array entries. +# E.g. "8080:8080,24566:24566" -> " { host = 8080, container = 8080 },\n..." +ports_to_toml() { + local ports="$1" result="" + IFS=',' read -ra pairs <<< "$ports" + for pair in "${pairs[@]}"; do + local host_port="${pair%%:*}" + local container_port="${pair##*:}" + result+=" { host =$host_port, container =$container_port }, +" + done + echo -n "$result" +} + +WORKDIR="/tmp/$USER/mpc_testnet_scale/$MPC_NETWORK_NAME" +mkdir -p "$WORKDIR" + +# Derived accounts +ACCOUNT_SUFFIX="${ACCOUNT_SUFFIX:-.testnet}" # localnet example: ".test.near" +ROOT_ACCOUNT="${MPC_NETWORK_NAME}${ACCOUNT_SUFFIX}" + +# Subaccount naming (REQUIRED for subaccounts mode) +MPC_CONTRACT_ACCOUNT="${MPC_CONTRACT_ACCOUNT:-mpc.${ROOT_ACCOUNT}}" +node_account_for_i() { echo "node$1.${ROOT_ACCOUNT}"; } + +NODE_RANGE_START="${NODE_RANGE_START:-0}" +NODE_RANGE_END="${NODE_RANGE_END:-$((N-1))}" + +# Artifact paths +KEYS_JSON="$WORKDIR/keys.json" +INIT_ARGS_JSON="$WORKDIR/init_args.json" +KEYS_NEW_JSON="$WORKDIR/keys_new.json" +VOTE_PARAMS_JSON="$WORKDIR/vote_new_parameters.json" + +# Retry/sleep knobs (FIX #1) +NEAR_TX_SLEEP_SEC="${NEAR_TX_SLEEP_SEC:-3}" + +near_sleep() { + local reason="${1:-after NEAR tx}" + log "Sleeping ${NEAR_TX_SLEEP_SEC}s (${reason})" + sleep "$NEAR_TX_SLEEP_SEC" +} + +# ---------- logging ---------- +log() { echo -e "\033[1;34m[INFO]\033[0m $*"; } +warn() { echo -e "\033[1;33m[WARN]\033[0m $*"; } +err() { echo -e "\033[1;31m[ERROR]\033[0m $*"; } + +pause_phase() { + local name="$1" + if [ "$NO_PAUSE" = "1" ]; then + log "NO_PAUSE=1 -> continuing automatically (phase: $name)" + return 0 + fi + echo + echo "------------------------------------------------------------" + echo "Phase: $name" + echo "Network: $MPC_NETWORK_NAME" + echo "Workdir: $WORKDIR" + echo "Account mode: $ACCOUNT_MODE" + echo "ROOT_ACCOUNT: $ROOT_ACCOUNT" + echo "CONTRACT_ACCOUNT: $MPC_CONTRACT_ACCOUNT" + echo "FUNDER_ACCOUNT: ${FUNDER_ACCOUNT:-}" + echo "------------------------------------------------------------" + read -r -p "Press ENTER to continue (or Ctrl+C to abort)..." _ + echo +} + +# ---------- helpers ---------- +ceil_2n_3() { local n="$1"; echo $(( (2*n + 2) / 3 )); } + +need_cmd() { + command -v "$1" >/dev/null 2>&1 || { err "Missing required command: $1"; exit 1; } +} + +ip_for_i() { + local i="$1" + if [ -n "$NODE_IP_OVERRIDES" ]; then + local ip + ip="$(echo " $NODE_IP_OVERRIDES " | sed -n "s/.*[[:space:]]${i}=\([^[:space:]]*\).*/\1/p")" + if [ -n "$ip" ]; then + echo "$ip" + return 0 + fi + fi + echo "${IP_PREFIX}$((IP_START_OCTET + i))" +} + +ssh_port_for_i() { echo $((SSH_BASE + $1)); } +agent_port_for_i() { echo $((AGENT_BASE + $1)); } +public_port_for_i() { echo 18082; } # fixed public_data port +local_dbg_port_for_i() { echo $((LOCAL_DEBUG_BASE + $1)); } + +# NOTE: this preflight check assumes the node IPs are configured as /32 addresses on the host +# (common for IP aliasing setups). If your environment uses a different prefix length, adjust +# the grep pattern accordingly. +host_has_ip() { local ip="$1"; ip addr show | grep -qE "inet ${ip}/32"; } + +port_free() { + local ip="$1" port="$2" + local addrs + addrs="$(ss -H -4 -ltn 2>/dev/null | awk '{print $4}')" + if echo "$addrs" | grep -Eq "0\.0\.0\.0:${port}$|${ip//./\\.}:${port}$"; then + return 1 + fi + return 0 +} + +file_nonempty() { local p="$1"; [ -f "$p" ] && [ -s "$p" ]; } + +# keys.json is a JSON array; treat [] as "empty" even though the file is non-empty. +json_array_has_entries() { + local p="$1" + [ -f "$p" ] || return 1 + set +e + local n + n="$(jq 'length' "$p" 2>/dev/null)" + local rc=$? + set -e + [ $rc -eq 0 ] && [[ "$n" =~ ^[0-9]+$ ]] && [ "$n" -gt 0 ] +} + +maybe_stop_after_phase() { + local phase="$1" + if [ -n "$STOP_AFTER_PHASE" ] && [ "$STOP_AFTER_PHASE" = "$phase" ]; then + warn "STOP_AFTER_PHASE=$STOP_AFTER_PHASE requested. Stopping now." + exit 0 + fi +} + +# ---------- phase gating ---------- +phase_rank() { + case "$1" in + preflight) echo 10 ;; + render) echo 20 ;; + near_accounts) echo 30 ;; + near_nodes) echo 40 ;; + near_contract) echo 50 ;; + deploy) echo 60 ;; + collect) echo 70 ;; + init_args) echo 75 ;; + near_keys) echo 80 ;; + near_init) echo 90 ;; + near_vote_hash) echo 93 ;; + near_vote_launcher_hash) echo 94 ;; + near_vote_measurement) echo 95 ;; + near_vote_domain) echo 96 ;; + near_vote_new_params) echo 98 ;; + near_vote_new_params_votes) echo 99 ;; + + auto) echo 0 ;; + *) err "Unknown phase name: $1"; exit 1 ;; + esac +} + +should_run_from_start() { + local phase="$1" + local start="$START_FROM_PHASE" + if [ "$start" = "auto" ]; then + return 0 + fi + local pr sr + pr="$(phase_rank "$phase")" + sr="$(phase_rank "$start")" + [ "$pr" -ge "$sr" ] +} + +compute_auto_start_phase() { + if [ "$START_FROM_PHASE" != "auto" ]; then + echo "$START_FROM_PHASE" + return 0 + fi + if [ "$RESUME" != "1" ]; then + echo "preflight" + return 0 + fi + if file_nonempty "$INIT_ARGS_JSON"; then + echo "near_keys" + return 0 + fi + if json_array_has_entries "$KEYS_JSON"; then + echo "init_args" + return 0 + fi + echo "preflight" +} + +### ========================= +### BOOTNODES (DEDUP BY ADDR) +### ========================= +fetch_bootnodes() { + # Bootnodes + # - Override via NEAR_BOOT_NODES_OVERRIDE (recommended for fully offline setups) + # - Otherwise, query the configured RPC (NEAR_RPC_URL) for network_info + if [ -n "${NEAR_BOOT_NODES_OVERRIDE:-}" ]; then + echo "$NEAR_BOOT_NODES_OVERRIDE" + return 0 + fi + + # Best-effort: if RPC doesn't support network_info or returns empty, return empty string + set +e + local out + out="$(curl -s -X POST "$NEAR_RPC_URL" \ + -H "Content-Type: application/json" \ + -d '{"jsonrpc": "2.0", "method": "network_info", "params": [], "id": "dontcare"}' 2>/dev/null)" + local rc=$? + set -e + if [ $rc -ne 0 ] || [ -z "$out" ]; then + echo "" + return 0 + fi + echo "$out" \ + | jq -r '.result.active_peers[]? | "\(.id)@\(.addr)"' 2>/dev/null \ + | awk -F'@' '!seen[$2]++ {print $0}' \ + | paste -sd',' - +} + + +### ========================= +### NEAR helpers (balance, create, topup) +### ========================= + +is_existing_key_error() { + local out="$1" + # This matches the exact near-cli-rs message + echo "$out" | grep -Eq "Public key is already used for an existing account ID <" +} + +near_add_key_skip_if_exists() { + # Usage: near_add_key_skip_if_exists "" "" "