Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 97 additions & 0 deletions pkg/capabilities/v2/actions/confidentialrelay/computerequest.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package confidentialrelay

import (
"crypto/sha256"

"github.com/smartcontractkit/libocr/ragep2p/peeridhelper"
)

// computeRequestDomainSeparator is vendored verbatim from confidential-compute
// types.DomainSeparator. It MUST stay byte-identical to the source, or
// ComputeRequest.Hash will not match the digest the Workflow DON nodes signed and
// F+1 verification at the relay DON will fail. chainlink-common cannot import
// confidential-compute, so the byte-for-byte conformance check lives in that repo
// (which can import this package).
const computeRequestDomainSeparator = "CONFIDENTIAL_COMPUTE_PAYLOAD"

// signedComputeRequestSignaturePrefix is vendored verbatim from confidential-compute
// util.GetConfidentialComputePayloadPrefix(). Each Workflow DON node signs the peerid
// domain-separated payload over ComputeRequest.Hash() using this prefix; the relay DON
// reconstructs the same payload (via SignedComputeRequestSignaturePayload) to verify the
// F+1 signatures against the Workflow DON signer set. Note the trailing underscore: this
// is the signature prefix, distinct from computeRequestDomainSeparator (the hash prefix).
const signedComputeRequestSignaturePrefix = "CONFIDENTIAL_COMPUTE_PAYLOAD_"

// SignedComputeRequestSignaturePayload reconstructs the exact payload a Workflow DON node
// signed over a ComputeRequest hash, so the relay DON can verify the signature with the
// node's public key.
func SignedComputeRequestSignaturePayload(computeRequestHash [32]byte) []byte {
return peeridhelper.MakePeerIDSignatureDomainSeparatedPayload(signedComputeRequestSignaturePrefix, computeRequestHash[:])
}

// ComputeRequest is vendored from confidential-compute types.ComputeRequest. The
// relay DON cannot import confidential-compute (the dependency runs the other way),
// so the type and its canonical Hash are copied here. The enclave forwards the
// Workflow-DON-signed compute request to the relay, which reconstructs the hash and
// verifies the F+1 signatures over it.
//
// PublicData carries the marshaled WorkflowExecution (owner, orgid, workflowID,
// executionID); the relay unmarshals it via chainlink-protos to recover the
// authorized identity.
type ComputeRequest struct {
RequestID [32]byte `json:"requestID"`
PublicData []byte `json:"publicData"`
Ciphertexts [][]byte `json:"ciphertexts"`
CiphertextNames []string `json:"CiphertextNames"`
EncryptedDecryptionKeyShares [][][]byte `json:"encryptedDecryptionKeyShares"`
Comment on lines +42 to +46
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Intentional. This type is vendored verbatim from confidential-compute types.ComputeRequest, where the tag is json:"CiphertextNames" (capital C). The relay must marshal and unmarshal exactly what the enclave emits, so the tag has to match the source byte for byte. The capital C is a quirk in the source, but matching it is what preserves wire compatibility; changing it here is what would break it. Leaving as-is.

EnclaveEphemeralPublicKey []byte `json:"enclaveEphemeralPublicKey"`
MasterPublicKey []byte `json:"masterPublicKey"`
AppID string `json:"appID"`
Version string `json:"version"`
}

// Hash mirrors confidential-compute types.ComputeRequest.Hash byte-for-byte. It
// reuses this package's length-prefix helpers (writeBytes/writeString/
// writeLengthPrefix), which are identical to the source's writeWithLength/
// writeLengthPrefix. EncryptedDecryptionKeyShares is intentionally excluded,
// matching the source.
func (cr ComputeRequest) Hash() [32]byte {
h := sha256.New()

h.Write([]byte(computeRequestDomainSeparator))
h.Write([]byte("\nComputeRequest\n"))

h.Write(cr.RequestID[:])

writeBytes(h, cr.PublicData)

writeLengthPrefix(h, len(cr.CiphertextNames))
for _, name := range cr.CiphertextNames {
writeString(h, name)
}

writeLengthPrefix(h, len(cr.Ciphertexts))
for _, ciphertext := range cr.Ciphertexts {
writeBytes(h, ciphertext)
}

writeBytes(h, cr.EnclaveEphemeralPublicKey)
writeBytes(h, cr.MasterPublicKey)

writeString(h, cr.AppID)
writeString(h, cr.Version)

var result [32]byte
h.Sum(result[:0])
return result
}

// SignedComputeRequest is vendored from confidential-compute
// types.SignedComputeRequest: a ComputeRequest plus one Workflow DON node's
// signature over ComputeRequest.Hash. The enclave forwards the F+1 signed requests
// to the relay DON as the authorization for a secrets request.
type SignedComputeRequest struct {
ComputeRequest
Signature []byte `json:"signature"`
PerNodeData map[string]string `json:"perNodeData,omitempty"`
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package confidentialrelay

import (
"testing"

"github.com/stretchr/testify/require"
)

func sampleComputeRequest() ComputeRequest {
var rid [32]byte
for i := range rid {
rid[i] = byte(i)
}
return ComputeRequest{
RequestID: rid,
PublicData: []byte("public-data"),
Ciphertexts: [][]byte{[]byte("ct-a"), []byte("ct-b")},
CiphertextNames: []string{"name-a", "name-b"},
EnclaveEphemeralPublicKey: []byte("ephemeral-pub-key"),
MasterPublicKey: []byte("master-pub-key"),
AppID: "test-app",
Version: "v1.2.3",
}
}

func TestComputeRequestHash_Deterministic(t *testing.T) {
require.Equal(t, sampleComputeRequest().Hash(), sampleComputeRequest().Hash())
}

// Every field the source binds must change the hash. (Conformance with
// confidential-compute's source Hash is enforced by a test in that repo, which can
// import this package; chainlink-common cannot import confidential-compute.)
func TestComputeRequestHash_BindsFields(t *testing.T) {
base := sampleComputeRequest().Hash()

mutations := map[string]func(*ComputeRequest){
"requestID": func(c *ComputeRequest) { c.RequestID = [32]byte{0xff} },
"publicData": func(c *ComputeRequest) { c.PublicData = []byte("other") },
"ciphertextNames": func(c *ComputeRequest) { c.CiphertextNames = []string{"x"} },
"ciphertexts": func(c *ComputeRequest) { c.Ciphertexts = [][]byte{[]byte("x")} },
"ephemeralKey": func(c *ComputeRequest) { c.EnclaveEphemeralPublicKey = []byte("x") },
"masterKey": func(c *ComputeRequest) { c.MasterPublicKey = []byte("x") },
"appID": func(c *ComputeRequest) { c.AppID = "other" },
"version": func(c *ComputeRequest) { c.Version = "other" },
}
for name, mutate := range mutations {
t.Run(name, func(t *testing.T) {
c := sampleComputeRequest()
mutate(&c)
require.NotEqual(t, base, c.Hash(), "hash must change when %s changes", name)
})
}
}

// EncryptedDecryptionKeyShares is intentionally excluded from the hash, matching the
// source; this pins that so a future copy edit can't silently start binding it.
func TestComputeRequestHash_IgnoresEncryptedShares(t *testing.T) {
withShares := sampleComputeRequest()
withShares.EncryptedDecryptionKeyShares = [][][]byte{{[]byte("share")}}
require.Equal(t, sampleComputeRequest().Hash(), withShares.Hash())
}
7 changes: 7 additions & 0 deletions pkg/capabilities/v2/actions/confidentialrelay/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,13 @@ type SecretsRequestParams struct {
// that omit the field. When present, it is validated and hash-bound.
EnclaveConfig *EnclaveConfig `json:"enclave_config,omitempty"`
Attestation string `json:"attestation,omitempty"`

// SignedComputeRequests carries the F+1 Workflow-DON-signed compute requests the
// enclave forwards verbatim. The relay DON verifies the signatures over
// ComputeRequest.Hash() against its Workflow DON signer set and reads the
// authorized identity from PublicData (the WorkflowExecution proto). Like
// Attestation, it is authorization input and is excluded from the response hash.
SignedComputeRequests []SignedComputeRequest `json:"signed_compute_requests,omitempty"`
}

// SecretEntry is a single secret in the relay DON's response.
Expand Down
Loading