Skip to content

Commit 5662ca1

Browse files
vreffjmank88
andauthored
Require domain separation for SignerDecrypter signature payloads using the StandardCapabilityAccount (#1578)
* Require domain separation for SignerDecrypter signature payloads * make more specific * remove redundant comment * more coverage * Update pkg/types/core/keystore.go Co-authored-by: Jordan Krage <jmank88@gmail.com> --------- Co-authored-by: Jordan Krage <jmank88@gmail.com>
1 parent b1fd7ca commit 5662ca1

4 files changed

Lines changed: 46 additions & 3 deletions

File tree

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ require (
4545
github.com/smartcontractkit/chainlink-protos/workflows/go v0.0.0-20250822025801-598d3d86f873
4646
github.com/smartcontractkit/freeport v0.1.3-0.20250716200817-cb5dfd0e369e
4747
github.com/smartcontractkit/grpc-proxy v0.0.0-20240830132753-a7e17fec5ab7
48-
github.com/smartcontractkit/libocr v0.0.0-20250707144819-babe0ec4e358
48+
github.com/smartcontractkit/libocr v0.0.0-20250912173940-f3ab0246e23d
4949
github.com/stretchr/testify v1.10.0
5050
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0
5151
go.opentelemetry.io/otel v1.37.0

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -342,8 +342,8 @@ github.com/smartcontractkit/freeport v0.1.3-0.20250716200817-cb5dfd0e369e h1:Hv9
342342
github.com/smartcontractkit/freeport v0.1.3-0.20250716200817-cb5dfd0e369e/go.mod h1:T4zH9R8R8lVWKfU7tUvYz2o2jMv1OpGCdpY2j2QZXzU=
343343
github.com/smartcontractkit/grpc-proxy v0.0.0-20240830132753-a7e17fec5ab7 h1:12ijqMM9tvYVEm+nR826WsrNi6zCKpwBhuApq127wHs=
344344
github.com/smartcontractkit/grpc-proxy v0.0.0-20240830132753-a7e17fec5ab7/go.mod h1:FX7/bVdoep147QQhsOPkYsPEXhGZjeYx6lBSaSXtZOA=
345-
github.com/smartcontractkit/libocr v0.0.0-20250707144819-babe0ec4e358 h1:+NVzR5LZVazRUunzVn34u+lwnpmn6NTVPCeZOVyQHLo=
346-
github.com/smartcontractkit/libocr v0.0.0-20250707144819-babe0ec4e358/go.mod h1:Acy3BTBxou83ooMESLO90s8PKSu7RvLCzwSTbxxfOK0=
345+
github.com/smartcontractkit/libocr v0.0.0-20250912173940-f3ab0246e23d h1:LokA9PoCNb8mm8mDT52c3RECPMRsGz1eCQORq+J3n74=
346+
github.com/smartcontractkit/libocr v0.0.0-20250912173940-f3ab0246e23d/go.mod h1:Acy3BTBxou83ooMESLO90s8PKSu7RvLCzwSTbxxfOK0=
347347
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
348348
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
349349
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=

pkg/types/core/keystore.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package core
22

33
import (
4+
"bytes"
45
"context"
56
"crypto"
67
"crypto/ed25519"
@@ -14,6 +15,7 @@ import (
1415
"google.golang.org/grpc/status"
1516

1617
"github.com/smartcontractkit/chainlink-common/pkg/services"
18+
"github.com/smartcontractkit/libocr/ragep2p/peeridhelper"
1719
)
1820

1921
// Implementations of this interface should embed the UnimplementedKeystore struct,
@@ -79,6 +81,9 @@ func (s *Ed25519Signer) Sign(r io.Reader, digest []byte, opts crypto.SignerOpts)
7981
}
8082

8183
var P2PAccountKey = "P2P_SIGNER"
84+
85+
// Notice: when using the `StandardCapabilityAccount`, signing payloads must be prefixed using the
86+
// `peeridhelper.MakePeerIDSignatureDomainSeparatedPayload` function.
8287
var StandardCapabilityAccount = "STANDARD_CAPABILITY_ACCOUNT"
8388

8489
// singleAccountSigner implements Keystore for a single account.
@@ -133,13 +138,25 @@ func (c *signerDecrypter) Accounts(ctx context.Context) ([]string, error) {
133138
return []string{c.account}, nil
134139
}
135140

141+
var genericPrefix = peeridhelper.MakePeerIDSignatureDomainSeparatedPayload("", []byte{})
142+
136143
func (c *signerDecrypter) Sign(ctx context.Context, account string, data []byte) (signed []byte, err error) {
137144
if c.account != account {
138145
return nil, fmt.Errorf("account not found: %s", account)
139146
}
140147
if c.signer == nil {
141148
return nil, fmt.Errorf("signer is nil")
142149
}
150+
151+
// For the `StandardCapabilityAccount`, assert that the user is passing in correctly domain separated data.
152+
// The first 97 bytes of any domain separated payload will match the generic prefix.
153+
// Implicitly, this also requires the message length to be <= 1024 bytes.
154+
if account == StandardCapabilityAccount {
155+
if !bytes.HasPrefix(data, genericPrefix[:97]) {
156+
return nil, fmt.Errorf("data does not have expected prefix")
157+
}
158+
}
159+
143160
return c.signer.Sign(rand.Reader, data, crypto.Hash(0))
144161
}
145162

pkg/types/core/keystore_test.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"golang.org/x/crypto/nacl/box"
1515

1616
"github.com/smartcontractkit/chainlink-common/pkg/types/core"
17+
"github.com/smartcontractkit/libocr/ragep2p/peeridhelper"
1718
)
1819

1920
// mockSigner implements crypto.Signer for testing
@@ -216,6 +217,31 @@ func TestSingleAccountSignerDecrypter(t *testing.T) {
216217
assert.True(t, valid, "signature should be valid")
217218
})
218219

220+
t.Run("real ed25519 keys integration with StandardCapabilityAccount", func(t *testing.T) {
221+
privKey := ed25519.NewKeyFromSeed([]byte("test_seed_that_is_32_bytes_long!"))
222+
account := core.StandardCapabilityAccount
223+
singleSigner, err := core.NewSignerDecrypter(account, privKey, nil)
224+
require.NoError(t, err)
225+
226+
accounts, err := singleSigner.Accounts(t.Context())
227+
require.NoError(t, err)
228+
assert.Equal(t, []string{account}, accounts)
229+
230+
ctx := context.Background()
231+
testData := []byte("integration test data")
232+
prefixedTestData := peeridhelper.MakePeerIDSignatureDomainSeparatedPayload("test", testData)
233+
234+
signature1, err := singleSigner.Sign(ctx, account, prefixedTestData)
235+
require.NoError(t, err)
236+
assert.NotEmpty(t, signature1)
237+
238+
valid := ed25519.Verify(privKey.Public().(ed25519.PublicKey), prefixedTestData, signature1)
239+
assert.True(t, valid, "signature should be valid")
240+
241+
_, err = singleSigner.Sign(ctx, account, []byte("foobar"))
242+
require.Equal(t, err.Error(), "data does not have expected prefix")
243+
})
244+
219245
t.Run("real nacl/box keys decrypt integration", func(t *testing.T) {
220246
pubKey, privKey, err := box.GenerateKey(rand.Reader)
221247
require.NoError(t, err)

0 commit comments

Comments
 (0)