diff --git a/.github/workflows/release-eif.yml b/.github/workflows/release-eif.yml new file mode 100644 index 0000000..1b3dd99 --- /dev/null +++ b/.github/workflows/release-eif.yml @@ -0,0 +1,115 @@ +name: Release EIF + +on: + workflow_dispatch: + inputs: + version: + description: 'Tag version (e.g. v0.0.29)' + required: true + +permissions: + contents: write + +jobs: + release-eif: + runs-on: ubuntu-latest + timeout-minutes: 30 + steps: + - uses: actions/checkout@v4 + + - name: Validate version + env: + GH_TOKEN: ${{ github.token }} + VERSION: ${{ inputs.version }} + run: | + set -euo pipefail + + if [[ ! "$VERSION" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo "::error::Invalid version: '$VERSION' (expected vX.Y.Z)" + exit 1 + fi + + if gh release view "$VERSION" >/dev/null 2>&1; then + echo "::error::Release $VERSION already exists" + exit 1 + fi + + if git ls-remote --exit-code --tags origin "refs/tags/${VERSION}" >/dev/null 2>&1; then + echo "::error::Tag $VERSION already exists on origin" + exit 1 + fi + + echo "Version $VERSION validated." + + - uses: actions/setup-go@v5 + with: + go-version: stable + + - name: Install enclave CLI + run: go install github.com/ArkLabsHQ/introspector-enclave/cli/cmd/enclave@latest + + - name: Pull Nix Docker image + run: docker pull nixos/nix:2.24.9 + + - name: Build EIF + run: enclave build + + - name: Extract PCR values + id: pcr + run: | + PCR0=$(jq -r '.PCR0 // .pcr0' .enclave/artifacts/pcr.json) + PCR1=$(jq -r '.PCR1 // .pcr1' .enclave/artifacts/pcr.json) + PCR2=$(jq -r '.PCR2 // .pcr2' .enclave/artifacts/pcr.json) + echo "pcr0=${PCR0}" >> "$GITHUB_OUTPUT" + echo "pcr1=${PCR1}" >> "$GITHUB_OUTPUT" + echo "pcr2=${PCR2}" >> "$GITHUB_OUTPUT" + echo "PCR0: ${PCR0:0:32}..." + + - name: Build deployment manifest + env: + PCR0: ${{ steps.pcr.outputs.pcr0 }} + PCR1: ${{ steps.pcr.outputs.pcr1 }} + PCR2: ${{ steps.pcr.outputs.pcr2 }} + REPO: ${{ github.repository }} + COMMIT_SHA: ${{ github.sha }} + TAG: ${{ inputs.version }} + run: | + TIMESTAMP=$(date -u +%Y-%m-%dT%H:%M:%SZ) + jq -n \ + --arg version "$TAG" \ + --arg pcr0 "$PCR0" \ + --arg pcr1 "$PCR1" \ + --arg pcr2 "$PCR2" \ + --arg timestamp "$TIMESTAMP" \ + --arg commit "$COMMIT_SHA" \ + --arg repo "$REPO" \ + '{version: $version, pcr0: $pcr0, pcr1: $pcr1, pcr2: $pcr2, timestamp: $timestamp, commit: $commit, repo: $repo}' \ + > deployment.json + + - name: Upload EIF artifact + uses: actions/upload-artifact@v4 + with: + name: enclave-eif-${{ inputs.version }} + path: | + .enclave/artifacts/image.eif + .enclave/artifacts/pcr.json + .enclave/artifacts/supervisor + deployment.json + retention-days: 30 + + - name: Create version release + env: + GH_TOKEN: ${{ github.token }} + PCR0: ${{ steps.pcr.outputs.pcr0 }} + COMMIT_SHA: ${{ github.sha }} + TAG: ${{ inputs.version }} + run: | + gh release create "$TAG" \ + --target "$COMMIT_SHA" \ + --title "EIF ${TAG}" \ + --notes "EIF release ${TAG} from commit ${COMMIT_SHA::8} + PCR0: ${PCR0}" \ + .enclave/artifacts/image.eif \ + .enclave/artifacts/pcr.json \ + .enclave/artifacts/supervisor \ + deployment.json diff --git a/.gitignore b/.gitignore index 308e668..bb50998 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,13 @@ .claude build/ bin/ -docs/plans/ \ No newline at end of file +.enclave/ +docs/plans/ + +# Go module vendor dir — only materialized by `enclave/test/` for the +# local-tree Nix EIF build; source of truth is go.mod/go.sum. +vendor/ + +# enclave-test scratch artifacts. +enclave/test/app/ +enclave/test/tofu-*.log diff --git a/Makefile b/Makefile index 683c711..a2f97e0 100755 --- a/Makefile +++ b/Makefile @@ -52,5 +52,50 @@ build-all: lint: golangci-lint run --fix +# === QEMU enclave integration test === +# Thin wrappers around `enclave test {build,init,start,down}` from the +# introspector-enclave CLI (must be on $PATH). `make enclave-test` runs +# the full loop: build EIF, scaffold compose + ensure mock-arkd is wired +# in, boot the stack, run integration-test.sh, tear down. +.PHONY: enclave-test enclave-test-build enclave-test-init enclave-test-start enclave-test-down + +define MOCK_ARKD_BLOCK + + mock-arkd: + build: + context: ../../ + dockerfile: enclave/test/mock-arkd/Dockerfile + network_mode: host + environment: + MOCK_ARKD_ADDR: ":8081" + healthcheck: + test: ["CMD-SHELL", "/usr/local/bin/grpcurl -plaintext -max-time 2 localhost:8081 list"] + interval: 5s + timeout: 3s + retries: 5 +endef +export MOCK_ARKD_BLOCK + +enclave-test: enclave-test-build enclave-test-init enclave-test-start + @./enclave/test/integration-test.sh + @$(MAKE) enclave-test-down + +enclave-test-build: + @enclave test build + +# `enclave test init` is merge-only-new on the compose file; the grep +# guard keeps the mock-arkd append idempotent across re-runs. +enclave-test-init: + @enclave test init + @if ! grep -q '^ mock-arkd:' enclave/test/docker-compose.yml; then \ + echo "$$MOCK_ARKD_BLOCK" >> enclave/test/docker-compose.yml; \ + fi + +enclave-test-start: + @enclave test start + +enclave-test-down: + @enclave test down + format: @go fmt ./... \ No newline at end of file diff --git a/README.md b/README.md index 4da308b..493d991 100644 --- a/README.md +++ b/README.md @@ -264,6 +264,45 @@ make docker-run make integrationtest ``` +### Enclave integration test + +Boots the introspector EIF inside a QEMU-emulated Nitro Enclave with mock AWS +services + mock-arkd, then runs the 6-test driver +(`enclave/test/integration-test.sh`: health, HTTP/2 ALPN, native gRPC `GetInfo` +with attestation, wrong-PCR0 reject, `SubmitTx` with a real PSBT, HTTP/1.1 +compat). Requires the `enclave` CLI from +[introspector-enclave](https://github.com/ArkLabsHQ/introspector-enclave) on +`$PATH`, plus Docker. + +```bash +enclave test build # auto-picks enclave/enclave_test.yaml +enclave test init # writes enclave/test/docker-compose.yml +``` + +Append the mock-arkd block once, below the +`# === user services below this line ===` marker (4-space indent matters): + +```yaml + mock-arkd: + build: + context: ../../ + dockerfile: enclave/test/mock-arkd/Dockerfile + network_mode: host + environment: + MOCK_ARKD_ADDR: ":8081" + healthcheck: + test: ["CMD-SHELL", "/usr/local/bin/grpcurl -plaintext -max-time 2 localhost:8081 list"] + interval: 5s + timeout: 3s + retries: 5 +``` + +```bash +enclave test start # boots the stack, waits for /health 200 +./enclave/test/integration-test.sh # runs the 6-test driver +enclave test down # tear down + remove volumes +``` + ## Supported Opcodes The following opcodes are supported by the Arkade script engine. They extend Bitcoin Script with additional introspection, data manipulation, and cryptographic operations. diff --git a/enclave/README.md b/enclave/README.md new file mode 100644 index 0000000..e47ad9f --- /dev/null +++ b/enclave/README.md @@ -0,0 +1,60 @@ +# enclave/ + +Reproducible Nitro Enclave (EIF) build for the introspector app. + +## Install the `enclave` CLI + +```sh +go install github.com/ArkLabsHQ/introspector-enclave/cli/cmd/enclave@latest +``` + +This puts `enclave` on your `$PATH` (assuming `$GOPATH/bin` is on it). + +To pin to a specific runtime release, replace `@latest` with a tag: + +```sh +go install github.com/ArkLabsHQ/introspector-enclave/cli/cmd/enclave@v0.0.72 +``` + +## Update the pinned app commit + +`enclave.yaml` pins the app to a specific commit via `app.nix_rev`, plus the +matching `nix_hash` and `nix_vendor_hash`. To bump to a new commit: + +```sh +enclave setup --commit +``` + +This rewrites `app.nix_rev`, `app.nix_hash`, and `app.nix_vendor_hash` in +`enclave.yaml` and regenerates `flake.lock`. Commit both files. + +Pass `--force-flake` if you intentionally want to regenerate it from the template +(e.g. after changing `app.language`). + +## Build the EIF locally + +```sh +enclave build +``` + +Outputs: + +- `.enclave/artifacts/image.eif` — the enclave image +- `.enclave/artifacts/pcr.json` — measurement values (PCR0/1/2) +- `.enclave/artifacts/supervisor` — runtime supervisor binary + +The build is fully reproducible — same inputs (commit, hashes, runtime pin) +produce byte-identical artifacts and the same PCR0. + +## Cut a tagged release + +Releases are dispatched manually via the `Release EIF` workflow: + +1. Bump `app.nix_rev` and the runtime pin in `enclave.yaml` if needed. +2. Commit and push to `master`. +3. Run **Actions → Release EIF → Run workflow** and supply a version + (e.g. `v0.1.0`). The workflow validates the version (`vX.Y.Z`, not + already a tag/release), builds the EIF, and publishes a single + immutable release containing `image.eif`, `pcr.json`, `supervisor`, + and `deployment.json`. + diff --git a/enclave/enclave.yaml b/enclave/enclave.yaml new file mode 100644 index 0000000..8aba8cb --- /dev/null +++ b/enclave/enclave.yaml @@ -0,0 +1,49 @@ +# Enclave configuration +# Edit this file then run: enclave init + +name: introspector # App name (used in stack name, EIF name) +version: 0.0.1 # Build version (semver, baked into binary via ldflags) +region: us-east-1 # AWS region +account: "" # AWS account ID (required) +prefix: dev # Deployment prefix (stack = {prefix}Nitro{Name}) +instance_type: m6i.xlarge # EC2 instance type +migration_cooldown: "1m" # Cooldown before migration proceeds +is_kms_key_locked: false # false (default): grant AWS root kms:PutKeyPolicy on the locked key — recovery from lockout works by adding a new PCR0 condition. + # true: strict mode — the locked policy is frozen; even root cannot rewrite it. Only the attested enclave can decrypt. + +# Runtime coordinates for the enclave supervisor binary. +# The supervisor handles attestation, secrets, PCR extension, and signing +# middleware automatically. Your app is a plain HTTP server with zero runtime imports. +runtime: + rev: "v0.0.76" + hash: "sha256-J/gbggHP7VD2uZT+itYH/x09uATJY2OHgte10N22VLM=" + vendor_hash: "sha256-kkgp+uxc9pohZiCnz84nyXroUzsPhhtb0VgGa3dQgUo=" + +app: + language: "go" # App language: go, nodejs, dotnet, rust + source: nix # "nix" = fetch from GitHub via Nix + + # GitHub coordinates for the app to run inside the enclave. + # Your app is a plain HTTP server that listens on ENCLAVE_APP_PORT (default 7074). + # Secrets are passed as environment variables. No runtime imports needed. + nix_owner: "ArkLabsHQ" # GitHub owner (required) + nix_repo: "introspector" # GitHub repo name (required) + nix_rev: "67616e261b6e90a839a250599f8afa6ac52f9264" # Git commit SHA (required) + nix_hash: "sha256-HH04xNAJnmkboWd/iwV5swCQ34Gko7EdlQnltB9G7r4=" # Nix source hash: nix-prefetch-url --unpack (required) + nix_vendor_hash: "sha256-y9AH8Ebus4IEL1YHf0kbCHCczRJJxhQedi7MMbUNYkA=" # Go vendor hash (required) + nix_sub_packages: + - cmd + binary_name: introspector + nix_subdir: "" # Subdirectory for monorepo (e.g. "server") + release_tag: "eif-latest" # GitHub Release tag used by 'enclave tofu --remote' + + # INTROSPECTOR_ARKD_URL is injected by tofu at deploy time; + # AWS credentials come from the EC2 role via IMDS. + env: + INTROSPECTOR_PORT: "7074" + INTROSPECTOR_NO_TLS: "true" + +secrets: + - name: signing_key + env_var: INTROSPECTOR_SECRET_KEY + diff --git a/enclave/enclave_test.yaml b/enclave/enclave_test.yaml new file mode 100644 index 0000000..75c60ae --- /dev/null +++ b/enclave/enclave_test.yaml @@ -0,0 +1,47 @@ +# Test EIF config — `enclave test {build,init,start}` auto-pick this. +# Identical to enclave.yaml except for app.env (localstack + mock-arkd). + +name: introspector +version: 0.0.1 +region: us-east-1 +account: "" +prefix: dev +instance_type: m6i.xlarge +migration_cooldown: "1m" +is_kms_key_locked: false + +runtime: + rev: "v0.0.76" + hash: "sha256-J/gbggHP7VD2uZT+itYH/x09uATJY2OHgte10N22VLM=" + vendor_hash: "sha256-kkgp+uxc9pohZiCnz84nyXroUzsPhhtb0VgGa3dQgUo=" + +app: + language: "go" + source: nix + + nix_owner: "ArkLabsHQ" + nix_repo: "introspector" + nix_rev: "67616e261b6e90a839a250599f8afa6ac52f9264" + nix_hash: "sha256-HH04xNAJnmkboWd/iwV5swCQ34Gko7EdlQnltB9G7r4=" + nix_vendor_hash: "sha256-y9AH8Ebus4IEL1YHf0kbCHCczRJJxhQedi7MMbUNYkA=" + nix_sub_packages: + - cmd + binary_name: introspector + nix_subdir: "" + release_tag: "eif-latest" + + env: + INTROSPECTOR_PORT: "7074" + INTROSPECTOR_NO_TLS: "true" + INTROSPECTOR_ARKD_URL: "host.containers.internal:8081" + AWS_ENDPOINT_URL_KMS: "http://host.containers.internal:4000" + AWS_ENDPOINT_URL_SSM: "http://host.containers.internal:4566" + AWS_ENDPOINT_URL_STS: "http://host.containers.internal:4566" + AWS_ENDPOINT_URL_S3: "http://host.containers.internal:4566" + AWS_ENDPOINT_URL_LOGS: "http://host.containers.internal:4566" + AWS_ACCESS_KEY_ID: "test" + AWS_SECRET_ACCESS_KEY: "test" + +secrets: + - name: signing_key + env_var: INTROSPECTOR_SECRET_KEY diff --git a/enclave/flake.lock b/enclave/flake.lock new file mode 100644 index 0000000..923d725 --- /dev/null +++ b/enclave/flake.lock @@ -0,0 +1,130 @@ +{ + "nodes": { + "aws-nitro-util": { + "inputs": { + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs" + }, + "locked": { + "lastModified": 1747844178, + "narHash": "sha256-x5CRn8TJDX7D+feUXjzOHKVlM4bLv2vUSz087WQCnUo=", + "owner": "monzo", + "repo": "aws-nitro-util", + "rev": "96f3bb204536dce32882a7e4affd6e8cea828b48", + "type": "github" + }, + "original": { + "owner": "monzo", + "repo": "aws-nitro-util", + "type": "github" + } + }, + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1705309234, + "narHash": "sha256-uNRRNRKmJyCRC/8y1RqBkqWBLM034y4qN7EprSdmgyA=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "1ef2e671c3b0c19053962c07dbda38332dcebf26", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "flake-utils_2": { + "inputs": { + "systems": "systems_2" + }, + "locked": { + "lastModified": 1731533236, + "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1711703276, + "narHash": "sha256-iMUFArF0WCatKK6RzfUJknjem0H9m4KgorO/p3Dopkk=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "d8fe5e6c92d0d190646fb9f1056741a229980089", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs_2": { + "locked": { + "lastModified": 1777077449, + "narHash": "sha256-AIiMJiqvGrN4HyLEbKAoCSRRYn0rnlW5VbKNIMIYqm4=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "a4bf06618f0b5ee50f14ed8f0da77d34ecc19160", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-25.11", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "aws-nitro-util": "aws-nitro-util", + "flake-utils": "flake-utils_2", + "nixpkgs": "nixpkgs_2" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + }, + "systems_2": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/enclave/flake.nix b/enclave/flake.nix new file mode 100644 index 0000000..b80c411 --- /dev/null +++ b/enclave/flake.nix @@ -0,0 +1,189 @@ +{ + description = "Nitro Enclave - reproducible build"; + + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.11"; + flake-utils.url = "github:numtide/flake-utils"; + aws-nitro-util.url = "github:monzo/aws-nitro-util"; + }; + + outputs = { self, nixpkgs, flake-utils, aws-nitro-util }: + flake-utils.lib.eachSystem [ "x86_64-linux" "aarch64-linux" "x86_64-darwin" "aarch64-darwin" ] (system: + let + pkgs = import nixpkgs { inherit system; }; + eifPkgs = if system == "x86_64-linux" then pkgs + else import nixpkgs { system = "x86_64-linux"; }; + eifBuildGoModule = eifPkgs.buildGoModule.override { go = eifPkgs.go_1_26; }; + nitro = aws-nitro-util.lib.x86_64-linux; + + # Read build config generated by `enclave build` from enclave.yaml. + # BUILD_CONFIG_PATH is set by the CLI; is set by the CLI as an absolute path. + # Requires --impure flag (already set by the CLI). + configPath = let p = builtins.getEnv "BUILD_CONFIG_PATH"; in + if p != "" then p else "../.enclave/build-config.json"; + buildCfg = builtins.fromJSON (builtins.readFile configPath); + appCfg = buildCfg.app; + runtimeCfg = buildCfg.runtime; + + # Fall back to env vars for backwards compatibility. + version = buildCfg.version; + region = buildCfg.region; + deployment = buildCfg.prefix; + + # Resolve user-supplied package names from enclave.yaml + # (nix_build_inputs / nix_native_build_inputs) against nixpkgs. + resolveInputs = names: map (n: eifPkgs.${n}) names; + + # Enclave supervisor — built from the runtime repo. + # Handles attestation, secrets, PCR extension, reverse proxy with + # signing middleware. The user's app is just a plain HTTP server. + runtime = eifBuildGoModule { + pname = "runtime"; + version = buildCfg.version; + + src = eifPkgs.fetchFromGitHub { + owner = "ArkLabsHQ"; + repo = "introspector-enclave"; + rev = runtimeCfg.rev; + hash = runtimeCfg.hash; + }; + + sourceRoot = "source/runtime"; + vendorHash = runtimeCfg.vendor_hash; + subPackages = [ "cmd/runtime" ]; + env.CGO_ENABLED = "0"; + ldflags = [ + "-X" "github.com/ArkLabsHQ/introspector-enclave/runtime.Version=${version}" + ]; + buildFlags = [ "-trimpath" ]; + tags = [ "netgo" ]; + doCheck = false; + }; + + # User's app — fetched from GitHub. No runtime dependency needed. + upstream-app = eifBuildGoModule ({ + pname = appCfg.binary_name; + version = buildCfg.version; + + src = eifPkgs.fetchFromGitHub { + owner = appCfg.nix_owner; + repo = appCfg.nix_repo; + rev = appCfg.nix_rev; + hash = appCfg.nix_hash; + }; + + vendorHash = if appCfg.nix_vendor_hash == "" then null else appCfg.nix_vendor_hash; + proxyVendor = true; + + subPackages = appCfg.nix_sub_packages; + env.CGO_ENABLED = "0"; + buildFlags = [ "-trimpath" ]; + tags = [ "netgo" ]; + doCheck = false; + + nativeBuildInputs = resolveInputs (appCfg.nix_native_build_inputs or []); + buildInputs = resolveInputs (appCfg.nix_build_inputs or []); + + postInstall = '' + # Rename whatever was built to the configured binary name. + for f in $out/bin/*; do + if [ "$(basename "$f")" != "${appCfg.binary_name}" ]; then + mv "$f" "$out/bin/${appCfg.binary_name}" + fi + done + ''; + } // (if (appCfg.nix_subdir or "") != "" then { + sourceRoot = "source/${appCfg.nix_subdir}"; + } else {})); + + # Nitriding and viproxy are vendored into the runtime binary (see + # runtime/nitriding/ and runtime/viproxy/), so no separate derivations + # are needed here — the runtime /app/runtime is the whole enclave + # userspace alongside the user's app. + + # Assemble the /app directory with all binaries and scripts. + appDir = eifPkgs.runCommand "enclave-app" { } '' + mkdir -p $out/app/data + cp ${upstream-app}/bin/${appCfg.binary_name} $out/app/${appCfg.binary_name} + cp ${runtime}/bin/runtime $out/app/runtime + ''; + + # Complete rootfs for the enclave. + enclaveRootfs = eifPkgs.buildEnv { + name = "enclave-rootfs"; + paths = [ + appDir + eifPkgs.busybox # provides /bin/sh and basic utils + eifPkgs.cacert # TLS CA certificates + ]; + pathsToLink = [ "/" ]; + }; + + # Secrets config JSON baked into the EIF for runtime discovery. + secretsCfgJson = builtins.toJSON (buildCfg.secrets or []); + + # Environment variables for the enclave. + # Standard vars + all app-specific vars from build-config.json. + enclaveEnv = let + appEnvLines = builtins.concatStringsSep "\n" + (builtins.map (k: "${k}=${builtins.getAttr k appCfg.env}") + (builtins.attrNames appCfg.env)); + in '' + PATH=/app:/bin:/usr/bin + SSL_CERT_FILE=/etc/ssl/certs/ca-bundle.crt + AWS_REGION=${region} + ENCLAVE_APP_NAME=${buildCfg.name} + ENCLAVE_SECRETS_CONFIG=${secretsCfgJson} + ENCLAVE_MIGRATION_COOLDOWN=${buildCfg.migration_cooldown or "0s"} + ENCLAVE_PREVIOUS_PCR0=${buildCfg.previous_pcr0 or "genesis"} + ENCLAVE_KMS_KEY_LOCKED=${if buildCfg.is_kms_key_locked or false then "true" else "false"} + ENCLAVE_DEPLOYMENT=${deployment} + ${appEnvLines} + ''; + + # Build EIF using monzo/aws-nitro-util (reproducible, no Docker). + eif = nitro.buildEif { + name = "${buildCfg.name}-enclave"; + inherit version; + + arch = "x86_64"; + kernel = nitro.blobs.x86_64.kernel; + kernelConfig = nitro.blobs.x86_64.kernelConfig; + nsmKo = nitro.blobs.x86_64.nsmKo; + + copyToRoot = enclaveRootfs; + entrypoint = "/app/runtime"; + env = enclaveEnv; + }; + + # Vendor hash check — used by enclave setup to discover the correct hash. + vendor-hash-check = eifBuildGoModule ({ + pname = "vendor-hash-check"; + version = buildCfg.version; + src = eifPkgs.fetchFromGitHub { + owner = appCfg.nix_owner; + repo = appCfg.nix_repo; + rev = appCfg.nix_rev; + hash = appCfg.nix_hash; + }; + vendorHash = ""; + proxyVendor = true; + subPackages = appCfg.nix_sub_packages; + env.CGO_ENABLED = "0"; + doCheck = false; + + nativeBuildInputs = resolveInputs (appCfg.nix_native_build_inputs or []); + buildInputs = resolveInputs (appCfg.nix_build_inputs or []); + } // (if (appCfg.nix_subdir or "") != "" then { + sourceRoot = "source/${appCfg.nix_subdir}"; + } else {})); + + in + { + packages = { + inherit upstream-app runtime eif vendor-hash-check; + default = eif; + }; + } + ); +} diff --git a/enclave/test/docker-compose.yml b/enclave/test/docker-compose.yml new file mode 100644 index 0000000..006fe18 --- /dev/null +++ b/enclave/test/docker-compose.yml @@ -0,0 +1,110 @@ +# Generated by 'enclave test init' for framework version 0.0.75. +# Re-runs of 'enclave test init' will SKIP this file if it exists. +# To regenerate from scratch (e.g. after bumping the framework +# version): 'enclave test init --force-compose'. +# +# Hand-add app-specific dependency containers below the marker at +# the bottom of this file. + +services: + aws-mocks: + image: ghcr.io/arklabshq/enclave-awsmocks:0.0.75 + ports: + - 4000:4000 + - 1338:1338 + environment: + IMDS_LISTEN_ADDR: :1338 + KMS_PROXY_LISTEN_ADDR: :4000 + UPSTREAM_KMS_URL: http://local-kms:8080 + depends_on: + local-kms: + condition: service_healthy + healthcheck: + test: + - CMD-SHELL + - wget -qO- http://localhost:4000/health && wget -qO- http://localhost:1338/health + interval: 5s + timeout: 3s + retries: 5 + local-kms: + image: nsmithuk/local-kms + ports: + - 8080:8080 + environment: + KMS_ACCOUNT_ID: "123456789012" + KMS_REGION: us-east-1 + healthcheck: + test: + - CMD-SHELL + - wget -qO- http://localhost:8080/ || exit 0 + interval: 5s + timeout: 3s + retries: 5 + localstack: + image: localstack/localstack:4.14 + ports: + - 4566:4566 + environment: + LOCALSTACK_ACKNOWLEDGE_ACCOUNT_REQUIREMENT: "1" + SERVICES: ssm,sts,s3,cloudformation,iam,logs + healthcheck: + test: + - CMD-SHELL + - curl -sf http://localhost:4566/_localstack/health || exit 1 + interval: 5s + timeout: 3s + retries: 20 + test-runner: + image: ghcr.io/arklabshq/enclave-test-runner:0.0.75 + environment: + AWS_ACCESS_KEY_ID: test + AWS_ENDPOINT_URL_KMS: http://127.0.0.1:4000 + AWS_ENDPOINT_URL_LOGS: http://127.0.0.1:4566 + AWS_ENDPOINT_URL_S3: http://127.0.0.1:4566 + AWS_ENDPOINT_URL_SSM: http://127.0.0.1:4566 + AWS_ENDPOINT_URL_STS: http://127.0.0.1:4566 + AWS_REGION: us-east-1 + AWS_SECRET_ACCESS_KEY: test + ENCLAVE_APP_NAME: introspector + ENCLAVE_DEPLOYMENT: dev + IMDS_PROXY_TARGET: 127.0.0.1:1338 + volumes: + - ../../.enclave/artifacts:/data:ro + depends_on: + aws-mocks: + condition: service_healthy + local-kms: + condition: service_healthy + localstack: + condition: service_healthy + privileged: true + network_mode: host + + # === user services below this line === + # Append additional service blocks at 4-space indent so they nest + # under the existing services: map. Do NOT add another "services:" + # key. Wire boot ordering by adding the service name to + # test-runner.depends_on. Example: + # + # mock-arkd: + # build: + # context: ../../ + # dockerfile: enclave/test/mock-arkd/Dockerfile + # network_mode: host + # healthcheck: + # test: ["CMD-SHELL", "nc -z 127.0.0.1 8081 || exit 1"] + # interval: 5s + # retries: 5 + + mock-arkd: + build: + context: ../../ + dockerfile: enclave/test/mock-arkd/Dockerfile + network_mode: host + environment: + MOCK_ARKD_ADDR: ":8081" + healthcheck: + test: ["CMD-SHELL", "/usr/local/bin/grpcurl -plaintext -max-time 2 localhost:8081 list"] + interval: 5s + timeout: 3s + retries: 5 diff --git a/enclave/test/grpc-client/go.mod b/enclave/test/grpc-client/go.mod new file mode 100644 index 0000000..d51d45b --- /dev/null +++ b/enclave/test/grpc-client/go.mod @@ -0,0 +1,54 @@ +module github.com/ArkLabsHQ/introspector/enclave/test/grpc-client + +go 1.26.2 + +// Both source repos are mounted into the test-runner Docker build +// context; replace directives point at the local working trees so the +// grpc-client picks up the in-development client.GRPCConn and the +// introspector api-spec stubs. +replace github.com/ArkLabsHQ/introspector-enclave => /home/joshua/introspector-enclave + +replace github.com/ArkLabsHQ/introspector/api-spec => /home/joshua/introspector/api-spec + +replace github.com/ArkLabsHQ/introspector/pkg/arkade => /home/joshua/introspector/pkg/arkade + +require ( + github.com/ArkLabsHQ/introspector-enclave v0.0.36 + github.com/ArkLabsHQ/introspector/api-spec v0.0.0-00010101000000-000000000000 + github.com/ArkLabsHQ/introspector/pkg/arkade v0.0.0-00010101000000-000000000000 + github.com/arkade-os/arkd/pkg/ark-lib v0.8.1-0.20260318170839-137daaec3a70 + github.com/btcsuite/btcd v0.25.0 + github.com/btcsuite/btcd/btcec/v2 v2.3.5 + github.com/btcsuite/btcd/btcutil/psbt v1.1.9 + github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0 + github.com/btcsuite/btcwallet v0.16.17 +) + +require ( + github.com/arkade-os/arkd/pkg/errors v0.0.0-20260303153651-8615412e4dea // indirect + github.com/btcsuite/btcd/btcutil v1.1.5 // indirect + github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f // indirect + github.com/btcsuite/btcwallet/walletdb v1.5.1 // indirect + github.com/decred/dcrd/crypto/blake256 v1.1.0 // indirect + github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 // indirect + github.com/fxamacker/cbor/v2 v2.9.2 // indirect + github.com/hf/nitrite v0.0.0-20211104000856-f9e0dcc73703 // indirect + github.com/julienschmidt/httprouter v1.3.0 // indirect + github.com/lightninglabs/neutrino/cache v1.1.2 // indirect + github.com/lightningnetwork/lnd/fn v1.2.1 // indirect + github.com/lightningnetwork/lnd/tlv v1.2.6 // indirect + github.com/meshapi/grpc-api-gateway v0.1.0 // indirect + github.com/sirupsen/logrus v1.9.4 // indirect + github.com/x448/float16 v0.8.4 // indirect + golang.org/x/crypto v0.48.0 // indirect + golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 // indirect + golang.org/x/net v0.49.0 // indirect + golang.org/x/sync v0.20.0 // indirect + golang.org/x/sys v0.41.0 // indirect + golang.org/x/text v0.34.0 // indirect + google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 // indirect + google.golang.org/grpc v1.79.3 // indirect + google.golang.org/protobuf v1.36.11 // indirect +) diff --git a/enclave/test/grpc-client/go.sum b/enclave/test/grpc-client/go.sum new file mode 100644 index 0000000..73818c0 --- /dev/null +++ b/enclave/test/grpc-client/go.sum @@ -0,0 +1,198 @@ +github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= +github.com/arkade-os/arkd/pkg/ark-lib v0.8.1-0.20260318170839-137daaec3a70 h1:qLdbRS0rlHYjQuX0FWgJsvb+CE0U/m0q6Amy8WOvocA= +github.com/arkade-os/arkd/pkg/ark-lib v0.8.1-0.20260318170839-137daaec3a70/go.mod h1:VpyqrRS8Qk3uAhUTiH417gyC52caAfan/o8aVPDO528= +github.com/arkade-os/arkd/pkg/errors v0.0.0-20260303153651-8615412e4dea h1:x9ZwZL+F2b9E0uBZYBVjCLGtlqIE4zahDOY4C89h3X4= +github.com/arkade-os/arkd/pkg/errors v0.0.0-20260303153651-8615412e4dea/go.mod h1:NYGE+baj57ynbXNwjISJddMDpMqAWOX27dV22xqFm2A= +github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= +github.com/btcsuite/btcd v0.22.0-beta.0.20220111032746-97732e52810c/go.mod h1:tjmYdS6MLJ5/s0Fj4DbLgSbDHbEqLJrtnHecBFkdz5M= +github.com/btcsuite/btcd v0.23.5-0.20231215221805-96c9fd8078fd/go.mod h1:nm3Bko6zh6bWP60UxwoT5LzdGJsQJaPo6HjduXq9p6A= +github.com/btcsuite/btcd v0.25.0 h1:JPbjwvHGpSywBRuorFFqTjaVP4y6Qw69XJ1nQ6MyWJM= +github.com/btcsuite/btcd v0.25.0/go.mod h1:qbPE+pEiR9643E1s1xu57awsRhlCIm1ZIi6FfeRA4KE= +github.com/btcsuite/btcd/btcec/v2 v2.1.0/go.mod h1:2VzYrv4Gm4apmbVVsSq5bqf1Ec8v56E48Vt0Y/umPgA= +github.com/btcsuite/btcd/btcec/v2 v2.1.3/go.mod h1:ctjw4H1kknNJmRN4iP1R7bTQ+v3GJkZBd6mui8ZsAZE= +github.com/btcsuite/btcd/btcec/v2 v2.3.5 h1:dpAlnAwmT1yIBm3exhT1/8iUSD98RDJM5vqJVQDQLiU= +github.com/btcsuite/btcd/btcec/v2 v2.3.5/go.mod h1:m22FrOAiuxl/tht9wIqAoGHcbnCCaPWyauO8y2LGGtQ= +github.com/btcsuite/btcd/btcutil v1.0.0/go.mod h1:Uoxwv0pqYWhD//tfTiipkxNfdhG9UrLwaeswfjfdF0A= +github.com/btcsuite/btcd/btcutil v1.1.0/go.mod h1:5OapHB7A2hBBWLm48mmw4MOHNJCcUBTwmWH/0Jn8VHE= +github.com/btcsuite/btcd/btcutil v1.1.5 h1:+wER79R5670vs/ZusMTF1yTcRYE5GUsFbdjdisflzM8= +github.com/btcsuite/btcd/btcutil v1.1.5/go.mod h1:PSZZ4UitpLBWzxGd5VGOrLnmOjtPP/a6HaFo12zMs00= +github.com/btcsuite/btcd/btcutil/psbt v1.1.9 h1:UmfOIiWMZcVMOLaN+lxbbLSuoINGS1WmK1TZNI0b4yk= +github.com/btcsuite/btcd/btcutil/psbt v1.1.9/go.mod h1:ehBEvU91lxSlXtA+zZz3iFYx7Yq9eqnKx4/kSrnsvMY= +github.com/btcsuite/btcd/chaincfg/chainhash v1.0.0/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= +github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= +github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0 h1:59Kx4K6lzOW5w6nFlA0v5+lk/6sjybR934QNHSJZPTQ= +github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= +github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f h1:bAs4lUbRJpnnkd9VhRV3jjAVU7DJVjMaK+IsvSeZvFo= +github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= +github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= +github.com/btcsuite/btcwallet v0.16.17 h1:1N6lHznRdcjDopBvcofxaIHknArkJ/EcVKgLKfGL4Dg= +github.com/btcsuite/btcwallet v0.16.17/go.mod h1:YO+W745BAH8n/Rpgj68QsLR6eLlgM4W2do4RejT0buo= +github.com/btcsuite/btcwallet/walletdb v1.5.1 h1:HgMhDNCrtEFPC+8q0ei5DQ5U9Tl4RCspA22DEKXlopI= +github.com/btcsuite/btcwallet/walletdb v1.5.1/go.mod h1:jk/hvpLFINF0C1kfTn0bfx2GbnFT+Nvnj6eblZALfjs= +github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg= +github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY= +github.com/btcsuite/goleveldb v1.0.0/go.mod h1:QiK9vBlgftBg6rWQIj6wFzbPfRjiykIEhBH4obrXJ/I= +github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= +github.com/btcsuite/snappy-go v1.0.0/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= +github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= +github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= +github.com/decred/dcrd/crypto/blake256 v1.1.0 h1:zPMNGQCm0g4QTY27fOCorQW7EryeQ/U0x++OzVrdms8= +github.com/decred/dcrd/crypto/blake256 v1.1.0/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 h1:NMZiJj8QnKe1LgsbDayM4UoHwbvwDRwnI3hwNaAHRnc= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0/go.mod h1:ZXNYxsqcloTdSy/rNShjYzMhyjf0LaoftYK0p+A3h40= +github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/fxamacker/cbor/v2 v2.2.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo= +github.com/fxamacker/cbor/v2 v2.9.2 h1:X4Ksno9+x3cz0TZv69ec1hxP/+tymuR8PXQJyDwfh78= +github.com/fxamacker/cbor/v2 v2.9.2/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= +github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/hf/nitrite v0.0.0-20211104000856-f9e0dcc73703 h1:oTi0zYvHo1sfk5sevGc4LrfgpLYB6cIhP/HllCUGcZ8= +github.com/hf/nitrite v0.0.0-20211104000856-f9e0dcc73703/go.mod h1:ycRhVmo6wegyEl6WN+zXOHUTJvB0J2tiuH88q/McTK8= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= +github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U= +github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= +github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= +github.com/lightninglabs/neutrino/cache v1.1.2 h1:C9DY/DAPaPxbFC+xNNEI/z1SJY9GS3shmlu5hIQ798g= +github.com/lightninglabs/neutrino/cache v1.1.2/go.mod h1:XJNcgdOw1LQnanGjw8Vj44CvguYA25IMKjWFZczwZuo= +github.com/lightningnetwork/lnd/fn v1.2.1 h1:pPsVGrwi9QBwdLJzaEGK33wmiVKOxs/zc8H7+MamFf0= +github.com/lightningnetwork/lnd/fn v1.2.1/go.mod h1:SyFohpVrARPKH3XVAJZlXdVe+IwMYc4OMAvrDY32kw0= +github.com/lightningnetwork/lnd/tlv v1.2.6 h1:icvQG2yDr6k3ZuZzfRdG3EJp6pHurcuh3R6dg0gv/Mw= +github.com/lightningnetwork/lnd/tlv v1.2.6/go.mod h1:/CmY4VbItpOldksocmGT4lxiJqRP9oLxwSZOda2kzNQ= +github.com/meshapi/grpc-api-gateway v0.1.0 h1:0rGp4qZQ6T9Ud0KfzdHYsEju4AX/Q3AQOU7unoBLssY= +github.com/meshapi/grpc-api-gateway v0.1.0/go.mod h1:lkFQUbwq7i/JqEPZMzCIRskp9Jb7tm1uLODwsOdw064= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= +github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= +github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w= +github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY= +github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= +github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= +github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= +go.etcd.io/bbolt v1.3.11 h1:yGEzV1wPz2yVCLsD8ZAiGHhHVlczyC9d1rP43/VCRJ0= +go.etcd.io/bbolt v1.3.11/go.mod h1:dksAq7YMXoljX0xu6VF5DMZGbhYYoLUalEiSySYAS4I= +go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= +go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= +go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= +go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= +go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= +go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= +go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= +go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= +go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= +go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= +go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= +go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= +golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts= +golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos= +golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 h1:yqrTHse8TCMW1M1ZCP+VAR/l0kKxwaAIqN/il7x4voA= +golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU= +golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o= +golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= +golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k= +golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk= +golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= +gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= +google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17 h1:wpZ8pe2x1Q3f2KyT5f8oP/fa9rHAKgFPr/HZdNuS+PQ= +google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:J7XzRzVy1+IPwWHZUzoD0IccYZIrXILAQpc+Qy9CMhY= +google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 h1:fCvbg86sFXwdrl5LgVcTEvNC+2txB5mgROGmRL5mrls= +google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:+rXWjjaukWZun3mLfjmVnQi18E1AsFbDN9QdJ5YXLto= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 h1:ggcbiqK8WWh6l1dnltU4BgWGIGo+EVYxCaAPih/zQXQ= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= +google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE= +google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= +google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +pgregory.net/rapid v1.2.0 h1:keKAYRcjm+e1F0oAuU5F5+YPAWcyxNNRK2wud503Gnk= +pgregory.net/rapid v1.2.0/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04= diff --git a/enclave/test/grpc-client/grpc-client b/enclave/test/grpc-client/grpc-client new file mode 100755 index 0000000..49d48aa Binary files /dev/null and b/enclave/test/grpc-client/grpc-client differ diff --git a/enclave/test/grpc-client/main.go b/enclave/test/grpc-client/main.go new file mode 100644 index 0000000..6854878 --- /dev/null +++ b/enclave/test/grpc-client/main.go @@ -0,0 +1,287 @@ +// grpc-client is the attestation-verifying gRPC client used by the +// introspector enclave integration test. It builds on +// github.com/ArkLabsHQ/introspector-enclave/client to verify the NSM +// attestation document, pin PCR0, and pin the TLS leaf-cert fingerprint +// against the attestation's user_data — then dials IntrospectorService +// over native gRPC. +// +// Modes: +// +// -rpc info Calls GetInfo and prints the signer pubkey. +// -rpc submit-tx Calls GetInfo to learn the signer pubkey, builds a valid +// arkade ark_tx + checkpoint pair on a non-finalizer closure +// (so introspector signs and returns without invoking the +// downstream arkd SubmitTx/FinalizeTx), then calls SubmitTx +// and asserts both ArkTx + Checkpoints come back signed. +package main + +import ( + "context" + "encoding/hex" + "flag" + "fmt" + "os" + + "github.com/ArkLabsHQ/introspector-enclave/client" + introspectorv1 "github.com/ArkLabsHQ/introspector/api-spec/protobuf/gen/introspector/v1" + "github.com/ArkLabsHQ/introspector/pkg/arkade" + arklib "github.com/arkade-os/arkd/pkg/ark-lib" + "github.com/arkade-os/arkd/pkg/ark-lib/extension" + "github.com/arkade-os/arkd/pkg/ark-lib/offchain" + "github.com/arkade-os/arkd/pkg/ark-lib/script" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcutil/psbt" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" + "github.com/btcsuite/btcwallet/waddrmgr" +) + +func main() { + url := flag.String("url", "", "enclave HTTPS URL (e.g. https://localhost:8443)") + pcr0 := flag.String("pcr0", "", "expected PCR0 hex") + insecureCOSE := flag.Bool("insecure-skip-cose", false, "skip COSE Sign1 signature + AWS Nitro chain verification (local QEMU tests only)") + rpc := flag.String("rpc", "info", "RPC to invoke: info | submit-tx") + flag.Parse() + + if *url == "" || *pcr0 == "" { + fmt.Fprintln(os.Stderr, "error: both -url and -pcr0 are required") + os.Exit(2) + } + + ctx := context.Background() + + c, err := client.New(*url, client.Options{ + ExpectedPCR0: *pcr0, + InsecureSkipCOSEVerify: *insecureCOSE, + }) + if err != nil { + fmt.Fprintf(os.Stderr, "error: build client: %v\n", err) + os.Exit(1) + } + + conn, err := c.GRPCConn(ctx) + if err != nil { + fmt.Fprintf(os.Stderr, "error: dial gRPC (attestation chain failed): %v\n", err) + os.Exit(1) + } + defer func() { _ = conn.Close() }() + + svc := introspectorv1.NewIntrospectorServiceClient(conn) + + switch *rpc { + case "info": + runGetInfo(ctx, svc) + case "submit-tx": + runSubmitTx(ctx, svc) + default: + fmt.Fprintf(os.Stderr, "error: unknown -rpc %q (want info | submit-tx)\n", *rpc) + os.Exit(2) + } +} + +func runGetInfo(ctx context.Context, svc introspectorv1.IntrospectorServiceClient) { + resp, err := svc.GetInfo(ctx, &introspectorv1.GetInfoRequest{}) + if err != nil { + fmt.Fprintf(os.Stderr, "error: GetInfo: %v\n", err) + os.Exit(1) + } + fmt.Printf("Attestation Verified: true\n") + fmt.Printf("Version: %s\n", resp.GetVersion()) + fmt.Printf("Signer Pubkey: %s\n", resp.GetSignerPubkey()) +} + +func runSubmitTx(ctx context.Context, svc introspectorv1.IntrospectorServiceClient) { + info, err := svc.GetInfo(ctx, &introspectorv1.GetInfoRequest{}) + if err != nil { + fmt.Fprintf(os.Stderr, "error: GetInfo (for signer pubkey): %v\n", err) + os.Exit(1) + } + signerPubkeyBytes, err := hex.DecodeString(info.GetSignerPubkey()) + if err != nil { + fmt.Fprintf(os.Stderr, "error: decode signer pubkey hex: %v\n", err) + os.Exit(1) + } + signerPubkey, err := btcec.ParsePubKey(signerPubkeyBytes) + if err != nil { + fmt.Fprintf(os.Stderr, "error: parse signer pubkey: %v\n", err) + os.Exit(1) + } + fmt.Printf("Signer Pubkey: %s\n", info.GetSignerPubkey()) + + arkTxB64, checkpointB64s, err := buildSubmitTxFixture(signerPubkey) + if err != nil { + fmt.Fprintf(os.Stderr, "error: build fixture: %v\n", err) + os.Exit(1) + } + + resp, err := svc.SubmitTx(ctx, &introspectorv1.SubmitTxRequest{ + ArkTx: arkTxB64, + CheckpointTxs: checkpointB64s, + }) + if err != nil { + fmt.Fprintf(os.Stderr, "error: SubmitTx: %v\n", err) + os.Exit(1) + } + + if resp.GetSignedArkTx() == "" { + fmt.Fprintln(os.Stderr, "error: SubmitTx response missing SignedArkTx") + os.Exit(1) + } + if len(resp.GetSignedCheckpointTxs()) == 0 { + fmt.Fprintln(os.Stderr, "error: SubmitTx response missing SignedCheckpointTxs") + os.Exit(1) + } + + fmt.Printf("SubmitTx OK: ark_tx=%d bytes, checkpoints=%d\n", + len(resp.GetSignedArkTx()), len(resp.GetSignedCheckpointTxs())) +} + +// buildSubmitTxFixture builds a valid arkade ark_tx + checkpoint pair that +// exercises introspector's SubmitTx without crossing into the downstream arkd +// SubmitTx/FinalizeTx call. It puts the introspector's tweaked key first and +// a random "bob" key last in the closure, which makes the finalizer +// accumulator decide that bob (not the introspector) is the finalizer — so +// introspector signs its input + checkpoint and returns immediately. +// +// Adapted from introspector-example/examples/enclave-client/main.go's +// buildExampleTx; closure order is the only deliberate difference. +func buildSubmitTxFixture(signerPubkey *btcec.PublicKey) (string, []string, error) { + bobPrivKey, err := btcec.NewPrivateKey() + if err != nil { + return "", nil, fmt.Errorf("generate bob key: %w", err) + } + bobPubKey := bobPrivKey.PubKey() + + destPrivKey, err := btcec.NewPrivateKey() + if err != nil { + return "", nil, fmt.Errorf("generate dest key: %w", err) + } + destPkScript, err := script.P2TRScript(destPrivKey.PubKey()) + if err != nil { + return "", nil, fmt.Errorf("build dest script: %w", err) + } + + // Arkade script: verify output 0's pubkey matches the destination's x-only key. + arkadeScriptBytes, err := txscript.NewScriptBuilder(). + AddInt64(0). + AddOp(arkade.OP_INSPECTOUTPUTSCRIPTPUBKEY). + AddOp(arkade.OP_1). + AddOp(arkade.OP_EQUALVERIFY). + AddData(destPkScript[2:]). + AddOp(arkade.OP_EQUAL). + Script() + if err != nil { + return "", nil, fmt.Errorf("build arkade script: %w", err) + } + + tweakedPubKey := arkade.ComputeArkadeScriptPublicKey( + signerPubkey, arkade.ArkadeScriptHash(arkadeScriptBytes), + ) + + // Non-finalizer closure: tweakedThis appears first, bob is last. The + // finalizer accumulator picks the last non-arkd key and compares against + // tweakedSigner — bob ≠ tweakedSigner, so isFinalizer=false and the + // introspector skips the downstream arkd SubmitTx call. + vtxoScript := script.TapscriptsVtxoScript{ + Closures: []script.Closure{ + &script.MultisigClosure{ + PubKeys: []*btcec.PublicKey{tweakedPubKey, bobPubKey}, + }, + }, + } + + _, vtxoTapTree, err := vtxoScript.TapTree() + if err != nil { + return "", nil, fmt.Errorf("build vtxo tap tree: %w", err) + } + + closure := vtxoScript.ForfeitClosures()[0] + closureScript, err := closure.Script() + if err != nil { + return "", nil, fmt.Errorf("build closure script: %w", err) + } + + merkleProof, err := vtxoTapTree.GetTaprootMerkleProof( + txscript.NewBaseTapLeaf(closureScript).TapHash(), + ) + if err != nil { + return "", nil, fmt.Errorf("get merkle proof: %w", err) + } + + ctrlBlock, err := txscript.ParseControlBlock(merkleProof.ControlBlock) + if err != nil { + return "", nil, fmt.Errorf("parse control block: %w", err) + } + + tapscript := &waddrmgr.Tapscript{ + ControlBlock: ctrlBlock, + RevealedScript: merkleProof.Script, + } + + unrollKey, err := btcec.NewPrivateKey() + if err != nil { + return "", nil, fmt.Errorf("generate unroll key: %w", err) + } + unrollClosure := &script.CSVMultisigClosure{ + MultisigClosure: script.MultisigClosure{ + PubKeys: []*btcec.PublicKey{unrollKey.PubKey()}, + }, + Locktime: arklib.RelativeLocktime{Type: arklib.LocktimeTypeBlock, Value: 10}, + } + unrollScript, err := unrollClosure.Script() + if err != nil { + return "", nil, fmt.Errorf("build unroll script: %w", err) + } + + const amount = int64(10000) + fakeOutpoint := &wire.OutPoint{ + Hash: chainhash.Hash{0x01}, + Index: 0, + } + + arkTx, checkpointPsbts, err := offchain.BuildTxs( + []offchain.VtxoInput{{ + Outpoint: fakeOutpoint, + Tapscript: tapscript, + Amount: amount, + RevealedTapscripts: []string{hex.EncodeToString(closureScript)}, + }}, + []*wire.TxOut{{Value: amount, PkScript: destPkScript}}, + unrollScript, + ) + if err != nil { + return "", nil, fmt.Errorf("build txs: %w", err) + } + + // Encode the arkade script as an IntrospectorPacket in an OP_RETURN + // extension output on the ark tx — this is the format introspector reads + // via arkade.FindIntrospectorPacket at SubmitTx time. + packet, err := arkade.NewPacket(arkade.IntrospectorEntry{Vin: 0, Script: arkadeScriptBytes}) + if err != nil { + return "", nil, fmt.Errorf("build introspector packet: %w", err) + } + ext := extension.Extension{packet} + extTxOut, err := ext.TxOut() + if err != nil { + return "", nil, fmt.Errorf("encode extension txout: %w", err) + } + arkTx.UnsignedTx.AddTxOut(extTxOut) + arkTx.Outputs = append(arkTx.Outputs, psbt.POutput{}) + + encodedArkTx, err := arkTx.B64Encode() + if err != nil { + return "", nil, fmt.Errorf("encode ark tx: %w", err) + } + + encodedCheckpoints := make([]string, 0, len(checkpointPsbts)) + for _, cp := range checkpointPsbts { + enc, err := cp.B64Encode() + if err != nil { + return "", nil, fmt.Errorf("encode checkpoint: %w", err) + } + encodedCheckpoints = append(encodedCheckpoints, enc) + } + + return encodedArkTx, encodedCheckpoints, nil +} diff --git a/enclave/test/integration-test.sh b/enclave/test/integration-test.sh new file mode 100755 index 0000000..433807b --- /dev/null +++ b/enclave/test/integration-test.sh @@ -0,0 +1,124 @@ +#!/usr/bin/env bash +# Integration tests for the introspector enclave. +# +# Boots an EIF that runs introspector inside the framework, then asserts: +# 1. /health returns 200 (enclave reaches a ready state) +# 2. HTTP/2 ALPN is negotiated (framework's TLS edge advertises h2) +# 3. enclave-client GetInfo over native gRPC succeeds with the right PCR0 +# (exercises Middleware bypass + h2c revProxy + introspector's +# grpc.Server.ServeHTTP) +# 4. enclave-client rejects a wrong PCR0 at the attestation handshake +# (proves attestation pinning isn't a silent no-op) +# 5. HTTP/1.1 still serves /health (backward compat with non-h2 clients) +set -euo pipefail + +HOST_TLS_PORT="${HOST_TLS_PORT:-8443}" +BASE_URL="${ENCLAVE_URL:-https://localhost:${HOST_TLS_PORT}}" +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" +PCR_FILE="${PCR_FILE:-$REPO_ROOT/.enclave/artifacts/pcr.json}" +GRPC_CLIENT="${GRPC_CLIENT:-$SCRIPT_DIR/grpc-client/grpc-client}" + +if [ ! -x "$GRPC_CLIENT" ]; then + echo "Building grpc-client at $GRPC_CLIENT ..." + (cd "$SCRIPT_DIR/grpc-client" && go build -o grpc-client . ) || { + echo "Error: failed to build grpc-client; install Go or pre-build the binary" >&2 + exit 1 + } +fi + +CURL="curl -sk --max-time 10" +PASSED=0 +FAILED=0 +TOTAL=0 + +pass() { echo " PASS: $1"; PASSED=$((PASSED + 1)); TOTAL=$((TOTAL + 1)); } +fail() { echo " FAIL: $1 — $2"; FAILED=$((FAILED + 1)); TOTAL=$((TOTAL + 1)); } + +echo "=== Integration tests against $BASE_URL ===" +echo "" + +# Test 1: /health returns 200. +echo "[1/6] Health check" +HEALTH_CODE=$($CURL -o /dev/null -w '%{http_code}' "${BASE_URL}/health" 2>/dev/null || echo "000") +if [ "$HEALTH_CODE" = "200" ]; then + pass "Health endpoint returns 200" +else + fail "Health endpoint" "expected 200, got HTTP $HEALTH_CODE" +fi + +# Test 2: HTTP/2 ALPN negotiation. +echo "[2/6] HTTP/2 ALPN negotiation" +H2_OUT=$(curl -sk --http2 -o /dev/null -w '%{http_version}' --max-time 10 \ + "${BASE_URL}/health" 2>/dev/null || echo "") +if [ "$H2_OUT" = "2" ]; then + pass "ALPN negotiated h2 (http_version=$H2_OUT)" +else + fail "HTTP/2 negotiation" "expected http_version=2, got '$H2_OUT'" +fi + +# Test 3: native gRPC GetInfo via enclave-client (attestation-verified). +echo "[3/6] enclave-client GetInfo (native gRPC + attestation)" +if [ ! -f "$PCR_FILE" ]; then + fail "enclave-client GetInfo" "PCR file not found at $PCR_FILE" +else + PCR0=$(jq -r '.PCR0' "$PCR_FILE") + INFO_OUT=$("$GRPC_CLIENT" \ + -url "${BASE_URL}" \ + -pcr0 "${PCR0}" \ + -insecure-skip-cose 2>&1 || true) + if echo "$INFO_OUT" | grep -q '^Signer Pubkey:'; then + PUBKEY=$(echo "$INFO_OUT" | awk '/^Signer Pubkey:/ {print $3}' | head -1) + pass "GetInfo succeeded (signer_pubkey=${PUBKEY:0:16}...)" + else + fail "enclave-client GetInfo" "stdout: ${INFO_OUT:0:300}" + fi +fi + +# Test 4: attestation rejection with wrong PCR0. +echo "[4/6] enclave-client rejects wrong PCR0" +WRONG_PCR0="00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" +REJECT_OUT=$("$GRPC_CLIENT" \ + -url "${BASE_URL}" \ + -pcr0 "${WRONG_PCR0}" \ + -insecure-skip-cose 2>&1 || echo "EXITED_NONZERO") +if echo "$REJECT_OUT" | grep -qiE 'PCR0|attestation|mismatch'; then + pass "client rejected wrong PCR0" +else + fail "PCR0 rejection" "expected an attestation error, got: ${REJECT_OUT:0:200}" +fi + +# Test 5: native gRPC SubmitTx (exercises the full RPC payload + framework +# routing for a *write* path, not just GetInfo). Uses a non-finalizer closure +# so introspector signs and returns without dialing the mock-arkd downstream. +echo "[5/6] enclave-client SubmitTx (native gRPC + attestation)" +if [ ! -f "$PCR_FILE" ]; then + fail "enclave-client SubmitTx" "PCR file not found at $PCR_FILE" +else + PCR0=$(jq -r '.PCR0' "$PCR_FILE") + SUBMIT_OUT=$("$GRPC_CLIENT" \ + -url "${BASE_URL}" \ + -pcr0 "${PCR0}" \ + -insecure-skip-cose \ + -rpc submit-tx 2>&1 || true) + if echo "$SUBMIT_OUT" | grep -q '^SubmitTx OK:'; then + pass "SubmitTx succeeded ($(echo "$SUBMIT_OUT" | grep '^SubmitTx OK:' | head -1 | sed 's/^SubmitTx OK: *//'))" + else + fail "enclave-client SubmitTx" "stdout: ${SUBMIT_OUT:0:500}" + fi +fi + +# Test 6: HTTP/1.1 backward compatibility. +echo "[6/6] HTTP/1.1 backward compatibility" +H1_OUT=$(curl -sk --http1.1 -o /dev/null -w '%{http_version}' --max-time 10 \ + "${BASE_URL}/health" 2>/dev/null || echo "") +if [ "$H1_OUT" = "1.1" ]; then + pass "HTTP/1.1 still serves /health (http_version=$H1_OUT)" +else + fail "HTTP/1.1 compat" "expected http_version=1.1, got '$H1_OUT'" +fi + +echo "" +echo "=== Results: $PASSED passed, $FAILED failed (of $TOTAL) ===" +[ "$FAILED" -gt 0 ] && exit 1 +exit 0 diff --git a/enclave/test/mock-arkd/Dockerfile b/enclave/test/mock-arkd/Dockerfile new file mode 100644 index 0000000..0dc757e --- /dev/null +++ b/enclave/test/mock-arkd/Dockerfile @@ -0,0 +1,18 @@ +# Build context is the introspector repo root so the mock can resolve +# any vendored deps from go.mod. Output binary listens on :8081. +FROM golang:1.26-bookworm AS builder +WORKDIR /src +COPY enclave/test/mock-arkd/go.mod enclave/test/mock-arkd/go.sum* ./ +COPY enclave/test/mock-arkd/main.go . +RUN go mod download +RUN CGO_ENABLED=0 go build -trimpath -o /mock-arkd . + +FROM debian:bookworm-slim +COPY --from=builder /mock-arkd /usr/local/bin/mock-arkd +# Bake grpcurl in so docker-compose's healthcheck can probe the gRPC port. +RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates wget \ + && wget -qO- https://github.com/fullstorydev/grpcurl/releases/download/v1.9.1/grpcurl_1.9.1_linux_x86_64.tar.gz \ + | tar xz -C /usr/local/bin grpcurl \ + && rm -rf /var/lib/apt/lists/* +EXPOSE 8081 +ENTRYPOINT ["/usr/local/bin/mock-arkd"] diff --git a/enclave/test/mock-arkd/go.mod b/enclave/test/mock-arkd/go.mod new file mode 100644 index 0000000..a94ab48 --- /dev/null +++ b/enclave/test/mock-arkd/go.mod @@ -0,0 +1,17 @@ +module github.com/ArkLabsHQ/introspector/enclave/test/mock-arkd + +go 1.25.7 + +require ( + github.com/arkade-os/go-sdk v0.8.2-0.20260303154656-f29d9e77d5c7 + google.golang.org/grpc v1.79.2 +) + +require ( + github.com/meshapi/grpc-api-gateway v0.1.0 // indirect + golang.org/x/net v0.49.0 // indirect + golang.org/x/sys v0.41.0 // indirect + golang.org/x/text v0.34.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 // indirect + google.golang.org/protobuf v1.36.11 // indirect +) diff --git a/enclave/test/mock-arkd/go.sum b/enclave/test/mock-arkd/go.sum new file mode 100644 index 0000000..94e0682 --- /dev/null +++ b/enclave/test/mock-arkd/go.sum @@ -0,0 +1,42 @@ +github.com/arkade-os/go-sdk v0.8.2-0.20260303154656-f29d9e77d5c7 h1:Z58MYILN93wLc2jmRIMGl0mVBw5lFU9ED5xIHDx9Y8s= +github.com/arkade-os/go-sdk v0.8.2-0.20260303154656-f29d9e77d5c7/go.mod h1:RRv10MIvBN96qCdIMJplMpJmr/Z2g0wqy/FmyADA/ME= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/meshapi/grpc-api-gateway v0.1.0 h1:0rGp4qZQ6T9Ud0KfzdHYsEju4AX/Q3AQOU7unoBLssY= +github.com/meshapi/grpc-api-gateway v0.1.0/go.mod h1:lkFQUbwq7i/JqEPZMzCIRskp9Jb7tm1uLODwsOdw064= +go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= +go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= +go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= +go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= +go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= +go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= +go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= +go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= +go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= +go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= +go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= +go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= +golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o= +golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8= +golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k= +golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk= +golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA= +gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= +gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 h1:ggcbiqK8WWh6l1dnltU4BgWGIGo+EVYxCaAPih/zQXQ= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= +google.golang.org/grpc v1.79.2 h1:fRMD94s2tITpyJGtBBn7MkMseNpOZU8ZxgC3MMBaXRU= +google.golang.org/grpc v1.79.2/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= +google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= +google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= diff --git a/enclave/test/mock-arkd/main.go b/enclave/test/mock-arkd/main.go new file mode 100644 index 0000000..8a72a10 --- /dev/null +++ b/enclave/test/mock-arkd/main.go @@ -0,0 +1,51 @@ +// mock-arkd is a stub implementation of the arkd gRPC service, sufficient +// for introspector's startup GetInfo() call to succeed. Only GetInfo is +// implemented; every other RPC returns Unimplemented via the embedded +// UnimplementedArkServiceServer. +package main + +import ( + "context" + "log" + "net" + "os" + + arkv1 "github.com/arkade-os/go-sdk/api-spec/protobuf/gen/ark/v1" + "google.golang.org/grpc" + "google.golang.org/grpc/reflection" +) + +// Hex-encoded compressed secp256k1 public key the mock returns. +// Real value derived from a known fixture private key; the only requirement +// from introspector's side (internal/application/service.go:62-89) is that +// it decodes as a valid compressed pubkey. +const fixtureSignerPubkey = "02b4632d08485ff1df2db55b9dafd23347d1c47a457072a1e87be26896549a8737" + +type mockArk struct { + arkv1.UnimplementedArkServiceServer +} + +func (mockArk) GetInfo(_ context.Context, _ *arkv1.GetInfoRequest) (*arkv1.GetInfoResponse, error) { + return &arkv1.GetInfoResponse{ + SignerPubkey: fixtureSignerPubkey, + Network: "regtest", + }, nil +} + +func main() { + addr := os.Getenv("MOCK_ARKD_ADDR") + if addr == "" { + addr = ":8081" + } + lis, err := net.Listen("tcp", addr) + if err != nil { + log.Fatalf("listen %s: %v", addr, err) + } + srv := grpc.NewServer() + arkv1.RegisterArkServiceServer(srv, mockArk{}) + reflection.Register(srv) + log.Printf("mock-arkd listening on %s, signer_pubkey=%s", addr, fixtureSignerPubkey) + if err := srv.Serve(lis); err != nil { + log.Fatalf("serve: %v", err) + } +}