Skip to content

Leader/Follower Static Secret Sharing - #139

Draft
aruokhai wants to merge 9 commits into
masterfrom
dkg
Draft

Leader/Follower Static Secret Sharing#139
aruokhai wants to merge 9 commits into
masterfrom
dkg

Conversation

@aruokhai

@aruokhai aruokhai commented Jul 1, 2026

Copy link
Copy Markdown
Collaborator

Leader/Follower threshold-shared static secrets

Adds a threshold-cryptography scaling model for enclave apps. A secret tagged
kind: signing is shared amongst enclaves: each node keeps only a share, a quorum is required to sign, and the
group public key stays pinned to the original secret as nodes join or leave. Ordinary
secrets and single-node deployments are unchanged (opt-in).

Full design: SCALING-ARCHITECTURE.md.

What's in scope

  • RolesScalingEntity (runtime/scaling.go), built in Runtime.Init only when
    ENCLAVE_SCALING_ROLE is set (nil otherwise). Leader runs sdk.NewLeader + the gRPC
    relay; follower runs sdk.NewJoiningFollower. Both hold a KMS-persisted secp256k1 host
    identity key.
  • Group-key pinningDkgStore (runtime/chilldkg.go) adapts sdk.Store onto
    encrypted Storage; GetLeaderSecret returns the leader's original secret (a0) so
    every ceremony reconstructs the same group key.
  • Attested admission — follower-initiated, mutually attested, over the leader's TLS
    port: GET /enclave/handshake/nonce then POST /enclave/handshake/admit. The leader
    verifies the COSE signature and PCRs 0/1/2 (nitriding.ArePCRsIdenticalForKeys), then
    seals {leaderPub, relayPort} to the follower's ephemeral key. The admitted host key
    backs sdk.WithAuthorize, so the relay rejects unadmitted keys.
  • Lifecycle — each join reshares over the same a0; all shares re-randomize, the
    group key holds. Results arrive on Keys(); a listener persists the sealed envelope,
    sets the secret's env var, and triggers a graceful upstream restart
    (RequestUpstreamRestart → SIGTERM/10s → re-fork) so the app adopts the new share.
  • Persistence — one KMS-encrypted JSON envelope per node in SSM
    (sharedSecret{leader_secret, share}); leader writes both atomically, followers only
    share.
  • Dev configruntime/devcmdline.go maps whitelisted enclavecfg.* kernel-cmdline
    tokens to env vars (no-op unless the measured ENCLAVE_DEV flag is set), letting
    identical-PCR QEMU enclaves take distinct roles.

Testing

  • New 3-node harness.github/workflows/scaling-test.yml, test/run-scaling.sh,
    test/scaling-test.sh. Boots 1 leader + 2 followers of the same EIF on QEMU/KVM and
    asserts: nodes healthy/role-correct, handshake live, every node holds a share (not
    the leader secret), the 2nd join re-randomizes the 1st follower's share, all shares
    distinct valid scalars.

Notes

  • Consumes threshold_sdk@v0.1.0 / threshold_core@v0.1.0 as public versioned modules.

aruokhai and others added 9 commits July 1, 2026 14:00
Lay the foundation for sharing a `signing` secret across a horizontally
scaled enclave fleet: the master holds the full secret and acts as a
threshold-sdk leader, and same-image joiner enclaves attest in to receive
their shares. Untagged/static secrets are unchanged.

Config:
- Add `kind` to secrets (static|signing) with validation, thread it through
  the CLI build config and enclave.yaml scaffolding.

Runtime:
- ScalingEntity (scaling.go): role-gated leader/follower, attested admission
  handshake (/enclave/handshake/{nonce,admit}) with mutual PCR verification,
  one-shot nonces, and a NaCl-sealed reply; loads/generates the threshold
  host key via KMS. Relay config is port-only (ENCLAVE_SCALING_LISTEN_PORT /
  ENCLAVE_SCALING_LEADER_ADDR), bound on the TAP address.
- DkgStore (chilldkg.go): thresholdsdk.Store over encrypted Storage; persists
  only public recovery blobs and returns the master's KMS secret as a0, so the
  reconstructed group secret equals the normally-created secret.
