diff --git a/pkg/capabilities/v2/actions/confidentialrelay/computerequest.go b/pkg/capabilities/v2/actions/confidentialrelay/computerequest.go index c262523de7..6d50da8756 100644 --- a/pkg/capabilities/v2/actions/confidentialrelay/computerequest.go +++ b/pkg/capabilities/v2/actions/confidentialrelay/computerequest.go @@ -3,6 +3,7 @@ package confidentialrelay import ( "crypto/sha256" + "github.com/Masterminds/semver/v3" "github.com/smartcontractkit/libocr/ragep2p/peeridhelper" ) @@ -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 @@ -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() @@ -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) @@ -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 diff --git a/pkg/capabilities/v2/actions/confidentialrelay/computerequest_test.go b/pkg/capabilities/v2/actions/confidentialrelay/computerequest_test.go index 45fed9ab48..99e5dd2ab0 100644 --- a/pkg/capabilities/v2/actions/confidentialrelay/computerequest_test.go +++ b/pkg/capabilities/v2/actions/confidentialrelay/computerequest_test.go @@ -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.) @@ -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" @@ -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 @@ -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"