diff --git a/core/internal/testutils/testutils.go b/core/internal/testutils/testutils.go index 81c09b1c362..a38b4707179 100644 --- a/core/internal/testutils/testutils.go +++ b/core/internal/testutils/testutils.go @@ -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 +} diff --git a/core/services/keystore/keys/ocr2key/cosmos_keyring.go b/core/services/keystore/keys/ocr2key/cosmos_keyring.go index 490fa0cbfcb..5f1b9b98198 100644 --- a/core/services/keystore/keys/ocr2key/cosmos_keyring.go +++ b/core/services/keystore/keys/ocr2key/cosmos_keyring.go @@ -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" @@ -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 { @@ -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 } diff --git a/core/services/keystore/keys/ocr2key/evm_keyring.go b/core/services/keystore/keys/ocr2key/evm_keyring.go index cc4076391b4..5d937e36a6e 100644 --- a/core/services/keystore/keys/ocr2key/evm_keyring.go +++ b/core/services/keystore/keys/ocr2key/evm_keyring.go @@ -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" ) @@ -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][:]...) @@ -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 } diff --git a/core/services/keystore/keys/ocr2key/evm_keyring_test.go b/core/services/keystore/keys/ocr2key/evm_keyring_test.go index 5400b0df6a0..20ac197159a 100644 --- a/core/services/keystore/keys/ocr2key/evm_keyring_test.go +++ b/core/services/keystore/keys/ocr2key/evm_keyring_test.go @@ -3,12 +3,16 @@ package ocr2key import ( "bytes" cryptorand "crypto/rand" + "math/rand" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/smartcontractkit/libocr/offchainreporting2/types" ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types" + + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" ) func TestEVMKeyring_SignVerify(t *testing.T) { @@ -43,6 +47,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) diff --git a/core/services/keystore/keys/ocr2key/generic_key_bundle.go b/core/services/keystore/keys/ocr2key/generic_key_bundle.go index be401becfb3..2c5e4bd8559 100644 --- a/core/services/keystore/keys/ocr2key/generic_key_bundle.go +++ b/core/services/keystore/keys/ocr2key/generic_key_bundle.go @@ -18,6 +18,7 @@ import ( type ( keyring interface { ocrtypes.OnchainKeyring + OCR3SignerVerifier Marshal() ([]byte, error) Unmarshal(in []byte) error } @@ -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()) diff --git a/core/services/keystore/keys/ocr2key/key_bundle.go b/core/services/keystore/keys/ocr2key/key_bundle.go index 79d8ad70d52..2c3a4bebeb0 100644 --- a/core/services/keystore/keys/ocr2key/key_bundle.go +++ b/core/services/keystore/keys/ocr2key/key_bundle.go @@ -14,12 +14,20 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/store/models" ) +type OCR3SignerVerifier interface { + 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) diff --git a/core/services/keystore/keys/ocr2key/solana_keyring.go b/core/services/keystore/keys/ocr2key/solana_keyring.go index aebe33e1d19..a5fa2f77dc0 100644 --- a/core/services/keystore/keys/ocr2key/solana_keyring.go +++ b/core/services/keystore/keys/ocr2key/solana_keyring.go @@ -7,6 +7,8 @@ import ( "io" "github.com/ethereum/go-ethereum/crypto" + "github.com/pkg/errors" + "github.com/smartcontractkit/libocr/offchainreporting2/types" "github.com/smartcontractkit/libocr/offchainreporting2plus/chains/evmutil" ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types" ) @@ -26,12 +28,12 @@ func newSolanaKeyring(material io.Reader) (*solanaKeyring, error) { } // XXX: PublicKey returns the evm-style address of the public key not the public key itself -func (ok *solanaKeyring) PublicKey() ocrtypes.OnchainPublicKey { - address := crypto.PubkeyToAddress(*(&ok.privateKey).Public().(*ecdsa.PublicKey)) +func (skr *solanaKeyring) PublicKey() ocrtypes.OnchainPublicKey { + address := crypto.PubkeyToAddress(*(&skr.privateKey).Public().(*ecdsa.PublicKey)) return address[:] } -func (ok *solanaKeyring) reportToSigData(reportCtx ocrtypes.ReportContext, report ocrtypes.Report) []byte { +func (skr *solanaKeyring) reportToSigData(reportCtx ocrtypes.ReportContext, report ocrtypes.Report) []byte { rawReportContext := evmutil.RawReportContext(reportCtx) h := sha256.New() h.Write([]byte{uint8(len(report))}) @@ -42,30 +44,47 @@ func (ok *solanaKeyring) reportToSigData(reportCtx ocrtypes.ReportContext, repor return h.Sum(nil) } -func (ok *solanaKeyring) Sign(reportCtx ocrtypes.ReportContext, report ocrtypes.Report) ([]byte, error) { - return crypto.Sign(ok.reportToSigData(reportCtx, report), &ok.privateKey) +func (skr *solanaKeyring) Sign(reportCtx ocrtypes.ReportContext, report ocrtypes.Report) ([]byte, error) { + return skr.signBlob(skr.reportToSigData(reportCtx, report)) } -func (ok *solanaKeyring) 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 (skr *solanaKeyring) Sign3(digest types.ConfigDigest, seqNr uint64, r ocrtypes.Report) (signature []byte, err error) { + return nil, errors.New("not implemented") +} + +func (skr *solanaKeyring) signBlob(b []byte) (sig []byte, err error) { + return crypto.Sign(b, &skr.privateKey) +} + +func (skr *solanaKeyring) Verify(publicKey ocrtypes.OnchainPublicKey, reportCtx ocrtypes.ReportContext, report ocrtypes.Report, signature []byte) bool { + hash := skr.reportToSigData(reportCtx, report) + return skr.verifyBlob(publicKey, hash, signature) +} + +func (skr *solanaKeyring) Verify3(publicKey ocrtypes.OnchainPublicKey, cd ocrtypes.ConfigDigest, seqNr uint64, r ocrtypes.Report, signature []byte) bool { + return false +} + +func (skr *solanaKeyring) 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 *solanaKeyring) MaxSignatureLength() int { +func (skr *solanaKeyring) MaxSignatureLength() int { return 65 } -func (ok *solanaKeyring) Marshal() ([]byte, error) { - return crypto.FromECDSA(&ok.privateKey), nil +func (skr *solanaKeyring) Marshal() ([]byte, error) { + return crypto.FromECDSA(&skr.privateKey), nil } -func (ok *solanaKeyring) Unmarshal(in []byte) error { +func (skr *solanaKeyring) Unmarshal(in []byte) error { privateKey, err := crypto.ToECDSA(in) - ok.privateKey = *privateKey + skr.privateKey = *privateKey return err } diff --git a/core/services/keystore/keys/starkkey/ocr2key.go b/core/services/keystore/keys/starkkey/ocr2key.go index 41ab3a4708d..417687b62a6 100644 --- a/core/services/keystore/keys/starkkey/ocr2key.go +++ b/core/services/keystore/keys/starkkey/ocr2key.go @@ -58,7 +58,6 @@ func (sk *OCR2Key) Sign(reportCtx types.ReportContext, report types.Report) ([]b if err != nil { return []byte{}, err } - r, s, err := caigo.Curve.Sign(hash, sk.priv) if err != nil { return []byte{}, err @@ -85,6 +84,10 @@ func (sk *OCR2Key) Sign(reportCtx types.ReportContext, report types.Report) ([]b return out, nil } +func (sk *OCR2Key) Sign3(digest types.ConfigDigest, seqNr uint64, r types.Report) (signature []byte, err error) { + return nil, errors.New("not implemented") +} + func (sk *OCR2Key) Verify(publicKey types.OnchainPublicKey, reportCtx types.ReportContext, report types.Report, signature []byte) bool { // check valid signature length if len(signature) != sk.MaxSignatureLength() { @@ -120,6 +123,10 @@ func (sk *OCR2Key) Verify(publicKey types.OnchainPublicKey, reportCtx types.Repo return caigo.Curve.Verify(hash, r, s, keys[0].X, keys[0].Y) || caigo.Curve.Verify(hash, r, s, keys[1].X, keys[1].Y) } +func (sk *OCR2Key) Verify3(publicKey types.OnchainPublicKey, cd types.ConfigDigest, seqNr uint64, r types.Report, signature []byte) bool { + return false +} + func (sk *OCR2Key) MaxSignatureLength() int { return 32 + 32 + 32 // publickey + r + s }