- nitriding.ArePCRsIdenticalForKeys: compare only code-identity PCRs (0,1,2)
  so same-image enclaves on different EC2 instances admit each other.
- KMS.decryptToBytes: attestation-gated decrypt to raw plaintext.
- StaticSecrets: wire the scaling subsystem, add the follower key-derivation
  path, and skip signing secrets in ExtendPCRs.
- Build ScalingEntity in Init when ENCLAVE_THRESHOLD_ROLE is set; mount the
  leader admission routes on the public listener.
Wire the scaling subsystem end to end so leader and follower enclaves hold
and reload their threshold signing material across reboots.

- Rename ENCLAVE_THRESHOLD_ROLE -> ENCLAVE_SCALING_ROLE (code + design doc).
- Persist derived shares: both roles drain the SDK Keys() channel into
  StoreDerivedSecret, which bundles the master (a0) and this node's share
  into one KMS-encrypted envelope per secret. A later boot loads both from
  the single SSM slot — EnvVar=master + ShareEnvVar=share on a leader,
  EnvVar=share on a follower — instead of re-running a ceremony.
- Pin the group key: DkgStore.GetLeaderSecret resolves a0 from the master's
  env var, so the group forms around the KMS-managed secret rather than a
  freshly generated one. Drop the no-op PutLeaderSecret (the SDK now owns
  a0's lifecycle through GetLeaderSecret alone).
- Wire ScalingEntity.kms and move scaling Init after EnsureKeyID so the
  KMS-backed host key can be generated.
- Build the scaling entity in New so a leader's admission handshake routes
  actually mount (gated on IsLeader); AWS-backed bring-up stays in Init.
…e scaling ceremony

Builds on the persisted-share scaffolding to run a 1-leader + N-follower
threshold ceremony across single-host enclaves, with re-randomized shares
taking effect in the already-running app.

Runtime:
- Re-fork the child app when a reshare re-materializes its share. A share
  reaches the app only through the env var it inherits at fork, so a post-init
  reshare isn't visible live; the share handler now signals main to restart the
  app once it has started. Collapse the app lifecycle into one upstreamState
  (NotStarted -> Running -> Exited), replacing the started/exited booleans.
- Initialize Storage before the scaling entity, so the leader can persist DKG
  recovery blobs the instant a follower joins (was initialized last, racing the
  handshake coming up).
- Construct the scaling entity in Init from the SSM-overlaid role and mount the
  leader handshake routes unconditionally, gated per-request by readyLeader.
- Reshare only "signing" secrets on a follower; keep the leader master (a0) in
  an in-memory cache so it survives once the env var holds the node's share.
  Drop ShareEnvVar -- one env var per secret.
- Skip TLS PKI on the follower->leader handshake (trust is mutual NSM
  attestation) and read local PCRs without strict verification in dev.
- Add a per-instance kernel-cmdline override channel (devcmdline) so same-image
  nodes get their deployment / role / vsock ports without changing PCRs.

Test:
- run-scaling.sh boots the ceremony on one host (per-node vsock CID, gvproxy
  port, SSM namespace); scaling-test.sh asserts three distinct valid shares and
  that a new join re-randomizes an incumbent. seed-scaling.sh provisions the
  per-deployment KMS placeholder + storage bucket (mirroring tofu). Adds
  `make test-scaling` + a docker-compose scaling-runner; /test/scaling exposes
  each node's share pubkey.
…aling CI

Replace the machine-local 'replace => /home/...' on the threshold SDK/core with versioned requires on the now-public modules github.com/ArkLabsHQ/threshold-magic/threshold_{sdk,core}@v0.1.0, so the runtime module resolves anywhere (CI, nix, fresh checkouts) instead of only on one dev's machine.

Add .github/workflows/scaling-test.yml: boots the 1-leader + 2-follower ceremony on ubuntu-latest (KVM + vhost-device-vsock), mirroring 'make test-scaling'. Validation-phase triggers (workflow_dispatch + push:[dkg]) until proven green on GitHub's kernel.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…improve related comments

- enhance metrics struct for clarity and consistency
- adjust scaling logic to reference leader secret instead of master
test(runtime): modify tests to reflect changes in leader secret handling
docs: add Threshold Scaling Architecture documentation
Add autonomous membership revocation to the threshold-scaling cluster. The
leader continuously monitors follower liveness and automatically evicts failed
members, triggering a resharing of all secrets among the remaining quorum. This
closes the "removal requires a leave-triggered reshare" gap documented in
SCALING-ARCHITECTURE.md.

Mechanism:
- Followers periodically sign a fresh leader nonce with their threshold host
  key (BIP-340 Schnorr) and POST it to /enclave/heartbeat.
- The leader verifies the signature and nonce, recording each follower's
  last-seen timestamp.
- A background monitor evicts followers that exceed
  ENCLAVE_SCALING_LIVENESS_TIMEOUT.
- Eviction invokes Leader.Ban, removing the member, rekeying all secrets, and
  invalidating the follower's share while preserving the threshold public key.
- Bans are persisted in encrypted storage and enforced during both
  authorization and attested admission, preventing re-admission across leader
  restarts.

Heartbeat intervals and liveness timeouts are configurable via the
enclavecfg.* command-line whitelist.

Also simplifies the follower join path by inlining followerJoinHandshake into
startFollower, and adds comprehensive unit and end-to-end tests covering
heartbeat authentication, automatic eviction, durable ban enforcement, and
resharing after follower failure.
@arkana-ai-bot

Copy link
Copy Markdown

This PR has been open for 34 days without a review decision. @aruokhai is anyone looking at this?

@arkana-ai-bot arkana-ai-bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review — Leader/Follower Static Secret Sharing (DKG)

⚠️ Security-critical cryptographic feature — mandatory human review required. This PR has been open since 2026-07-01 with no review.

Summary

Adds threshold cryptography for enclave signing: a signing secret is shared amongst N enclaves, with a quorum required to sign, while the group public key stays pinned to the original secret across resharing.

Areas requiring human expert review

  1. DKG correctness — Uses threshold_sdk@v0.1.0 / threshold_core@v0.1.0. These are v0.1.0 public modules with no track record. The DKG ceremony correctness (especially a0 leader secret re-use across reshares to pin the group key) should be reviewed by a cryptographer.

  2. Attested admission — Followers are admitted via mutually attested handshake over TLS. The PCR check (nitriding.ArePCRsIdenticalForKeys) must be reviewed carefully — a lenient PCR check would allow a compromised node to join the quorum.

  3. Share persistence atomicity — Leader writes {leader_secret, share} atomically to SSM. If the write fails between the two fields, the node has an inconsistent state. Verify error handling covers this.

  4. Group restart on reshare — The lifecycle triggers RequestUpstreamRestart (SIGTERM → 10s → re-fork) on new share receipt. Verify that in-flight signing operations are drained before restart, and that a failed restart doesn't leave the signer in a non-responsive state while still holding a valid share.

  5. Dev mode kernel-cmdlinedevcmdline.go maps kernel-cmdline tokens to env vars in dev mode. Verify this path is entirely gated on the measured ENCLAVE_DEV flag and cannot be activated in production images.

  6. No quorum = no signing — If the threshold is t-of-n and fewer than t nodes are live, signing halts. This should be documented as an availability tradeoff — especially relevant if funds are gated on enclave signing.

This needs expert cryptographic review before merge.

@arkana-ai-bot

Copy link
Copy Markdown

This PR has been open for 5+ weeks without review. @aruokhai this is a security-critical cryptographic feature (threshold signing DKG). Is there a reviewer assigned?

@arkana-ai-bot

Copy link
Copy Markdown

This PR has been open for 41 days without a review decision. @aruokhai is anyone looking at this?

@arkana-ai-bot

Copy link
Copy Markdown

This PR has been open for 44+ days without review. @aruokhai is anyone looking at this?

@arkana-ai-bot

Copy link
Copy Markdown

This PR has been open for weeks without review. @aruokhai is anyone looking at this?

@arkana-ai-bot

Copy link
Copy Markdown

This PR has been open for 44+ days without review. @aruokhai is anyone looking at this?

@arkana-ai-bot

Copy link
Copy Markdown

This PR has been open for 49 days without a review decision. @aruokhai is anyone looking at this? The Leader/Follower Secret Sharing design looks significant — would benefit from a dedicated reviewer.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants