Skip to content
Draft
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
37 changes: 29 additions & 8 deletions pkg/capabilities/v2/actions/confidentialrelay/computerequest.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package confidentialrelay
import (
"crypto/sha256"

"github.com/Masterminds/semver/v3"
"github.com/smartcontractkit/libocr/ragep2p/peeridhelper"
)

Expand All @@ -24,10 +25,10 @@ const signedComputeRequestSignaturePrefix = "CONFIDENTIAL_COMPUTE_PAYLOAD_"

// computeRequestLegacyVersion is vendored from confidential-compute
// types.ServiceConfidentialComputeVersionLegacy. Hash includes the Version field only
// when it equals this value, matching the source (confidential-compute is migrating
// Version out of the hash for newer versions). It MUST stay in sync with the source, or
// ComputeRequest.Hash will diverge from the digest the Workflow DON nodes signed once the
// enclave moves past the legacy version.
// when the request uses the legacy scheme, matching the source (confidential-compute
// is migrating Version out of the hash for newer versions). It MUST stay in sync with
// the source, or ComputeRequest.Hash will diverge from the digest the Workflow DON nodes
// signed once the enclave moves past the legacy version.
const computeRequestLegacyVersion = "0.0.6"

// SignedComputeRequestSignaturePayload reconstructs the exact payload a Workflow DON node
Expand Down Expand Up @@ -63,8 +64,8 @@ type ComputeRequest struct {
// reuses this package's length-prefix helpers (writeBytes/writeString/
// writeLengthPrefix), which are identical to the source's writeWithLength/
// writeLengthPrefix. EncryptedDecryptionKeyShares is intentionally excluded, and
// Version is included only for the legacy version, and ApplicationRequestID is
// included only for non-legacy versions, both matching the source.
// Version is included only for the legacy hashing scheme, and ApplicationRequestID
// is included only for non-legacy versions, both matching the source.
func (cr ComputeRequest) Hash() [32]byte {
h := sha256.New()

Expand All @@ -89,10 +90,10 @@ func (cr ComputeRequest) Hash() [32]byte {
writeBytes(h, cr.MasterPublicKey)

writeString(h, cr.AppID)
// Version is included in the hash only for the legacy version, matching
// Version is included in the hash only for the legacy scheme, matching
// confidential-compute (which is migrating Version out of the hash). Newer
// versions bind the application-specific request ID instead.
if cr.Version == computeRequestLegacyVersion {
if usesLegacyComputeRequestHash(cr.Version) {
writeString(h, cr.Version)
} else {
writeString(h, cr.ApplicationRequestID)
Expand All @@ -103,6 +104,26 @@ func (cr ComputeRequest) Hash() [32]byte {
return result
}

func usesLegacyComputeRequestHash(version string) bool {
c, err := compareComputeRequestVersions(version, computeRequestLegacyVersion)
if err != nil {
return version == computeRequestLegacyVersion
}
return c <= 0
}

func compareComputeRequestVersions(a, b string) (int, error) {
av, err := semver.NewVersion(a)
if err != nil {
return 0, err
}
bv, err := semver.NewVersion(b)
if err != nil {
return 0, err
}
return av.Compare(bv), nil
}

// 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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,20 @@ func TestComputeRequestHash_Deterministic(t *testing.T) {
require.Equal(t, sampleComputeRequest().Hash(), sampleComputeRequest().Hash())
}

func TestUsesLegacyComputeRequestHash(t *testing.T) {
for _, v := range []string{computeRequestLegacyVersion, "0.0.1", "0.0.5"} {
require.True(t, usesLegacyComputeRequestHash(v), "version %q should use the legacy hashing scheme", v)
}

for _, v := range []string{"0.0.7", "0.1.0", "1.2.3"} {
require.False(t, usesLegacyComputeRequestHash(v), "version %q should use the current hashing scheme", v)
}

for _, v := range []string{"", "not-a-version", "1.x"} {
require.False(t, usesLegacyComputeRequestHash(v), "unparseable version %q degrades to the current scheme", v)
}
}

// 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.)
Expand Down Expand Up @@ -61,9 +75,9 @@ func TestComputeRequestHash_IgnoresEncryptedShares(t *testing.T) {
require.Equal(t, sampleComputeRequest().Hash(), withShares.Hash())
}

// Version is hashed only for the legacy version, matching confidential-compute (which is
// Version is hashed for the legacy scheme, matching confidential-compute (which is
// migrating Version out of the hash). Non-legacy versions are excluded, so different
// non-legacy versions hash identically, while the legacy version is bound.
// non-legacy versions hash identically, while legacy-scheme versions are bound.
func TestComputeRequestHash_VersionOnlyHashedForLegacy(t *testing.T) {
nonLegacyA := sampleComputeRequest()
nonLegacyA.Version = "0.0.7"
Expand All @@ -74,6 +88,12 @@ func TestComputeRequestHash_VersionOnlyHashedForLegacy(t *testing.T) {
legacy := sampleComputeRequest()
legacy.Version = computeRequestLegacyVersion
require.NotEqual(t, legacy.Hash(), nonLegacyA.Hash(), "legacy Version must be bound into the hash")

olderLegacyA := sampleComputeRequest()
olderLegacyA.Version = "0.0.5"
olderLegacyB := sampleComputeRequest()
olderLegacyB.Version = "0.0.4"
require.NotEqual(t, olderLegacyA.Hash(), olderLegacyB.Hash(), "versions at or below legacy must be bound into the hash")
}

// ApplicationRequestID is the post-legacy replacement for binding application-level
Expand All @@ -85,6 +105,14 @@ func TestComputeRequestHash_ApplicationRequestIDOnlyHashedForNonLegacy(t *testin
legacyB.ApplicationRequestID = "exec-b"
require.Equal(t, legacyA.Hash(), legacyB.Hash(), "legacy ApplicationRequestID must not affect the hash")

olderLegacyA := sampleComputeRequest()
olderLegacyA.Version = "0.0.5"
olderLegacyA.ApplicationRequestID = "exec-a"
olderLegacyB := sampleComputeRequest()
olderLegacyB.Version = "0.0.5"
olderLegacyB.ApplicationRequestID = "exec-b"
require.Equal(t, olderLegacyA.Hash(), olderLegacyB.Hash(), "legacy-scheme ApplicationRequestID must not affect the hash")

nonLegacyA := sampleComputeRequest()
nonLegacyA.Version = "0.0.7"
nonLegacyA.ApplicationRequestID = "exec-a"
Expand Down
Loading