Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

OCR3 keyring support #11956

Merged
Show file tree
Hide file tree
Changes from 4 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
9 changes: 9 additions & 0 deletions core/internal/testutils/testutils.go
Original file line number Diff line number Diff line change
Expand Up @@ -446,3 +446,12 @@ func MustDecodeBase64(s string) (b []byte) {
func SkipFlakey(t *testing.T, ticketURL string) {
t.Skip("Flakey", ticketURL)
}

func MustRandBytes(n int) (b []byte) {
b = make([]byte, n)
_, err := rand.Read(b)
if err != nil {
panic(err)
}
return
}
59 changes: 38 additions & 21 deletions core/services/keystore/keys/ocr2key/cosmos_keyring.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (

"github.com/hdevalence/ed25519consensus"
"github.com/pkg/errors"
"github.com/smartcontractkit/libocr/offchainreporting2/types"
"github.com/smartcontractkit/libocr/offchainreporting2plus/chains/evmutil"
ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types"
"golang.org/x/crypto/blake2s"
Expand All @@ -29,11 +30,11 @@ func newCosmosKeyring(material io.Reader) (*cosmosKeyring, error) {
return &cosmosKeyring{pubKey: pubKey, privKey: privKey}, nil
}

func (tk *cosmosKeyring) PublicKey() ocrtypes.OnchainPublicKey {
return []byte(tk.pubKey)
func (ckr *cosmosKeyring) PublicKey() ocrtypes.OnchainPublicKey {
return []byte(ckr.pubKey)
}

func (tk *cosmosKeyring) reportToSigData(reportCtx ocrtypes.ReportContext, report ocrtypes.Report) ([]byte, error) {
func (ckr *cosmosKeyring) reportToSigData(reportCtx ocrtypes.ReportContext, report ocrtypes.Report) ([]byte, error) {
rawReportContext := evmutil.RawReportContext(reportCtx)
h, err := blake2s.New256(nil)
if err != nil {
Expand All @@ -49,48 +50,64 @@ func (tk *cosmosKeyring) reportToSigData(reportCtx ocrtypes.ReportContext, repor
return h.Sum(nil), nil
}

func (tk *cosmosKeyring) Sign(reportCtx ocrtypes.ReportContext, report ocrtypes.Report) ([]byte, error) {
sigData, err := tk.reportToSigData(reportCtx, report)
func (ckr *cosmosKeyring) Sign(reportCtx ocrtypes.ReportContext, report ocrtypes.Report) ([]byte, error) {
sigData, err := ckr.reportToSigData(reportCtx, report)
if err != nil {
return nil, err
}
signedMsg := ed25519.Sign(tk.privKey, sigData)
return ckr.signBlob(sigData)
}

func (ckr *cosmosKeyring) Sign3(digest types.ConfigDigest, seqNr uint64, r ocrtypes.Report) (signature []byte, err error) {
return nil, errors.New("not implemented")
}

func (ckr *cosmosKeyring) signBlob(b []byte) ([]byte, error) {
signedMsg := ed25519.Sign(ckr.privKey, b)
// match on-chain parsing (first 32 bytes are for pubkey, remaining are for signature)
return utils.ConcatBytes(tk.PublicKey(), signedMsg), nil
return utils.ConcatBytes(ckr.PublicKey(), signedMsg), nil
}

func (ckr *cosmosKeyring) Verify(publicKey ocrtypes.OnchainPublicKey, reportCtx ocrtypes.ReportContext, report ocrtypes.Report, signature []byte) bool {
hash, err := ckr.reportToSigData(reportCtx, report)
if err != nil {
return false
}
return ckr.verifyBlob(publicKey, hash, signature)
}

func (ckr *cosmosKeyring) Verify3(publicKey ocrtypes.OnchainPublicKey, cd ocrtypes.ConfigDigest, seqNr uint64, r ocrtypes.Report, signature []byte) bool {
return false
}

func (tk *cosmosKeyring) Verify(publicKey ocrtypes.OnchainPublicKey, reportCtx ocrtypes.ReportContext, report ocrtypes.Report, signature []byte) bool {
func (ckr *cosmosKeyring) verifyBlob(pubkey ocrtypes.OnchainPublicKey, b, sig []byte) bool {
// Ed25519 signatures are always 64 bytes and the
// public key (always prefixed, see Sign above) is always,
// 32 bytes, so we always require the max signature length.
if len(signature) != tk.MaxSignatureLength() {
if len(sig) != ckr.MaxSignatureLength() {
return false
}
if len(publicKey) != ed25519.PublicKeySize {
return false
}
hash, err := tk.reportToSigData(reportCtx, report)
if err != nil {
if len(pubkey) != ed25519.PublicKeySize {
return false
}
return ed25519consensus.Verify(ed25519.PublicKey(publicKey), hash, signature[32:])
return ed25519consensus.Verify(ed25519.PublicKey(pubkey), b, sig[32:])
}

func (tk *cosmosKeyring) MaxSignatureLength() int {
func (ckr *cosmosKeyring) MaxSignatureLength() int {
// Reference: https://pkg.go.dev/crypto/ed25519
return ed25519.PublicKeySize + ed25519.SignatureSize // 32 + 64
}

func (tk *cosmosKeyring) Marshal() ([]byte, error) {
return tk.privKey.Seed(), nil
func (ckr *cosmosKeyring) Marshal() ([]byte, error) {
return ckr.privKey.Seed(), nil
}

func (tk *cosmosKeyring) Unmarshal(in []byte) error {
func (ckr *cosmosKeyring) Unmarshal(in []byte) error {
if len(in) != ed25519.SeedSize {
return errors.Errorf("unexpected seed size, got %d want %d", len(in), ed25519.SeedSize)
}
privKey := ed25519.NewKeyFromSeed(in)
tk.privKey = privKey
tk.pubKey = privKey.Public().(ed25519.PublicKey)
ckr.privKey = privKey
ckr.pubKey = privKey.Public().(ed25519.PublicKey)
return nil
}
68 changes: 52 additions & 16 deletions core/services/keystore/keys/ocr2key/evm_keyring.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@ package ocr2key
import (
"bytes"
"crypto/ecdsa"
"encoding/binary"
"io"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/smartcontractkit/libocr/offchainreporting2/types"
"github.com/smartcontractkit/libocr/offchainreporting2plus/chains/evmutil"
ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types"
)
Expand All @@ -26,12 +28,16 @@ func newEVMKeyring(material io.Reader) (*evmKeyring, error) {
}

// XXX: PublicKey returns the address of the public key not the public key itself
func (ok *evmKeyring) PublicKey() ocrtypes.OnchainPublicKey {
address := ok.signingAddress()
func (ekr *evmKeyring) PublicKey() ocrtypes.OnchainPublicKey {
address := ekr.signingAddress()
return address[:]
}

func (ok *evmKeyring) reportToSigData(reportCtx ocrtypes.ReportContext, report ocrtypes.Report) []byte {
func (ekr *evmKeyring) Sign(reportCtx ocrtypes.ReportContext, report ocrtypes.Report) ([]byte, error) {
return ekr.signBlob(ekr.reportToSigData(reportCtx, report))
}

func (ekr *evmKeyring) reportToSigData(reportCtx ocrtypes.ReportContext, report ocrtypes.Report) []byte {
rawReportContext := evmutil.RawReportContext(reportCtx)
sigData := crypto.Keccak256(report)
sigData = append(sigData, rawReportContext[0][:]...)
Expand All @@ -40,38 +46,68 @@ func (ok *evmKeyring) reportToSigData(reportCtx ocrtypes.ReportContext, report o
return crypto.Keccak256(sigData)
}

func (ok *evmKeyring) Sign(reportCtx ocrtypes.ReportContext, report ocrtypes.Report) ([]byte, error) {
return crypto.Sign(ok.reportToSigData(reportCtx, report), &ok.privateKey)
func (ekr *evmKeyring) Sign3(digest types.ConfigDigest, seqNr uint64, r ocrtypes.Report) (signature []byte, err error) {
return ekr.signBlob(ekr.reportToSigData3(digest, seqNr, r))
}

func (ekr *evmKeyring) reportToSigData3(digest types.ConfigDigest, seqNr uint64, r ocrtypes.Report) []byte {
rawReportContext := RawReportContext3(digest, seqNr)
sigData := crypto.Keccak256(r)
sigData = append(sigData, rawReportContext[0][:]...)
sigData = append(sigData, rawReportContext[1][:]...)
return crypto.Keccak256(sigData)
}

func RawReportContext3(digest types.ConfigDigest, seqNr uint64) [2][32]byte {
seqNrBytes := [32]byte{}
binary.BigEndian.PutUint64(seqNrBytes[:], seqNr)
return [2][32]byte{
digest,
seqNrBytes,
}
}

func (ekr *evmKeyring) signBlob(b []byte) (sig []byte, err error) {
return crypto.Sign(b, &ekr.privateKey)
}

func (ekr *evmKeyring) Verify(publicKey ocrtypes.OnchainPublicKey, reportCtx ocrtypes.ReportContext, report ocrtypes.Report, signature []byte) bool {
hash := ekr.reportToSigData(reportCtx, report)
return ekr.verifyBlob(publicKey, hash, signature)
}

func (ekr *evmKeyring) Verify3(publicKey ocrtypes.OnchainPublicKey, cd ocrtypes.ConfigDigest, seqNr uint64, r ocrtypes.Report, signature []byte) bool {
hash := ekr.reportToSigData3(cd, seqNr, r)
return ekr.verifyBlob(publicKey, hash, signature)
}

func (ok *evmKeyring) Verify(publicKey ocrtypes.OnchainPublicKey, reportCtx ocrtypes.ReportContext, report ocrtypes.Report, signature []byte) bool {
hash := ok.reportToSigData(reportCtx, report)
authorPubkey, err := crypto.SigToPub(hash, signature)
func (ekr *evmKeyring) verifyBlob(pubkey types.OnchainPublicKey, b, sig []byte) bool {
authorPubkey, err := crypto.SigToPub(b, sig)
if err != nil {
return false
}
authorAddress := crypto.PubkeyToAddress(*authorPubkey)
return bytes.Equal(publicKey[:], authorAddress[:])
// no need for constant time compare since neither arg is sensitive
return bytes.Equal(pubkey[:], authorAddress[:])
}

func (ok *evmKeyring) MaxSignatureLength() int {
func (ekr *evmKeyring) MaxSignatureLength() int {
return 65
}

func (ok *evmKeyring) signingAddress() common.Address {
return crypto.PubkeyToAddress(*(&ok.privateKey).Public().(*ecdsa.PublicKey))
func (ekr *evmKeyring) signingAddress() common.Address {
return crypto.PubkeyToAddress(*(&ekr.privateKey).Public().(*ecdsa.PublicKey))
}

func (ok *evmKeyring) Marshal() ([]byte, error) {
return crypto.FromECDSA(&ok.privateKey), nil
func (ekr *evmKeyring) Marshal() ([]byte, error) {
return crypto.FromECDSA(&ekr.privateKey), nil
}

func (ok *evmKeyring) Unmarshal(in []byte) error {
func (ekr *evmKeyring) Unmarshal(in []byte) error {
privateKey, err := crypto.ToECDSA(in)
if err != nil {
return err
}
ok.privateKey = *privateKey
ekr.privateKey = *privateKey
return nil
}
35 changes: 35 additions & 0 deletions core/services/keystore/keys/ocr2key/evm_keyring_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,14 @@ package ocr2key
import (
"bytes"
cryptorand "crypto/rand"
"math/rand"
"testing"

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

"github.com/smartcontractkit/chainlink/v2/core/internal/testutils"
"github.com/smartcontractkit/libocr/offchainreporting2/types"
ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types"
)

Expand Down Expand Up @@ -43,6 +46,38 @@ func TestEVMKeyring_SignVerify(t *testing.T) {
})
}

func TestEVMKeyring_Sign3Verify3(t *testing.T) {
kr1, err := newEVMKeyring(cryptorand.Reader)
require.NoError(t, err)
kr2, err := newEVMKeyring(cryptorand.Reader)
require.NoError(t, err)

digest, err := types.BytesToConfigDigest(testutils.MustRandBytes(32))
require.NoError(t, err)
seqNr := rand.Uint64()
r := ocrtypes.Report(testutils.MustRandBytes(rand.Intn(1024)))

t.Run("can verify", func(t *testing.T) {
sig, err := kr1.Sign3(digest, seqNr, r)
require.NoError(t, err)
t.Log(len(sig))
result := kr2.Verify3(kr1.PublicKey(), digest, seqNr, r, sig)
assert.True(t, result)
})

t.Run("invalid sig", func(t *testing.T) {
result := kr2.Verify3(kr1.PublicKey(), digest, seqNr, r, []byte{0x01})
assert.False(t, result)
})

t.Run("invalid pubkey", func(t *testing.T) {
sig, err := kr1.Sign3(digest, seqNr, r)
require.NoError(t, err)
result := kr2.Verify3([]byte{0x01}, digest, seqNr, r, sig)
assert.False(t, result)
})
}

func TestEVMKeyring_Marshalling(t *testing.T) {
kr1, err := newEVMKeyring(cryptorand.Reader)
require.NoError(t, err)
Expand Down
9 changes: 9 additions & 0 deletions core/services/keystore/keys/ocr2key/generic_key_bundle.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
type (
keyring interface {
ocrtypes.OnchainKeyring
OCR3SignerVerifier
Marshal() ([]byte, error)
Unmarshal(in []byte) error
}
Expand Down Expand Up @@ -92,10 +93,18 @@ func (kb *keyBundle[K]) Sign(reportCtx ocrtypes.ReportContext, report ocrtypes.R
return kb.keyring.Sign(reportCtx, report)
}

func (kb *keyBundle[K]) Sign3(digest ocrtypes.ConfigDigest, seqNr uint64, r ocrtypes.Report) (signature []byte, err error) {
return kb.keyring.Sign3(digest, seqNr, r)
}

func (kb *keyBundle[K]) Verify(publicKey ocrtypes.OnchainPublicKey, reportCtx ocrtypes.ReportContext, report ocrtypes.Report, signature []byte) bool {
return kb.keyring.Verify(publicKey, reportCtx, report, signature)
}

func (kb *keyBundle[K]) Verify3(publicKey ocrtypes.OnchainPublicKey, cd ocrtypes.ConfigDigest, seqNr uint64, r ocrtypes.Report, signature []byte) bool {
return kb.keyring.Verify3(publicKey, cd, seqNr, r, signature)
}

// OnChainPublicKey returns public component of the keypair used on chain
func (kb *keyBundle[K]) OnChainPublicKey() string {
return hex.EncodeToString(kb.keyring.PublicKey())
Expand Down
8 changes: 8 additions & 0 deletions core/services/keystore/keys/ocr2key/key_bundle.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,20 @@ import (
"github.com/smartcontractkit/chainlink/v2/core/store/models"
)

type OCR3SignerVerifier interface {
Copy link
Contributor

Choose a reason for hiding this comment

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

@samsondav How does this get used in the code base? Not wild about the Sign3/Verify3 (and even less about the fact that this is located in ocr2key)

Also IIRC the Sign3/Verify3 isn't the interface that OCR3 expects is it? So do you need an adapter somewhere?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Tried renaming to ocr2plus but its 1,000s of changes and makes the salient points of this PR unreadable.

Not to mention we use OCR2 all over the codebase to refer to both OCR2 and OCR3. There is probably a larger ticket to clean up that naming.

Sign3(digest ocrtypes.ConfigDigest, seqNr uint64, r ocrtypes.Report) (signature []byte, err error)
Verify3(publicKey ocrtypes.OnchainPublicKey, cd ocrtypes.ConfigDigest, seqNr uint64, r ocrtypes.Report, signature []byte) bool
}

// nolint
type KeyBundle interface {
// OnchainKeyring is used for signing reports (groups of observations, verified onchain)
ocrtypes.OnchainKeyring
// OffchainKeyring is used for signing observations
ocrtypes.OffchainKeyring

OCR3SignerVerifier

ID() string
ChainType() chaintype.ChainType
Marshal() ([]byte, error)
Expand Down
Loading
Loading