Skip to content

sign and aggregate confidential relay responses#22302

Open
nadahalli wants to merge 12 commits intodevelopfrom
tejaswi/relay-response-signatures
Open

sign and aggregate confidential relay responses#22302
nadahalli wants to merge 12 commits intodevelopfrom
tejaswi/relay-response-signatures

Conversation

@nadahalli
Copy link
Copy Markdown
Contributor

@nadahalli nadahalli commented May 5, 2026

What changed

This PR covers both halves of the relay-DON response-signature work for the reverse path back to the enclave:

Per-node signing (was #22302). Each confidential relay node signs the logical relay response it returns toward the enclave path. Adds signed secrets and capability responses from core/capabilities/confidentialrelay, CRE wiring to resolve the node P2P key and inject a relay-response signer into the confidential relay service, and handler tests that verify the returned ed25519 signatures against the returned signer public key.

Gateway aggregation (was #22304). The confidential-relay gateway aggregator now buckets per-node signed responses by their canonical logical hash (via Hash(params) helpers in chainlink-common) and merges signatures into a single envelope once F+1 unique signers vouch for the same hash. Aggregator dispatches on req.Method to handle both confidential.secrets.get and confidential.capability.execute. Transport-level JSON-RPC error responses are dropped from quorum counting per the design doc; only signed logical responses count.

A defense-in-depth comment in aggregator.go describes the future signature-verification hook for when the gateway has access to the relay-DON signer set; the trust anchor remains the enclave.

This PR also bumps chainlink-common to the merged relay-signature types from smartcontractkit/chainlink-common#2021.

Why

After chainlink-common defined the shared signed-response wrappers and hash helpers, signing alone is not deployable: the previous aggregator bucketed by full-envelope digest, which would break once each honest node's envelope started carrying a different signature for identical logical content. Combining signing and aggregation here makes the change deployable end-to-end, so the enclave (PRIV-432, confidential-compute #324) can verify F+1 unique relay signatures over a shared logical payload at E9.

These two parts were previously split across #22302 (signing) and #22304 (aggregation, stacked on signing). Per review feedback, they belong together.

Impact

  • Logical relay success responses are returned in signed wrapper types.
  • Logical capability error results are also signed.
  • The gateway aggregator counts F+1 unique signers over the canonical logical hash.
  • Transport-level JSON-RPC errors remain unsigned and do not count toward quorum.

Validation

  • go test ./core/capabilities/confidentialrelay
  • go test ./core/services/gateway/handlers/confidentialrelay
  • go test ./core/services/cre -run TestDoesNotExist

@nadahalli nadahalli requested review from a team as code owners May 5, 2026 11:58
Copilot AI review requested due to automatic review settings May 5, 2026 11:58
@nadahalli nadahalli requested review from a team as code owners May 5, 2026 11:58
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Risk Rating: HIGH

This PR adds cryptographic signing to confidential relay logical responses (secrets + capability execution results) so the enclave path can verify authenticity on the reverse path, and wires CRE to inject the node’s P2P key as the signing identity.

Changes:

  • Injected a P2P-key-backed signer into the confidential relay service/handler and wrapped success + logical error results in signed response types.
  • Added focused handler tests that verify returned ed25519 signatures against the returned signer public key.
  • Bumped chainlink-common (for shared signed response wrappers/hash helpers) and updated related Go module dependencies.

Scrupulous human review recommended (high-impact areas):

  • PeerID/key selection logic for signing (confidentialRelayPeerID + P2P.GetOrFirst) to avoid startup failures or unintended key selection in multi-key environments.
  • Signature payload/hash compatibility with the enclave verifier and chainlink-common’s expectations (domain separation, hash function, and encoding).
  • Service startup behavior when keystore is locked/uninitialized (confidential relay now hard-depends on a usable P2P key at start).

Reviewed changes

Copilot reviewed 6 out of 7 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
go.mod Bumps chainlink-common, go-plugin, and adds an indirect dependency.
go.sum Updates checksums for bumped dependencies.
core/services/cre/cre.go Wires P2P keystore + peer ID into confidential relay service creation.
core/capabilities/confidentialrelay/service.go Fetches P2P key and injects signer into handler at service start.
core/capabilities/confidentialrelay/handler.go Signs secrets + capability responses and returns signed wrapper types.
core/capabilities/confidentialrelay/handler_test.go Verifies signatures for signed secrets/capability responses.
.changeset/confidential-relay-response-signatures.md Adds a patch changeset entry describing the new signing behavior.

Comment thread core/services/cre/cre.go Outdated
Comment thread core/capabilities/confidentialrelay/handler.go
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 5, 2026

✅ No conflicts with other open PRs targeting develop

@trunk-io
Copy link
Copy Markdown

trunk-io Bot commented May 5, 2026

Static BadgeStatic BadgeStatic BadgeStatic Badge

Failed Test Failure Summary Logs
TestDocsTOMLComplete Logs ↗︎

View Full Report ↗︎Docs

return errors.New("gateway connector not available")
}
h, err := NewHandler(s.capRegistry, conn, s.lggr, s.limitsFactory)
key, err := s.p2pKeystore.GetOrFirst(s.peerID)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

ok - here's startup logic (GetOrFirst(peerID), peerID selection, handler injection), but there are no tests covering success/failure wiring paths

Comment thread core/capabilities/confidentialrelay/handler.go
@mchain0
Copy link
Copy Markdown
Contributor

mchain0 commented May 5, 2026

general notes:

  1. Hashing includes execution_id / reference_id, but those are optional in CapabilityRequestParams; if omitted by caller, signatures are still valid over a less-unique context ... I'm not sure this is a good/desired design
  2. This branch signs responses, but I don’t see corresponding verification for SignedSecretsResponseResult / SignedCapabilityResponseResult

@nadahalli
Copy link
Copy Markdown
Contributor Author

general notes:

  1. Hashing includes execution_id / reference_id, but those are optional in CapabilityRequestParams; if omitted by caller, signatures are still valid over a less-unique context ... I'm not sure this is a good/desired design
  2. This branch signs responses, but I don’t see corresponding verification for SignedSecretsResponseResult / SignedCapabilityResponseResult

Regarding (1), I now have smartcontractkit/chainlink-common#2032 - please review :-)

Regarding (2) There are followup PRs coming in core(gateway) and confidential-compute (enclave).

@nadahalli nadahalli changed the title sign confidential relay responses sign and aggregate confidential relay responses May 7, 2026
nadahalli added 2 commits May 7, 2026 17:54
…e paths

chainlink-common#2032 made SecretsResponseResult.Hash and
CapabilityResponseResult.Hash run params.Validate first and return
([32]byte, error), so a relay node can no longer accidentally sign
over a payload missing fields the canonical hash binds to.

Production callsites:

- core/capabilities/confidentialrelay/handler.go: sign{Secrets,Capability}Response
  now wrap any Hash error and return it; the handler's existing error path
  surfaces it back to the gateway as a JSON-RPC error so a request with
  malformed identity fields (Owner format, ExecutionID length, etc.) is
  rejected at signing time rather than silently producing an unbinding
  signature.

- core/services/gateway/handlers/confidentialrelay/aggregator.go:
  decodeSignedResponse propagates the Hash error so a single node's
  response that fails canonical hashing is dropped from quorum counting
  in the existing skip-this-response path, without aborting the whole
  aggregation.

Also widens the changeset to mention the aggregator change folded in
from PR #22304.
chainlink-common#2032's Validate rejects request params missing any
field the canonical hash binds to (workflow_id, owner, execution_id,
plus the per-method binders) and pins formats: Owner is a 0x-prefixed
20-byte hex Ethereum address, ExecutionID is 32-byte hex with no
prefix, EnclavePublicKey is hex, and SecretIdentifier needs both Key
and Namespace. Hash returns an error if any of these are missing or
malformed, so any test that exercises the real signing or aggregator
path with a minimal {workflow_id} param now hangs the test (gateway
side, aggregator drops responses, quorum never reached) or returns
ErrInternal (relay-DON side, signSecretsResponse / signCapabilityResponse
fail).

Refreshed test fixtures to use canonical valid params:

- gateway aggregator_test: validCapParams / validSecretsParams helpers
  threaded through every site that uses the real aggregator.

- gateway handler_test: validCapParamsJSON helper for the three tests
  that swap in the real aggregator.

- relay-DON handler_test: a single canonical secretsGetTestParams /
  testOwner pair used by the request builder, the response-signature
  verification step, and the matching mock vault response (the mock's
  EncryptionKey now matches the request's EnclavePublicKey, "aabbcc",
  which is a non-trivial hex value that satisfies the new validator).

Subtests that error out before signing (attestation_failure,
not_found, unsupported_method, invalid_params_JSON) are unchanged.

`go test ./core/services/gateway/handlers/confidentialrelay/`: PASS.
`go test ./core/capabilities/confidentialrelay/`: PASS.
…onse-signatures

# Conflicts:
#	go.mod
#	go.sum
@cl-sonarqube-production
Copy link
Copy Markdown

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.

3 participants