GroupAuth creates a peer network where GitHub runners and Dstack TEEs register as equal members, prove their code identity, and onboard each other with encrypted group secrets.
Components:
- GroupAuth contract — deployed to Base mainnet
- Dstack TEE agent — long-running container on Phala Cloud, watches for new members
- GitHub runner — ephemeral, registers via ZK proof during workflow
┌─────────────────┐ ┌──────────────────┐
│ GitHub Runner │ │ Phala TEE │
│ (ephemeral) │ │ (long-running) │
│ │ │ │
│ 1. Generate ZK │ │ 1. Get derived │
│ proof from │ │ key from KMS │
│ Sigstore att │ │ 2. registerDstack│
│ 2. registerGitHub│ │ 3. Watch events │
│ 3. Read onboard │ │ 4. Onboard new │
│ messages │ │ members │
└────────┬─────────┘ └────────┬─────────┘
│ │
└───────┐ ┌────────────┘
▼ ▼
┌───────────────────┐
│ GroupAuth.sol │
│ (Base mainnet) │
│ │
│ allowedCode[] │
│ members[] │
│ onboarding[] │
└───────────────────┘
When deploying with phala deploy, the --kms flag (default: phala) determines:
- Which KMS signs your derived keys — each KMS type has a different root address
- The gateway URL prefix — the URL infix matches the KMS type
--kms value |
URL infix | Example domain |
|---|---|---|
phala (default) |
pha- |
dstack-pha-prod7.phala.network |
base |
base- |
dstack-base-prod7.phala.network |
ethereum/eth |
eth- |
dstack-eth-prod7.phala.network |
CRITICAL: phala deploy does NOT output the endpoint URL. You must run
phala cvms get <name> --json and check gateway.base_domain or endpoints[0].app
to get the correct URL. The URL format is:
https://<app_id>-<port>.<gateway.base_domain>
Each KMS type has a different root signer. You CANNOT derive the KMS root from the attestation's P-256 DER key — the KMS signs with secp256k1 internally.
To discover the production KMS root:
- Deploy a CVM with your chosen
--kmstype - Have your app recover the KMS signer from an actual KMS signature via
ecrecover - Use that address as the
kmsRootin your contract
| KMS type | Root address |
|---|---|
| Simulator | 0x8f2cF602C9695b23130367ed78d8F557554de7C5 |
| Production (phala) | 0xd5BDeB037F237Baac161EA37999B6aA37f7f4C77 |
| Production (base) | TBD |
- Foundry installed with deployer key at
~/.foundry/keystores/deployer.key
cd contracts
# Deploy HonkVerifier + SigstoreVerifier + GroupAuth
KMS_ROOT=0xd5BDeB037F237Baac161EA37999B6aA37f7f4C77 # phala KMS
SIGSTORE_VERIFIER=0x904Ae91989C4C96F2f51f1F8c9eF65C3730b3d8d # or deploy fresh
forge script script/DeployGroupAuth.s.sol \
--rpc-url https://mainnet.base.org \
--keystore ~/.foundry/keystores/deployer.key \
--broadcast
# Add allowed code IDs (owner only)
# GitHub: bytes32(bytes20(commitSha)) — right-padded
# Dstack: bytes32(bytes20(appId)) — right-padded
cast send <GROUPAUTH_ADDR> "addAllowedCode(bytes32)" <CODE_ID> \
--rpc-url https://mainnet.base.org \
--keystore ~/.foundry/keystores/deployer.key \
--gas-limit 50000The TEE agent is a Python container that:
- Derives a key from Dstack KMS
- Registers itself via
registerDstack() - Watches
MemberRegisteredevents - Posts onboarding messages (encrypted group secret) for new members
services:
ssh:
build:
context: .
dockerfile_inline: |
FROM ubuntu:22.04
RUN apt-get update && apt-get install -y openssh-server
RUN mkdir /run/sshd
RUN echo 'root:groupauth' | chpasswd
RUN sed -i 's/#PermitRootLogin prohibit-password/PermitRootLogin yes/' /etc/ssh/sshd_config
RUN sed -i 's/#Port 22/Port 1022/' /etc/ssh/sshd_config
CMD ["/usr/sbin/sshd", "-D"]
restart: unless-stopped
network_mode: host
groupauth-agent:
image: ghcr.io/amiller/groupauth-agent:v5
ports:
- "8080:8080"
environment:
GROUPAUTH_ADDRESS: ${GROUPAUTH_ADDRESS}
RPC_URL: ${RPC_URL:-https://sepolia.base.org}
GROUP_SECRET: ${GROUP_SECRET}
volumes:
- /var/run/dstack.sock:/var/run/dstack.sock
restart: unless-stopped# Build OCI image
docker buildx build \
--file Dockerfile \
--output type=oci,dest=image.tar \
.
# Push to GHCR
skopeo copy oci-archive:image.tar docker://ghcr.io/amiller/groupauth-agent:v5
# Or simpler:
docker build -t ghcr.io/amiller/groupauth-agent:v5 .
docker push ghcr.io/amiller/groupauth-agent:v5# Auth (already done if phala CLI configured)
phala auth login <API_KEY>
# Deploy — NOTE: --kms flag determines KMS type AND gateway URL prefix
# Default is "phala". Use --kms base for Base KMS.
phala deploy -n groupauth-agent -c docker-compose.yaml
# or: phala deploy -n groupauth-agent -c docker-compose.yaml --kms base
# IMPORTANT: phala deploy does NOT output the endpoint URL!
# Get it from:
phala cvms get groupauth-agent --json | jq '.endpoints[0].app'
# → https://<app_id>-8080.dstack-<kms>-prod7.phala.network
# Check status
phala cvms list
phala cvms logs groupauth-agent# Tunnel through TLS
socat TCP-LISTEN:11025,bind=127.0.0.1,fork,reuseaddr \
OPENSSL:<cvm-id>-1022.dstack-pha-prod7.phala.network:443 &
sshpass -p 'groupauth' ssh root@127.0.0.1 -p 11025The GitHub runner registers via the existing ZK proof pipeline:
# In .github/workflows/groupauth.yml
- name: Generate ZK proof
run: |
docker run --rm \
-v $GITHUB_WORKSPACE/bundle.json:/work/bundle.json:ro \
-v /tmp/proof:/output \
ghcr.io/amiller/zkproof generate /work/bundle.json /output
- name: Register and read onboarding
run: |
# Generate ephemeral keypair
PRIVKEY=$(openssl rand -hex 32)
PUBKEY=$(cast wallet address --private-key $PRIVKEY)
# Register via GroupAuth
cast send $GROUPAUTH_ADDRESS \
"registerGitHub(bytes,bytes32[],bytes)" \
$(cat /tmp/proof/proof.bin | xxd -p -c0) \
"[$(cat /tmp/proof/inputs.bin | xxd -p -c0 | fold -w64 | sed 's/^/0x/' | paste -sd,)]" \
$PUBKEY \
--rpc-url $RPC_URL --private-key $PRIVKEY
# Read onboarding messages
MEMBER_ID=$(cast keccak $PUBKEY)
cast call $GROUPAUTH_ADDRESS "getOnboarding(bytes32)" $MEMBER_ID --rpc-url $RPC_URL| Component | Address | Network |
|---|---|---|
| GroupAuth | 0xdd29de730b99b876f21f3ab5dafba6711ff2c6ac |
Base mainnet |
| SigstoreVerifier | 0x904Ae91989C4C96F2f51f1F8c9eF65C3730b3d8d |
Base mainnet |
| HonkVerifier | 0xd317A58C478a18CA71BfC60Aab85538aB28b98ab |
Base mainnet |
| Dstack KMS Root (sim) | 0x8f2cF602C9695b23130367ed78d8F557554de7C5 |
— |
| Dstack KMS Root (phala prod) | 0xd5BDeB037F237Baac161EA37999B6aA37f7f4C77 |
— |
| TEE Agent CVM | app_7385b203510cc6735e512ca776ad27c37a52d249 |
Phala Cloud (phala KMS) |
| TEE Agent health | https://7385b203510cc6735e512ca776ad27c37a52d249-8080.dstack-pha-prod7.phala.network/ |
— |
| TEE Agent memberId | 0x66a87d52... |
— |
- 22 forge unit tests pass
- Integration: GitHub→GitHub (real ZK proof on Anvil)
- Integration: GitHub→Dstack (real Dstack simulator on Anvil)
- Integration: Dstack→GitHub (real Dstack simulator on Anvil)
- Deploy contracts to Base mainnet
- Verify contracts on Basescan
- Deploy TEE agent to Phala Cloud
- TEE agent registers on-chain
- End-to-end: GitHub registers + TEE auto-onboards
- GitHub workflow for GroupAuth registration