├── internal ├── pvss │ ├── 00_doc.go │ ├── 02_share_types.go │ ├── 01_share_set_public_interface.go │ ├── pub_poly_marshal.go │ ├── share_implementation.go │ ├── share_marshalling.go │ ├── share_set_marshal.go │ └── share_set_implementation.go ├── crypto │ ├── player_idx │ │ ├── doc.go │ │ ├── util.go │ │ ├── player_idx.go │ │ └── public_utils.go │ ├── ciphertext │ │ ├── memoized_plaintexts.go │ │ ├── get_share_bits.go │ │ ├── 00_cipher_text_public_interface.go │ │ ├── bit_pair_decrypt.go │ │ ├── dl_proofs.go │ │ ├── bit_pair_encrypt.go │ │ ├── proofs.go │ │ ├── schnorr │ │ │ └── schnorr.go │ │ ├── cipher_text.go │ │ ├── bit_pair_implementation.go │ │ └── cipher_text_marshal.go │ ├── point_translation │ │ ├── point_translation.go │ │ ├── trivial_translation.go │ │ └── pairing_translation.go │ ├── product_group │ │ └── product_group_incidentals.go │ └── change_base_group │ │ └── change_base_group.go ├── dkg │ ├── contract │ │ ├── types.go │ │ ├── onchain_contract.go │ │ └── key_data.go │ ├── group_registries.go │ ├── key_consumer.go │ ├── 00_public_interface.go │ ├── share_records.go │ ├── ocr.go │ ├── persist_share_records.go │ ├── reporting_plugin_factory.go │ ├── share_record.go │ ├── secret_share.go │ ├── report.go │ ├── protobuf │ │ └── offchain_config.pb.go │ └── dkg.go ├── util │ ├── blockchain.go │ ├── wrap_error.go │ ├── logger.go │ └── backend.go ├── vrf │ ├── bls_validate_signature.go │ ├── key_provider.go │ ├── sigs.go │ ├── key_transceiver.go │ ├── sig_request_report_helpers.go │ ├── sigrequest.go │ ├── 00_public_interface.go │ ├── reporting_plugin_factory.go │ ├── stub_dkg.go │ └── ethereum_serialization.go ├── common │ └── ocr │ │ └── ocr_committee.go └── signatures │ └── secp256k1 │ ├── curve.go │ ├── suite.go │ ├── field.go │ ├── public_key.go │ └── scalar.go ├── gethwrappers ├── abigen_log.go └── go.mod ├── altbn_128 ├── g2_point_incidentals.go ├── gt.go ├── g2.go ├── g1_message_incidentals.go ├── bn256_interface.go ├── pairing.go ├── g1.go ├── gt_point.go ├── g2_point.go ├── scalar │ └── scalar.go ├── g1_point.go └── g1_hash_to_curve.go ├── types ├── hash │ └── hash.go ├── db.go ├── helpers.go └── types.go ├── ocr2vrf ├── dkgvrfargs.go └── 00_public_interface.go ├── go.mod ├── dkg └── dkg.go └── .github └── workflows ├── push-main.yml └── pull-request-main.yml /internal/pvss/00_doc.go: -------------------------------------------------------------------------------- 1 | package pvss 2 | -------------------------------------------------------------------------------- /internal/crypto/player_idx/doc.go: -------------------------------------------------------------------------------- 1 | package player_idx 2 | -------------------------------------------------------------------------------- /gethwrappers/abigen_log.go: -------------------------------------------------------------------------------- 1 | package gethwrappers 2 | 3 | import ( 4 | "github.com/ethereum/go-ethereum/common" 5 | ) 6 | 7 | type AbigenLog interface { 8 | Topic() common.Hash 9 | } 10 | -------------------------------------------------------------------------------- /altbn_128/g2_point_incidentals.go: -------------------------------------------------------------------------------- 1 | package altbn_128 2 | 3 | import ( 4 | "crypto/cipher" 5 | 6 | "go.dedis.ch/kyber/v3/util/random" 7 | ) 8 | 9 | func (g *G2) RandomStream() cipher.Stream { 10 | if g.r != nil { 11 | return g.r 12 | } 13 | return random.New() 14 | } 15 | -------------------------------------------------------------------------------- /internal/dkg/contract/types.go: -------------------------------------------------------------------------------- 1 | package contract 2 | 3 | import ( 4 | "go.dedis.ch/kyber/v3" 5 | ) 6 | 7 | type EncryptionSecretKey kyber.Scalar 8 | 9 | type EncryptionPublicKeys []kyber.Point 10 | 11 | type SigningSecretKey kyber.Scalar 12 | 13 | type SigningPublicKeys []kyber.Point 14 | -------------------------------------------------------------------------------- /internal/util/blockchain.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "github.com/ethereum/go-ethereum/core/types" 5 | "github.com/ethereum/go-ethereum/eth/downloader" 6 | ) 7 | 8 | type Blockchain interface { 9 | downloader.BlockChain 10 | GetBlockByNumber(number uint64) *types.Block 11 | } 12 | -------------------------------------------------------------------------------- /internal/vrf/bls_validate_signature.go: -------------------------------------------------------------------------------- 1 | package vrf 2 | 3 | import ( 4 | "go.dedis.ch/kyber/v3" 5 | "go.dedis.ch/kyber/v3/pairing" 6 | ) 7 | 8 | func validateSignature(p pairing.Suite, msg, pk, sig kyber.Point) bool { 9 | 10 | return p.Pair(msg, pk).Equal(p.Pair(sig, p.G2().Point().Base())) 11 | } 12 | -------------------------------------------------------------------------------- /internal/vrf/key_provider.go: -------------------------------------------------------------------------------- 1 | package vrf 2 | 3 | import ( 4 | "github.com/smartcontractkit/chainlink-vrf/internal/dkg" 5 | dkg_contract "github.com/smartcontractkit/chainlink-vrf/internal/dkg/contract" 6 | ) 7 | 8 | type KeyProvider interface { 9 | KeyLookup(p dkg_contract.KeyID) (kd dkg.KeyData) 10 | } 11 | -------------------------------------------------------------------------------- /internal/vrf/sigs.go: -------------------------------------------------------------------------------- 1 | package vrf 2 | 3 | import ( 4 | "bytes" 5 | "sort" 6 | ) 7 | 8 | type sig []byte 9 | 10 | type sigs []sig 11 | 12 | var _ sort.Interface = sigs{} 13 | 14 | func (s sigs) Len() int { return len(s) } 15 | func (s sigs) Less(i, j int) bool { return bytes.Compare(s[i], s[j]) < 0 } 16 | func (s sigs) Swap(i, j int) { s[i], s[j] = s[j], s[i] } 17 | -------------------------------------------------------------------------------- /internal/crypto/player_idx/util.go: -------------------------------------------------------------------------------- 1 | package player_idx 2 | 3 | func init() { 4 | if int(MaxPlayer) < 0 { 5 | panic("idx must be an unsigned type") 6 | } 7 | if one+MaxPlayer != 0 { 8 | panic("maxPlayer must be twos complement representation of -1") 9 | } 10 | } 11 | 12 | func (pi *PlayerIdx) mustBeNonZero() { 13 | if err := pi.NonZero(); err != nil { 14 | panic(err) 15 | } 16 | } 17 | 18 | var one = Int(1) 19 | -------------------------------------------------------------------------------- /internal/util/wrap_error.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | ) 7 | 8 | func WrapErrorf(err error, fmtString string, args ...interface{}) error { 9 | if err == nil { 10 | return nil 11 | } 12 | return fmt.Errorf(fmtString+": %s", append(args, err)...) 13 | } 14 | 15 | func WrapError(err error, msg string) error { 16 | return WrapErrorf(err, strings.ReplaceAll(msg, "%", "%%")) 17 | } 18 | -------------------------------------------------------------------------------- /internal/dkg/group_registries.go: -------------------------------------------------------------------------------- 1 | package dkg 2 | 3 | import ( 4 | "go.dedis.ch/kyber/v3/sign/anon" 5 | 6 | "github.com/smartcontractkit/chainlink-vrf/altbn_128" 7 | "github.com/smartcontractkit/chainlink-vrf/internal/crypto/point_translation" 8 | ) 9 | 10 | var translatorRegistry = point_translation.TranslatorRegistry 11 | 12 | var altBN128Pairing = &altbn_128.PairingSuite{} 13 | 14 | var encryptionGroupRegistry = map[string]anon.Suite{ 15 | "AltBN-128 G₁": altBN128Pairing.G1().(anon.Suite), 16 | } 17 | -------------------------------------------------------------------------------- /internal/pvss/02_share_types.go: -------------------------------------------------------------------------------- 1 | package pvss 2 | 3 | import ( 4 | "github.com/smartcontractkit/chainlink-vrf/internal/crypto/player_idx" 5 | "github.com/smartcontractkit/chainlink-vrf/internal/crypto/point_translation" 6 | 7 | "go.dedis.ch/kyber/v3" 8 | kshare "go.dedis.ch/kyber/v3/share" 9 | ) 10 | 11 | type ShareSet struct { 12 | dealer *player_idx.PlayerIdx 13 | 14 | coeffCommitments *kshare.PubPoly 15 | 16 | pvssKey kyber.Point 17 | 18 | shares []*share 19 | 20 | translation point_translation.PubKeyTranslation 21 | 22 | xXXToxicWaste *kshare.PriPoly 23 | } 24 | -------------------------------------------------------------------------------- /internal/crypto/ciphertext/memoized_plaintexts.go: -------------------------------------------------------------------------------- 1 | package ciphertext 2 | 3 | import ( 4 | "go.dedis.ch/kyber/v3" 5 | ) 6 | 7 | var memoizedPlainTexts map[string][]kyber.Point 8 | 9 | func rawPlainTexts(g kyber.Group) (rv []kyber.Point) { 10 | gen := g.Point().Base() 11 | rv = append(rv, g.Point().Null()) 12 | for bi := 1; bi < 4; bi++ { 13 | rv = append(rv, g.Point().Add(rv[len(rv)-1], gen)) 14 | } 15 | return rv 16 | } 17 | 18 | func memPlainTexts(g kyber.Group) []kyber.Point { 19 | rv, ok := memoizedPlainTexts[g.String()] 20 | if !ok { 21 | rv = rawPlainTexts(g) 22 | } 23 | return rv 24 | } 25 | -------------------------------------------------------------------------------- /altbn_128/gt.go: -------------------------------------------------------------------------------- 1 | package altbn_128 2 | 3 | import ( 4 | "github.com/smartcontractkit/chainlink-vrf/altbn_128/scalar" 5 | 6 | "go.dedis.ch/kyber/v3" 7 | ) 8 | 9 | type GT struct{} 10 | 11 | var _ kyber.Group = (*GT)(nil) 12 | 13 | func (c *GT) String() string { 14 | return "AltBN-128 GT" 15 | } 16 | 17 | func (c *GT) ScalarLen() int { 18 | panic("not implemented") 19 | } 20 | 21 | func (c *GT) Scalar() kyber.Scalar { 22 | 23 | return scalar.NewScalarInt64(0) 24 | } 25 | 26 | func (c *GT) PointLen() int { 27 | panic("not implemented") 28 | } 29 | 30 | func (c *GT) Point() kyber.Point { 31 | return newGTPoint() 32 | } 33 | -------------------------------------------------------------------------------- /internal/common/ocr/ocr_committee.go: -------------------------------------------------------------------------------- 1 | package ocr 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | "github.com/ethereum/go-ethereum/common" 8 | ) 9 | 10 | type OCRCommittee struct{ Signers, Transmitters []common.Address } 11 | 12 | func (o OCRCommittee) String() string { 13 | sportions := make([]string, len(o.Signers)) 14 | for i, s := range o.Signers { 15 | sportions[i] = s.Hex() 16 | } 17 | tportions := make([]string, len(o.Transmitters)) 18 | for i, t := range o.Transmitters { 19 | tportions[i] = t.Hex() 20 | } 21 | return fmt.Sprintf("OCRCommittee{Signers: %s, Transmitters: %s}", 22 | strings.Join(sportions, ", "), strings.Join(tportions, ", ")) 23 | } 24 | -------------------------------------------------------------------------------- /altbn_128/g2.go: -------------------------------------------------------------------------------- 1 | package altbn_128 2 | 3 | import ( 4 | "crypto/cipher" 5 | 6 | "github.com/smartcontractkit/chainlink-vrf/altbn_128/scalar" 7 | 8 | "go.dedis.ch/kyber/v3" 9 | ) 10 | 11 | type G2 struct{ r cipher.Stream } 12 | 13 | var _ kyber.Group = (*G2)(nil) 14 | 15 | func (g *G2) String() string { 16 | return "AltBN-128 G₂" 17 | } 18 | 19 | func (g *G2) ScalarLen() int { 20 | panic("not implemented") 21 | } 22 | 23 | func (g *G2) Scalar() kyber.Scalar { 24 | return scalar.NewScalarInt64(0) 25 | } 26 | 27 | func (g *G2) PointLen() int { 28 | return len(g.Point().(*g2Point).G2.Marshal()) 29 | } 30 | 31 | func (g *G2) Point() kyber.Point { 32 | return newG2Point() 33 | } 34 | -------------------------------------------------------------------------------- /internal/crypto/point_translation/point_translation.go: -------------------------------------------------------------------------------- 1 | package point_translation 2 | 3 | import ( 4 | "go.dedis.ch/kyber/v3" 5 | 6 | "github.com/smartcontractkit/chainlink-vrf/altbn_128" 7 | ) 8 | 9 | type PubKeyTranslation interface { 10 | TranslateKey(share kyber.Scalar) (kyber.Point, error) 11 | 12 | VerifyTranslation(pk1, pk2 kyber.Point) error 13 | 14 | Name() string 15 | 16 | TargetGroup(sourceGroup kyber.Group) (targetGroup kyber.Group, err error) 17 | } 18 | 19 | var TranslatorRegistry = map[string]PubKeyTranslation{ 20 | 21 | "translator from AltBN-128 G₁ to AltBN-128 G₂": &PairingTranslation{ 22 | &altbn_128.PairingSuite{}, 23 | }, 24 | 25 | "trivial": &TrivialTranslation{}, 26 | } 27 | -------------------------------------------------------------------------------- /altbn_128/g1_message_incidentals.go: -------------------------------------------------------------------------------- 1 | package altbn_128 2 | 3 | import ( 4 | "crypto/cipher" 5 | "io" 6 | "reflect" 7 | 8 | "go.dedis.ch/kyber/v3" 9 | "go.dedis.ch/kyber/v3/util/random" 10 | "go.dedis.ch/kyber/v3/xof/blake2xb" 11 | ) 12 | 13 | func (g *G1) XOF(seed []byte) kyber.XOF { 14 | return blake2xb.New(seed) 15 | } 16 | 17 | func (g *G1) RandomStream() cipher.Stream { 18 | if g.r != nil { 19 | return g.r 20 | } 21 | return random.New() 22 | } 23 | 24 | func (g *G1) Write(w io.Writer, objs ...interface{}) error { panic("not implemented") } 25 | func (g *G1) Read(r io.Reader, objs ...interface{}) error { panic("not implemented") } 26 | func (g *G1) New(t reflect.Type) interface{} { panic("not implemented") } 27 | -------------------------------------------------------------------------------- /types/hash/hash.go: -------------------------------------------------------------------------------- 1 | package hash 2 | 3 | import ( 4 | hashAlg "crypto/sha256" 5 | "fmt" 6 | "strings" 7 | 8 | "github.com/ethereum/go-ethereum/common/hexutil" 9 | ) 10 | 11 | const Size = hashAlg.Size 12 | 13 | type Hash [Size]byte 14 | 15 | func GetHash(s []byte) Hash { 16 | return hashAlg.Sum256(s) 17 | } 18 | 19 | var Zero Hash 20 | 21 | type Hashes []Hash 22 | 23 | func MakeHashes() Hashes { return Hashes{} } 24 | 25 | func (hs *Hashes) Add(h Hash) { 26 | *hs = append(*hs, h) 27 | } 28 | 29 | func (hs Hashes) String() string { 30 | shs := make([]string, len(hs)) 31 | for i, h := range hs { 32 | shs[i] = h.String() 33 | } 34 | return fmt.Sprintf("[%s]", strings.Join(shs, ", ")) 35 | } 36 | 37 | func (h Hash) String() string { 38 | return hexutil.Encode(h[:]) 39 | } 40 | -------------------------------------------------------------------------------- /types/db.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "context" 5 | 6 | ocr_types "github.com/smartcontractkit/libocr/offchainreporting2plus/types" 7 | 8 | "github.com/smartcontractkit/chainlink-vrf/internal/crypto/player_idx" 9 | "github.com/smartcontractkit/chainlink-vrf/types/hash" 10 | ) 11 | 12 | type DKGSharePersistence interface { 13 | WriteShareRecords( 14 | ctx context.Context, 15 | cfgDgst ocr_types.ConfigDigest, 16 | keyID [32]byte, 17 | shareRecords []PersistentShareSetRecord, 18 | ) error 19 | 20 | ReadShareRecords( 21 | cfgDgst ocr_types.ConfigDigest, 22 | keyID [32]byte, 23 | ) (retrievedShares []PersistentShareSetRecord, err error) 24 | } 25 | 26 | type PersistentShareSetRecord struct { 27 | Dealer player_idx.PlayerIdx 28 | MarshaledShareRecord []byte 29 | Hash hash.Hash 30 | } 31 | -------------------------------------------------------------------------------- /internal/signatures/secp256k1/curve.go: -------------------------------------------------------------------------------- 1 | package secp256k1 2 | 3 | import ( 4 | "math/big" 5 | 6 | secp256k1BTCD "github.com/btcsuite/btcd/btcec" 7 | 8 | "go.dedis.ch/kyber/v3" 9 | ) 10 | 11 | type Secp256k1 struct{} 12 | 13 | var s256 *secp256k1BTCD.KoblitzCurve = secp256k1BTCD.S256() 14 | 15 | func (*Secp256k1) String() string { return "Secp256k1" } 16 | 17 | var egScalar kyber.Scalar = newScalar(big.NewInt(0)) 18 | var egPoint kyber.Point = &secp256k1Point{newFieldZero(), newFieldZero()} 19 | 20 | func (*Secp256k1) ScalarLen() int { return egScalar.MarshalSize() } 21 | 22 | func (*Secp256k1) Scalar() kyber.Scalar { return newScalar(big.NewInt(0)) } 23 | 24 | func (*Secp256k1) PointLen() int { return egPoint.MarshalSize() } 25 | 26 | func (*Secp256k1) Point() kyber.Point { 27 | return &secp256k1Point{newFieldZero(), newFieldZero()} 28 | } 29 | -------------------------------------------------------------------------------- /altbn_128/bn256_interface.go: -------------------------------------------------------------------------------- 1 | package altbn_128 2 | 3 | import ( 4 | "math/big" 5 | 6 | bn256 "github.com/ethereum/go-ethereum/crypto/bn256/cloudflare" 7 | ) 8 | 9 | type g1Interface interface { 10 | String() string 11 | ScalarBaseMult(k *big.Int) *bn256.G1 12 | ScalarMult(a *bn256.G1, k *big.Int) *bn256.G1 13 | Add(a, b *bn256.G1) *bn256.G1 14 | Neg(a *bn256.G1) *bn256.G1 15 | Set(a *bn256.G1) *bn256.G1 16 | Marshal() []byte 17 | Unmarshal(m []byte) ([]byte, error) 18 | } 19 | 20 | var _ g1Interface = (*bn256.G1)(nil) 21 | 22 | type g2Interface interface { 23 | String() string 24 | ScalarBaseMult(k *big.Int) *bn256.G2 25 | ScalarMult(a *bn256.G2, k *big.Int) *bn256.G2 26 | Add(a, b *bn256.G2) *bn256.G2 27 | Neg(a *bn256.G2) *bn256.G2 28 | Set(a *bn256.G2) *bn256.G2 29 | Marshal() []byte 30 | Unmarshal(m []byte) ([]byte, error) 31 | } 32 | 33 | var _ g2Interface = (*bn256.G2)(nil) 34 | -------------------------------------------------------------------------------- /internal/crypto/point_translation/trivial_translation.go: -------------------------------------------------------------------------------- 1 | package point_translation 2 | 3 | import ( 4 | "github.com/pkg/errors" 5 | "go.dedis.ch/kyber/v3" 6 | ) 7 | 8 | type TrivialTranslation struct{ base kyber.Point } 9 | 10 | var _ PubKeyTranslation = (*TrivialTranslation)(nil) 11 | 12 | func NewTrivialTranslation(base kyber.Point) *TrivialTranslation { 13 | return &TrivialTranslation{base} 14 | } 15 | 16 | func (t *TrivialTranslation) TranslateKey(share kyber.Scalar) (kyber.Point, error) { 17 | return t.base.Clone().Mul(share, nil), nil 18 | } 19 | 20 | func (t *TrivialTranslation) VerifyTranslation(pk1, pk2 kyber.Point) error { 21 | if pk1.Equal(pk2) { 22 | return nil 23 | } 24 | return errors.Errorf("putative translated points are not equal") 25 | } 26 | 27 | func (t *TrivialTranslation) Name() string { 28 | return "trivial translator" 29 | } 30 | 31 | func (t *TrivialTranslation) TargetGroup( 32 | sourceGroup kyber.Group, 33 | ) (targetGroup kyber.Group, err error) { 34 | return sourceGroup, nil 35 | } 36 | -------------------------------------------------------------------------------- /internal/crypto/product_group/product_group_incidentals.go: -------------------------------------------------------------------------------- 1 | package product_group 2 | 3 | import ( 4 | "crypto/cipher" 5 | "io" 6 | "reflect" 7 | 8 | "go.dedis.ch/fixbuf" 9 | "go.dedis.ch/kyber/v3" 10 | "go.dedis.ch/kyber/v3/util/random" 11 | "go.dedis.ch/kyber/v3/xof/blake2xb" 12 | ) 13 | 14 | func (pg *ProductGroup) Write(w io.Writer, objs ...interface{}) error { 15 | return fixbuf.Write(w, objs) 16 | } 17 | 18 | func (pg *ProductGroup) Read(r io.Reader, objs ...interface{}) error { 19 | return fixbuf.Read(r, pg, objs) 20 | } 21 | 22 | var aScalar kyber.Scalar 23 | var aPoint kyber.Point 24 | 25 | var tScalar = reflect.TypeOf(&aScalar).Elem() 26 | var tPoint = reflect.TypeOf(&aPoint).Elem() 27 | 28 | func (pg *ProductGroup) New(t reflect.Type) interface{} { 29 | switch t { 30 | case tScalar: 31 | return pg.Scalar() 32 | case tPoint: 33 | return pg.Point() 34 | } 35 | return nil 36 | } 37 | 38 | func (pg *ProductGroup) XOF(seed []byte) kyber.XOF { 39 | return blake2xb.New(seed) 40 | } 41 | 42 | func (pg *ProductGroup) RandomStream() cipher.Stream { 43 | if pg.r != nil { 44 | return pg.r 45 | } 46 | return random.New() 47 | } 48 | -------------------------------------------------------------------------------- /internal/dkg/key_consumer.go: -------------------------------------------------------------------------------- 1 | package dkg 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/smartcontractkit/chainlink-vrf/internal/crypto/player_idx" 7 | "github.com/smartcontractkit/chainlink-vrf/internal/dkg/contract" 8 | 9 | "go.dedis.ch/kyber/v3" 10 | "go.dedis.ch/kyber/v3/share" 11 | ) 12 | 13 | type KeyConsumer interface { 14 | KeyInvalidated(contract.KeyID) 15 | 16 | NewKey(contract.KeyID, *KeyData) 17 | } 18 | 19 | type KeyData struct { 20 | PublicKey kyber.Point 21 | Shares []share.PubShare 22 | SecretShare *SecretShare 23 | 24 | T player_idx.Int 25 | Present bool 26 | } 27 | 28 | func (kd KeyData) Clone() *KeyData { 29 | shares := make([]share.PubShare, len(kd.Shares)) 30 | for i := 0; i < len(kd.Shares); i++ { 31 | if kd.Shares[i].V == nil { 32 | panic(fmt.Errorf("%dth public share is nil", i)) 33 | } 34 | s := share.PubShare{kd.Shares[i].I, kd.Shares[i].V.Clone()} 35 | shares[i] = s 36 | } 37 | if kd.PublicKey == nil { 38 | panic("nil public key") 39 | } 40 | if kd.SecretShare == nil { 41 | panic("nil secret share") 42 | } 43 | return &KeyData{ 44 | kd.PublicKey.Clone(), 45 | shares, kd.SecretShare.Clone(), 46 | kd.T, 47 | kd.Present, 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /internal/crypto/ciphertext/get_share_bits.go: -------------------------------------------------------------------------------- 1 | package ciphertext 2 | 3 | import ( 4 | "github.com/pkg/errors" 5 | 6 | "github.com/smartcontractkit/chainlink-vrf/internal/crypto/player_idx" 7 | 8 | "go.dedis.ch/kyber/v3" 9 | "go.dedis.ch/kyber/v3/share" 10 | ) 11 | 12 | func getShareBits(receiver *player_idx.PlayerIdx, secretPoly *share.PriPoly) (kyber.Scalar, []byte, error) { 13 | privateShare := receiver.Eval(secretPoly) 14 | rawShare, err := privateShare.MarshalBinary() 15 | if err != nil { 16 | return nil, nil, errors.Wrapf(err, "while computing bit reperesentation of secret share") 17 | } 18 | if err := verifyMarshalOutputBigEndian(privateShare, rawShare); err != nil { 19 | return nil, nil, err 20 | } 21 | return privateShare, rawShare, nil 22 | } 23 | 24 | func verifyMarshalOutputBigEndian(share kyber.Scalar, rawShare []byte) error { 25 | tot := share.Clone().Zero() 26 | for bitIdx := 0; bitIdx < len(rawShare)*8; bitIdx++ { 27 | tot = tot.Clone().Add(tot, tot) 28 | if rawShare[bitIdx/8]&(1<<(7-(bitIdx%8))) > 0 { 29 | tot = tot.Clone().Add(tot, tot.Clone().One()) 30 | } 31 | } 32 | if !tot.Equal(share) { 33 | return errors.Errorf("scalars do not marshal to big-endian representation") 34 | } 35 | return nil 36 | } 37 | -------------------------------------------------------------------------------- /altbn_128/pairing.go: -------------------------------------------------------------------------------- 1 | package altbn_128 2 | 3 | import ( 4 | "crypto/cipher" 5 | "hash" 6 | "io" 7 | 8 | bn256 "github.com/ethereum/go-ethereum/crypto/bn256/cloudflare" 9 | 10 | "go.dedis.ch/kyber/v3" 11 | "go.dedis.ch/kyber/v3/pairing" 12 | "go.dedis.ch/kyber/v3/util/random" 13 | ) 14 | 15 | type PairingSuite struct{} 16 | 17 | var _ pairing.Suite = (*PairingSuite)(nil) 18 | 19 | func (p *PairingSuite) G1() kyber.Group { 20 | return newG1() 21 | } 22 | 23 | func (p *PairingSuite) G2() kyber.Group { 24 | return &G2{} 25 | } 26 | 27 | func (p *PairingSuite) GT() kyber.Group { 28 | return >{} 29 | } 30 | 31 | func (p *PairingSuite) Pair(p1 kyber.Point, p2 kyber.Point) kyber.Point { 32 | pg1, pg2 := p1.(*g1Point), p2.(*g2Point) 33 | return &gTPoint{bn256.Pair(pg1.G1.(*bn256.G1), pg2.G2.(*bn256.G2))} 34 | } 35 | 36 | func (p *PairingSuite) Write(w io.Writer, objs ...interface{}) error { 37 | panic("not implemented") 38 | } 39 | 40 | func (p *PairingSuite) Read(r io.Reader, objs ...interface{}) error { 41 | panic("not implemented") 42 | } 43 | 44 | func (p *PairingSuite) Hash() hash.Hash { 45 | panic("not implemented") 46 | } 47 | 48 | func (p *PairingSuite) XOF(seed []byte) kyber.XOF { 49 | panic("not implemented") 50 | } 51 | 52 | func (p *PairingSuite) RandomStream() cipher.Stream { 53 | return random.New() 54 | } 55 | -------------------------------------------------------------------------------- /internal/vrf/key_transceiver.go: -------------------------------------------------------------------------------- 1 | package vrf 2 | 3 | import ( 4 | "sync" 5 | 6 | "github.com/smartcontractkit/chainlink-vrf/internal/dkg" 7 | "github.com/smartcontractkit/chainlink-vrf/internal/dkg/contract" 8 | ) 9 | 10 | type KeyTransceiver struct { 11 | keyID contract.KeyID 12 | kd *dkg.KeyData 13 | mu sync.RWMutex 14 | } 15 | 16 | var _ dkg.KeyConsumer = (*KeyTransceiver)(nil) 17 | var _ KeyProvider = (*KeyTransceiver)(nil) 18 | 19 | func NewKeyTransceiver(keyID contract.KeyID) *KeyTransceiver { 20 | return &KeyTransceiver{keyID, nil, sync.RWMutex{}} 21 | } 22 | 23 | func (kt *KeyTransceiver) KeyInvalidated(kID contract.KeyID) { 24 | 25 | kt.mu.Lock() 26 | defer kt.mu.Unlock() 27 | 28 | if kt.keyID == kID { 29 | kt.kd = nil 30 | } 31 | } 32 | 33 | func (kt *KeyTransceiver) NewKey(kID contract.KeyID, kd *dkg.KeyData) { 34 | 35 | kt.mu.Lock() 36 | defer kt.mu.Unlock() 37 | 38 | if kt.keyID == kID { 39 | kt.kd = kd.Clone() 40 | } 41 | } 42 | 43 | func (kt *KeyTransceiver) KeyLookup(p contract.KeyID) dkg.KeyData { 44 | 45 | kt.mu.RLock() 46 | defer kt.mu.RUnlock() 47 | 48 | if p == kt.keyID { 49 | if kt.kd != nil { 50 | return *kt.kd.Clone() 51 | } 52 | return dkg.KeyData{nil, nil, nil, 0, false} 53 | } 54 | 55 | panic("key consumer is asking for unknown key ID") 56 | } 57 | 58 | func (kt *KeyTransceiver) KeyGenerated() bool { 59 | return (kt.kd != nil) && kt.kd.Present 60 | } 61 | -------------------------------------------------------------------------------- /internal/signatures/secp256k1/suite.go: -------------------------------------------------------------------------------- 1 | package secp256k1 2 | 3 | import ( 4 | "crypto/cipher" 5 | "hash" 6 | "io" 7 | "reflect" 8 | 9 | "golang.org/x/crypto/sha3" 10 | 11 | "go.dedis.ch/fixbuf" 12 | "go.dedis.ch/kyber/v3" 13 | "go.dedis.ch/kyber/v3/util/random" 14 | "go.dedis.ch/kyber/v3/xof/blake2xb" 15 | ) 16 | 17 | type SuiteSecp256k1 struct { 18 | Secp256k1 19 | r cipher.Stream 20 | } 21 | 22 | func (s *SuiteSecp256k1) Hash() hash.Hash { 23 | return sha3.NewLegacyKeccak256() 24 | } 25 | 26 | func (s *SuiteSecp256k1) XOF(key []byte) kyber.XOF { 27 | return blake2xb.New(key) 28 | } 29 | 30 | func (s *SuiteSecp256k1) Read(r io.Reader, objs ...interface{}) error { 31 | return fixbuf.Read(r, s, objs...) 32 | } 33 | 34 | func (s *SuiteSecp256k1) Write(w io.Writer, objs ...interface{}) error { 35 | return fixbuf.Write(w, objs) 36 | } 37 | 38 | var aScalar kyber.Scalar 39 | var tScalar = reflect.TypeOf(aScalar) 40 | var aPoint kyber.Point 41 | var tPoint = reflect.TypeOf(aPoint) 42 | 43 | func (s *SuiteSecp256k1) New(t reflect.Type) interface{} { 44 | switch t { 45 | case tScalar: 46 | return s.Scalar() 47 | case tPoint: 48 | return s.Point() 49 | } 50 | return nil 51 | } 52 | 53 | func (s *SuiteSecp256k1) RandomStream() cipher.Stream { 54 | if s.r != nil { 55 | return s.r 56 | } 57 | return random.New() 58 | } 59 | 60 | func NewBlakeKeccackSecp256k1() *SuiteSecp256k1 { 61 | return new(SuiteSecp256k1) 62 | } 63 | -------------------------------------------------------------------------------- /internal/crypto/player_idx/player_idx.go: -------------------------------------------------------------------------------- 1 | package player_idx 2 | 3 | import ( 4 | "reflect" 5 | 6 | "github.com/pkg/errors" 7 | "go.dedis.ch/kyber/v3" 8 | "go.dedis.ch/kyber/v3/share" 9 | ) 10 | 11 | type PlayerIdx struct{ idx Int } 12 | 13 | type Int = uint8 14 | 15 | var MaxPlayer = -Int(one) 16 | 17 | func PlayerIdxs(n Int) (rv []*PlayerIdx, err error) { 18 | if n < 1 { 19 | return nil, errors.Errorf("%d is too few players", n) 20 | } 21 | for i := Int(1); i <= n; i++ { 22 | rv = append(rv, &PlayerIdx{i}) 23 | } 24 | return rv, nil 25 | } 26 | 27 | func (pi PlayerIdx) Eval(f *share.PriPoly) kyber.Scalar { 28 | pi.mustBeNonZero() 29 | return f.Eval(int(pi.idx - 1)).V 30 | } 31 | 32 | func (pi PlayerIdx) EvalPoint(f *share.PubPoly) kyber.Point { 33 | pi.mustBeNonZero() 34 | return f.Eval(int(pi.idx - 1)).V 35 | } 36 | 37 | func (pi PlayerIdx) Index(a interface{}) interface{} { 38 | pi.mustBeNonZero() 39 | return reflect.ValueOf(a).Index(int(pi.idx) - 1).Interface() 40 | } 41 | 42 | func (pi PlayerIdx) Set(a, e interface{}) { 43 | pi.mustBeNonZero() 44 | reflect.ValueOf(a).Index(int(pi.idx) - 1).Set(reflect.ValueOf(e)) 45 | } 46 | 47 | func (pi PlayerIdx) PriShare(sk kyber.Scalar) share.PriShare { 48 | pi.mustBeNonZero() 49 | return share.PriShare{int(pi.idx) - 1, sk} 50 | } 51 | 52 | func (pi PlayerIdx) PubShare(p kyber.Point) share.PubShare { 53 | pi.mustBeNonZero() 54 | return share.PubShare{int(pi.idx) - 1, p} 55 | } 56 | -------------------------------------------------------------------------------- /altbn_128/g1.go: -------------------------------------------------------------------------------- 1 | package altbn_128 2 | 3 | import ( 4 | "crypto/cipher" 5 | "math/big" 6 | 7 | "go.dedis.ch/kyber/v3" 8 | "go.dedis.ch/kyber/v3/sign/anon" 9 | 10 | bn256 "github.com/ethereum/go-ethereum/crypto/bn256/cloudflare" 11 | 12 | "github.com/smartcontractkit/chainlink-vrf/altbn_128/scalar" 13 | ) 14 | 15 | type G1 struct{ r cipher.Stream } 16 | 17 | var _ kyber.Group = (*G1)(nil) 18 | var _ anon.Suite = (*G1)(nil) 19 | 20 | func newG1() *G1 { 21 | return &G1{} 22 | } 23 | 24 | func (g *G1) String() string { 25 | return "AltBN-128 G₁" 26 | } 27 | 28 | func (g *G1) ScalarLen() int { 29 | return g1ScalarLength 30 | } 31 | 32 | func (g *G1) Scalar() kyber.Scalar { 33 | return scalar.NewScalarInt64(0) 34 | } 35 | 36 | func (g *G1) PointLen() int { 37 | return g1PointLength 38 | } 39 | 40 | func (g *G1) Point() kyber.Point { 41 | return newG1Point() 42 | } 43 | 44 | var g1ScalarLength, g1PointLength int 45 | var zero, one *big.Int 46 | var null, g1Base *g1Point 47 | 48 | func init() { 49 | zero, one = big.NewInt(0), big.NewInt(1) 50 | 51 | b, err := new(G1).Scalar().Zero().MarshalBinary() 52 | if err != nil { 53 | panic(err) 54 | } 55 | g1ScalarLength = len(b) 56 | 57 | rawG1Base := new(bn256.G1).ScalarBaseMult(one) 58 | g1Base = &g1Point{rawG1Base} 59 | negG1Base := new(bn256.G1).Neg(rawG1Base) 60 | g1Null := new(bn256.G1).Add(rawG1Base, negG1Base) 61 | null = &g1Point{g1Null} 62 | b, err = null.MarshalBinary() 63 | if err != nil { 64 | panic(err) 65 | } 66 | g1PointLength = len(b) 67 | } 68 | -------------------------------------------------------------------------------- /internal/dkg/contract/onchain_contract.go: -------------------------------------------------------------------------------- 1 | package contract 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/ethereum/go-ethereum/common" 7 | 8 | "github.com/smartcontractkit/libocr/offchainreporting2plus/types" 9 | 10 | "github.com/smartcontractkit/chainlink-vrf/internal/common/ocr" 11 | "github.com/smartcontractkit/chainlink-vrf/internal/util" 12 | 13 | "go.dedis.ch/kyber/v3" 14 | ) 15 | 16 | type KeyID [32]byte 17 | 18 | type OnchainContract struct { 19 | DKG DKG 20 | KeyGroup kyber.Group 21 | } 22 | 23 | type DKG interface { 24 | GetKey( 25 | ctx context.Context, 26 | keyID KeyID, 27 | configDigest [32]byte, 28 | ) (OnchainKeyData, error) 29 | 30 | AddClient( 31 | ctx context.Context, 32 | keyID [32]byte, 33 | clientAddress common.Address, 34 | ) error 35 | 36 | Address() common.Address 37 | 38 | CurrentCommittee(ctx context.Context) (ocr.OCRCommittee, error) 39 | } 40 | 41 | type OnchainKeyData struct { 42 | PublicKey []byte 43 | Hashes [][32]byte 44 | } 45 | 46 | func (o OnchainContract) KeyData( 47 | ctx context.Context, keyID KeyID, cfgDgst types.ConfigDigest, 48 | ) (KeyData, error) { 49 | kd, err := o.DKG.GetKey(ctx, keyID, cfgDgst) 50 | if err != nil { 51 | return KeyData{}, util.WrapError(err, "could not retrieve key from contract") 52 | } 53 | if len(kd.PublicKey) == 0 || len(kd.Hashes) == 0 { 54 | return KeyData{}, nil 55 | } 56 | return MakeKeyDataFromOnchainKeyData(kd, o.KeyGroup) 57 | } 58 | 59 | func (o OnchainContract) Address() common.Address { 60 | return o.DKG.Address() 61 | } 62 | -------------------------------------------------------------------------------- /types/helpers.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "fmt" 7 | "sort" 8 | 9 | "github.com/ethereum/go-ethereum/common" 10 | "github.com/ethereum/go-ethereum/crypto" 11 | "go.dedis.ch/kyber/v3" 12 | ) 13 | 14 | func (b Block) VRFHash(separator common.Hash, pKey kyber.Point) common.Hash { 15 | var heightBytes [8]byte 16 | binary.BigEndian.PutUint64(heightBytes[:], b.Height) 17 | var delayBytes [4]byte 18 | binary.BigEndian.PutUint32(delayBytes[:], b.ConfirmationDelay) 19 | key, err := pKey.MarshalBinary() 20 | if err != nil { 21 | panic("could not serialize key as domain separator") 22 | } 23 | hashMsg := bytes.Join( 24 | [][]byte{separator[:], key, delayBytes[:], heightBytes[:], b.Hash.Bytes()}, 25 | nil, 26 | ) 27 | return common.BytesToHash(crypto.Keccak256(hashMsg)) 28 | } 29 | 30 | func (b Block) String() string { 31 | return fmt.Sprintf( 32 | "Block{Height: %d, ConfirmationDelay: %d, Hash: 0x%x}", 33 | b.Height, b.ConfirmationDelay, b.Hash, 34 | ) 35 | } 36 | 37 | type Blocks []Block 38 | 39 | var _ sort.Interface = (*Blocks)(nil) 40 | 41 | func (b Blocks) Len() int { return len(b) } 42 | func (b Blocks) Less(i, j int) bool { 43 | if b[i].Height < b[j].Height { 44 | return true 45 | } 46 | if b[i].Height > b[j].Height { 47 | return false 48 | } 49 | if b[i].ConfirmationDelay < b[j].ConfirmationDelay { 50 | return true 51 | } 52 | if b[i].ConfirmationDelay > b[j].ConfirmationDelay { 53 | return false 54 | } 55 | return b[i].Hash.Hex() < b[j].Hash.Hex() 56 | } 57 | 58 | func (b Blocks) Swap(i, j int) { b[i], b[j] = b[j], b[i] } 59 | -------------------------------------------------------------------------------- /internal/crypto/point_translation/pairing_translation.go: -------------------------------------------------------------------------------- 1 | package point_translation 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | 7 | "github.com/pkg/errors" 8 | "go.dedis.ch/kyber/v3" 9 | "go.dedis.ch/kyber/v3/pairing" 10 | ) 11 | 12 | type PairingTranslation struct{ pairing.Suite } 13 | 14 | func (t *PairingTranslation) TranslateKey(s kyber.Scalar) (kyber.Point, error) { 15 | if reflect.TypeOf(s) != reflect.TypeOf(t.G1().Scalar()) { 16 | return nil, errors.Errorf("need scalar of type %T, got %T", t.G1().Scalar(), s) 17 | } 18 | return t.G2().Point().Mul(s, nil), nil 19 | } 20 | 21 | func (t *PairingTranslation) VerifyTranslation(pk1, pk2 kyber.Point) error { 22 | if reflect.TypeOf(pk1) != reflect.TypeOf(t.G1().Point()) { 23 | return fmt.Errorf("point for translation must be on G1, not %T", pk1) 24 | } 25 | if reflect.TypeOf(pk2) != reflect.TypeOf(t.G2().Point()) { 26 | return fmt.Errorf("translation must be on G2, not %T", pk2) 27 | } 28 | g1, g2 := t.G1().Point().Base(), t.G2().Point().Base() 29 | 30 | if !t.Pair(pk1, g2).Equal(t.Pair(g1, pk2)) { 31 | return errors.Errorf("putative G₂ public key has wrong discrete log") 32 | } 33 | return nil 34 | } 35 | 36 | func (t *PairingTranslation) Name() string { 37 | return fmt.Sprintf("translator from %s to %s", t.G1().String(), t.G2().String()) 38 | } 39 | 40 | func (t *PairingTranslation) TargetGroup( 41 | sourceGroup kyber.Group, 42 | ) (targetGroup kyber.Group, err error) { 43 | if sourceGroup.String() != t.G1().String() { 44 | return nil, errors.Errorf("attempt to get target group from wrong source group") 45 | } 46 | return t.G2(), nil 47 | } 48 | -------------------------------------------------------------------------------- /internal/crypto/ciphertext/00_cipher_text_public_interface.go: -------------------------------------------------------------------------------- 1 | package ciphertext 2 | 3 | import ( 4 | "io" 5 | 6 | "go.dedis.ch/kyber/v3" 7 | "go.dedis.ch/kyber/v3/share" 8 | "go.dedis.ch/kyber/v3/sign/anon" 9 | 10 | "github.com/smartcontractkit/chainlink-vrf/internal/crypto/player_idx" 11 | ) 12 | 13 | type CipherText struct { 14 | *cipherText 15 | } 16 | 17 | func Encrypt( 18 | domainSep []byte, group anon.Suite, f *share.PriPoly, 19 | receiver *player_idx.PlayerIdx, pk kyber.Point, 20 | ) (cipherText *CipherText, secretShare kyber.Scalar, err error) { 21 | ct, secretShare, err := newCipherText(domainSep, group, f, receiver, pk) 22 | if err != nil { 23 | return nil, nil, err 24 | } 25 | return &CipherText{ct}, secretShare, nil 26 | } 27 | 28 | func (c *CipherText) Verify( 29 | group anon.Suite, domainSep []byte, pk, sharePublicCommitment kyber.Point, 30 | ) error { 31 | return c.verify(group, domainSep, pk, sharePublicCommitment) 32 | } 33 | 34 | func (c *CipherText) Decrypt( 35 | sk kyber.Scalar, 36 | group anon.Suite, domainSep []byte, sharePublicCommitment kyber.Point, 37 | receiver player_idx.PlayerIdx, 38 | ) (kyber.Scalar, error) { 39 | return c.decrypt(sk, group, domainSep, sharePublicCommitment) 40 | } 41 | 42 | func (c *CipherText) Marshal() ([]byte, error) { 43 | return c.marshal() 44 | } 45 | 46 | func Unmarshal(suite anon.Suite, byteStream io.Reader) (*CipherText, error) { 47 | c, err := unmarshal(suite, byteStream) 48 | if err != nil { 49 | return nil, err 50 | } 51 | return &CipherText{c}, nil 52 | } 53 | 54 | func (c *CipherText) Equal(c2 *CipherText) bool { 55 | return c.equal(c2.cipherText) 56 | } 57 | -------------------------------------------------------------------------------- /internal/vrf/sig_request_report_helpers.go: -------------------------------------------------------------------------------- 1 | package vrf 2 | 3 | import ( 4 | "math/big" 5 | 6 | "github.com/ethereum/go-ethereum/common" 7 | bn256 "github.com/ethereum/go-ethereum/crypto/bn256/cloudflare" 8 | 9 | "github.com/pkg/errors" 10 | 11 | "go.dedis.ch/kyber/v3" 12 | "go.dedis.ch/kyber/v3/group/mod" 13 | kshare "go.dedis.ch/kyber/v3/share" 14 | 15 | "github.com/smartcontractkit/chainlink-vrf/altbn_128" 16 | "github.com/smartcontractkit/chainlink-vrf/internal/dkg" 17 | vrf_types "github.com/smartcontractkit/chainlink-vrf/types" 18 | ) 19 | 20 | func m(x int64) *mod.Int { return mod.NewInt64(x, bn256.P) } 21 | 22 | var three = m(3) 23 | 24 | func affineCoordinates(p kyber.Point) (*big.Int, *big.Int) { 25 | 26 | b := altbn_128.LongMarshal(p) 27 | return big.NewInt(0).SetBytes(b[:32]), big.NewInt(0).SetBytes(b[32:]) 28 | } 29 | 30 | func (s *sigRequest) computePartialSig( 31 | block vrf_types.Block, kd dkg.KeyData, 32 | ) (kyber.Point, error) { 33 | 34 | seed := blsSeed(s.configDigest, block, kd.PublicKey) 35 | 36 | output := kd.SecretShare.Mul(seed) 37 | 38 | pk := s.i.Index(kd.Shares).(kshare.PubShare).V 39 | 40 | if !validateSignature(s.pairing, seed, pk, output) { 41 | return nil, errors.Errorf(failedVerifyOwnContributionMsg) 42 | } 43 | return output, nil 44 | } 45 | 46 | func blsSeed( 47 | domainSeparator common.Hash, 48 | block vrf_types.Block, 49 | pk kyber.Point, 50 | ) kyber.Point { 51 | 52 | h := block.VRFHash(domainSeparator, pk) 53 | 54 | hpoint := altbn_128.NewHashProof(h).HashPoint 55 | return hpoint 56 | } 57 | 58 | const ( 59 | failedVerifyOwnContributionMsg = "could not verify own contribution to signature" 60 | ) 61 | -------------------------------------------------------------------------------- /internal/util/logger.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "bytes" 5 | "io" 6 | 7 | "github.com/sirupsen/logrus" 8 | 9 | "github.com/smartcontractkit/libocr/commontypes" 10 | ) 11 | 12 | func XXXTestingOnly_MakePrefixLogger(prefix string) Logger { 13 | logger := MakeLogger() 14 | logger.logger.Out = prefixWriter{prefix, logger.logger.Out} 15 | return logger 16 | } 17 | 18 | type prefixWriter struct { 19 | prefix string 20 | writer io.Writer 21 | } 22 | 23 | func (p prefixWriter) Write(po []byte) (n int, err error) { 24 | 25 | if len(po) > 1000 { 26 | po = bytes.Join([][]byte{po[:1000], []byte("\n")}, nil) 27 | } 28 | return p.writer.Write( 29 | bytes.Join([][]byte{[]byte(p.prefix), []byte(" "), po}, nil), 30 | ) 31 | } 32 | 33 | type Logger struct { 34 | logger *logrus.Logger 35 | } 36 | 37 | func MakeLogger() Logger { 38 | logger := logrus.New() 39 | logger.SetLevel(logrus.TraceLevel) 40 | return Logger{ 41 | logger, 42 | } 43 | } 44 | 45 | func (l Logger) Trace(msg string, fields commontypes.LogFields) { 46 | l.logger.WithFields(logrus.Fields(fields)).Trace(msg) 47 | } 48 | 49 | func (l Logger) Debug(msg string, fields commontypes.LogFields) { 50 | l.logger.WithFields(logrus.Fields(fields)).Debug(msg) 51 | } 52 | 53 | func (l Logger) Info(msg string, fields commontypes.LogFields) { 54 | l.logger.WithFields(logrus.Fields(fields)).Info(msg) 55 | } 56 | 57 | func (l Logger) Warn(msg string, fields commontypes.LogFields) { 58 | l.logger.WithFields(logrus.Fields(fields)).Warn(msg) 59 | } 60 | 61 | func (l Logger) Error(msg string, fields commontypes.LogFields) { 62 | l.logger.WithFields(logrus.Fields(fields)).Error(msg) 63 | } 64 | 65 | func (l Logger) Critical(msg string, fields commontypes.LogFields) { 66 | l.logger.WithFields(logrus.Fields(fields)).Error("CRITICAL: " + msg) 67 | } 68 | -------------------------------------------------------------------------------- /internal/crypto/ciphertext/bit_pair_decrypt.go: -------------------------------------------------------------------------------- 1 | package ciphertext 2 | 3 | import ( 4 | "github.com/pkg/errors" 5 | "go.dedis.ch/kyber/v3" 6 | "go.dedis.ch/kyber/v3/sign/anon" 7 | ) 8 | 9 | var _ = ((*CipherText)(nil)).Decrypt 10 | 11 | func (c *cipherText) decrypt( 12 | sk kyber.Scalar, 13 | group anon.Suite, domainSep []byte, sharePublicCommitment kyber.Point, 14 | ) (plaintextShare kyber.Scalar, err error) { 15 | if len(c.cipherText) > plaintextMaxSizeBytes*4 { 16 | return nil, errors.Errorf("ciphertext too large (max %d pairs)", 17 | plaintextMaxSizeBytes*4, 18 | ) 19 | } 20 | encryptionPK := group.Point().Mul(sk, nil) 21 | err = c.verify(group, domainSep, encryptionPK, sharePublicCommitment) 22 | if err != nil { 23 | return nil, errors.Wrap(err, "refusing to decrypt unverifiable share") 24 | } 25 | 26 | plaintextShare = group.Scalar() 27 | 28 | zero := group.Scalar().Zero() 29 | one := group.Scalar().One() 30 | two := group.Scalar().Add(one, one) 31 | three := group.Scalar().Add(two, one) 32 | four := group.Scalar().Add(three, one) 33 | bitPairs := map[int]kyber.Scalar{0: zero, 1: one, 2: two, 3: three} 34 | 35 | fourPower := group.Scalar().One() 36 | 37 | for _, bitPair := range c.cipherText { 38 | numericPair, err := bitPair.decrypt(sk) 39 | if err != nil { 40 | 41 | return nil, errors.Wrapf(err, "could not decrypt %+v as part of share", bitPair) 42 | } 43 | if numericPair < 0 || numericPair > 3 { 44 | 45 | return nil, errors.Errorf( 46 | "bit pair decryption (%d) out of range. Must be less than 4", 47 | numericPair, 48 | ) 49 | } 50 | 51 | shiftedBitPair := group.Scalar().Mul(bitPairs[numericPair], fourPower) 52 | plaintextShare = group.Scalar().Add(plaintextShare, shiftedBitPair) 53 | fourPower = group.Scalar().Mul(fourPower, four) 54 | } 55 | return plaintextShare, nil 56 | } 57 | -------------------------------------------------------------------------------- /internal/crypto/ciphertext/dl_proofs.go: -------------------------------------------------------------------------------- 1 | package ciphertext 2 | 3 | import ( 4 | "bytes" 5 | 6 | "github.com/pkg/errors" 7 | 8 | "github.com/smartcontractkit/chainlink-vrf/internal/crypto/change_base_group" 9 | "github.com/smartcontractkit/chainlink-vrf/internal/crypto/ciphertext/schnorr" 10 | 11 | "go.dedis.ch/kyber/v3" 12 | "go.dedis.ch/kyber/v3/sign/anon" 13 | ) 14 | 15 | type dLKnowledgeProof []byte 16 | 17 | func newDLKnowledgeProof( 18 | domainSep []byte, group anon.Suite, h kyber.Point, b kyber.Scalar, 19 | ) (dLKnowledgeProof, error) { 20 | 21 | g, err := change_base_group.NewChangeBaseGroup(group, h) 22 | if err != nil { 23 | return nil, errors.Wrapf(err, "could not create cyclic group from given generator") 24 | } 25 | 26 | rv, err := schnorr.Sign(g, b, domainSep) 27 | if err != nil { 28 | return nil, errors.Wrap(err, "failed to generate knowledge proof") 29 | } 30 | 31 | pk := g.Point().Mul(b, nil) 32 | msg := domainSep 33 | sig := rv 34 | if err2 := schnorr.Verify(g, pk, msg, sig); err2 != nil { 35 | 36 | panic(errors.Wrapf(err2, "created unverifiable signature")) 37 | } 38 | return rv, err 39 | } 40 | 41 | func (p dLKnowledgeProof) verify( 42 | group anon.Suite, domainSep []byte, blindingPK, signingPK kyber.Point, 43 | ) error { 44 | g, err := change_base_group.NewChangeBaseGroup(group, blindingPK) 45 | if err != nil { 46 | return errors.Wrapf(err, "could not create cyclic group from given generator") 47 | } 48 | pk, err := g.Lift(signingPK) 49 | if err != nil { 50 | return errors.Wrapf(err, "could not create point pair for DL proof") 51 | } 52 | 53 | sig := []byte(p) 54 | msg := domainSep 55 | return errors.Wrap(schnorr.Verify(g, pk, msg, sig), 56 | "could not verify share proof") 57 | } 58 | 59 | func (p dLKnowledgeProof) equal(p2 dLKnowledgeProof) bool { 60 | return bytes.Equal(p, p2) 61 | } 62 | -------------------------------------------------------------------------------- /gethwrappers/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/smartcontractkit/vrf/gethwrappers 2 | 3 | go 1.22.3 4 | 5 | require ( 6 | github.com/ethereum/go-ethereum v1.13.8 7 | github.com/smartcontractkit/chainlink-vrf v0.0.0-20240229152020-3390c480411a 8 | ) 9 | 10 | require ( 11 | github.com/Microsoft/go-winio v0.6.1 // indirect 12 | github.com/StackExchange/wmi v1.2.1 // indirect 13 | github.com/bits-and-blooms/bitset v1.10.0 // indirect 14 | github.com/btcsuite/btcd v0.22.0-beta // indirect 15 | github.com/btcsuite/btcd/btcec/v2 v2.2.0 // indirect 16 | github.com/consensys/bavard v0.1.13 // indirect 17 | github.com/consensys/gnark-crypto v0.12.1 // indirect 18 | github.com/crate-crypto/go-kzg-4844 v1.0.0 // indirect 19 | github.com/deckarep/golang-set/v2 v2.1.0 // indirect 20 | github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect 21 | github.com/ethereum/c-kzg-4844 v1.0.0 // indirect 22 | github.com/fsnotify/fsnotify v1.6.0 // indirect 23 | github.com/go-ole/go-ole v1.3.0 // indirect 24 | github.com/google/uuid v1.3.0 // indirect 25 | github.com/gorilla/websocket v1.4.2 // indirect 26 | github.com/holiman/uint256 v1.2.4 // indirect 27 | github.com/mmcloughlin/addchain v0.4.0 // indirect 28 | github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible // indirect 29 | github.com/supranational/blst v0.3.11 // indirect 30 | github.com/tklauser/go-sysconf v0.3.12 // indirect 31 | github.com/tklauser/numcpus v0.6.1 // indirect 32 | golang.org/x/crypto v0.22.0 // indirect 33 | golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa // indirect 34 | golang.org/x/mod v0.17.0 // indirect 35 | golang.org/x/sync v0.7.0 // indirect 36 | golang.org/x/sys v0.19.0 // indirect 37 | golang.org/x/tools v0.20.0 // indirect 38 | rsc.io/tmplfunc v0.0.3 // indirect 39 | ) 40 | 41 | // replicating the replace directive on cosmos SDK 42 | replace github.com/gogo/protobuf => github.com/regen-network/protobuf v1.3.3-alpha.regen.1 43 | -------------------------------------------------------------------------------- /ocr2vrf/dkgvrfargs.go: -------------------------------------------------------------------------------- 1 | package ocr2vrf 2 | 3 | import ( 4 | "github.com/smartcontractkit/libocr/commontypes" 5 | "github.com/smartcontractkit/libocr/offchainreporting2plus/types" 6 | 7 | dkg_contract "github.com/smartcontractkit/chainlink-vrf/internal/dkg/contract" 8 | vrf_types "github.com/smartcontractkit/chainlink-vrf/types" 9 | ) 10 | 11 | type DKGVRFArgs struct { 12 | DKGLogger commontypes.Logger 13 | VRFLogger commontypes.Logger 14 | 15 | BinaryNetworkEndpointFactory types.BinaryNetworkEndpointFactory 16 | 17 | V2Bootstrappers []commontypes.BootstrapperLocator 18 | 19 | OffchainKeyring types.OffchainKeyring 20 | 21 | OnchainKeyring types.OnchainKeyring 22 | 23 | DKGOffchainConfigDigester types.OffchainConfigDigester 24 | 25 | VRFOffchainConfigDigester types.OffchainConfigDigester 26 | 27 | VRFContractConfigTracker types.ContractConfigTracker 28 | 29 | VRFContractTransmitter types.ContractTransmitter 30 | 31 | VRFDatabase types.Database 32 | 33 | VRFLocalConfig types.LocalConfig 34 | 35 | VRFMonitoringEndpoint commontypes.MonitoringEndpoint 36 | 37 | DKGContractConfigTracker types.ContractConfigTracker 38 | 39 | DKGContract dkg_contract.OnchainContract 40 | 41 | DKGContractTransmitter types.ContractTransmitter 42 | 43 | DKGDatabase types.Database 44 | 45 | DKGLocalConfig types.LocalConfig 46 | 47 | DKGMonitoringEndpoint commontypes.MonitoringEndpoint 48 | 49 | DKGSharePersistence vrf_types.DKGSharePersistence 50 | 51 | Serializer vrf_types.ReportSerializer 52 | JuelsPerFeeCoin vrf_types.JuelsPerFeeCoin 53 | ReasonableGasPrice vrf_types.ReasonableGasPrice 54 | Coordinator vrf_types.CoordinatorInterface 55 | 56 | ConfirmationDelays []uint32 57 | 58 | Esk dkg_contract.EncryptionSecretKey 59 | Ssk dkg_contract.SigningSecretKey 60 | KeyID dkg_contract.KeyID 61 | 62 | DKGReportingPluginFactoryDecorator func(factory types.ReportingPluginFactory) types.ReportingPluginFactory 63 | VRFReportingPluginFactoryDecorator func(factory types.ReportingPluginFactory) types.ReportingPluginFactory 64 | } 65 | -------------------------------------------------------------------------------- /internal/crypto/ciphertext/bit_pair_encrypt.go: -------------------------------------------------------------------------------- 1 | package ciphertext 2 | 3 | import ( 4 | "github.com/pkg/errors" 5 | 6 | "go.dedis.ch/kyber/v3" 7 | "go.dedis.ch/kyber/v3/sign/anon" 8 | ) 9 | 10 | func encrypt( 11 | domainSep []byte, group anon.Suite, plaintext []byte, pk kyber.Point, 12 | ) (cipherText []*elGamalBitPair, totalBlindingSecret kyber.Scalar, err error) { 13 | if len(plaintext) > plaintextMaxSizeBytes { 14 | return nil, nil, errors.Errorf("plaintext longer than 256 bits") 15 | } 16 | totalBlindingSecret = group.Scalar().Zero() 17 | one := group.Scalar().One() 18 | two := group.Scalar().Add(one, one) 19 | four := two.Add(two, two) 20 | fourPower := one 21 | 22 | for byteIdx := len(plaintext) - 1; byteIdx >= 0; byteIdx-- { 23 | cbyte := plaintext[byteIdx] 24 | for pairIdx := 0; pairIdx < 4; pairIdx++ { 25 | 26 | bitPair := int(cbyte & 0b11) 27 | cbyte >>= 2 28 | 29 | pairCipherText, blindingSecret, err := newElGamalBitPair( 30 | group, 31 | encryptDomainSep(domainSep, uint8(len(cipherText))), 32 | bitPair, pk, 33 | ) 34 | if err != nil { 35 | 36 | return nil, nil, errors.Wrapf(err, "while encrypting secret share") 37 | } 38 | totalBlindingSecret.Add( 39 | totalBlindingSecret.Clone(), blindingSecret.Mul(fourPower, blindingSecret.Clone()), 40 | ) 41 | cipherText = append(cipherText, pairCipherText) 42 | fourPower = fourPower.Mul(fourPower, four) 43 | } 44 | } 45 | return cipherText, totalBlindingSecret, nil 46 | } 47 | 48 | func encryptDomainSep(domainSep []byte, pairIdx uint8) []byte { 49 | return append(domainSep, pairIdx) 50 | } 51 | 52 | func combinedCipherTexts(cipherText []*elGamalBitPair, s anon.Suite) kyber.Point { 53 | rv := cipherText[0].cipherTextTerm.Clone().Null() 54 | fourPower := s.Scalar().One() 55 | two := s.Scalar().Add(fourPower, fourPower) 56 | four := two.Add(two, two) 57 | for _, bitpair := range cipherText { 58 | rv = rv.Add(rv, rv.Clone().Mul(fourPower, bitpair.cipherTextTerm)) 59 | fourPower = fourPower.Mul(fourPower, four) 60 | } 61 | return rv 62 | } 63 | 64 | const plaintextMaxSizeBytes = 32 65 | -------------------------------------------------------------------------------- /internal/crypto/player_idx/public_utils.go: -------------------------------------------------------------------------------- 1 | package player_idx 2 | 3 | import ( 4 | "fmt" 5 | "math" 6 | 7 | "github.com/pkg/errors" 8 | "github.com/smartcontractkit/libocr/commontypes" 9 | ) 10 | 11 | var MarshalLen = int(math.Round(math.Log(float64(MaxPlayer))/math.Log(2))) / 8 12 | 13 | func (pi *PlayerIdx) Marshal() []byte { 14 | return RawMarshal(pi.idx) 15 | } 16 | 17 | func RawMarshal(toMarshal Int) []byte { 18 | 19 | tm := uint64(toMarshal) 20 | rv := make([]byte, MarshalLen) 21 | for i := 0; tm != 0; i++ { 22 | rv[i] = uint8(tm & 0xFF) 23 | tm >>= 8 24 | } 25 | return rv 26 | } 27 | 28 | func Unmarshal(d []byte) (*PlayerIdx, []byte, error) { 29 | i, rem, err := RawUnmarshal(d) 30 | if err != nil { 31 | return nil, nil, err 32 | } 33 | if i == 0 { 34 | return nil, nil, errors.Errorf("player index must not be zero") 35 | } 36 | return &PlayerIdx{i}, rem, nil 37 | } 38 | 39 | func RawUnmarshal(d []byte) (Int, []byte, error) { 40 | if len(d) < MarshalLen { 41 | errMsg := "wrong length for marshalled player idx: expected %d, got %d" 42 | return 0, nil, errors.Errorf(errMsg, MarshalLen, len(d)) 43 | } 44 | i := int64(0) 45 | for j := 0; j < MarshalLen; j++ { 46 | i <<= 8 47 | i += int64(d[j]) 48 | } 49 | return Int(i), d[MarshalLen:], nil 50 | } 51 | 52 | func (pi PlayerIdx) Equal(pi2 *PlayerIdx) bool { 53 | return pi.idx == pi2.idx 54 | } 55 | 56 | func (pi PlayerIdx) NonZero() error { 57 | if pi.idx == 0 { 58 | return errors.Errorf("player index cannot be zero") 59 | } 60 | return nil 61 | } 62 | 63 | func (pi *PlayerIdx) Check() (*PlayerIdx, error) { 64 | if pi == nil { 65 | return nil, errors.Errorf("player index cannot be nil") 66 | } 67 | if err := pi.NonZero(); err != nil { 68 | return nil, err 69 | } 70 | return pi, nil 71 | } 72 | 73 | func (pi PlayerIdx) AtMost(n Int) bool { 74 | return pi.NonZero() == nil && pi.idx <= n 75 | } 76 | 77 | func (pi PlayerIdx) OracleID() commontypes.OracleID { 78 | return commontypes.OracleID(pi.idx - 1) 79 | } 80 | 81 | func (pi PlayerIdx) String() string { 82 | return fmt.Sprintf("player %d", pi.idx) 83 | } 84 | -------------------------------------------------------------------------------- /internal/dkg/contract/key_data.go: -------------------------------------------------------------------------------- 1 | package contract 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | "github.com/ethereum/go-ethereum/accounts/abi" 8 | 9 | "github.com/pkg/errors" 10 | 11 | dkg_hash "github.com/smartcontractkit/chainlink-vrf/types/hash" 12 | 13 | "go.dedis.ch/kyber/v3" 14 | ) 15 | 16 | type KeyData struct { 17 | PublicKey kyber.Point 18 | Hashes []dkg_hash.Hash 19 | } 20 | 21 | func (kd *KeyData) MarshalBinary(keyID [32]byte) (rv []byte, err error) { 22 | km, err := kd.PublicKey.MarshalBinary() 23 | if err != nil { 24 | return nil, errors.Wrap(err, "could not serialize key for onchain report") 25 | } 26 | return keyDataEncoding.Pack(keyID, km, kd.Hashes) 27 | } 28 | 29 | func (kd *KeyData) String() string { 30 | km, err := kd.PublicKey.MarshalBinary() 31 | if err != nil { 32 | panic(errors.Errorf("could not extract binary data for key %v", kd.PublicKey)) 33 | } 34 | hashStrings := make([]string, len(kd.Hashes)) 35 | for i, h := range kd.Hashes { 36 | hashStrings[i] = h.String() 37 | } 38 | return fmt.Sprintf("{Key: 0x%x, Hashes: [%s]}", km, strings.Join(hashStrings, ", ")) 39 | } 40 | 41 | var keyDataEncoding = getKeyDataEncoding() 42 | 43 | func getKeyDataEncoding() abi.Arguments { 44 | mustNewType := func(t string) abi.Type { 45 | result, err := abi.NewType(t, "", []abi.ArgumentMarshaling{}) 46 | if err != nil { 47 | panic(fmt.Sprintf("Unexpected error during abi.NewType: %s", err)) 48 | } 49 | return result 50 | } 51 | return []abi.Argument{ 52 | {Name: "keyID", Type: mustNewType("bytes32")}, 53 | {Name: "publicKey", Type: mustNewType("bytes")}, 54 | {Name: "hashes", Type: mustNewType("bytes32[]")}, 55 | } 56 | } 57 | 58 | func MakeKeyDataFromOnchainKeyData( 59 | kd OnchainKeyData, kg kyber.Group, 60 | ) (KeyData, error) { 61 | pk := kg.Point() 62 | if err := pk.UnmarshalBinary(kd.PublicKey); err != nil { 63 | return KeyData{}, errors.Wrap(err, "could not unmarshal onchain key") 64 | } 65 | var hashes []dkg_hash.Hash 66 | for _, h := range kd.Hashes { 67 | hashes = append(hashes, h) 68 | } 69 | return KeyData{pk, hashes}, nil 70 | } 71 | -------------------------------------------------------------------------------- /internal/pvss/01_share_set_public_interface.go: -------------------------------------------------------------------------------- 1 | package pvss 2 | 3 | import ( 4 | "github.com/smartcontractkit/libocr/offchainreporting2plus/types" 5 | 6 | "github.com/smartcontractkit/chainlink-vrf/internal/crypto/player_idx" 7 | "github.com/smartcontractkit/chainlink-vrf/internal/crypto/point_translation" 8 | 9 | "go.dedis.ch/kyber/v3" 10 | kshare "go.dedis.ch/kyber/v3/share" 11 | "go.dedis.ch/kyber/v3/sign/anon" 12 | ) 13 | 14 | func NewShareSet(domainSep types.ConfigDigest, threshold player_idx.Int, 15 | dealer *player_idx.PlayerIdx, group anon.Suite, 16 | translation point_translation.PubKeyTranslation, pks []kyber.Point, 17 | ) (*ShareSet, error) { 18 | return newShareSet(domainSep, threshold, dealer, group, translation, pks) 19 | } 20 | 21 | func (s *ShareSet) Marshal() (m []byte, err error) { 22 | return s.marshal() 23 | } 24 | 25 | func UnmarshalShareSet( 26 | g anon.Suite, translationGroup kyber.Group, data []byte, 27 | translation point_translation.PubKeyTranslation, domainSep types.ConfigDigest, 28 | pks []kyber.Point, 29 | ) (ss *ShareSet, rem []byte, err error) { 30 | return unmarshalShareSet(g, translationGroup, data, translation, domainSep, pks) 31 | } 32 | 33 | func (s *ShareSet) Verify(group anon.Suite, domainSep types.ConfigDigest, pks []kyber.Point) error { 34 | return s.verify(group, domainSep, pks) 35 | } 36 | 37 | func (s *ShareSet) Decrypt( 38 | playerIdx player_idx.PlayerIdx, 39 | sk kyber.Scalar, 40 | keyGroup anon.Suite, 41 | domainSep types.ConfigDigest, 42 | ) (kshare.PriShare, error) { 43 | sharePublicCommitment := playerIdx.EvalPoint(s.coeffCommitments) 44 | return s.decrypt( 45 | playerIdx, sk, keyGroup, domainSep, sharePublicCommitment, 46 | ) 47 | } 48 | 49 | func (s *ShareSet) Dealer() (*player_idx.PlayerIdx, error) { 50 | return s.dealer.Check() 51 | } 52 | 53 | func UnmarshalDealer(data []byte) (*player_idx.PlayerIdx, error) { 54 | rv, _, err := unmarshalDealer(data) 55 | return rv, err 56 | } 57 | 58 | func (s *ShareSet) PublicKey() kyber.Point { 59 | return s.pvssKey 60 | } 61 | 62 | func (s *ShareSet) PublicShares() []kyber.Point { 63 | return s.publicShares() 64 | } 65 | -------------------------------------------------------------------------------- /altbn_128/gt_point.go: -------------------------------------------------------------------------------- 1 | package altbn_128 2 | 3 | import ( 4 | "bytes" 5 | "crypto/cipher" 6 | "fmt" 7 | "io" 8 | 9 | bn256 "github.com/ethereum/go-ethereum/crypto/bn256/cloudflare" 10 | 11 | "go.dedis.ch/kyber/v3" 12 | ) 13 | 14 | type gTPoint struct{ *bn256.GT } 15 | 16 | var _ kyber.Point = (*gTPoint)(nil) 17 | 18 | func newGTPoint() *gTPoint { return &gTPoint{new(bn256.GT)} } 19 | 20 | func (p *gTPoint) Equal(s2 kyber.Point) bool { 21 | gtS2, ok := s2.(*gTPoint) 22 | return ok && bytes.Equal(p.Marshal(), gtS2.Marshal()) 23 | } 24 | 25 | func (p *gTPoint) String() string { return fmt.Sprintf("&gTPoint{%s}", p.GT.String()) } 26 | 27 | func (p *gTPoint) MarshalBinary() (data []byte, err error) { panic("not implemented") } 28 | func (p *gTPoint) UnmarshalBinary(data []byte) error { panic("not implemented") } 29 | func (p *gTPoint) MarshalSize() int { panic("not implemented") } 30 | func (p *gTPoint) MarshalTo(w io.Writer) (int, error) { panic("not implemented") } 31 | func (p *gTPoint) UnmarshalFrom(r io.Reader) (int, error) { panic("not implemented") } 32 | func (p *gTPoint) Null() kyber.Point { panic("not implemented") } 33 | func (p *gTPoint) Base() kyber.Point { panic("not implemented") } 34 | func (p *gTPoint) Pick(rand cipher.Stream) kyber.Point { panic("not implemented") } 35 | func (p *gTPoint) Set(p2 kyber.Point) kyber.Point { panic("not implemented") } 36 | func (p *gTPoint) Clone() kyber.Point { panic("not implemented") } 37 | func (p *gTPoint) EmbedLen() int { panic("not implemented") } 38 | func (p *gTPoint) Embed(data []byte, r cipher.Stream) kyber.Point { panic("not implemented") } 39 | func (p *gTPoint) Data() ([]byte, error) { panic("not implemented") } 40 | func (p *gTPoint) Add(a kyber.Point, b kyber.Point) kyber.Point { panic("not implemented") } 41 | func (p *gTPoint) Sub(a kyber.Point, b kyber.Point) kyber.Point { panic("not implemented") } 42 | func (p *gTPoint) Neg(a kyber.Point) kyber.Point { panic("not implemented") } 43 | func (p *gTPoint) Mul(s kyber.Scalar, p2 kyber.Point) kyber.Point { panic("not implemented") } 44 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/smartcontractkit/chainlink-vrf 2 | 3 | go 1.20 4 | 5 | require ( 6 | github.com/btcsuite/btcd v0.22.0-beta 7 | github.com/ethereum/go-ethereum v1.10.26 8 | github.com/pkg/errors v0.9.1 9 | github.com/sirupsen/logrus v1.9.3 10 | github.com/smartcontractkit/libocr v0.0.0-20230802221916-2271752fa829 11 | go.dedis.ch/fixbuf v1.0.3 12 | go.dedis.ch/kyber/v3 v3.0.13 13 | go.uber.org/multierr v1.11.0 14 | golang.org/x/crypto v0.9.0 15 | google.golang.org/protobuf v1.30.0 16 | ) 17 | 18 | require ( 19 | github.com/StackExchange/wmi v1.2.1 // indirect 20 | github.com/VictoriaMetrics/fastcache v1.6.0 // indirect 21 | github.com/btcsuite/btcd/btcec/v2 v2.2.0 // indirect 22 | github.com/cespare/xxhash/v2 v2.2.0 // indirect 23 | github.com/deckarep/golang-set v1.8.0 // indirect 24 | github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect 25 | github.com/go-ole/go-ole v1.2.5 // indirect 26 | github.com/go-stack/stack v1.8.1 // indirect 27 | github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb // indirect 28 | github.com/google/uuid v1.3.0 // indirect 29 | github.com/gorilla/websocket v1.4.2 // indirect 30 | github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d // indirect 31 | github.com/holiman/bloomfilter/v2 v2.0.3 // indirect 32 | github.com/holiman/uint256 v1.2.2-0.20230321075855-87b91420868c // indirect 33 | github.com/huin/goupnp v1.0.3 // indirect 34 | github.com/jackpal/go-nat-pmp v1.0.2 // indirect 35 | github.com/mattn/go-runewidth v0.0.12 // indirect 36 | github.com/mr-tron/base58 v1.2.0 // indirect 37 | github.com/olekukonko/tablewriter v0.0.5 // indirect 38 | github.com/onsi/ginkgo v1.16.4 // indirect 39 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect 40 | github.com/prometheus/tsdb v0.10.0 // indirect 41 | github.com/rivo/uniseg v0.2.0 // indirect 42 | github.com/rjeczalik/notify v0.9.2 // indirect 43 | github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible // indirect 44 | github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect 45 | github.com/tklauser/go-sysconf v0.3.7 // indirect 46 | github.com/tklauser/numcpus v0.2.3 // indirect 47 | golang.org/x/sync v0.1.0 // indirect 48 | golang.org/x/sys v0.8.0 // indirect 49 | gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect 50 | ) 51 | 52 | exclude github.com/btcsuite/btcd/chaincfg/chainhash v1.0.0 53 | -------------------------------------------------------------------------------- /internal/util/backend.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "errors" 7 | "fmt" 8 | "time" 9 | 10 | "github.com/ethereum/go-ethereum" 11 | "github.com/ethereum/go-ethereum/accounts/abi/bind" 12 | "github.com/ethereum/go-ethereum/common" 13 | "github.com/ethereum/go-ethereum/core/types" 14 | ) 15 | 16 | type Client interface { 17 | bind.ContractBackend 18 | bind.DeployBackend 19 | } 20 | 21 | func MaybeAddStubCommitMethod(b Client) CommittingClient { 22 | client, ok := b.(CommittingClient) 23 | if !ok { 24 | client = noncommittalClient{b} 25 | } 26 | return client 27 | } 28 | 29 | type committer interface { 30 | Commit() common.Hash 31 | } 32 | 33 | type CommittingClient interface { 34 | Client 35 | committer 36 | } 37 | 38 | type noncommittalClient struct{ Client } 39 | 40 | var _ CommittingClient = noncommittalClient{} 41 | 42 | func (n noncommittalClient) Commit() common.Hash { return common.Hash{} } 43 | 44 | func CheckStatus( 45 | ctx context.Context, 46 | tx *types.Transaction, 47 | client Client, 48 | ) (err error) { 49 | timeout := time.After(5 * time.Second) 50 | var buf bytes.Buffer 51 | var receipt *types.Receipt 52 | errMsg := "could not get byte encoding of tx with hash 0x%x while " + 53 | "reporting on %s while checking its status, due to error \"%w\"" 54 | for receipt == nil { 55 | receipt, err = client.TransactionReceipt(ctx, tx.Hash()) 56 | if err != nil && !errors.Is(err, ethereum.NotFound) { 57 | berr := tx.EncodeRLP(&buf) 58 | if berr != nil { 59 | return WrapErrorf(berr, errMsg, "error", tx.Hash(), err) 60 | } 61 | b := buf.Bytes() 62 | return WrapErrorf(err, "could not retrieve receipt for tx 0x%x", b) 63 | } 64 | select { 65 | case <-time.After(10 * time.Millisecond): 66 | continue 67 | case <-timeout: 68 | berr := tx.EncodeRLP(&buf) 69 | if berr != nil { 70 | return WrapErrorf(berr, errMsg, "timeout", tx.Hash(), err) 71 | } 72 | return fmt.Errorf("timeout while checking status on tx 0x%x", buf.Bytes()) 73 | case <-ctx.Done(): 74 | return fmt.Errorf("context expired while checking tx status") 75 | } 76 | } 77 | if receipt.Status != 1 { 78 | berr := tx.EncodeRLP(&buf) 79 | if berr != nil { 80 | return WrapErrorf(berr, errMsg, "error", tx.Hash(), err) 81 | } 82 | h := tx.Hash() 83 | b := buf.Bytes() 84 | return fmt.Errorf("transaction failed: hash: 0x%x; contents: 0x%x", h, b) 85 | } 86 | return nil 87 | } 88 | -------------------------------------------------------------------------------- /internal/pvss/pub_poly_marshal.go: -------------------------------------------------------------------------------- 1 | package pvss 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | 7 | "github.com/pkg/errors" 8 | "go.dedis.ch/kyber/v3" 9 | kshare "go.dedis.ch/kyber/v3/share" 10 | 11 | "github.com/smartcontractkit/chainlink-vrf/internal/crypto/player_idx" 12 | ) 13 | 14 | type pubPoly struct{ *kshare.PubPoly } 15 | 16 | func (p *pubPoly) marshal() ([]byte, error) { 17 | _, commits := p.Info() 18 | if len(commits) > int(player_idx.MaxPlayer) { 19 | return nil, errors.Errorf("too many coefficients to marshal") 20 | } 21 | rv := make([][]byte, 1+len(commits)) 22 | cursor := 0 23 | 24 | rv[cursor] = player_idx.RawMarshal(player_idx.Int(len(commits))) 25 | cursor++ 26 | 27 | pex, err := commits[0].MarshalBinary() 28 | if err != nil { 29 | return nil, errors.Wrap(err, "could not determine marshalled-point length") 30 | } 31 | pointLen := len(pex) 32 | 33 | for _, c := range commits { 34 | pb, err := c.MarshalBinary() 35 | if err != nil { 36 | return nil, errors.Wrap(err, "could not marshal point in coefficient commitments") 37 | } 38 | if len(pb) != pointLen { 39 | return nil, errors.Errorf("length of marshalled points varies") 40 | } 41 | rv[cursor] = pb 42 | cursor++ 43 | } 44 | if cursor != len(rv) { 45 | panic(errors.Errorf("marshalled coefficient commitments fields out of registration")) 46 | } 47 | return bytes.Join(rv, nil), nil 48 | } 49 | 50 | func unmarshalPubPoly(g kyber.Group, data []byte) (commitments *pubPoly, rem []byte, err error) { 51 | 52 | numPoints, data, err := player_idx.RawUnmarshal(data) 53 | if err != nil { 54 | return nil, nil, errors.Wrap(err, "could not parse number of coefficient commitments") 55 | } 56 | 57 | bytesRequired := int(numPoints) * g.PointLen() 58 | if bytesRequired > len(data) { 59 | return nil, nil, errors.Errorf( 60 | "data is too short to encode %d points (need %d bytes, got %d)", 61 | numPoints, bytesRequired, len(data)) 62 | } 63 | commits := make([]kyber.Point, numPoints) 64 | for i := 0; i < int(numPoints); i++ { 65 | commits[i] = g.Point() 66 | if err := commits[i].UnmarshalBinary(data[:g.PointLen()]); err != nil { 67 | return nil, nil, errors.Wrap(err, "could not unmarshal coefficient commitment") 68 | } 69 | data = data[g.PointLen():] 70 | } 71 | return &pubPoly{kshare.NewPubPoly(g, g.Point().Base(), commits)}, data, nil 72 | } 73 | 74 | func (p *pubPoly) String() string { 75 | b, commits := p.PubPoly.Info() 76 | return fmt.Sprintf("&pubPoly{base: %s, commits: %s}", b, commits) 77 | } 78 | -------------------------------------------------------------------------------- /internal/dkg/00_public_interface.go: -------------------------------------------------------------------------------- 1 | package dkg 2 | 3 | import ( 4 | "crypto/rand" 5 | "sync" 6 | 7 | "github.com/pkg/errors" 8 | 9 | "github.com/smartcontractkit/libocr/commontypes" 10 | "github.com/smartcontractkit/libocr/offchainreporting2plus/types" 11 | 12 | "go.dedis.ch/kyber/v3/sign/anon" 13 | 14 | "github.com/smartcontractkit/chainlink-vrf/internal/crypto/point_translation" 15 | "github.com/smartcontractkit/chainlink-vrf/internal/dkg/contract" 16 | dkg_types "github.com/smartcontractkit/chainlink-vrf/types" 17 | ) 18 | 19 | func NewReportingPluginFactory( 20 | esk contract.EncryptionSecretKey, 21 | ssk contract.SigningSecretKey, 22 | keyID contract.KeyID, 23 | contract contract.OnchainContract, 24 | logger commontypes.Logger, 25 | keyConsumer KeyConsumer, 26 | db dkg_types.DKGSharePersistence, 27 | ) types.ReportingPluginFactory { 28 | dkgInProgress, testmode, xxxDKGTestingOnly := false, false, (*dkg)(nil) 29 | return &dkgReportingPluginFactory{ 30 | &localArgs{ 31 | esk, 32 | ssk, 33 | keyID, 34 | contract, 35 | logger, 36 | keyConsumer, 37 | rand.Reader, 38 | db, 39 | }, 40 | sync.RWMutex{}, 41 | dkgInProgress, 42 | testmode, 43 | xxxDKGTestingOnly, 44 | } 45 | } 46 | 47 | func OffchainConfig( 48 | epks contract.EncryptionPublicKeys, 49 | spks contract.SigningPublicKeys, 50 | encryptionGroup anon.Suite, 51 | translator point_translation.PubKeyTranslation, 52 | ) ([]byte, error) { 53 | rc := &offchainConfig{epks, spks, encryptionGroup, translator} 54 | return rc.MarshalBinary() 55 | } 56 | 57 | func OnchainConfig(keyID contract.KeyID) ([]byte, error) { 58 | return (&onchainConfig{keyID}).Marshal(), nil 59 | } 60 | 61 | func NewPluginConfig( 62 | epks contract.EncryptionPublicKeys, 63 | spks contract.SigningPublicKeys, 64 | encryptionGroup anon.Suite, 65 | translator point_translation.PubKeyTranslation, 66 | keyID contract.KeyID, 67 | ) *PluginConfig { 68 | return &PluginConfig{ 69 | offchainConfig{epks, spks, encryptionGroup, translator}, 70 | onchainConfig{keyID}, 71 | } 72 | } 73 | 74 | func SanityCheckConfigs(p *PluginConfig, rpf types.ReportingPluginFactory) error { 75 | d, ok := rpf.(*dkgReportingPluginFactory) 76 | if !ok { 77 | return errors.Errorf("plugin factory is not for DKG") 78 | } 79 | args, err := p.NewDKGArgs([32]byte{}, d.l, 0, 5, 3) 80 | if err != nil { 81 | return errors.Wrap(err, "could not construct DKG args") 82 | } 83 | return args.SanityCheckArgs() 84 | } 85 | 86 | func UnmarshalPluginConfig(offchainBinaryConfig, onchainBinaryConfig []byte) (*PluginConfig, error) { 87 | return unmarshalPluginConfig(offchainBinaryConfig, onchainBinaryConfig) 88 | } 89 | -------------------------------------------------------------------------------- /internal/vrf/sigrequest.go: -------------------------------------------------------------------------------- 1 | package vrf 2 | 3 | import ( 4 | "sync" 5 | "time" 6 | 7 | "github.com/ethereum/go-ethereum/common" 8 | 9 | "github.com/pkg/errors" 10 | 11 | "github.com/smartcontractkit/libocr/commontypes" 12 | "github.com/smartcontractkit/libocr/offchainreporting2plus/types" 13 | 14 | "go.dedis.ch/kyber/v3" 15 | "go.dedis.ch/kyber/v3/pairing" 16 | 17 | "github.com/smartcontractkit/chainlink-vrf/internal/crypto/player_idx" 18 | dkg_contract "github.com/smartcontractkit/chainlink-vrf/internal/dkg/contract" 19 | vrf_types "github.com/smartcontractkit/chainlink-vrf/types" 20 | ) 21 | 22 | var _ types.ReportingPlugin = (*sigRequest)(nil) 23 | 24 | type sigRequest struct { 25 | keyID dkg_contract.KeyID 26 | keyProvider KeyProvider 27 | n player_idx.Int 28 | 29 | t player_idx.Int 30 | configDigest common.Hash 31 | i player_idx.PlayerIdx 32 | pairing pairing.Suite 33 | serializer vrf_types.ReportSerializer 34 | blockProofs map[vrf_types.Block]kyber.Point 35 | proofLock sync.RWMutex 36 | 37 | logger commontypes.Logger 38 | 39 | retransmissionDelay time.Duration 40 | juelsPerFeeCoin vrf_types.JuelsPerFeeCoin 41 | reasonableGasPrice vrf_types.ReasonableGasPrice 42 | confirmationDelays map[uint32]struct{} 43 | 44 | period uint16 45 | coordinator vrf_types.CoordinatorInterface 46 | reports map[types.ReportTimestamp]report 47 | reportsLock sync.RWMutex 48 | } 49 | 50 | func newSigRequest( 51 | keyID dkg_contract.KeyID, 52 | keyProvider KeyProvider, 53 | n player_idx.Int, 54 | t player_idx.Int, 55 | configDigest common.Hash, 56 | i player_idx.PlayerIdx, 57 | pairing pairing.Suite, 58 | serializer vrf_types.ReportSerializer, 59 | retransmissionDelay time.Duration, 60 | logger commontypes.Logger, 61 | juelsPerFeeCoin vrf_types.JuelsPerFeeCoin, 62 | reasonableGasPrice vrf_types.ReasonableGasPrice, 63 | coordinator vrf_types.CoordinatorInterface, 64 | confirmationDelays map[uint32]struct{}, 65 | period uint16, 66 | ) (*sigRequest, error) { 67 | if n <= t { 68 | return nil, errors.Errorf( 69 | "committee size must be larger than the fault-tolerance threshold", 70 | ) 71 | } 72 | return &sigRequest{ 73 | keyID, 74 | keyProvider, 75 | n, 76 | t, 77 | configDigest, 78 | i, 79 | pairing, 80 | serializer, 81 | map[vrf_types.Block]kyber.Point{}, 82 | sync.RWMutex{}, 83 | logger, 84 | retransmissionDelay, 85 | juelsPerFeeCoin, 86 | reasonableGasPrice, 87 | confirmationDelays, 88 | period, 89 | coordinator, 90 | make(map[types.ReportTimestamp]report), 91 | sync.RWMutex{}, 92 | }, nil 93 | } 94 | 95 | type report struct { 96 | r vrf_types.AbstractReport 97 | s []byte 98 | } 99 | -------------------------------------------------------------------------------- /dkg/dkg.go: -------------------------------------------------------------------------------- 1 | package dkg 2 | 3 | import ( 4 | "github.com/smartcontractkit/libocr/commontypes" 5 | "github.com/smartcontractkit/libocr/offchainreporting2plus/types" 6 | 7 | "go.dedis.ch/kyber/v3" 8 | "go.dedis.ch/kyber/v3/sign/anon" 9 | 10 | "github.com/smartcontractkit/chainlink-vrf/internal/crypto/point_translation" 11 | "github.com/smartcontractkit/chainlink-vrf/internal/dkg" 12 | "github.com/smartcontractkit/chainlink-vrf/internal/dkg/contract" 13 | dkg_types "github.com/smartcontractkit/chainlink-vrf/types" 14 | ) 15 | 16 | func NewReportingPluginFactory( 17 | esk EncryptionSecretKey, 18 | ssk SigningSecretKey, 19 | keyID KeyID, 20 | contract OnchainContract, 21 | logger commontypes.Logger, 22 | keyConsumer KeyConsumer, 23 | db dkg_types.DKGSharePersistence, 24 | ) types.ReportingPluginFactory { 25 | return dkg.NewReportingPluginFactory( 26 | esk, 27 | ssk, 28 | keyID, 29 | contract, 30 | logger, 31 | keyConsumer, 32 | db, 33 | ) 34 | } 35 | 36 | func NewOnchainContract( 37 | dkg DKG, keyGroup kyber.Group, 38 | ) contract.OnchainContract { 39 | return contract.OnchainContract{dkg, keyGroup} 40 | } 41 | 42 | func OffchainConfig( 43 | epks EncryptionPublicKeys, 44 | spks SigningPublicKeys, 45 | encryptionGroup anon.Suite, 46 | translator point_translation.PubKeyTranslation, 47 | ) ([]byte, error) { 48 | return dkg.OffchainConfig(epks, spks, encryptionGroup, translator) 49 | } 50 | 51 | func OnchainConfig(keyID KeyID) ([]byte, error) { 52 | return dkg.OnchainConfig(keyID) 53 | } 54 | 55 | func NewPluginConfig( 56 | epks EncryptionPublicKeys, 57 | spks SigningPublicKeys, 58 | encryptionGroup anon.Suite, 59 | translator point_translation.PubKeyTranslation, 60 | keyID KeyID, 61 | ) *PluginConfig { 62 | return dkg.NewPluginConfig(epks, spks, encryptionGroup, translator, keyID) 63 | } 64 | 65 | func SanityCheckConfigs( 66 | p *PluginConfig, 67 | rpf types.ReportingPluginFactory, 68 | ) error { 69 | return dkg.SanityCheckConfigs(p, rpf) 70 | } 71 | 72 | func UnmarshalPluginConfig( 73 | offchainBinaryConfig, onchainBinaryConfig []byte) (*PluginConfig, error) { 74 | return dkg.UnmarshalPluginConfig(offchainBinaryConfig, onchainBinaryConfig) 75 | } 76 | 77 | type ( 78 | EncryptionPublicKeys = contract.EncryptionPublicKeys 79 | EncryptionSecretKey = contract.EncryptionSecretKey 80 | SigningPublicKeys = contract.SigningPublicKeys 81 | SigningSecretKey = contract.SigningSecretKey 82 | PluginConfig = dkg.PluginConfig 83 | KeyConsumer = dkg.KeyConsumer 84 | KeyData = dkg.KeyData 85 | 86 | KeyID = contract.KeyID 87 | DKG = contract.DKG 88 | OnchainContract = contract.OnchainContract 89 | OnchainKeyData = contract.OnchainKeyData 90 | ) 91 | -------------------------------------------------------------------------------- /.github/workflows/push-main.yml: -------------------------------------------------------------------------------- 1 | name: push-master 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | env: 9 | PACKAGES: "./..." 10 | 11 | jobs: 12 | init: 13 | runs-on: ubuntu-latest 14 | outputs: 15 | matrix_packages: ${{ steps.set-matrix-packages.outputs.matrix_packages }} 16 | lint_args_packages: ${{ steps.set-matrix-packages.outputs.lint_args_packages }} 17 | steps: 18 | - name: Checkout code 19 | uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 20 | - name: Set matrix packages 21 | id: set-matrix-packages 22 | shell: bash 23 | env: 24 | PACKAGES: ${{ env.PACKAGES }} 25 | run: | 26 | matrix_packages=$(echo "${PACKAGES}" | jq -R 'split(",")' | tr -d "\n\t") 27 | echo "matrix_packages=${matrix_packages}" | tee -a "${GITHUB_OUTPUT}" 28 | - name: Set lint args packages 29 | id: set-lint-args-packages 30 | shell: bash 31 | env: 32 | PACKAGES: ${{ env.PACKAGES }} 33 | run: echo "lint_args_packages=$(echo "./$(echo $PACKAGES | sed 's/,/\/... .\//g;s/$/\/.../')")" | tee -a "${GITHUB_OUTPUT}" 34 | 35 | ci-lint: 36 | runs-on: ubuntu-latest 37 | permissions: 38 | id-token: write 39 | contents: read 40 | actions: read 41 | steps: 42 | - name: ci-lint 43 | uses: smartcontractkit/.github/actions/ci-lint-go@5b1046c28343660ecb84844c6fa95a66d1cdb52e # ci-lint-go@0.2.1 44 | with: 45 | # grafana inputs 46 | metrics-job-name: ci-lint 47 | gc-basic-auth: ${{ secrets.GRAFANA_INTERNAL_BASIC_AUTH }} 48 | gc-host: ${{ secrets.GRAFANA_INTERNAL_HOST }} 49 | gc-org-id: ${{ secrets.GRAFANA_INTERNAL_TENANT_ID }} 50 | # env inputs 51 | use-env-files: "true" 52 | env-files: ./tools/env/ci.env 53 | # go inputs 54 | use-go-cache: true 55 | go-cache-dep-path: "**/go.sum" 56 | go-version-file: go.mod 57 | golangci-lint-version: "v1.55.2" 58 | golangci-lint-args: --out-format colored-line-number,checkstyle:golangci-lint-report.xml 59 | 60 | ci-test: 61 | runs-on: ubuntu-latest 62 | permissions: 63 | id-token: write 64 | contents: read 65 | actions: read 66 | steps: 67 | - name: ci-test 68 | uses: smartcontractkit/.github/actions/ci-test-go@ci-test-go/0.3.5 69 | with: 70 | # docker inputs 71 | use-docker-compose: "true" 72 | docker-compose-workdir: ./tools/docker/setup-postgres 73 | # env inputs 74 | use-env-files: "true" 75 | env-files: ./tools/env/ci.env 76 | # go inputs 77 | use-go-cache: "true" 78 | go-cache-dep-path: "**/go.sum" 79 | go-version-file: go.mod 80 | go-test-cmd: make test-ci 81 | -------------------------------------------------------------------------------- /internal/vrf/00_public_interface.go: -------------------------------------------------------------------------------- 1 | package vrf 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "crypto/rand" 7 | "fmt" 8 | "math/big" 9 | "sort" 10 | 11 | "github.com/pkg/errors" 12 | 13 | proto "google.golang.org/protobuf/proto" 14 | 15 | "github.com/smartcontractkit/libocr/commontypes" 16 | "github.com/smartcontractkit/libocr/offchainreporting2plus/types" 17 | 18 | "github.com/smartcontractkit/chainlink-vrf/internal/dkg/contract" 19 | "github.com/smartcontractkit/chainlink-vrf/internal/vrf/protobuf" 20 | vrf_types "github.com/smartcontractkit/chainlink-vrf/types" 21 | ) 22 | 23 | func OffchainConfig(v *protobuf.CoordinatorConfig) []byte { 24 | b, err := proto.Marshal(v) 25 | if err != nil { 26 | panic(fmt.Sprintf("error marshalling offchain config %v", err)) 27 | } 28 | 29 | return b 30 | } 31 | 32 | func OnchainConfig(confDelays map[uint32]struct{}) []byte { 33 | var onchainConfig [256]byte 34 | if len(confDelays) != 8 { 35 | panic("There must be 8 confirmation delays") 36 | } 37 | index := 0 38 | delays := make([]int, 0, 8) 39 | for delay := range confDelays { 40 | delays = append(delays, int(delay)) 41 | } 42 | sort.Ints(delays) 43 | for _, delay := range delays { 44 | delayBigInt := big.NewInt(0).SetUint64(uint64(delay)) 45 | delayBinary := delayBigInt.Bytes() 46 | paddingBytes := bytes.Repeat([]byte{0}, 32-len(delayBinary)) 47 | delayBinaryFull := bytes.Join([][]byte{paddingBytes, delayBinary}, []byte{}) 48 | copy(onchainConfig[index*32:(index+1)*32], delayBinaryFull) 49 | index++ 50 | } 51 | return onchainConfig[:] 52 | } 53 | 54 | func NewVRFReportingPluginFactory( 55 | keyID contract.KeyID, 56 | keyProvider KeyProvider, 57 | coordinator vrf_types.CoordinatorInterface, 58 | serializer vrf_types.ReportSerializer, 59 | logger commontypes.Logger, 60 | juelsPerFeeCoin vrf_types.JuelsPerFeeCoin, 61 | reasonableGasPrice vrf_types.ReasonableGasPrice, 62 | ) (types.ReportingPluginFactory, error) { 63 | contractKeyID, err := coordinator.KeyID(context.Background()) 64 | if err != nil { 65 | return &vrfReportingPluginFactory{}, errors.Wrap(err, "could not get key ID") 66 | } 67 | if keyID != contractKeyID { 68 | return &vrfReportingPluginFactory{}, errors.New("provided keyID is different from coordinator keyID") 69 | } 70 | period, err := coordinator.BeaconPeriod(context.Background()) 71 | if err != nil { 72 | return &vrfReportingPluginFactory{}, errors.Wrap(err, "could not get beacon period") 73 | } 74 | return &vrfReportingPluginFactory{ 75 | &localArgs{ 76 | keyID: keyID, 77 | coordinator: coordinator, 78 | keyProvider: keyProvider, 79 | serializer: serializer, 80 | juelsPerFeeCoin: juelsPerFeeCoin, 81 | reasonableGasPrice: reasonableGasPrice, 82 | period: period, 83 | logger: logger, 84 | randomness: rand.Reader, 85 | }, 86 | }, nil 87 | } 88 | -------------------------------------------------------------------------------- /internal/pvss/share_implementation.go: -------------------------------------------------------------------------------- 1 | package pvss 2 | 3 | import ( 4 | "bytes" 5 | 6 | "github.com/pkg/errors" 7 | 8 | "github.com/smartcontractkit/chainlink-vrf/internal/crypto/ciphertext" 9 | "github.com/smartcontractkit/chainlink-vrf/internal/crypto/player_idx" 10 | 11 | "go.dedis.ch/kyber/v3" 12 | kshare "go.dedis.ch/kyber/v3/share" 13 | "go.dedis.ch/kyber/v3/sign/anon" 14 | ) 15 | 16 | type share struct { 17 | cipherText *ciphertext.CipherText 18 | 19 | encryptionKey kyber.Point 20 | 21 | subKeyTranslation kyber.Point 22 | 23 | shareSet *ShareSet 24 | } 25 | 26 | func newShare(domainSep []byte, group anon.Suite, 27 | secretPoly *kshare.PriPoly, shareSet *ShareSet, receiver *player_idx.PlayerIdx, 28 | pk kyber.Point, 29 | ) (rv *share, err error) { 30 | rv = &share{shareSet: shareSet, encryptionKey: pk} 31 | domainSep = rv.domainSep(domainSep, receiver) 32 | var secretShare kyber.Scalar 33 | rv.cipherText, secretShare, err = ciphertext.Encrypt( 34 | domainSep, group, secretPoly, receiver, pk, 35 | ) 36 | if err != nil { 37 | return nil, errors.Wrapf(err, "could not encrypt PVSS share") 38 | } 39 | rv.subKeyTranslation, err = shareSet.translation.TranslateKey(secretShare) 40 | if err != nil { 41 | return nil, errors.Wrapf(err, "could not translate pub key for secret share") 42 | } 43 | if err2 := rv.verify(group, domainSep, receiver); err2 != nil { 44 | panic("could not verify share just constructed") 45 | } 46 | return rv, err 47 | } 48 | 49 | func (s *share) decrypt( 50 | sk kyber.Scalar, 51 | keyGroup anon.Suite, 52 | domainSep []byte, 53 | sharePublicCommitment kyber.Point, 54 | receiver player_idx.PlayerIdx, 55 | ) (kshare.PriShare, error) { 56 | plaintextShare, err := s.cipherText.Decrypt( 57 | sk, keyGroup, s.domainSep(domainSep, &receiver), sharePublicCommitment, 58 | receiver, 59 | ) 60 | if err != nil { 61 | return kshare.PriShare{}, errors.Wrap(err, "could not decrypt share") 62 | } 63 | return receiver.PriShare(plaintextShare), nil 64 | } 65 | 66 | func (s *share) verify( 67 | group anon.Suite, domainSep []byte, receiver *player_idx.PlayerIdx, 68 | ) error { 69 | 70 | sharePublicCommitment := receiver.EvalPoint(s.shareSet.coeffCommitments) 71 | err := s.shareSet.translation.VerifyTranslation(sharePublicCommitment, s.subKeyTranslation) 72 | if err != nil { 73 | return errors.Wrapf(err, "bad translation of share public key") 74 | } 75 | return s.cipherText.Verify(group, domainSep, s.encryptionKey, sharePublicCommitment) 76 | } 77 | 78 | func (s *share) domainSep(domainSep []byte, receiver *player_idx.PlayerIdx) []byte { 79 | return bytes.Join([][]byte{domainSep, receiver.Marshal()}, nil) 80 | } 81 | 82 | func (s *share) equal(s2 *share) bool { 83 | return s.cipherText.Equal(s2.cipherText) && 84 | s.encryptionKey.Equal(s2.encryptionKey) && 85 | ((s.subKeyTranslation == nil && s2.subKeyTranslation == nil) || 86 | s.subKeyTranslation.Equal(s2.subKeyTranslation)) 87 | } 88 | -------------------------------------------------------------------------------- /internal/crypto/ciphertext/proofs.go: -------------------------------------------------------------------------------- 1 | package ciphertext 2 | 3 | import ( 4 | "github.com/pkg/errors" 5 | 6 | "github.com/smartcontractkit/chainlink-vrf/internal/crypto/product_group" 7 | 8 | "go.dedis.ch/kyber/v3" 9 | "go.dedis.ch/kyber/v3/sign/anon" 10 | ) 11 | 12 | type bitPairProof []byte 13 | 14 | func proveBitPair( 15 | suite anon.Suite, domainSep []byte, 16 | pk, blindingCommitment, cipherTextTerm kyber.Point, 17 | bitPair int, secret kyber.Scalar, 18 | ) (rv bitPairProof, err error) { 19 | if (bitPair < 0) || (bitPair > 3) { 20 | return rv, errors.Errorf("bitPair must be in {0,1,2,3}, got %d", bitPair) 21 | } 22 | 23 | pgroup, err := makeProductGroup(suite, pk) 24 | if err != nil { 25 | return rv, errors.Wrap(err, "while proving common discrete log") 26 | } 27 | 28 | possiblePKs, err := blindingPKs(suite, pgroup, blindingCommitment, cipherTextTerm) 29 | if err != nil { 30 | return rv, errors.Wrapf(err, "while constructing bit-pair proof") 31 | } 32 | pPKs := pksToPoints(possiblePKs) 33 | 34 | sig := anon.Sign(pgroup, domainSep, pPKs, nil, bitPair, secret) 35 | if len(sig) != bitPairProofLen(suite) { 36 | return rv, errors.Errorf("signature wrong length") 37 | } 38 | return sig, nil 39 | } 40 | 41 | func bitPairProofLen(suite anon.Suite) int { 42 | 43 | return (4 + 1) * suite.ScalarLen() 44 | } 45 | 46 | func makeProductGroup(suite anon.Suite, pk kyber.Point) (*product_group.ProductGroup, error) { 47 | return product_group.NewProductGroup( 48 | []kyber.Group{suite, suite}, []kyber.Point{suite.Point().Base(), pk}, suite.RandomStream(), 49 | ) 50 | } 51 | 52 | func (p bitPairProof) verify(suite anon.Suite, 53 | domainSep []byte, pgroup *product_group.ProductGroup, 54 | blindingCommitment, cipherTextTerm, pk kyber.Point, 55 | ) error { 56 | possibleBlindingTerms, err := blindingPKs(suite, pgroup, blindingCommitment, cipherTextTerm) 57 | if err != nil { 58 | return errors.Wrap(err, "while constructing possible keys for bit-pair proof") 59 | } 60 | possiblePKs := pksToPoints(possibleBlindingTerms) 61 | 62 | _, err = anon.Verify(pgroup, domainSep, possiblePKs, nil, p) 63 | if err != nil { 64 | return errors.Wrap(err, "while verifying bit-pair proof") 65 | } 66 | return nil 67 | } 68 | 69 | func blindingPKs( 70 | suite anon.Suite, pgroup *product_group.ProductGroup, 71 | blindingCommitment, cipherTextTerm kyber.Point, 72 | ) ([]*product_group.PointTuple, error) { 73 | var possibleBlindingTerms []*product_group.PointTuple 74 | for _, possiblePlaintextTerm := range memPlainTexts(suite) { 75 | possibleBlindingTerm := cipherTextTerm.Clone().Sub(cipherTextTerm, possiblePlaintextTerm) 76 | possibleKnownDLPoint, err := pgroup.NewPoint( 77 | []kyber.Point{blindingCommitment, possibleBlindingTerm}, 78 | ) 79 | if err != nil { 80 | return nil, errors.Wrapf(err, "while constructing possible binding term") 81 | } 82 | possibleBlindingTerms = append(possibleBlindingTerms, possibleKnownDLPoint) 83 | } 84 | return possibleBlindingTerms, nil 85 | } 86 | 87 | func pksToPoints(pks []*product_group.PointTuple) (rv []kyber.Point) { 88 | for _, pk := range pks { 89 | rv = append(rv, kyber.Point(pk)) 90 | } 91 | return rv 92 | } 93 | -------------------------------------------------------------------------------- /internal/signatures/secp256k1/field.go: -------------------------------------------------------------------------------- 1 | package secp256k1 2 | 3 | import ( 4 | "crypto/cipher" 5 | "fmt" 6 | "math/big" 7 | 8 | "go.dedis.ch/kyber/v3/util/random" 9 | ) 10 | 11 | var q = s256.P 12 | 13 | type fieldElt big.Int 14 | 15 | func newFieldZero() *fieldElt { return (*fieldElt)(big.NewInt(0)) } 16 | 17 | func (f *fieldElt) int() *big.Int { return (*big.Int)(f) } 18 | 19 | func (f *fieldElt) modQ() *fieldElt { 20 | if f.int().Cmp(q) != -1 || f.int().Cmp(bigZero) == -1 { 21 | 22 | f.int().Mod(f.int(), q) 23 | } 24 | return f 25 | } 26 | 27 | func fieldEltFromBigInt(v *big.Int) *fieldElt { return (*fieldElt)(v).modQ() } 28 | 29 | func fieldEltFromInt(v int64) *fieldElt { 30 | return fieldEltFromBigInt(big.NewInt(int64(v))).modQ() 31 | } 32 | 33 | var fieldZero = fieldEltFromInt(0) 34 | var bigZero = big.NewInt(0) 35 | 36 | func (f *fieldElt) String() string { 37 | return fmt.Sprintf("fieldElt{%x}", f.int()) 38 | } 39 | 40 | func (f *fieldElt) Equal(g *fieldElt) bool { 41 | if f == (*fieldElt)(nil) && g == (*fieldElt)(nil) { 42 | return true 43 | } 44 | if f == (*fieldElt)(nil) { 45 | return false 46 | } 47 | if g == (*fieldElt)(nil) { 48 | return false 49 | } 50 | return bigZero.Cmp(newFieldZero().Sub(f, g).modQ().int()) == 0 51 | } 52 | 53 | func (f *fieldElt) Add(a, b *fieldElt) *fieldElt { 54 | f.int().Add(a.int(), b.int()) 55 | return f.modQ() 56 | } 57 | 58 | func (f *fieldElt) Sub(a, b *fieldElt) *fieldElt { 59 | f.int().Sub(a.int(), b.int()) 60 | return f.modQ() 61 | } 62 | 63 | func (f *fieldElt) Set(v *fieldElt) *fieldElt { 64 | f.int().Set(v.int()) 65 | return f.modQ() 66 | } 67 | 68 | func (f *fieldElt) SetInt(v *big.Int) *fieldElt { 69 | f.int().Set(v) 70 | return f.modQ() 71 | } 72 | 73 | func (f *fieldElt) Pick(rand cipher.Stream) *fieldElt { 74 | return f.SetInt(random.Int(q, rand)) 75 | } 76 | 77 | func (f *fieldElt) Neg(g *fieldElt) *fieldElt { 78 | f.int().Neg(g.int()) 79 | return f.modQ() 80 | } 81 | 82 | func (f *fieldElt) Clone() *fieldElt { return newFieldZero().Set(f.modQ()) } 83 | 84 | func (f *fieldElt) SetBytes(buf [32]byte) *fieldElt { 85 | f.int().SetBytes(buf[:]) 86 | return f.modQ() 87 | } 88 | 89 | func (f *fieldElt) Bytes() [32]byte { 90 | bytes := f.modQ().int().Bytes() 91 | if len(bytes) > 32 { 92 | panic("field element longer than 256 bits") 93 | } 94 | var rv [32]byte 95 | copy(rv[32-len(bytes):], bytes) 96 | return rv 97 | } 98 | 99 | var two = big.NewInt(2) 100 | 101 | func fieldSquare(y *fieldElt) *fieldElt { 102 | return fieldEltFromBigInt(newFieldZero().int().Exp(y.int(), two, q)) 103 | } 104 | 105 | var sqrtPower = s256.QPlus1Div4() 106 | 107 | func maybeSqrtInField(v *fieldElt) *fieldElt { 108 | s := newFieldZero() 109 | s.int().Exp(v.int(), sqrtPower, q) 110 | if !fieldSquare(s).Equal(v) { 111 | return nil 112 | } 113 | return s 114 | } 115 | 116 | var three = big.NewInt(3) 117 | var seven = fieldEltFromInt(7) 118 | 119 | func rightHandSide(x *fieldElt) *fieldElt { 120 | xCubed := newFieldZero() 121 | xCubed.int().Exp(x.int(), three, q) 122 | return xCubed.Add(xCubed, seven) 123 | } 124 | 125 | func (f *fieldElt) isEven() bool { 126 | return big.NewInt(0).Mod(f.int(), two).Cmp(big.NewInt(0)) == 0 127 | } 128 | -------------------------------------------------------------------------------- /internal/vrf/reporting_plugin_factory.go: -------------------------------------------------------------------------------- 1 | package vrf 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "io" 7 | "time" 8 | 9 | "github.com/ethereum/go-ethereum/common" 10 | 11 | "github.com/pkg/errors" 12 | 13 | "github.com/smartcontractkit/libocr/commontypes" 14 | "github.com/smartcontractkit/libocr/offchainreporting2plus/types" 15 | 16 | "github.com/smartcontractkit/chainlink-vrf/altbn_128" 17 | "github.com/smartcontractkit/chainlink-vrf/internal/crypto/player_idx" 18 | dkg_contract "github.com/smartcontractkit/chainlink-vrf/internal/dkg/contract" 19 | vrf_types "github.com/smartcontractkit/chainlink-vrf/types" 20 | ) 21 | 22 | type vrfReportingPluginFactory struct { 23 | l *localArgs 24 | } 25 | 26 | var _ types.ReportingPluginFactory = (*vrfReportingPluginFactory)(nil) 27 | 28 | type localArgs struct { 29 | keyID dkg_contract.KeyID 30 | coordinator vrf_types.CoordinatorInterface 31 | keyProvider KeyProvider 32 | serializer vrf_types.ReportSerializer 33 | juelsPerFeeCoin vrf_types.JuelsPerFeeCoin 34 | reasonableGasPrice vrf_types.ReasonableGasPrice 35 | period uint16 36 | 37 | logger commontypes.Logger 38 | randomness io.Reader 39 | } 40 | 41 | func (v *vrfReportingPluginFactory) NewReportingPlugin( 42 | c types.ReportingPluginConfig, 43 | ) (types.ReportingPlugin, types.ReportingPluginInfo, error) { 44 | if c.N > int(player_idx.MaxPlayer) { 45 | return nil, types.ReportingPluginInfo{}, 46 | errors.Errorf("too many players: %d > %d", c.N, player_idx.MaxPlayer) 47 | } 48 | players, err := player_idx.PlayerIdxs(player_idx.Int(c.N)) 49 | if err != nil { 50 | return nil, types.ReportingPluginInfo{}, 51 | errors.Wrap(err, "could not determine local player DKG index") 52 | } 53 | ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) 54 | defer cancel() 55 | 56 | confDelays, err := v.l.coordinator.ConfirmationDelays(ctx) 57 | if err != nil { 58 | return nil, types.ReportingPluginInfo{}, errors.Wrap(err, "could not get confirmation delays") 59 | } 60 | confDelaysSet := make(map[uint32]struct{}) 61 | for _, d := range confDelays { 62 | confDelaysSet[d] = struct{}{} 63 | } 64 | 65 | err = v.l.coordinator.UpdateConfiguration(c.OffchainConfig, c.ConfigDigest, c.OracleID) 66 | if err != nil { 67 | return nil, types.ReportingPluginInfo{}, errors.Wrap(err, "could not update off-chain config") 68 | } 69 | 70 | tbls, err := newSigRequest( 71 | v.l.keyID, 72 | v.l.keyProvider, 73 | player_idx.Int(c.N), 74 | player_idx.Int(c.F), 75 | common.Hash(c.ConfigDigest), 76 | *players[c.OracleID], 77 | &altbn_128.PairingSuite{}, 78 | v.l.serializer, 79 | time.Hour, 80 | v.l.logger, 81 | v.l.juelsPerFeeCoin, 82 | v.l.reasonableGasPrice, 83 | v.l.coordinator, 84 | confDelaysSet, 85 | v.l.period, 86 | ) 87 | if err != nil { 88 | return nil, types.ReportingPluginInfo{}, 89 | errors.Wrap(err, "could not create new VRF Beacon reporting plugin") 90 | } 91 | return tbls, types.ReportingPluginInfo{ 92 | Name: fmt.Sprintf("vrf instance %v", tbls.i), 93 | Limits: types.ReportingPluginLimits{ 94 | MaxQueryLength: 200000, 95 | MaxObservationLength: 200000, 96 | MaxReportLength: 200000, 97 | }, 98 | }, nil 99 | } 100 | -------------------------------------------------------------------------------- /internal/dkg/share_records.go: -------------------------------------------------------------------------------- 1 | package dkg 2 | 3 | import ( 4 | "crypto/rand" 5 | "io" 6 | "math/big" 7 | 8 | "github.com/pkg/errors" 9 | 10 | "github.com/smartcontractkit/libocr/offchainreporting2plus/types" 11 | 12 | "github.com/smartcontractkit/chainlink-vrf/internal/crypto/player_idx" 13 | "github.com/smartcontractkit/chainlink-vrf/internal/dkg/contract" 14 | "github.com/smartcontractkit/chainlink-vrf/types/hash" 15 | 16 | "go.dedis.ch/kyber/v3" 17 | kshare "go.dedis.ch/kyber/v3/share" 18 | "go.dedis.ch/kyber/v3/sign/anon" 19 | ) 20 | 21 | type shareRecords map[hash.Hash]*shareRecord 22 | 23 | func newShareRecords() shareRecords { 24 | return map[hash.Hash]*shareRecord{} 25 | } 26 | 27 | func (rs shareRecords) set(r *shareRecord, h hash.Hash) error { 28 | if h == hash.Zero { 29 | m, err := r.marshal() 30 | if err != nil { 31 | return errors.Wrap(err, "could not marshal share record to get content address") 32 | } 33 | h = hash.GetHash(m) 34 | } 35 | rs[h] = r 36 | return nil 37 | } 38 | 39 | func (rs shareRecords) getRandom(givenHashes []hash.Hash, randomness io.Reader) (hash.Hash, error) { 40 | extantHashes := make([]hash.Hash, 0, len(givenHashes)) 41 | for _, h := range givenHashes { 42 | if _, ok := rs[h]; ok { 43 | extantHashes = append(extantHashes, h) 44 | } 45 | } 46 | if len(extantHashes) == 0 { 47 | return hash.Hash{}, errors.Errorf( 48 | "don't know any of the share records in the chosen key", 49 | ) 50 | } 51 | hIdx, err := rand.Int(randomness, big.NewInt(int64(len(extantHashes)))) 52 | if err != nil { 53 | return hash.Hash{}, errors.Wrap(err, "could not choose random hash") 54 | } 55 | return extantHashes[hIdx.Uint64()], nil 56 | } 57 | 58 | func (rs shareRecords) allKeysPresent(hs []hash.Hash) bool { 59 | for _, h := range hs { 60 | if _, ok := rs[h]; !ok { 61 | return false 62 | } 63 | } 64 | return true 65 | } 66 | 67 | func (rs shareRecords) recoverDistributedKeyShare( 68 | encryptionSecretKey kyber.Scalar, 69 | receiver player_idx.PlayerIdx, 70 | keyData *contract.KeyData, 71 | keyGroup anon.Suite, 72 | domainSep types.ConfigDigest, 73 | ) (*kshare.PriShare, error) { 74 | acc := keyGroup.Scalar().Zero() 75 | for _, h := range keyData.Hashes { 76 | publicShare, present := rs[h] 77 | if !present { 78 | return nil, errors.Errorf("no share record for key hash 0x%x", h) 79 | } 80 | recvShare, err := publicShare.shareSet.Decrypt( 81 | receiver, encryptionSecretKey, keyGroup, domainSep, 82 | ) 83 | if err != nil { 84 | return nil, errors.Wrapf(err, "could not decrypt share for key hash 0x%x", h) 85 | } 86 | acc = acc.Clone().Add(acc, recvShare.V) 87 | } 88 | rv := receiver.PriShare(acc) 89 | return &rv, nil 90 | } 91 | 92 | func (rs shareRecords) recoverPublicShares( 93 | keyData *contract.KeyData, 94 | ) ([]kyber.Point, error) { 95 | var partialShares [][]kyber.Point 96 | if len(keyData.Hashes) == 0 { 97 | return nil, errors.Errorf("can't reconstruct shares from 0 share records") 98 | } 99 | for _, h := range keyData.Hashes { 100 | sr, ok := rs[h] 101 | if !ok { 102 | return nil, errors.Errorf("no share found for hash %s", h) 103 | } 104 | 105 | partialShares = append(partialShares, sr.shareSet.PublicShares()) 106 | } 107 | rv := make([]kyber.Point, len(partialShares[0])) 108 | for _, ps := range partialShares { 109 | for playerIdx, partialShare := range ps { 110 | if rv[playerIdx] == nil { 111 | rv[playerIdx] = partialShare.Clone().Null() 112 | } 113 | 114 | rv[playerIdx] = partialShare.Clone().Add(rv[playerIdx], partialShare) 115 | } 116 | } 117 | return rv, nil 118 | } 119 | -------------------------------------------------------------------------------- /internal/pvss/share_marshalling.go: -------------------------------------------------------------------------------- 1 | package pvss 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "math" 7 | 8 | "github.com/pkg/errors" 9 | "go.dedis.ch/kyber/v3" 10 | "go.dedis.ch/kyber/v3/sign/anon" 11 | 12 | "github.com/smartcontractkit/chainlink-vrf/internal/crypto/ciphertext" 13 | ) 14 | 15 | func (s *share) marshal() ([]byte, error) { 16 | rv := make([][]byte, 3) 17 | cursor := 0 18 | 19 | cm, err := s.cipherText.Marshal() 20 | if err != nil { 21 | return nil, errors.Wrap(err, "could not marshal ciphertext of share") 22 | } 23 | if len(cm) > math.MaxUint16 { 24 | return nil, errors.Errorf("marshaled ciphertext too long") 25 | } 26 | ctLen := make([]byte, 2) 27 | binary.BigEndian.PutUint16(ctLen, uint16(len(cm))) 28 | rv[cursor] = append(ctLen, cm...) 29 | cursor++ 30 | 31 | if rv[cursor], err = marshalKyberPointWithLen(s.encryptionKey); err != nil { 32 | return nil, errors.Wrap(err, "could not marshal encryptionKey") 33 | } 34 | cursor++ 35 | 36 | if rv[cursor], err = marshalKyberPointWithLen(s.subKeyTranslation); err != nil { 37 | return nil, errors.Wrap(err, "could not marshal subKeyTranslation") 38 | } 39 | cursor++ 40 | 41 | if cursor != len(rv) { 42 | panic(errors.Errorf("marshal fields out of alignment")) 43 | } 44 | 45 | return bytes.Join(rv, nil), nil 46 | } 47 | 48 | func marshalKyberPointWithLen(p kyber.Point) ([]byte, error) { 49 | pm, err := p.MarshalBinary() 50 | if err != nil { 51 | return nil, errors.Wrap(err, "could not marshal key of share") 52 | } 53 | if len(pm) > math.MaxUint8 { 54 | return nil, errors.Errorf("marshalled point too long") 55 | } 56 | return bytes.Join([][]byte{{uint8(len(pm))}, pm}, nil), nil 57 | } 58 | 59 | func unmarshal( 60 | group anon.Suite, translationGroup kyber.Group, data []byte, ss *ShareSet, 61 | ) (*share, []byte, error) { 62 | 63 | cipherTextB, data, err := readLenPrefixedBytes(data, 2) 64 | if err != nil { 65 | return nil, nil, errors.Wrap(err, "could not unmarshal ciphertext of share") 66 | } 67 | cipherText, err := ciphertext.Unmarshal(group, bytes.NewBuffer(cipherTextB)) 68 | if err != nil { 69 | return nil, nil, errors.Wrap(err, "could not unmarshal ciphertext of share") 70 | } 71 | 72 | encryptionKeyB, data, err := readLenPrefixedBytes(data, 1) 73 | if err != nil { 74 | return nil, nil, errors.Wrap(err, "could not unmarshal encryptionKey of share") 75 | } 76 | encryptionKey := group.Point() 77 | if err2 := encryptionKey.UnmarshalBinary(encryptionKeyB); err2 != nil { 78 | return nil, nil, errors.Wrap(err2, "could not unmarshal encryptionKey of share") 79 | } 80 | 81 | subKeyTranslationB, data, err := readLenPrefixedBytes(data, 1) 82 | if err != nil { 83 | return nil, nil, errors.Wrap(err, "could not unmarshal subKeyTranslation of share") 84 | } 85 | subKeyTranslation := translationGroup.Point() 86 | if err2 := subKeyTranslation.UnmarshalBinary(subKeyTranslationB); err2 != nil { 87 | return nil, nil, errors.Wrap(err2, "could not unmarshal subKeyTranslation of share") 88 | } 89 | 90 | return &share{cipherText, encryptionKey, subKeyTranslation, ss}, data, nil 91 | } 92 | 93 | func readLenPrefixedBytes(data []byte, prefixLen uint8) (read, remains []byte, err error) { 94 | if len(data) < int(prefixLen) { 95 | return nil, nil, errors.Errorf("marshalled data too short for length prefix") 96 | } 97 | length := 0 98 | for i := uint8(0); i < prefixLen; i++ { 99 | length <<= 8 100 | length += int(data[i]) 101 | } 102 | readEnd := int(prefixLen) + length 103 | if len(data) < readEnd { 104 | return nil, nil, errors.Errorf("read length longer than marshal data") 105 | } 106 | read = data[prefixLen:readEnd] 107 | return read, data[readEnd:], nil 108 | } 109 | -------------------------------------------------------------------------------- /.github/workflows/pull-request-main.yml: -------------------------------------------------------------------------------- 1 | name: pull-request-master 2 | 3 | on: 4 | merge_group: 5 | pull_request: 6 | branches: 7 | - master 8 | # Only run 1 of this workflow at a time per PR 9 | concurrency: 10 | group: chainlink-vrf-${{ github.ref }} 11 | cancel-in-progress: true 12 | 13 | env: 14 | PACKAGES: "./.." 15 | 16 | jobs: 17 | init: 18 | runs-on: ubuntu-latest 19 | outputs: 20 | matrix_packages: ${{ steps.set-matrix-packages.outputs.matrix_packages }} 21 | lint_args_packages: ${{ steps.set-matrix-packages.outputs.lint_args_packages }} 22 | steps: 23 | - name: Checkout code 24 | uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 25 | with: 26 | fetch-depth: 0 27 | - name: Set matrix packages 28 | id: set-matrix-packages 29 | shell: bash 30 | env: 31 | PACKAGES: ${{ env.PACKAGES }} 32 | run: | 33 | matrix_packages=$(echo "${PACKAGES}" | jq -R 'split(",")' | tr -d "\n\t") 34 | echo "matrix_packages=${matrix_packages}" | tee -a "${GITHUB_OUTPUT}" 35 | - name: Set lint args packages 36 | id: set-lint-args-packages 37 | shell: bash 38 | env: 39 | PACKAGES: ${{ env.PACKAGES }} 40 | # Convert "producer,reports_consumer" to "./producer/... ./reports_consumer/..." 41 | run: echo "lint_args_packages=$(echo "./$(echo $PACKAGES | sed 's/,/\/... .\//g;s/$/\/.../')")" | tee -a "${GITHUB_OUTPUT}" 42 | 43 | ci-lint: 44 | runs-on: ubuntu-latest 45 | needs: [init] 46 | permissions: 47 | id-token: write 48 | contents: read 49 | actions: read 50 | steps: 51 | - name: ci-lint 52 | uses: smartcontractkit/.github/actions/ci-lint-go@5b1046c28343660ecb84844c6fa95a66d1cdb52e # ci-lint-go@0.2.1 53 | with: 54 | # grafana inputs 55 | metrics-job-name: ci-lint 56 | gc-basic-auth: ${{ secrets.GRAFANA_INTERNAL_BASIC_AUTH }} 57 | gc-host: ${{ secrets.GRAFANA_INTERNAL_HOST }} 58 | gc-org-id: ${{ secrets.GRAFANA_INTERNAL_TENANT_ID }} 59 | # env inputs 60 | use-env-files: "true" 61 | env-files: ./tools/env/ci.env 62 | # go inputs 63 | use-go-cache: true 64 | go-cache-dep-path: "**/go.sum" 65 | go-version-file: go.mod 66 | golangci-lint-version: "v1.55.2" 67 | golangci-lint-args: --out-format colored-line-number,checkstyle:golangci-lint-report.xml 68 | 69 | ci-lint-misc: 70 | runs-on: ubuntu-latest 71 | steps: 72 | - name: ci-lint-misc 73 | uses: smartcontractkit/.github/actions/ci-lint-misc@6b08487b176ef7cad086526d0b54ddff6691c044 # ci-lint-misc@0.1.1 74 | with: 75 | # grafana inputs 76 | metrics-job-name: ci-lint-misc 77 | gc-basic-auth: ${{ secrets.GRAFANA_INTERNAL_BASIC_AUTH }} 78 | gc-host: ${{ secrets.GRAFANA_INTERNAL_HOST }} 79 | gc-org-id: ${{ secrets.GRAFANA_INTERNAL_TENANT_ID }} 80 | 81 | ci-test: 82 | runs-on: ubuntu-latest 83 | permissions: 84 | id-token: write 85 | contents: read 86 | actions: read 87 | steps: 88 | - name: ci-test 89 | uses: smartcontractkit/.github/actions/ci-test-go@ci-test-go/0.3.5 90 | with: 91 | # docker inputs 92 | use-docker-compose: "true" 93 | docker-compose-workdir: ./tools/docker/setup-postgres 94 | # env inputs 95 | use-env-files: "true" 96 | env-files: ./tools/env/ci.env 97 | # go inputs 98 | use-go-cache: "true" 99 | go-cache-dep-path: "**/go.sum" 100 | go-version-file: go.mod 101 | go-test-cmd: make test-ci 102 | -------------------------------------------------------------------------------- /internal/dkg/ocr.go: -------------------------------------------------------------------------------- 1 | package dkg 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/pkg/errors" 7 | 8 | "github.com/smartcontractkit/libocr/commontypes" 9 | "github.com/smartcontractkit/libocr/offchainreporting2plus/types" 10 | ) 11 | 12 | func (d *dkg) Query(context.Context, types.ReportTimestamp, 13 | ) (types.Query, error) { 14 | return nil, nil 15 | } 16 | 17 | func (d *dkg) Observation( 18 | ctx context.Context, _ types.ReportTimestamp, _ types.Query, 19 | ) (o types.Observation, err error) { 20 | d.lock.RLock() 21 | defer d.lock.RUnlock() 22 | var respondingShareRecord *shareRecord 23 | if !d.keyReportedOnchain(ctx) { 24 | respondingShareRecord = d.myShareRecord 25 | } else { 26 | 27 | keyData, err2 := d.contract.KeyData(ctx, d.keyID, d.cfgDgst) 28 | if err2 != nil { 29 | 30 | return nil, errors.Wrap(err2, "digest marked as complete, but key data is unavailable") 31 | } 32 | respondingShareRecordHash, err2 := d.shareSets.getRandom(keyData.Hashes, d.randomness) 33 | if err2 != nil { 34 | 35 | d.logger.Error( 36 | "could not choose random hash to send for data-availability phase", 37 | commontypes.LogFields{ 38 | "required hashes": keyData.Hashes, 39 | "existing share sets": d.shareSets, 40 | }, 41 | ) 42 | return types.Observation{}, nil 43 | } 44 | var ok bool 45 | respondingShareRecord, ok = d.shareSets[respondingShareRecordHash] 46 | if !ok { 47 | d.logger.Error( 48 | "could not choose random share set to send. No record for the hash.", 49 | commontypes.LogFields{ 50 | "randomly chosen hash": respondingShareRecordHash, 51 | }, 52 | ) 53 | return types.Observation{}, nil 54 | } 55 | } 56 | o, err = respondingShareRecord.marshal() 57 | if err != nil { 58 | return nil, errors.Wrap(err, "could not construct observation") 59 | } 60 | if _, err := unmarshalSignedShareRecord(d, o); err != nil { 61 | panic(err) 62 | } 63 | return o, nil 64 | } 65 | 66 | func (d *dkg) Report( 67 | ctx context.Context, _ types.ReportTimestamp, _ types.Query, 68 | shares []types.AttributedObservation, 69 | ) (shouldReport bool, report types.Report, err error) { 70 | d.lock.Lock() 71 | defer d.lock.Unlock() 72 | 73 | v, err := d.newValidShareRecords(ctx) 74 | if err != nil { 75 | return false, nil, errors.Wrap( 76 | err, "could not create record for valid shares", 77 | ) 78 | } 79 | for _, aobs := range shares { 80 | senderField := commontypes.LogFields{"sender": aobs.Observer} 81 | d.logger.Debug("processing share set", senderField) 82 | 83 | v.processShareSet(aobs) 84 | } 85 | if d.keyReportedOnchain(ctx) { 86 | 87 | return false, nil, d.recoverDistributedKeyShare(ctx) 88 | } 89 | 90 | if !v.enoughShareSets() { 91 | d.logger.Warn( 92 | "need quorum of unique share sets to construct secure distributed key", 93 | commontypes.LogFields{ 94 | "required": d.t + 1, 95 | "received": v.validShareCount, 96 | "players": v.players, 97 | }, 98 | ) 99 | return false, nil, nil 100 | } 101 | report, err = v.report() 102 | if err != nil { 103 | return false, nil, errors.Wrap(err, 104 | "could not extract onchain report from share set we just constructed", 105 | ) 106 | } 107 | return true, report, nil 108 | } 109 | 110 | func (d *dkg) ShouldAcceptFinalizedReport( 111 | _ context.Context, _ types.ReportTimestamp, _ types.Report) (bool, error) { 112 | 113 | return true, nil 114 | } 115 | 116 | func (d *dkg) ShouldTransmitAcceptedReport( 117 | c context.Context, t types.ReportTimestamp, r types.Report) (bool, error) { 118 | 119 | return !d.keyReportedOnchain(c), nil 120 | } 121 | 122 | func (d *dkg) Start() error { 123 | return nil 124 | } 125 | 126 | func (d *dkg) Close() error { 127 | 128 | d.cancelFunc() 129 | return nil 130 | } 131 | -------------------------------------------------------------------------------- /types/types.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "context" 5 | "math/big" 6 | "time" 7 | 8 | "github.com/ethereum/go-ethereum/common" 9 | 10 | "github.com/smartcontractkit/libocr/commontypes" 11 | "github.com/smartcontractkit/libocr/offchainreporting2plus/types" 12 | 13 | "github.com/smartcontractkit/chainlink-vrf/internal/common/ocr" 14 | "github.com/smartcontractkit/chainlink-vrf/internal/crypto/player_idx" 15 | "github.com/smartcontractkit/chainlink-vrf/internal/crypto/point_translation" 16 | "github.com/smartcontractkit/chainlink-vrf/internal/dkg/contract" 17 | "github.com/smartcontractkit/chainlink-vrf/internal/vrf/protobuf" 18 | ) 19 | 20 | type CoordinatorInterface interface { 21 | ReportBlocks( 22 | ctx context.Context, 23 | slotInterval uint16, 24 | confirmationDelays map[uint32]struct{}, 25 | retransmissionDelay time.Duration, 26 | maxBlocks, maxCallbacks int, 27 | ) ( 28 | blocks []Block, 29 | callbacks []AbstractCostedCallbackRequest, 30 | recentBlockHashesStartHeight uint64, 31 | recentBlockHashes []common.Hash, 32 | err error, 33 | ) 34 | 35 | UpdateConfiguration(offchainConfig []byte, configDigest types.ConfigDigest, oracleID commontypes.OracleID) error 36 | 37 | ReportWillBeTransmitted(context.Context, AbstractReport) error 38 | 39 | DKGVRFCommittees(context.Context) (dkg, vrf OCRCommittee, err error) 40 | 41 | ProvingKeyHash(context.Context) (common.Hash, error) 42 | 43 | BeaconPeriod(ctx context.Context) (uint16, error) 44 | 45 | ReportIsOnchain( 46 | ctx context.Context, 47 | epoch uint32, round uint8, configDigest [32]byte, 48 | ) (presentOnchain bool, err error) 49 | 50 | ConfirmationDelays(ctx context.Context) ([]uint32, error) 51 | 52 | KeyID(ctx context.Context) (contract.KeyID, error) 53 | 54 | CurrentChainHeight(context.Context) (height uint64, err error) 55 | } 56 | 57 | type ReportSerializer interface { 58 | SerializeReport(AbstractReport) ([]byte, error) 59 | 60 | DeserializeReport([]byte) (AbstractReport, error) 61 | 62 | MaxReportLength() uint 63 | 64 | ReportLength(AbstractReport) uint 65 | } 66 | 67 | type JuelsPerFeeCoin interface { 68 | JuelsPerFeeCoin() (*big.Int, error) 69 | } 70 | 71 | type ReasonableGasPrice interface { 72 | ReasonableGasPrice() (*big.Int, error) 73 | } 74 | 75 | type AbstractCostedCallbackRequest struct { 76 | BeaconHeight uint64 77 | ConfirmationDelay uint32 78 | SubscriptionID *big.Int 79 | Price *big.Int 80 | RequestID *big.Int 81 | NumWords uint16 82 | Requester common.Address 83 | Arguments []byte 84 | GasAllowance *big.Int 85 | GasPrice *big.Int 86 | WeiPerUnitLink *big.Int 87 | } 88 | 89 | type AbstractVRFOutput struct { 90 | BlockHeight uint64 91 | ConfirmationDelay uint32 92 | 93 | VRFProof [32]byte 94 | 95 | Callbacks []AbstractCostedCallbackRequest 96 | ShouldStore bool 97 | } 98 | 99 | type AbstractReport struct { 100 | Outputs []AbstractVRFOutput 101 | 102 | JuelsPerFeeCoin *big.Int 103 | 104 | ReasonableGasPrice uint64 105 | 106 | RecentBlockHeight uint64 107 | 108 | RecentBlockHash common.Hash 109 | } 110 | 111 | type Block struct { 112 | Height uint64 113 | ConfirmationDelay uint32 114 | Hash common.Hash 115 | ShouldStore bool 116 | } 117 | 118 | type ( 119 | PubKeyTranslation = point_translation.PubKeyTranslation 120 | PairingTranslation = point_translation.PairingTranslation 121 | PlayerIdx = player_idx.PlayerIdx 122 | PlayerIdxInt = player_idx.Int 123 | OCRCommittee = ocr.OCRCommittee 124 | CoordinatorConfig = protobuf.CoordinatorConfig 125 | ) 126 | 127 | func UnmarshalPlayerIdx(b []byte) (*PlayerIdx, []byte, error) { 128 | return player_idx.Unmarshal(b) 129 | } 130 | 131 | func RawMarshalPlayerIdxInt(i PlayerIdxInt) []byte { 132 | return player_idx.RawMarshal(i) 133 | } 134 | -------------------------------------------------------------------------------- /internal/signatures/secp256k1/public_key.go: -------------------------------------------------------------------------------- 1 | package secp256k1 2 | 3 | import ( 4 | "database/sql/driver" 5 | "fmt" 6 | 7 | "github.com/pkg/errors" 8 | "golang.org/x/crypto/sha3" 9 | 10 | "github.com/ethereum/go-ethereum/common" 11 | "github.com/ethereum/go-ethereum/common/hexutil" 12 | "go.dedis.ch/kyber/v3" 13 | ) 14 | 15 | type PublicKey [CompressedPublicKeyLength]byte 16 | 17 | const CompressedPublicKeyLength = 33 18 | 19 | func init() { 20 | if CompressedPublicKeyLength != (&Secp256k1{}).Point().MarshalSize() { 21 | panic("disparity in expected public key lengths") 22 | } 23 | } 24 | 25 | func (k *PublicKey) Set(l PublicKey) { 26 | if copy(k[:], l[:]) != CompressedPublicKeyLength { 27 | panic(fmt.Errorf("failed to copy entire public key %x to %x", l, k)) 28 | } 29 | } 30 | 31 | func (k *PublicKey) Point() (kyber.Point, error) { 32 | p := (&Secp256k1{}).Point() 33 | return p, p.UnmarshalBinary(k[:]) 34 | } 35 | 36 | func NewPublicKeyFromHex(hex string) (PublicKey, error) { 37 | rawKey, err := hexutil.Decode(hex) 38 | if err != nil { 39 | return PublicKey{}, err 40 | } 41 | return NewPublicKeyFromBytes(rawKey) 42 | } 43 | 44 | func NewPublicKeyFromBytes(rawKey []byte) (PublicKey, error) { 45 | if l := len(rawKey); l != CompressedPublicKeyLength { 46 | return PublicKey{}, fmt.Errorf( 47 | "wrong length for public key: %s of length %d", rawKey, l) 48 | } 49 | var k PublicKey 50 | if c := copy(k[:], rawKey); c != CompressedPublicKeyLength { 51 | panic(fmt.Errorf("failed to copy entire key to return value")) 52 | } 53 | return k, nil 54 | } 55 | 56 | func (k *PublicKey) SetFromHex(hex string) error { 57 | nk, err := NewPublicKeyFromHex(hex) 58 | if err != nil { 59 | return err 60 | } 61 | k.Set(nk) 62 | return nil 63 | } 64 | 65 | func (k PublicKey) String() string { 66 | return hexutil.Encode(k[:]) 67 | } 68 | 69 | func (k *PublicKey) StringUncompressed() (string, error) { 70 | p, err := k.Point() 71 | if err != nil { 72 | return "", err 73 | } 74 | return hexutil.Encode(LongMarshal(p)), nil 75 | } 76 | 77 | func (k *PublicKey) Hash() (common.Hash, error) { 78 | p, err := k.Point() 79 | if err != nil { 80 | return common.Hash{}, err 81 | } 82 | return MustHash(string(LongMarshal(p))), nil 83 | } 84 | 85 | func (k *PublicKey) MustHash() common.Hash { 86 | hash, err := k.Hash() 87 | if err != nil { 88 | panic(fmt.Sprintf("Failed to compute hash of public vrf key %v", k)) 89 | } 90 | return hash 91 | } 92 | 93 | func (k *PublicKey) Address() common.Address { 94 | hash, err := k.Hash() 95 | if err != nil { 96 | return common.Address{} 97 | } 98 | return common.BytesToAddress(hash.Bytes()[12:]) 99 | } 100 | 101 | func (k *PublicKey) IsZero() bool { 102 | return *k == PublicKey{} 103 | } 104 | 105 | func (k PublicKey) MarshalText() ([]byte, error) { 106 | return []byte(k.String()), nil 107 | } 108 | 109 | func (k *PublicKey) UnmarshalText(text []byte) error { 110 | if err := k.SetFromHex(string(text)); err != nil { 111 | return errors.Wrapf(err, "while parsing %s as public key", text) 112 | } 113 | return nil 114 | } 115 | 116 | func (k PublicKey) Value() (driver.Value, error) { 117 | return k.String(), nil 118 | } 119 | 120 | func (k *PublicKey) Scan(value interface{}) error { 121 | rawKey, ok := value.(string) 122 | if !ok { 123 | return errors.Wrap(fmt.Errorf("unable to convert %+v of type %T to PublicKey", value, value), "scan failure") 124 | } 125 | if err := k.SetFromHex(rawKey); err != nil { 126 | return errors.Wrapf(err, "while scanning %s as PublicKey", rawKey) 127 | } 128 | return nil 129 | } 130 | 131 | func MustHash(in string) common.Hash { 132 | out, err := Keccak256([]byte(in)) 133 | if err != nil { 134 | panic(err) 135 | } 136 | return common.BytesToHash(out) 137 | } 138 | 139 | func Keccak256(in []byte) ([]byte, error) { 140 | hash := sha3.NewLegacyKeccak256() 141 | _, err := hash.Write(in) 142 | return hash.Sum(nil), err 143 | } 144 | -------------------------------------------------------------------------------- /internal/dkg/persist_share_records.go: -------------------------------------------------------------------------------- 1 | package dkg 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/smartcontractkit/libocr/commontypes" 8 | 9 | "github.com/smartcontractkit/chainlink-vrf/internal/crypto/player_idx" 10 | "github.com/smartcontractkit/chainlink-vrf/internal/pvss" 11 | "github.com/smartcontractkit/chainlink-vrf/internal/util" 12 | "github.com/smartcontractkit/chainlink-vrf/types" 13 | "github.com/smartcontractkit/chainlink-vrf/types/hash" 14 | 15 | "go.dedis.ch/kyber/v3/sign/anon" 16 | ) 17 | 18 | func (sr shareRecord) PersistentRecord() (types.PersistentShareSetRecord, error) { 19 | marshaled, err := sr.marshal() 20 | if err != nil { 21 | errMsg := "could not marshal share records for local persistence" 22 | return types.PersistentShareSetRecord{}, 23 | util.WrapError(err, errMsg) 24 | } 25 | dealer, err := sr.shareSet.Dealer() 26 | if err != nil { 27 | errMsg := "could not marshal share record for local persistence" 28 | return types.PersistentShareSetRecord{}, 29 | util.WrapError(err, errMsg) 30 | } 31 | rv := types.PersistentShareSetRecord{ 32 | *dealer, 33 | marshaled, 34 | hash.Hash{}, 35 | } 36 | return rv, nil 37 | } 38 | 39 | func (d *dkg) initializeShareSets(signingGroup anon.Suite) error { 40 | d.lock.Lock() 41 | defer d.lock.Unlock() 42 | persistedShares, err := d.db.ReadShareRecords(d.cfgDgst, d.keyID) 43 | if err != nil { 44 | return util.WrapError(err, "could not recover persisted shares") 45 | } 46 | myShareRecovered := false 47 | for _, storedShare := range persistedShares { 48 | m := storedShare.MarshaledShareRecord 49 | 50 | mHash := hash.GetHash(m) 51 | if storedShare.Hash != mHash { 52 | dealer := storedShare.Dealer 53 | return fmt.Errorf("hash mismatch on record from %d", dealer) 54 | } 55 | share, err := unmarshalSignedShareRecord(d, m) 56 | if err != nil { 57 | errMsg := "could not unmarshal persisted share record" 58 | return util.WrapError(err, errMsg) 59 | } 60 | if err := d.shareSets.set(share, mHash); err != nil { 61 | errMsg := "could not record persisted share from %s" 62 | return util.WrapErrorf(err, errMsg, storedShare.Dealer) 63 | } 64 | if storedShare.Dealer.Equal(d.selfIdx) { 65 | myShareRecovered = true 66 | d.myShareRecord = share 67 | } 68 | } 69 | if !myShareRecovered { 70 | 71 | shareSet, err := pvss.NewShareSet( 72 | d.cfgDgst, d.t, d.selfIdx, d.encryptionGroup, d.translator, d.epks, 73 | ) 74 | if err != nil { 75 | return util.WrapError(err, "could not create own share set") 76 | } 77 | msr, err := newShareRecord(signingGroup, shareSet, d.ssk, d.cfgDgst) 78 | if err != nil { 79 | return util.WrapError(err, "could not create own share record") 80 | } 81 | d.myShareRecord = msr 82 | psr, err := d.myShareRecord.PersistentRecord() 83 | if err != nil { 84 | errMsg := "could not construct own persistent share record" 85 | return util.WrapError(err, errMsg) 86 | } 87 | lpsr := []types.PersistentShareSetRecord{psr} 88 | err = d.db.WriteShareRecords(context.Background(), d.cfgDgst, d.keyID, lpsr) 89 | if err != nil { 90 | return util.WrapError(err, "could not write own share record") 91 | } 92 | err = d.shareSets.set(d.myShareRecord, hash.Zero) 93 | if err != nil { 94 | return util.WrapError(err, "could not set own share record") 95 | } 96 | } 97 | return nil 98 | } 99 | 100 | func (v *validShareRecords) persistShares( 101 | reportedDealer player_idx.PlayerIdx, 102 | marshaledShareSet []byte, 103 | h hash.Hash, 104 | ) { 105 | psr := types.PersistentShareSetRecord{reportedDealer, marshaledShareSet, h} 106 | lpsr := []types.PersistentShareSetRecord{psr} 107 | v.d.lock.Lock() 108 | defer v.d.lock.Unlock() 109 | err := v.d.db.WriteShareRecords(v.context, v.d.cfgDgst, v.d.keyID, lpsr) 110 | if err != nil { 111 | v.d.logger.Warn("failed to persist share", commontypes.LogFields{ 112 | "player": reportedDealer, 113 | "err": err, 114 | "share hash": h, 115 | }) 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /internal/crypto/ciphertext/schnorr/schnorr.go: -------------------------------------------------------------------------------- 1 | package schnorr 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | 7 | "go.dedis.ch/kyber/v3" 8 | 9 | "github.com/smartcontractkit/chainlink-vrf/internal/util" 10 | ) 11 | 12 | type Suite interface { 13 | kyber.Group 14 | kyber.Random 15 | kyber.XOFFactory 16 | } 17 | 18 | func Sign(s Suite, private kyber.Scalar, msg []byte) ([]byte, error) { 19 | var g kyber.Group = s 20 | 21 | k := g.Scalar().Pick(s.RandomStream()) 22 | R := g.Point().Mul(k, nil) 23 | 24 | public := g.Point().Mul(private, nil) 25 | h, err := hash(s, public, R, msg) 26 | if err != nil { 27 | return nil, err 28 | } 29 | 30 | xh := g.Scalar().Mul(private, h) 31 | S := g.Scalar().Add(k, xh) 32 | 33 | var b bytes.Buffer 34 | if _, err := R.MarshalTo(&b); err != nil { 35 | return nil, err 36 | } 37 | if _, err := S.MarshalTo(&b); err != nil { 38 | return nil, err 39 | } 40 | return b.Bytes(), nil 41 | } 42 | 43 | func VerifyWithChecks(g Suite, pub, msg, sig []byte) error { 44 | type scalarCanCheckCanonical interface { 45 | IsCanonical(b []byte) bool 46 | } 47 | 48 | type pointCanCheckCanonicalAndSmallOrder interface { 49 | HasSmallOrder() bool 50 | IsCanonical(b []byte) bool 51 | } 52 | 53 | R := g.Point() 54 | s := g.Scalar() 55 | pointSize := R.MarshalSize() 56 | scalarSize := s.MarshalSize() 57 | sigSize := scalarSize + pointSize 58 | if len(sig) != sigSize { 59 | return fmt.Errorf( 60 | "schnorr: signature of invalid length %d instead of %d", 61 | len(sig), 62 | sigSize, 63 | ) 64 | } 65 | if err := R.UnmarshalBinary(sig[:pointSize]); err != nil { 66 | return util.WrapErrorf(err, "could not unmarshal R (0x%x)", sig[:pointSize]) 67 | } 68 | if p, ok := R.(pointCanCheckCanonicalAndSmallOrder); ok { 69 | if !p.IsCanonical(sig[:pointSize]) { 70 | return fmt.Errorf("the point R is not canonical") 71 | } 72 | if p.HasSmallOrder() { 73 | return fmt.Errorf("the point R has small order") 74 | } 75 | } 76 | sc, ok := g.Scalar().(scalarCanCheckCanonical) 77 | if ok && !sc.IsCanonical(sig[pointSize:]) { 78 | return fmt.Errorf( 79 | "signature is not canonical: 0x%x %d", 80 | sig[pointSize:], 81 | len(sig[pointSize:]), 82 | ) 83 | } 84 | if err := s.UnmarshalBinary(sig[pointSize:]); err != nil { 85 | return util.WrapError(err, "could not unmarshal S") 86 | } 87 | 88 | public := g.Point() 89 | err := public.UnmarshalBinary(pub) 90 | if err != nil { 91 | return util.WrapError(err, "schnorr: error unmarshalling public key") 92 | } 93 | if p, ok := public.(pointCanCheckCanonicalAndSmallOrder); ok { 94 | if !p.IsCanonical(pub) { 95 | return fmt.Errorf("public key is not canonical") 96 | } 97 | if p.HasSmallOrder() { 98 | return fmt.Errorf("public key has small order") 99 | } 100 | } 101 | 102 | h, err := hash(g, public, R, msg) 103 | if err != nil { 104 | return util.WrapError(err, "could not compute hash") 105 | } 106 | 107 | S := g.Point().Mul(s, nil) 108 | 109 | Ah := g.Point().Mul(h, public) 110 | RAs := g.Point().Add(R, Ah) 111 | 112 | if !S.Equal(RAs) { 113 | return fmt.Errorf("schnorr: invalid signature") 114 | } 115 | 116 | return nil 117 | 118 | } 119 | 120 | func Verify(g Suite, public kyber.Point, msg, sig []byte) error { 121 | PBuf, err := public.MarshalBinary() 122 | if err != nil { 123 | return util.WrapError(err, "could not marshal point for sig verification") 124 | } 125 | if err = g.Point().UnmarshalBinary(PBuf); err != nil { 126 | panic(err) 127 | } 128 | if err != nil { 129 | return util.WrapError(err, "error unmarshalling public key") 130 | } 131 | return VerifyWithChecks(g, PBuf, msg, sig) 132 | } 133 | 134 | func hash(g Suite, public, r kyber.Point, msg []byte) (kyber.Scalar, error) { 135 | h := g.XOF(nil) 136 | if _, err := r.MarshalTo(h); err != nil { 137 | return nil, err 138 | } 139 | if _, err := public.MarshalTo(h); err != nil { 140 | return nil, err 141 | } 142 | if _, err := h.Write(msg); err != nil { 143 | return nil, err 144 | } 145 | return g.Scalar().Pick(h), nil 146 | } 147 | -------------------------------------------------------------------------------- /internal/dkg/reporting_plugin_factory.go: -------------------------------------------------------------------------------- 1 | package dkg 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "sync" 7 | 8 | "github.com/smartcontractkit/libocr/commontypes" 9 | "github.com/smartcontractkit/libocr/offchainreporting2plus/types" 10 | 11 | "github.com/smartcontractkit/chainlink-vrf/internal/crypto/player_idx" 12 | "github.com/smartcontractkit/chainlink-vrf/internal/util" 13 | ) 14 | 15 | type dkgReportingPluginFactory struct { 16 | l *localArgs 17 | lock sync.RWMutex 18 | 19 | dkgInProgress bool 20 | 21 | testmode bool 22 | xxxDKGTestingOnly *dkg 23 | } 24 | 25 | var _ types.ReportingPluginFactory = (*dkgReportingPluginFactory)(nil) 26 | 27 | func (d *dkgReportingPluginFactory) NewReportingPlugin( 28 | c types.ReportingPluginConfig, 29 | ) (types.ReportingPlugin, types.ReportingPluginInfo, error) { 30 | d.lock.Lock() 31 | defer d.lock.Unlock() 32 | emptyInfo := types.ReportingPluginInfo{} 33 | if d.dkgInProgress { 34 | return nil, emptyInfo, fmt.Errorf( 35 | "attempt to initiate DKG round while an earlier DKG round is in progress", 36 | ) 37 | } 38 | d.dkgInProgress = true 39 | a, err := unmarshalPluginConfig(c.OffchainConfig, c.OnchainConfig) 40 | if err != nil { 41 | return nil, emptyInfo, 42 | util.WrapError(err, "could not read offchain plugin config") 43 | } 44 | if c.N > int(player_idx.MaxPlayer) { 45 | return nil, emptyInfo, 46 | fmt.Errorf("too many players: %d > %d", c.N, player_idx.MaxPlayer) 47 | } 48 | args, err := a.NewDKGArgs( 49 | c.ConfigDigest, d.l, c.OracleID, player_idx.Int(c.N), player_idx.Int(c.F), 50 | ) 51 | if err != nil { 52 | return nil, emptyInfo, util.WrapError(err, "could not construct DKG args") 53 | } 54 | d.l.logger.Debug("constructing share set", commontypes.LogFields{}) 55 | dkg, err := d.NewDKG(args) 56 | if err != nil { 57 | return nil, emptyInfo, util.WrapError(err, "while creating reporting plugin") 58 | } 59 | d.l.logger.Debug("finished constructing share set", commontypes.LogFields{}) 60 | if d.testmode { 61 | d.xxxDKGTestingOnly = dkg 62 | } 63 | dkg.keyConsumer.KeyInvalidated(dkg.keyID) 64 | return dkg, types.ReportingPluginInfo{ 65 | Name: fmt.Sprintf("dkg instance %v", dkg.selfIdx), 66 | Limits: types.ReportingPluginLimits{ 67 | MaxQueryLength: 1000, 68 | MaxObservationLength: 1_000_000, 69 | MaxReportLength: 10_000, 70 | }, 71 | UniqueReports: true, 72 | }, nil 73 | } 74 | 75 | func (d *dkgReportingPluginFactory) NewDKG(a *NewDKGArgs) (*dkg, error) { 76 | if err := a.SanityCheckArgs(); err != nil { 77 | return nil, util.WrapError(err, "could not construct new DKG") 78 | } 79 | 80 | ctx, cancelFunc := context.WithCancel(context.Background()) 81 | factory := &dkg{ 82 | a.t, 83 | sync.RWMutex{}, 84 | a.selfIdx, 85 | a.cfgDgst, 86 | a.keyID, 87 | a.keyConsumer, 88 | newShareRecords(), 89 | nil, 90 | a.esk, 91 | a.epks, 92 | a.ssk, 93 | a.spks, 94 | a.encryptionGroup, 95 | a.translationGroup, 96 | a.translator, 97 | nil, 98 | a.contract, 99 | false, 100 | d.markCompleted, 101 | a.db, 102 | a.logger, 103 | a.randomness, 104 | ctx, 105 | cancelFunc, 106 | } 107 | if err := factory.initializeShareSets(a.signingGroup()); err != nil { 108 | return nil, util.WrapError(err, "could not initialize share sets") 109 | } 110 | 111 | res := make(chan error, 1) 112 | go func(ctx context.Context) { 113 | if factory.keyReportedOnchain(ctx) { 114 | err := factory.recoverDistributedKeyShare(ctx) 115 | if err != nil { 116 | errMsg := "could not reconstruct shares for an available distributed key" 117 | err2 := util.WrapError(err, errMsg) 118 | res <- err2 119 | } 120 | } 121 | res <- nil 122 | }(ctx) 123 | err := <-res 124 | if err != nil { 125 | return nil, err 126 | } 127 | return factory, nil 128 | } 129 | 130 | func (d *dkgReportingPluginFactory) markCompleted() { 131 | d.lock.Lock() 132 | defer d.lock.Unlock() 133 | d.dkgInProgress = false 134 | } 135 | 136 | func (d *dkgReportingPluginFactory) SetKeyConsumer(k KeyConsumer) { 137 | d.l.keyConsumer = k 138 | } 139 | -------------------------------------------------------------------------------- /internal/pvss/share_set_marshal.go: -------------------------------------------------------------------------------- 1 | package pvss 2 | 3 | import ( 4 | "bytes" 5 | 6 | "github.com/pkg/errors" 7 | "github.com/smartcontractkit/libocr/offchainreporting2plus/types" 8 | "go.dedis.ch/kyber/v3" 9 | "go.dedis.ch/kyber/v3/sign/anon" 10 | 11 | "github.com/smartcontractkit/chainlink-vrf/internal/crypto/player_idx" 12 | "github.com/smartcontractkit/chainlink-vrf/internal/crypto/point_translation" 13 | "github.com/smartcontractkit/chainlink-vrf/internal/util" 14 | ) 15 | 16 | var _ = (&ShareSet{}).Marshal 17 | 18 | func (s *ShareSet) marshal() (m []byte, err error) { 19 | if s == nil { 20 | return nil, errors.Errorf("attempt to marshal non-existent share set") 21 | } 22 | rv := make([][]byte, 4+len(s.shares)) 23 | cursor := 0 24 | 25 | if s.dealer == nil { 26 | return nil, errors.Errorf("can't marshal share set with no dealer specified") 27 | } 28 | rv[cursor] = s.dealer.Marshal() 29 | cursor++ 30 | 31 | rv[cursor], err = (&pubPoly{s.coeffCommitments}).marshal() 32 | if err != nil { 33 | return nil, errors.Wrap(err, "could not marshal coefficient commitments") 34 | } 35 | cursor++ 36 | 37 | rv[cursor], err = marshalKyberPointWithLen(s.pvssKey) 38 | if err != nil { 39 | return nil, errors.Wrap(err, "could not marshal translated PVSS public key") 40 | } 41 | cursor++ 42 | 43 | if len(s.shares) > int(player_idx.MaxPlayer) { 44 | return nil, errors.Errorf("too many shares to marshal") 45 | } 46 | rv[cursor] = player_idx.RawMarshal(player_idx.Int(len(s.shares))) 47 | cursor++ 48 | 49 | for _, sh := range s.shares { 50 | rv[cursor], err = sh.marshal() 51 | if err != nil { 52 | return nil, errors.Wrap(err, "could not marshal share in share-set") 53 | } 54 | cursor++ 55 | } 56 | 57 | if cursor != len(rv) { 58 | return nil, errors.Errorf( 59 | "ShareSet marshal fields out of registration: cursor: %d, fields: %d", 60 | cursor, len(rv), 61 | ) 62 | } 63 | return bytes.Join(rv, nil), nil 64 | } 65 | 66 | var _ = UnmarshalShareSet 67 | 68 | func unmarshalShareSet( 69 | g anon.Suite, translationGroup kyber.Group, data []byte, 70 | translation point_translation.PubKeyTranslation, domainSep types.ConfigDigest, 71 | pks []kyber.Point, 72 | ) (ss *ShareSet, rem []byte, err error) { 73 | 74 | dealer, data, err := unmarshalDealer(data) 75 | if err != nil { 76 | return nil, nil, err 77 | } 78 | 79 | coeffCommitments, data, err := unmarshalPubPoly(g, data) 80 | if err != nil { 81 | return nil, nil, errors.Wrap(err, "could not unmarshal coefficient commitments for share set") 82 | } 83 | 84 | pvssKeyB, data, err := readLenPrefixedBytes(data, 1) 85 | if err != nil { 86 | return nil, nil, util.WrapError(err, "could not read PVSS key bytes") 87 | } 88 | pvssKey := translationGroup.Point() 89 | if err2 := pvssKey.UnmarshalBinary(pvssKeyB); err2 != nil { 90 | return nil, nil, errors.Wrap(err2, "could not read translated PVSS public key") 91 | } 92 | numShares, data, err := player_idx.RawUnmarshal(data) 93 | if err != nil { 94 | return nil, nil, errors.Wrap(err, "could not get number of shares in marshalled share set") 95 | } 96 | 97 | shareSpots := make([]*share, numShares) 98 | ss = &ShareSet{ 99 | dealer, coeffCommitments.PubPoly, pvssKey, shareSpots, translation, nil, 100 | } 101 | for i := range ss.shares { 102 | ss.shares[i], data, err = unmarshal(g, translationGroup, data, ss) 103 | if err != nil { 104 | return nil, nil, errors.Wrap(err, "could not unmarshal shares in share set") 105 | } 106 | } 107 | if err := ss.verify(g, domainSep, pks); err != nil { 108 | return nil, nil, errors.Wrap(err, "unmarshaled to invalid share set") 109 | } 110 | return ss, data, nil 111 | } 112 | 113 | var _ = UnmarshalDealer 114 | 115 | func unmarshalDealer(data []byte) (*player_idx.PlayerIdx, []byte, error) { 116 | dealer, data, err := player_idx.Unmarshal(data) 117 | if err != nil { 118 | return nil, nil, errors.Wrap(err, "could not unmarshal share set dealer") 119 | } 120 | if err := dealer.NonZero(); err != nil { 121 | return nil, nil, errors.Wrap(err, "dealer index for marshalled share set is zero") 122 | } 123 | return dealer, data, nil 124 | } 125 | -------------------------------------------------------------------------------- /internal/crypto/ciphertext/cipher_text.go: -------------------------------------------------------------------------------- 1 | package ciphertext 2 | 3 | import ( 4 | "bytes" 5 | 6 | "github.com/pkg/errors" 7 | "go.dedis.ch/kyber/v3" 8 | "go.dedis.ch/kyber/v3/share" 9 | "go.dedis.ch/kyber/v3/sign/anon" 10 | 11 | "github.com/smartcontractkit/chainlink-vrf/internal/crypto/player_idx" 12 | ) 13 | 14 | type cipherText struct { 15 | cipherText []*elGamalBitPair 16 | receiver *player_idx.PlayerIdx 17 | encryptionKey kyber.Point 18 | encodesShareProof dLKnowledgeProof 19 | suite anon.Suite 20 | sharePublicCommitment kyber.Point 21 | } 22 | 23 | var _ = Encrypt 24 | 25 | func newCipherText( 26 | domainSep []byte, group anon.Suite, f *share.PriPoly, 27 | receiver *player_idx.PlayerIdx, pk kyber.Point, 28 | ) (rv *cipherText, secretShare kyber.Scalar, err error) { 29 | rv = &cipherText{suite: group, receiver: receiver, encryptionKey: pk} 30 | secretShare, rawShare, err := getShareBits(receiver, f) 31 | if err != nil { 32 | return nil, nil, err 33 | } 34 | var totalBlindingSecret kyber.Scalar 35 | 36 | rv.cipherText, totalBlindingSecret, err = encrypt(domainSep, group, rawShare, pk) 37 | if err != nil { 38 | return nil, nil, errors.Wrapf(err, "could not encrypt secret share") 39 | } 40 | 41 | if err := rv.proveFinalDLKnowledge(domainSep, group, pk, totalBlindingSecret); err != nil { 42 | 43 | return nil, nil, errors.Wrap(err, "could not prove ciphertext encodes secret share") 44 | } 45 | blindingPK := group.Point().Mul(secretShare, nil) 46 | if err := rv.verify(group, domainSep, pk, blindingPK); err != nil { 47 | 48 | panic(err) 49 | } 50 | return rv, secretShare, nil 51 | 52 | } 53 | 54 | func (c *cipherText) cipherTextDomainSep(domainSep []byte) (ds []byte, err error) { 55 | encm := make([][]byte, len(c.cipherText)+1) 56 | encm[0] = domainSep 57 | offset := 1 58 | for ctidx, ct := range c.cipherText { 59 | encm[ctidx+offset], err = ct.marshal() 60 | if err != nil { 61 | return nil, errors.Wrapf(err, "could not marshal ciphertext for domain separator") 62 | } 63 | } 64 | return bytes.Join(encm, nil), nil 65 | } 66 | 67 | var _ = ((*CipherText)(nil)).Verify 68 | 69 | func (c *cipherText) verify( 70 | group anon.Suite, domainSep []byte, encryptionPK, sharePublicCommitment kyber.Point, 71 | ) error { 72 | if len(c.cipherText) > plaintextMaxSizeBytes*4 { 73 | return errors.Errorf("ciphertext too large (max %d pairs)", 74 | plaintextMaxSizeBytes*4, 75 | ) 76 | } 77 | 78 | combinedBlindingFactors := group.Point().Sub( 79 | combinedCipherTexts(c.cipherText, group), 80 | sharePublicCommitment, 81 | ) 82 | edomain, err := c.cipherTextDomainSep(domainSep) 83 | if err != nil { 84 | return err 85 | } 86 | 87 | err = c.encodesShareProof.verify( 88 | group, edomain, encryptionPK, combinedBlindingFactors, 89 | ) 90 | if err != nil { 91 | return errors.Wrapf(err, "could not verify overall share-encoding proof") 92 | } 93 | for pairIdx, bitPair := range c.cipherText { 94 | err := bitPair.verify( 95 | encryptDomainSep(domainSep, uint8(pairIdx)), 96 | c.encryptionKey, 97 | ) 98 | if err != nil { 99 | 100 | return errors.Wrapf(err, "part of ciphertext failed to verify") 101 | } 102 | } 103 | return nil 104 | } 105 | 106 | func (c *cipherText) proveFinalDLKnowledge( 107 | domainSep []byte, group anon.Suite, pk kyber.Point, 108 | totalBlindingSecret kyber.Scalar, 109 | ) error { 110 | 111 | dlDomainSep, err := c.cipherTextDomainSep(domainSep) 112 | if err != nil { 113 | return err 114 | } 115 | c.encodesShareProof, err = newDLKnowledgeProof(dlDomainSep, group, pk, totalBlindingSecret) 116 | if err != nil { 117 | return errors.Wrapf(err, "could not construct proof that ciphertext contains secret share") 118 | } 119 | return nil 120 | } 121 | 122 | func (c *cipherText) equal(c2 *cipherText) bool { 123 | if c.receiver.Equal(c2.receiver) && 124 | c.encryptionKey.Equal(c2.encryptionKey) && 125 | c.encodesShareProof.equal(c2.encodesShareProof) && 126 | c.suite.String() == c2.suite.String() && 127 | len(c.cipherText) == len(c2.cipherText) { 128 | 129 | for i, ct := range c.cipherText { 130 | if !ct.equal(c2.cipherText[i]) { 131 | return false 132 | } 133 | } 134 | return true 135 | } 136 | return false 137 | } 138 | -------------------------------------------------------------------------------- /internal/vrf/stub_dkg.go: -------------------------------------------------------------------------------- 1 | package vrf 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/ethereum/go-ethereum/accounts/abi/bind" 7 | "github.com/ethereum/go-ethereum/common" 8 | 9 | vrf_types "github.com/smartcontractkit/chainlink-vrf/types" 10 | 11 | "go.dedis.ch/kyber/v3" 12 | "go.dedis.ch/kyber/v3/sign/anon" 13 | 14 | "github.com/smartcontractkit/chainlink-vrf/gethwrappers/testdkgstub" 15 | "github.com/smartcontractkit/chainlink-vrf/internal/common/ocr" 16 | "github.com/smartcontractkit/chainlink-vrf/internal/crypto/player_idx" 17 | "github.com/smartcontractkit/chainlink-vrf/internal/crypto/point_translation" 18 | "github.com/smartcontractkit/chainlink-vrf/internal/dkg/contract" 19 | "github.com/smartcontractkit/chainlink-vrf/internal/util" 20 | ) 21 | 22 | type testDKG interface { 23 | contract.DKG 24 | TransmitKeyToVRFContract() error 25 | } 26 | 27 | type stubDKG struct { 28 | pk kyber.Point 29 | binaryPk []byte 30 | sk kyber.Scalar 31 | contract *testdkgstub.TestDKGStub 32 | auth *bind.TransactOpts 33 | client util.CommittingClient 34 | address common.Address 35 | keyID contract.KeyID 36 | signers []common.Address 37 | transmitters []common.Address 38 | } 39 | 40 | var _ contract.DKG = (*stubDKG)(nil) 41 | var _ testDKG = (*stubDKG)(nil) 42 | 43 | func newStubDKG( 44 | auth *bind.TransactOpts, 45 | client util.CommittingClient, 46 | keyID contract.KeyID, 47 | pkGroup kyber.Group, 48 | sk kyber.Scalar, 49 | pk kyber.Point, 50 | signers []common.Address, 51 | transmitters []common.Address, 52 | 53 | ) contract.OnchainContract { 54 | key, err := pk.MarshalBinary() 55 | if err != nil { 56 | panic(util.WrapError(err, "could not marshal key")) 57 | } 58 | address, tx, stubContract, err := testdkgstub.DeployTestDKGStub( 59 | auth, 60 | client, 61 | key, 62 | keyID, 63 | ) 64 | if err != nil { 65 | panic(util.WrapError(err, "could not deploy stub DKG")) 66 | } 67 | client.Commit() 68 | if err := util.CheckStatus(context.TODO(), tx, client); err != nil { 69 | panic(util.WrapError(err, "deployment of stub DKG failed")) 70 | } 71 | return contract.OnchainContract{ 72 | &stubDKG{pk, 73 | key, 74 | sk, stubContract, 75 | auth, 76 | client, 77 | address, 78 | keyID, 79 | signers, 80 | transmitters, 81 | }, 82 | pkGroup, 83 | } 84 | } 85 | 86 | func (d *stubDKG) GetKey( 87 | _ context.Context, 88 | _ contract.KeyID, 89 | _ [32]byte, 90 | ) (contract.OnchainKeyData, error) { 91 | return contract.OnchainKeyData{PublicKey: d.binaryPk}, nil 92 | } 93 | 94 | func (d *stubDKG) AddClient( 95 | _ context.Context, 96 | _ [32]byte, 97 | clientAddress common.Address, 98 | ) error { 99 | tx, err := d.contract.AddClient(d.auth, d.keyID, clientAddress) 100 | if err != nil { 101 | return util.WrapErrorf( 102 | err, 103 | "could not add client 0x%x to stub DKG", 104 | clientAddress, 105 | ) 106 | } 107 | d.client.Commit() 108 | if err := util.CheckStatus(context.TODO(), tx, d.client); err != nil { 109 | return util.WrapErrorf( 110 | err, 111 | "failed to add client 0x%x to stub DKG", 112 | clientAddress, 113 | ) 114 | } 115 | return nil 116 | } 117 | 118 | func (d *stubDKG) Address() common.Address { 119 | return d.address 120 | } 121 | 122 | func (d *stubDKG) CurrentCommittee( 123 | ctx context.Context, 124 | ) (ocr.OCRCommittee, error) { 125 | return vrf_types.OCRCommittee{d.signers, d.transmitters}, nil 126 | } 127 | 128 | func (d *stubDKG) TransmitKeyToVRFContract() error { 129 | tx, err := d.contract.KeyGenerated( 130 | d.auth, 131 | testdkgstub.KeyDataStructKeyData{d.binaryPk, nil}, 132 | ) 133 | if err != nil { 134 | return util.WrapErrorf( 135 | err, 136 | "failed in call to KeyGenerated", 137 | ) 138 | } 139 | d.client.Commit() 140 | if err := util.CheckStatus(context.TODO(), tx, d.client); err != nil { 141 | return util.WrapErrorf( 142 | err, 143 | "failed to generate key", 144 | ) 145 | } 146 | return nil 147 | } 148 | 149 | func (d *stubDKG) InitiateDKG( 150 | _ context.Context, 151 | _ ocr.OCRCommittee, 152 | _ player_idx.Int, 153 | _ contract.KeyID, 154 | _ contract.EncryptionPublicKeys, 155 | _ contract.SigningPublicKeys, 156 | _ anon.Suite, 157 | _ point_translation.PubKeyTranslation, 158 | ) error { 159 | panic("implement me") 160 | } 161 | -------------------------------------------------------------------------------- /internal/dkg/share_record.go: -------------------------------------------------------------------------------- 1 | package dkg 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | 7 | "github.com/pkg/errors" 8 | 9 | "github.com/smartcontractkit/libocr/offchainreporting2plus/types" 10 | 11 | "github.com/smartcontractkit/chainlink-vrf/internal/crypto/ciphertext/schnorr" 12 | "github.com/smartcontractkit/chainlink-vrf/internal/crypto/point_translation" 13 | "github.com/smartcontractkit/chainlink-vrf/internal/pvss" 14 | 15 | "go.dedis.ch/kyber/v3" 16 | "go.dedis.ch/kyber/v3/sign/anon" 17 | ) 18 | 19 | type shareRecord struct { 20 | shareSet *pvss.ShareSet 21 | 22 | marshaledShareRecord []byte 23 | 24 | sig signature 25 | } 26 | 27 | type signature struct{ sig []byte } 28 | 29 | func newShareRecord( 30 | suite schnorr.Suite, shareSet *pvss.ShareSet, sk kyber.Scalar, 31 | domainSep types.ConfigDigest, 32 | ) (*shareRecord, error) { 33 | rv := &shareRecord{shareSet: shareSet} 34 | 35 | if err := rv.sign(suite, domainSep, sk); err != nil { 36 | return nil, errors.Wrapf(err, "could not sign new share record") 37 | } 38 | return rv, nil 39 | } 40 | 41 | const shareLenBound = 10_000_000 42 | 43 | func (r *shareRecord) marshal() ([]byte, error) { 44 | msr := r.marshaledShareRecord 45 | if msr == nil { 46 | ss, err := r.shareSet.Marshal() 47 | if err != nil { 48 | return nil, errors.Wrap(err, "could not marshal share record") 49 | } 50 | if len(ss) > shareLenBound { 51 | return nil, errors.Wrap(err, "could not marshal share record: marshalled share set too long") 52 | } 53 | msr = msrComponents(ss, r.sig.sig) 54 | r.marshaledShareRecord = msr 55 | } 56 | return msr, nil 57 | } 58 | 59 | func msrComponents(ssBytes, sig []byte) []byte { 60 | ssLenData := make([]byte, 4) 61 | binary.BigEndian.PutUint32(ssLenData, uint32(len(ssBytes))) 62 | return bytes.Join([][]byte{ssLenData, ssBytes, sig}, nil) 63 | } 64 | 65 | func unmarshalShareRecord( 66 | sigSuite schnorr.Suite, g anon.Suite, translationGroup kyber.Group, 67 | data []byte, translation point_translation.PubKeyTranslation, 68 | cfgDgst types.ConfigDigest, pks []kyber.Point, spks []kyber.Point, 69 | ) (*shareRecord, []byte, error) { 70 | if len(data) < 4 { 71 | return nil, nil, errors.Errorf("marshalled share record too short, %d bytes", len(data)) 72 | } 73 | if len(data) > shareLenBound { 74 | return nil, nil, errors.Errorf("marshalled share record too long, %d bytes", len(data)) 75 | } 76 | ssLenData, data := data[:4], data[4:] 77 | ssLen := binary.BigEndian.Uint32(ssLenData) 78 | if int(ssLen) > len(data)+1 { 79 | return nil, nil, errors.Errorf( 80 | "marshalled share record too short, %d bytes, need %d bytes", 81 | len(data), ssLen, 82 | ) 83 | } 84 | if ssLen > shareLenBound { 85 | return nil, nil, errors.Errorf("marshalled share record too long, %d bytes", len(data)) 86 | } 87 | ssBytes, data := data[:ssLen], data[ssLen:] 88 | 89 | dealer, err := pvss.UnmarshalDealer(ssBytes) 90 | if err != nil { 91 | return nil, nil, errors.Wrap(err, "could not get signer identity for share record") 92 | } 93 | if !dealer.AtMost(uint8(len(spks))) { 94 | return nil, nil, errors.Errorf("dealer out of range") 95 | } 96 | 97 | sig, data := data[:64], data[64:] 98 | dealerPK := dealer.Index(spks).(kyber.Point) 99 | 100 | msg := append(cfgDgst[:], ssBytes...) 101 | err = schnorr.Verify(sigSuite, dealerPK, msg, sig) 102 | if err != nil { 103 | return nil, nil, errors.Wrap(err, "invalid signature on marshalled share set") 104 | } 105 | 106 | shareSet, _, err := pvss.UnmarshalShareSet( 107 | g, translationGroup, ssBytes, translation, cfgDgst, pks, 108 | ) 109 | if err != nil { 110 | return nil, nil, errors.Wrap(err, "could not unmarshal share record") 111 | } 112 | 113 | msr := msrComponents(ssBytes, sig) 114 | return &shareRecord{shareSet, msr, signature{sig}}, data, nil 115 | } 116 | 117 | func (r *shareRecord) sign(suite schnorr.Suite, 118 | domainSep types.ConfigDigest, 119 | sk kyber.Scalar) error { 120 | ss, err := r.shareSet.Marshal() 121 | if err != nil { 122 | return errors.Wrap(err, "could not marshal share set for signing") 123 | } 124 | msg := append(domainSep[:], ss...) 125 | r.sig.sig, err = schnorr.Sign(suite, sk, msg) 126 | if err != nil { 127 | return errors.Wrap(err, "could sign share set") 128 | } 129 | 130 | pk := suite.Point().Mul(sk, nil) 131 | 132 | if err := schnorr.Verify(suite, pk, msg, r.sig.sig); err != nil { 133 | panic("failed to verify own signature: " + err.Error()) 134 | } 135 | return nil 136 | } 137 | -------------------------------------------------------------------------------- /ocr2vrf/00_public_interface.go: -------------------------------------------------------------------------------- 1 | package ocr2vrf 2 | 3 | import ( 4 | "go.uber.org/multierr" 5 | 6 | "github.com/pkg/errors" 7 | offchainreporting "github.com/smartcontractkit/libocr/offchainreporting2plus" 8 | 9 | "github.com/smartcontractkit/chainlink-vrf/internal/dkg" 10 | "github.com/smartcontractkit/chainlink-vrf/internal/util" 11 | "github.com/smartcontractkit/chainlink-vrf/internal/vrf" 12 | "github.com/smartcontractkit/chainlink-vrf/internal/vrf/protobuf" 13 | ) 14 | 15 | type OCR2VRF struct { 16 | dkg, vrf offchainreporting.Oracle 17 | keyTransceiver *vrf.KeyTransceiver 18 | } 19 | 20 | type EthereumReportSerializer = vrf.EthereumReportSerializer 21 | 22 | func NewOCR2VRF(a DKGVRFArgs) (*OCR2VRF, error) { 23 | transceiver := vrf.NewKeyTransceiver(a.KeyID) 24 | dkgReportingPluginFactory := dkg.NewReportingPluginFactory( 25 | a.Esk, 26 | a.Ssk, 27 | a.KeyID, 28 | a.DKGContract, 29 | a.DKGLogger, 30 | transceiver, 31 | a.DKGSharePersistence, 32 | ) 33 | 34 | vrfReportingPluginFactory, err := vrf.NewVRFReportingPluginFactory( 35 | a.KeyID, 36 | transceiver, 37 | a.Coordinator, 38 | a.Serializer, 39 | a.VRFLogger, 40 | a.JuelsPerFeeCoin, 41 | a.ReasonableGasPrice, 42 | ) 43 | if err != nil { 44 | return nil, errors.Wrap(err, "could not instantiate VRF reporting plugin factory") 45 | } 46 | 47 | if a.DKGReportingPluginFactoryDecorator != nil { 48 | dkgReportingPluginFactory = a.DKGReportingPluginFactoryDecorator(dkgReportingPluginFactory) 49 | } 50 | 51 | if a.VRFReportingPluginFactoryDecorator != nil { 52 | vrfReportingPluginFactory = a.VRFReportingPluginFactoryDecorator(vrfReportingPluginFactory) 53 | } 54 | 55 | deployedDKG, err := offchainreporting.NewOracle(offchainreporting.OCR2OracleArgs{ 56 | BinaryNetworkEndpointFactory: a.BinaryNetworkEndpointFactory, 57 | V2Bootstrappers: a.V2Bootstrappers, 58 | ContractConfigTracker: a.DKGContractConfigTracker, 59 | ContractTransmitter: a.DKGContractTransmitter, 60 | Database: a.DKGDatabase, 61 | LocalConfig: a.DKGLocalConfig, 62 | Logger: a.DKGLogger, 63 | MonitoringEndpoint: a.DKGMonitoringEndpoint, 64 | OffchainConfigDigester: a.DKGOffchainConfigDigester, 65 | OffchainKeyring: a.OffchainKeyring, 66 | OnchainKeyring: a.OnchainKeyring, 67 | ReportingPluginFactory: dkgReportingPluginFactory, 68 | }) 69 | if err != nil { 70 | return nil, util.WrapError(err, "while setting up new DKG oracle") 71 | } 72 | confirmationDelays := make(map[uint32]struct{}, len(a.ConfirmationDelays)) 73 | for _, d := range a.ConfirmationDelays { 74 | confirmationDelays[d] = struct{}{} 75 | } 76 | 77 | deployedVRF, err := offchainreporting.NewOracle(offchainreporting.OCR2OracleArgs{ 78 | BinaryNetworkEndpointFactory: a.BinaryNetworkEndpointFactory, 79 | V2Bootstrappers: a.V2Bootstrappers, 80 | ContractConfigTracker: a.VRFContractConfigTracker, 81 | ContractTransmitter: a.VRFContractTransmitter, 82 | Database: a.VRFDatabase, 83 | LocalConfig: a.VRFLocalConfig, 84 | Logger: a.VRFLogger, 85 | MonitoringEndpoint: a.VRFMonitoringEndpoint, 86 | OffchainConfigDigester: a.VRFOffchainConfigDigester, 87 | OffchainKeyring: a.OffchainKeyring, 88 | OnchainKeyring: a.OnchainKeyring, 89 | ReportingPluginFactory: vrfReportingPluginFactory, 90 | }) 91 | if err != nil { 92 | return nil, util.WrapError(err, "while setting up VRF oracle") 93 | } 94 | return &OCR2VRF{deployedDKG, deployedVRF, transceiver}, nil 95 | } 96 | 97 | func OffchainConfig(v *protobuf.CoordinatorConfig) []byte { 98 | return vrf.OffchainConfig(v) 99 | } 100 | 101 | func OnchainConfig(confDelays map[uint32]struct{}) []byte { 102 | return vrf.OnchainConfig(confDelays) 103 | } 104 | 105 | func (o *OCR2VRF) Start() error { 106 | if err := o.dkg.Start(); err != nil { 107 | return util.WrapError(err, "starting DKG oracle") 108 | } 109 | if err := util.WrapError(o.vrf.Start(), "starting VRF oracle"); err != nil { 110 | return multierr.Append(err, util.WrapError( 111 | o.dkg.Close(), 112 | "closing DKG process after starting VRF process failed", 113 | )) 114 | } 115 | return nil 116 | } 117 | 118 | func (o *OCR2VRF) Close() error { 119 | return multierr.Append( 120 | util.WrapError(o.dkg.Close(), "while closing DKG process"), 121 | util.WrapError(o.vrf.Close(), "while closing VRF process"), 122 | ) 123 | } 124 | -------------------------------------------------------------------------------- /altbn_128/g2_point.go: -------------------------------------------------------------------------------- 1 | package altbn_128 2 | 3 | import ( 4 | "bytes" 5 | "crypto/cipher" 6 | "fmt" 7 | "io" 8 | "math/big" 9 | "sync" 10 | 11 | bn256 "github.com/ethereum/go-ethereum/crypto/bn256/cloudflare" 12 | 13 | "github.com/smartcontractkit/chainlink-vrf/altbn_128/scalar" 14 | "github.com/smartcontractkit/chainlink-vrf/internal/util" 15 | 16 | "go.dedis.ch/kyber/v3" 17 | ) 18 | 19 | type g2Point struct{ G2 g2Interface } 20 | 21 | var _ kyber.Point = (*g2Point)(nil) 22 | 23 | func bn256G2Null() *bn256.G2 { 24 | g := new(bn256.G2).ScalarBaseMult(big.NewInt(1)) 25 | ng := new(bn256.G2).Neg(g) 26 | return new(bn256.G2).Add(g, ng) 27 | } 28 | 29 | func newG2Point() *g2Point { 30 | return &g2Point{bn256G2Null()} 31 | } 32 | 33 | func (p *g2Point) ensurePG2() { 34 | 35 | if p == nil { 36 | panic("attempt to ensure allocation of nil *g2Point") 37 | } 38 | if p.G2 == nil { 39 | p.G2 = bn256G2Null() 40 | } 41 | } 42 | 43 | func (p *g2Point) mustBeValidPoint() { 44 | if _, err := new(bn256.G2).Unmarshal(p.G2.Marshal()); err != nil { 45 | panic(util.WrapErrorf(err, "invalid G₂ point: 0x%x", p.G2.Marshal())) 46 | } 47 | } 48 | 49 | func (p *g2Point) MarshalSize() int { 50 | panic("not implemented") 51 | } 52 | 53 | func (p *g2Point) MarshalBinary() (data []byte, err error) { 54 | p.mustBeValidPoint() 55 | return p.G2.Marshal(), nil 56 | } 57 | 58 | func (p *g2Point) UnmarshalBinary(data []byte) error { 59 | if p == nil { 60 | return fmt.Errorf("can't assign to nil pointer") 61 | } 62 | 63 | p.G2 = new(bn256.G2) 64 | rem, err := p.G2.Unmarshal(data) 65 | if err != nil { 66 | return util.WrapErrorf(err, "while unmarshalling to G₂ point: 0x%x", data) 67 | } 68 | if len(rem) > 0 { 69 | errMsg := "overage of %d bytes in representation of AltBN-128 G2 point" 70 | return fmt.Errorf(errMsg, len(rem)) 71 | } 72 | return nil 73 | } 74 | 75 | func (p *g2Point) Mul(s kyber.Scalar, p2 kyber.Point) kyber.Point { 76 | sc := s.(*scalar.Scalar) 77 | if p2 == nil { 78 | p2 = newG2Point().Base() 79 | } 80 | pP := p2.(*g2Point) 81 | p.ensurePG2() 82 | _ = p.G2.ScalarMult(pP.G2.(*bn256.G2), sc.Big()) 83 | return p 84 | } 85 | 86 | func (p *g2Point) Add(a kyber.Point, b kyber.Point) kyber.Point { 87 | aG2 := a.(*g2Point) 88 | bG2 := b.(*g2Point) 89 | aG2.mustBeValidPoint() 90 | bG2.mustBeValidPoint() 91 | p.ensurePG2() 92 | _ = p.G2.Add(aG2.G2.(*bn256.G2), bG2.G2.(*bn256.G2)) 93 | return p 94 | } 95 | 96 | var g2BasePoint *g2Point 97 | var g2BasePointLock = sync.RWMutex{} 98 | 99 | func (p *g2Point) Base() kyber.Point { 100 | g2BasePointLock.Lock() 101 | defer g2BasePointLock.Unlock() 102 | if g2BasePoint == nil { 103 | g2BasePoint = &g2Point{new(bn256.G2).ScalarBaseMult(one)} 104 | } 105 | p.Set(g2BasePoint) 106 | return p 107 | } 108 | 109 | func (p *g2Point) Null() kyber.Point { 110 | p.ensurePG2() 111 | p.G2 = bn256G2Null() 112 | return p 113 | } 114 | 115 | func (p *g2Point) Sub(a kyber.Point, b kyber.Point) kyber.Point { 116 | aG2 := a.(*g2Point) 117 | bG2 := b.Clone().Neg(b).(*g2Point) 118 | aG2.mustBeValidPoint() 119 | bG2.mustBeValidPoint() 120 | p.ensurePG2() 121 | _ = p.G2.Add(aG2.G2.(*bn256.G2), bG2.G2.(*bn256.G2)) 122 | return p 123 | } 124 | 125 | func (p *g2Point) Neg(a kyber.Point) kyber.Point { 126 | _ = p.G2.Neg(a.(*g2Point).G2.(*bn256.G2)) 127 | return p 128 | } 129 | 130 | func (p *g2Point) Set(p2 kyber.Point) kyber.Point { 131 | p.ensurePG2() 132 | p.G2 = new(bn256.G2) 133 | _ = p.G2.Set(p2.(*g2Point).G2.(*bn256.G2)) 134 | return p 135 | } 136 | 137 | func (p *g2Point) Equal(p2 kyber.Point) bool { 138 | p2g2, ok := p2.(*g2Point) 139 | return ok && bytes.Equal(p.G2.Marshal(), p2g2.G2.Marshal()) 140 | } 141 | 142 | func (p *g2Point) Clone() kyber.Point { 143 | p.ensurePG2() 144 | rv := newG2Point() 145 | rv.G2.Set(p.G2.(*bn256.G2)) 146 | return rv 147 | } 148 | 149 | func (p *g2Point) Pick(rand cipher.Stream) kyber.Point { 150 | _ = p.G2.ScalarBaseMult(i(0).Pick(rand).(*scalar.Scalar).Big()) 151 | return p 152 | } 153 | 154 | func (p *g2Point) String() string { return fmt.Sprintf("&g2Point{%s}", p.G2.String()) } 155 | 156 | func (p *g2Point) MarshalTo(w io.Writer) (int, error) { panic("not implemented") } 157 | func (p *g2Point) UnmarshalFrom(r io.Reader) (int, error) { panic("not implemented") } 158 | func (p *g2Point) EmbedLen() int { panic("not implemented") } 159 | func (p *g2Point) Embed(data []byte, r cipher.Stream) kyber.Point { panic("not implemented") } 160 | func (p *g2Point) Data() ([]byte, error) { panic("not implemented") } 161 | -------------------------------------------------------------------------------- /internal/dkg/secret_share.go: -------------------------------------------------------------------------------- 1 | package dkg 2 | 3 | import ( 4 | "bytes" 5 | "crypto/aes" 6 | "crypto/cipher" 7 | "crypto/rand" 8 | "crypto/sha256" 9 | "encoding/binary" 10 | "io" 11 | "testing" 12 | 13 | "github.com/pkg/errors" 14 | 15 | "github.com/smartcontractkit/chainlink-vrf/internal/crypto/player_idx" 16 | 17 | "go.dedis.ch/kyber/v3" 18 | 19 | "golang.org/x/crypto/pbkdf2" 20 | ) 21 | 22 | type SecretShare struct { 23 | Idx player_idx.PlayerIdx 24 | share kyber.Scalar 25 | } 26 | 27 | func (s *SecretShare) Mul(p kyber.Point) kyber.Point { 28 | return p.Clone().Mul(s.share, p) 29 | } 30 | 31 | var defaultPBKDF2NumberOfIterations uint32 = 150_000 32 | 33 | var encryptionVersionNum uint8 34 | 35 | const saltLen, nonceLen = 32, 12 36 | 37 | const preambleLength = 1 + saltLen + 4 + nonceLen 38 | 39 | func (s *SecretShare) Encrypt( 40 | passphrase []byte, itersTestingOnly ...uint32, 41 | ) ([]byte, error) { 42 | if len(itersTestingOnly) > 1 { 43 | return nil, errors.Errorf("at most one derived-key iteration value allowed") 44 | } 45 | var salt [saltLen]byte 46 | if _, err := io.ReadFull(rand.Reader, salt[:]); err != nil { 47 | return nil, errors.Wrap(err, "could not sample salt value for derived key "+ 48 | "in encryption of secret share") 49 | } 50 | iter := defaultPBKDF2NumberOfIterations 51 | if len(itersTestingOnly) > 0 { 52 | iter = itersTestingOnly[0] 53 | } 54 | var iterBin [4]byte 55 | binary.BigEndian.PutUint32(iterBin[:], iter) 56 | 57 | var nonce [nonceLen]byte 58 | if _, err := io.ReadFull(rand.Reader, nonce[:]); err != nil { 59 | return nil, errors.Wrap(err, "could not sample nonce for encryption of "+ 60 | "secret share") 61 | } 62 | plaintext, err := s.share.MarshalBinary() 63 | if err != nil { 64 | return nil, errors.Wrap(err, "could not serialize secret share for "+ 65 | "encryption") 66 | } 67 | preamble := bytes.Join( 68 | [][]byte{{encryptionVersionNum}, salt[:], iterBin[:], nonce[:]}, nil, 69 | ) 70 | if len(preamble) != preambleLength { 71 | panic("wrong length for preamble") 72 | } 73 | gcm, err := newGCM(passphrase, salt, iter, "en") 74 | if err != nil { 75 | return nil, err 76 | } 77 | return gcm.Seal(preamble, nonce[:], plaintext, nil), nil 78 | } 79 | 80 | func (s *SecretShare) Decrypt( 81 | passphrase, ciphertext []byte, shareGroup kyber.Group, 82 | ) error { 83 | cursor := 0 84 | versionNum := ciphertext[0] 85 | cursor++ 86 | if versionNum != encryptionVersionNum { 87 | return errors.Errorf( 88 | "don't know how to decrypt version %d ciphertexts", versionNum, 89 | ) 90 | } 91 | var salt [saltLen]byte 92 | if n := copy(salt[:], ciphertext[cursor:cursor+saltLen]); n != saltLen { 93 | return errors.Errorf("failed to read entire salt") 94 | } 95 | cursor += saltLen 96 | var iterBin [4]byte 97 | if n := copy(iterBin[:], ciphertext[cursor:cursor+4]); n != 4 { 98 | return errors.Errorf("failed to read entire number of pbkdf2 iterations") 99 | } 100 | cursor += 4 101 | iter := binary.BigEndian.Uint32(iterBin[:]) 102 | var nonce [nonceLen]byte 103 | if n := copy(nonce[:], ciphertext[cursor:cursor+nonceLen]); n != nonceLen { 104 | return errors.Errorf("failed to read entire nonce") 105 | } 106 | cursor += nonceLen 107 | if cursor != preambleLength { 108 | panic("wrong preamble length") 109 | } 110 | gcm, err := newGCM(passphrase, salt, iter, "de") 111 | if err != nil { 112 | return err 113 | } 114 | plaintext, err := gcm.Open(nil, nonce[:], ciphertext[preambleLength:], nil) 115 | if err != nil { 116 | return errors.Wrap(err, "could not decrypt secret share") 117 | } 118 | s.share = shareGroup.Scalar() 119 | return errors.Wrap( 120 | s.share.UnmarshalBinary(plaintext), 121 | "could not deserialize decrypted secret share", 122 | ) 123 | } 124 | 125 | func (s SecretShare) Equal(os SecretShare) bool { 126 | return s.Idx.Equal(&os.Idx) && s.share.Equal(os.share) 127 | } 128 | 129 | func (s SecretShare) Clone() *SecretShare { 130 | return &SecretShare{s.Idx, s.share.Clone()} 131 | } 132 | 133 | func newGCM( 134 | passphrase []byte, salt [saltLen]byte, iter uint32, dir string, 135 | ) (cipher.AEAD, error) { 136 | errSuff := dir + "cryption of secret share" 137 | dk := pbkdf2.Key(passphrase, salt[:], int(iter), 32, sha256.New) 138 | block, err := aes.NewCipher(dk) 139 | if err != nil { 140 | return nil, errors.Wrap(err, "could not construct block cipher for "+errSuff) 141 | } 142 | gcm, err := cipher.NewGCM(block) 143 | if err != nil { 144 | return nil, errors.Wrap(err, "could not construct GCM cipher for "+errSuff) 145 | } 146 | return gcm, nil 147 | } 148 | 149 | func XXXNewSecretShareTestingOnly( 150 | t *testing.T, i player_idx.PlayerIdx, s kyber.Scalar, 151 | ) *SecretShare { 152 | return &SecretShare{i, s} 153 | } 154 | -------------------------------------------------------------------------------- /internal/crypto/ciphertext/bit_pair_implementation.go: -------------------------------------------------------------------------------- 1 | package ciphertext 2 | 3 | import ( 4 | "bytes" 5 | "reflect" 6 | 7 | "github.com/pkg/errors" 8 | "go.dedis.ch/kyber/v3" 9 | "go.dedis.ch/kyber/v3/sign/anon" 10 | ) 11 | 12 | type elGamalBitPair struct { 13 | blindingCommitment, cipherTextTerm kyber.Point 14 | 15 | proof bitPairProof 16 | 17 | suite anon.Suite 18 | } 19 | 20 | func newElGamalBitPair(suite anon.Suite, domainSep []byte, b int, pk kyber.Point, 21 | ) (bp *elGamalBitPair, blindingSecret kyber.Scalar, err error) { 22 | if reflect.TypeOf(pk) != reflect.TypeOf(suite.Point()) { 23 | return nil, nil, errors.Errorf( 24 | "public key (of type %T) does not match group points (of type %T)", 25 | pk, suite.Point(), 26 | ) 27 | } 28 | if (b > 3) || (b < 0) { 29 | return nil, nil, errors.Errorf("can only encode 0b00, 0b01, 0b10 or 0b11, got 0b%#b", b) 30 | } 31 | x := suite.Scalar().Pick(suite.RandomStream()) 32 | blindingCommitment := suite.Point().Mul(x, nil) 33 | plaintextTerm := suite.Point().Mul(suite.Scalar().SetInt64(int64(b)), nil) 34 | blindingTerm := suite.Point().Mul(x, pk) 35 | cipherTextTerm := suite.Point().Add(blindingTerm, plaintextTerm) 36 | proof, err := proveBitPair(suite, domainSep, pk, blindingCommitment, cipherTextTerm, b, x) 37 | if err != nil { 38 | 39 | return nil, nil, errors.Wrapf(err, "while constructing encrypted bit pair") 40 | } 41 | rv := &elGamalBitPair{blindingCommitment, cipherTextTerm, proof, suite} 42 | if err := rv.verify(domainSep, pk); err != nil { 43 | 44 | pkm, err2 := pk.MarshalBinary() 45 | if err2 != nil { 46 | return nil, nil, 47 | errors.Wrapf( 48 | err2, "error while marshalling public key in newElGamalBitPair: %s", err, 49 | ) 50 | } 51 | return nil, nil, 52 | errors.Wrapf( 53 | err, 54 | "made unverifiable bit pair! domainSep: 0x%x pk: 0x%x %s x: %s", 55 | domainSep, pkm, err, x) 56 | } 57 | return rv, x, nil 58 | } 59 | 60 | var _ = (&CipherText{}).Verify 61 | 62 | func (e *elGamalBitPair) verify(domainSep []byte, encryptionKey kyber.Point) error { 63 | pgroup, err := makeProductGroup(e.suite, encryptionKey) 64 | if err != nil { 65 | return errors.Wrapf(err, "while verifying bit pair encryption") 66 | } 67 | return e.proof.verify(e.suite, domainSep, pgroup, e.blindingCommitment, e.cipherTextTerm, 68 | encryptionKey) 69 | } 70 | 71 | func (e *elGamalBitPair) decrypt(sk kyber.Scalar) (int, error) { 72 | if reflect.TypeOf(sk) != reflect.TypeOf(e.suite.Scalar()) { 73 | return 0, errors.Errorf("need scalar of type %T, got type %T", e.suite.Scalar(), sk) 74 | } 75 | plainText := e.suite.Point() 76 | 77 | plainText.Sub(e.cipherTextTerm, plainText.Mul(sk, e.blindingCommitment)) 78 | for i, pt := range memPlainTexts(e.suite) { 79 | if pt.Equal(plainText) { 80 | return i, nil 81 | } 82 | } 83 | return 0, errors.Errorf("plaintext unknown") 84 | } 85 | 86 | func (e *elGamalBitPair) marshal() (m []byte, err error) { 87 | rv := make([][]byte, 3) 88 | cursor := 0 89 | 90 | rv[cursor], err = e.blindingCommitment.MarshalBinary() 91 | cursor++ 92 | if err != nil { 93 | return nil, errors.Wrapf(err, "could not marshal blinding commitment") 94 | } 95 | 96 | rv[cursor], err = e.cipherTextTerm.MarshalBinary() 97 | cursor++ 98 | if err != nil { 99 | return nil, errors.Wrapf(err, "could not marshal ciphertext point") 100 | } 101 | 102 | rv[cursor] = e.proof[:] 103 | if cursor != len(rv)-1 { 104 | panic("return values out of registration") 105 | } 106 | 107 | return bytes.Join(rv, nil), nil 108 | } 109 | 110 | func unmarshalElGamalBitPair(suite anon.Suite, d []byte, 111 | ) (e *elGamalBitPair, err error) { 112 | if len(d) < elGamalBitPairMarshalLength(suite) { 113 | return nil, errors.Errorf("marshal data too short to contain elGamalBitPair") 114 | } 115 | e = &elGamalBitPair{suite: suite} 116 | e.blindingCommitment = suite.Point() 117 | pointLen := e.blindingCommitment.MarshalSize() 118 | 119 | if err := e.blindingCommitment.UnmarshalBinary(d[:pointLen]); err != nil { 120 | return nil, errors.Wrap(err, "could not unmarshal blinding commitment") 121 | } 122 | remainder := d[pointLen:] 123 | 124 | e.cipherTextTerm = suite.Point() 125 | if err := e.cipherTextTerm.UnmarshalBinary(remainder[:pointLen]); err != nil { 126 | return nil, errors.Wrap(err, "could not unmarshal ciphertext point") 127 | } 128 | remainder = remainder[pointLen:] 129 | 130 | proofLen := bitPairProofLen(suite) 131 | e.proof = append([]byte{}, remainder[:proofLen]...) 132 | 133 | return e, nil 134 | } 135 | 136 | func elGamalBitPairMarshalLength(suite anon.Suite) int { 137 | pointLen := suite.PointLen() 138 | return pointLen + 139 | pointLen + 140 | bitPairProofLen(suite) 141 | } 142 | 143 | func (e *elGamalBitPair) equal(e2 *elGamalBitPair) bool { 144 | return e.blindingCommitment.Equal(e2.blindingCommitment) && 145 | e.cipherTextTerm.Equal(e2.cipherTextTerm) && 146 | bytes.Equal(e.proof[:], e2.proof[:]) && 147 | e.suite.String() == e2.suite.String() 148 | } 149 | -------------------------------------------------------------------------------- /internal/dkg/report.go: -------------------------------------------------------------------------------- 1 | package dkg 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/pkg/errors" 7 | "go.dedis.ch/kyber/v3" 8 | 9 | "github.com/smartcontractkit/libocr/commontypes" 10 | "github.com/smartcontractkit/libocr/offchainreporting2plus/types" 11 | 12 | "github.com/smartcontractkit/chainlink-vrf/internal/crypto/player_idx" 13 | "github.com/smartcontractkit/chainlink-vrf/internal/dkg/contract" 14 | "github.com/smartcontractkit/chainlink-vrf/internal/pvss" 15 | "github.com/smartcontractkit/chainlink-vrf/types/hash" 16 | ) 17 | 18 | var _ = (&dkg{}).Report 19 | 20 | func (d *dkg) recoverShareRecord(report []byte) (r *shareRecord, h hash.Hash, err error) { 21 | h = hash.GetHash(report) 22 | r, present := d.shareSets[h] 23 | if !present { 24 | r, err = unmarshalSignedShareRecord(d, report) 25 | if err != nil { 26 | return nil, hash.Zero, err 27 | } 28 | d.shareSets[h] = r 29 | } 30 | return r, h, nil 31 | } 32 | 33 | type validShareRecords struct { 34 | includedDealers map[player_idx.PlayerIdx]bool 35 | 36 | includedHashes hash.Hashes 37 | 38 | validShareCount int 39 | players []*player_idx.PlayerIdx 40 | d *dkg 41 | 42 | aggregatePublicKey kyber.Point 43 | 44 | context context.Context 45 | } 46 | 47 | func (d *dkg) newValidShareRecords( 48 | ctx context.Context, 49 | ) (*validShareRecords, error) { 50 | players, err := player_idx.PlayerIdxs(player_idx.Int(len(d.epks))) 51 | if err != nil { 52 | return nil, errors.Wrap(err, "could not construct player list") 53 | } 54 | return &validShareRecords{ 55 | map[player_idx.PlayerIdx]bool{}, 56 | hash.MakeHashes(), 57 | 0, 58 | players, 59 | d, 60 | d.translationGroup.Point(), 61 | ctx, 62 | }, nil 63 | } 64 | 65 | func (v *validShareRecords) validateShareRecord( 66 | r *shareRecord, 67 | sender *player_idx.PlayerIdx, 68 | ) (reportedDealer *player_idx.PlayerIdx, err error) { 69 | reportedDealer, err = r.shareSet.Dealer() 70 | if err != nil { 71 | return nil, errors.Wrap( 72 | err, "bad dealer on prospective share record for report", 73 | ) 74 | } 75 | if !v.d.keyReportedOnchain(v.context) { 76 | if v.includedDealers[*reportedDealer] { 77 | return nil, errors.Errorf( 78 | "excluding share set from dealer %d, which already has a share set "+ 79 | "incorporated in the current report", 80 | reportedDealer, 81 | ) 82 | } 83 | if !sender.Equal(reportedDealer) && !v.d.keyReportedOnchain(v.context) { 84 | return nil, errors.Errorf( 85 | "share-set Observer (%v) and report's self-attributed player (%v) "+ 86 | "indices don't match, during DKG-construction phase", 87 | sender, reportedDealer, 88 | ) 89 | } 90 | } 91 | return reportedDealer, nil 92 | } 93 | 94 | func (v *validShareRecords) storeValidShareSet( 95 | marshaledShareSet []byte, 96 | reportedDealer player_idx.PlayerIdx, 97 | shareSet *pvss.ShareSet, 98 | h *hash.Hash, 99 | ) { 100 | v.includedDealers[reportedDealer] = true 101 | 102 | agg := v.aggregatePublicKey.Clone() 103 | _ = v.aggregatePublicKey.Add(agg, shareSet.PublicKey()) 104 | v.includedHashes.Add(*h) 105 | go v.persistShares(reportedDealer, marshaledShareSet, *h) 106 | v.validShareCount++ 107 | } 108 | 109 | func (v *validShareRecords) processShareSet(aobs types.AttributedObservation) { 110 | if int(aobs.Observer) >= len(v.players) { 111 | v.d.logger.Debug("observer index out of range", commontypes.LogFields{ 112 | "observer index": aobs.Observer, "max index": len(v.players) - 1, 113 | }) 114 | return 115 | } 116 | sender := v.players[aobs.Observer] 117 | 118 | r, h, err := v.d.recoverShareRecord(aobs.Observation) 119 | if err != nil { 120 | v.d.logger.Warn("excluding invalid share set from report", 121 | commontypes.LogFields{"err": err, "sender": sender}) 122 | return 123 | } 124 | 125 | reportedDealer, err := v.validateShareRecord(r, sender) 126 | if err != nil { 127 | v.d.logger.Warn("invalid share set", commontypes.LogFields{"err": err}) 128 | return 129 | } 130 | v.storeValidShareSet(aobs.Observation, *reportedDealer, r.shareSet, &h) 131 | } 132 | 133 | func (v *validShareRecords) enoughShareSets() bool { 134 | return v.validShareCount > int(v.d.t) 135 | } 136 | 137 | func (v *validShareRecords) report() (rv []byte, err error) { 138 | 139 | kd := &contract.KeyData{v.aggregatePublicKey, v.includedHashes} 140 | kb, err := kd.MarshalBinary(v.d.keyID) 141 | if err != nil { 142 | return nil, errors.Wrap(err, "could not marshal key for onchain report") 143 | } 144 | return kb, nil 145 | } 146 | 147 | func unmarshalSignedShareRecord(d *dkg, report []byte) (*shareRecord, error) { 148 | r, rem, err := unmarshalShareRecord( 149 | SigningGroup, d.encryptionGroup, d.translationGroup, 150 | report, d.translator, d.cfgDgst, d.epks, d.spks, 151 | ) 152 | if err != nil { 153 | return nil, errors.Wrap(err, "could not unmarshal unknown share record") 154 | } 155 | if len(rem) > 0 { 156 | return nil, errors.Errorf( 157 | "overage of %d bytes in marshalled share record: 0x%x", len(rem), rem, 158 | ) 159 | } 160 | return r, nil 161 | } 162 | -------------------------------------------------------------------------------- /internal/signatures/secp256k1/scalar.go: -------------------------------------------------------------------------------- 1 | package secp256k1 2 | 3 | import ( 4 | "crypto/cipher" 5 | "fmt" 6 | "io" 7 | "math/big" 8 | 9 | secp256k1BTCD "github.com/btcsuite/btcd/btcec" 10 | "github.com/ethereum/go-ethereum/common" 11 | 12 | "go.dedis.ch/kyber/v3" 13 | "go.dedis.ch/kyber/v3/util/random" 14 | ) 15 | 16 | var GroupOrder = secp256k1BTCD.S256().N 17 | var FieldSize = secp256k1BTCD.S256().P 18 | 19 | type secp256k1Scalar big.Int 20 | 21 | func (s *secp256k1Scalar) AllowVarTime(varTimeAllowed bool) { 22 | 23 | if !varTimeAllowed { 24 | panic("implementation is not constant-time!") 25 | } 26 | } 27 | 28 | func newScalar(v *big.Int) kyber.Scalar { 29 | return (*secp256k1Scalar)(zero().Mod(v, GroupOrder)) 30 | } 31 | 32 | func zero() *big.Int { return big.NewInt(0) } 33 | 34 | func ToInt(s kyber.Scalar) *big.Int { return (*big.Int)(s.(*secp256k1Scalar)) } 35 | 36 | func (s *secp256k1Scalar) int() *big.Int { return (*big.Int)(s) } 37 | 38 | func (s *secp256k1Scalar) modG() kyber.Scalar { 39 | 40 | s.int().Mod(s.int(), GroupOrder) 41 | return s 42 | } 43 | 44 | func (s *secp256k1Scalar) String() string { 45 | return fmt.Sprintf("scalar{%x}", (*big.Int)(s)) 46 | } 47 | 48 | var scalarZero = zero() 49 | 50 | func (s *secp256k1Scalar) Equal(sPrime kyber.Scalar) bool { 51 | difference := zero().Sub(s.int(), ToInt(sPrime)) 52 | return scalarZero.Cmp(difference.Mod(difference, GroupOrder)) == 0 53 | } 54 | 55 | func (s *secp256k1Scalar) Set(sPrime kyber.Scalar) kyber.Scalar { 56 | return (*secp256k1Scalar)(s.int().Mod(ToInt(sPrime), GroupOrder)) 57 | } 58 | 59 | func (s *secp256k1Scalar) Clone() kyber.Scalar { 60 | return (*secp256k1Scalar)(zero().Mod(s.int(), GroupOrder)) 61 | } 62 | 63 | func (s *secp256k1Scalar) SetInt64(v int64) kyber.Scalar { 64 | return (*secp256k1Scalar)(s.int().SetInt64(v)).modG() 65 | } 66 | 67 | func (s *secp256k1Scalar) Zero() kyber.Scalar { 68 | return s.SetInt64(0) 69 | } 70 | 71 | func (s *secp256k1Scalar) Add(a, b kyber.Scalar) kyber.Scalar { 72 | s.int().Add(ToInt(a), ToInt(b)) 73 | return s.modG() 74 | } 75 | 76 | func (s *secp256k1Scalar) Sub(a, b kyber.Scalar) kyber.Scalar { 77 | s.int().Sub(ToInt(a), ToInt(b)) 78 | return s.modG() 79 | } 80 | 81 | func (s *secp256k1Scalar) Neg(a kyber.Scalar) kyber.Scalar { 82 | s.int().Neg(ToInt(a)) 83 | return s.modG() 84 | } 85 | 86 | func (s *secp256k1Scalar) One() kyber.Scalar { 87 | return s.SetInt64(1) 88 | } 89 | 90 | func (s *secp256k1Scalar) Mul(a, b kyber.Scalar) kyber.Scalar { 91 | 92 | s.int().Mul(ToInt(a), ToInt(b)) 93 | return s.modG() 94 | } 95 | 96 | func (s *secp256k1Scalar) Div(a, b kyber.Scalar) kyber.Scalar { 97 | if ToInt(b).Cmp(scalarZero) == 0 { 98 | panic("attempt to divide by zero") 99 | } 100 | 101 | s.int().Mul(ToInt(a), zero().ModInverse(ToInt(b), GroupOrder)) 102 | return s.modG() 103 | } 104 | 105 | func (s *secp256k1Scalar) Inv(a kyber.Scalar) kyber.Scalar { 106 | if ToInt(a).Cmp(scalarZero) == 0 { 107 | panic("attempt to divide by zero") 108 | } 109 | s.int().ModInverse(ToInt(a), GroupOrder) 110 | return s 111 | } 112 | 113 | func (s *secp256k1Scalar) Pick(rand cipher.Stream) kyber.Scalar { 114 | return s.Set((*secp256k1Scalar)(random.Int(GroupOrder, rand))) 115 | } 116 | 117 | func (s *secp256k1Scalar) MarshalBinary() ([]byte, error) { 118 | b := ToInt(s.modG()).Bytes() 119 | 120 | rv := append(make([]byte, s.MarshalSize()-len(b)), b...) 121 | if len(rv) != s.MarshalSize() { 122 | return nil, fmt.Errorf("marshalled scalar to wrong length") 123 | } 124 | return rv, nil 125 | } 126 | 127 | func (s *secp256k1Scalar) MarshalSize() int { return 32 } 128 | 129 | func (s *secp256k1Scalar) MarshalID() [8]byte { 130 | return [8]byte{'s', 'p', '2', '5', '6', '.', 's', 'c'} 131 | } 132 | 133 | func (s *secp256k1Scalar) UnmarshalBinary(buf []byte) error { 134 | if len(buf) != s.MarshalSize() { 135 | return fmt.Errorf("cannot unmarshal to scalar: wrong length") 136 | } 137 | s.int().Mod(s.int().SetBytes(buf), GroupOrder) 138 | return nil 139 | } 140 | 141 | func (s *secp256k1Scalar) MarshalTo(w io.Writer) (int, error) { 142 | buf, err := s.MarshalBinary() 143 | if err != nil { 144 | return 0, fmt.Errorf("cannot marshal binary: %w", err) 145 | } 146 | return w.Write(buf) 147 | } 148 | 149 | func (s *secp256k1Scalar) UnmarshalFrom(r io.Reader) (int, error) { 150 | buf := make([]byte, s.MarshalSize()) 151 | n, err := io.ReadFull(r, buf) 152 | if err != nil { 153 | return n, err 154 | } 155 | return n, s.UnmarshalBinary(buf) 156 | } 157 | 158 | func (s *secp256k1Scalar) SetBytes(a []byte) kyber.Scalar { 159 | return ((*secp256k1Scalar)(s.int().SetBytes(a))).modG() 160 | } 161 | 162 | func IsSecp256k1Scalar(s kyber.Scalar) bool { 163 | switch s := s.(type) { 164 | case *secp256k1Scalar: 165 | s.modG() 166 | return true 167 | default: 168 | return false 169 | } 170 | } 171 | 172 | func IntToScalar(i *big.Int) kyber.Scalar { 173 | return ((*secp256k1Scalar)(i)).modG() 174 | } 175 | 176 | func ScalarToHash(s kyber.Scalar) common.Hash { 177 | return common.BigToHash(ToInt(s.(*secp256k1Scalar))) 178 | } 179 | 180 | func RepresentsScalar(i *big.Int) bool { 181 | return i.Cmp(GroupOrder) == -1 182 | } 183 | -------------------------------------------------------------------------------- /altbn_128/scalar/scalar.go: -------------------------------------------------------------------------------- 1 | package scalar 2 | 3 | import ( 4 | "bytes" 5 | "crypto/cipher" 6 | "fmt" 7 | "io" 8 | "math/big" 9 | 10 | bn256 "github.com/ethereum/go-ethereum/crypto/bn256/cloudflare" 11 | "github.com/pkg/errors" 12 | "go.dedis.ch/kyber/v3" 13 | "go.dedis.ch/kyber/v3/group/mod" 14 | ) 15 | 16 | type Scalar struct{ int *mod.Int } 17 | 18 | var _ kyber.Scalar = (*Scalar)(nil) 19 | 20 | var order = bn256.Order 21 | 22 | func NewScalar() *Scalar { return &Scalar{mod.NewInt64(0, order)} } 23 | 24 | func NewScalarInt64(i int64) *Scalar { 25 | rv := NewScalar() 26 | _ = rv.int.SetInt64(i) 27 | return rv 28 | } 29 | 30 | func (s *Scalar) MarshalBinary() (data []byte, err error) { 31 | s.ensureAllocation() 32 | if s.int.V.Cmp(s.int.M) >= 0 { 33 | 34 | return nil, errors.Errorf("0x%x too large for AltBN128Scalar", s) 35 | } 36 | data = s.int.V.Bytes() 37 | if len(data) > s.MarshalSize() { 38 | panic(fmt.Sprintf("0x%x too large for AltBN128Scalar", s)) 39 | } 40 | 41 | return append(bytes.Repeat([]byte{0}, s.MarshalSize()-len(data)), data...), nil 42 | } 43 | 44 | func (s *Scalar) UnmarshalBinary(data []byte) error { 45 | if s == nil { 46 | return errors.Errorf("can't set the value of a nil *Scalar") 47 | } 48 | s.ensureAllocation() 49 | 50 | r := big.NewInt(0).SetBytes(data) 51 | if r.Cmp(order) >= 0 { 52 | return errors.Errorf("0x%x too large for AltBN128Scalar", s) 53 | } 54 | s.int.V = *r 55 | return nil 56 | } 57 | 58 | func (s *Scalar) String() string { 59 | s.ensureAllocation() 60 | return fmt.Sprintf("AltBN128Scalar{0x%s}", s.int) 61 | } 62 | 63 | var marshalSize = len(order.Bytes()) 64 | 65 | func (s Scalar) MarshalSize() int { 66 | return marshalSize 67 | } 68 | 69 | func (s *Scalar) MarshalTo(w io.Writer) (int, error) { 70 | data, err := s.MarshalBinary() 71 | if err != nil { 72 | return 0, errors.Wrapf(err, "while marshalling for writing") 73 | } 74 | n, err := w.Write(data) 75 | return n, errors.Wrapf(err, "while writing marshaled value 0x%x", data) 76 | } 77 | 78 | func (s *Scalar) UnmarshalFrom(r io.Reader) (int, error) { 79 | buf := make([]byte, s.MarshalSize()) 80 | n, err := io.ReadFull(r, buf) 81 | if err != nil { 82 | return n, errors.Wrap(err, "while reading for unmarshalling") 83 | } 84 | return n, errors.Wrapf(s.UnmarshalBinary(buf), "while unmarshalling 0x%x", buf) 85 | } 86 | 87 | var zero = big.NewInt(0) 88 | 89 | func (s *Scalar) Equal(s2 kyber.Scalar) bool { 90 | _, ok := s2.(*Scalar) 91 | if !ok || s == nil || s2 == nil { 92 | return false 93 | } 94 | diff := &NewScalar().Sub(s, s2).(*Scalar).int.V 95 | 96 | return diff.Mod(diff, s.int.M).Cmp(zero) == 0 97 | } 98 | 99 | func mustScalar(s, a kyber.Scalar, verb string) *Scalar { 100 | if aScalar, ok := a.(*Scalar); ok { 101 | return aScalar 102 | } 103 | panic(fmt.Sprintf( 104 | "attempt to combine %s non AltBN-128 Scalar %s with AltBN-128 Scalar "+ 105 | "operation %s", a, s, verb), 106 | ) 107 | } 108 | 109 | func (s *Scalar) Set(a kyber.Scalar) kyber.Scalar { 110 | aScalar := mustScalar(s, a, "set") 111 | _ = s.int.Set(aScalar.int) 112 | return s 113 | } 114 | 115 | func (s *Scalar) Clone() kyber.Scalar { 116 | return &Scalar{s.int.Clone().(*mod.Int)} 117 | } 118 | 119 | func (s *Scalar) ensureAllocation() { 120 | if s == nil { 121 | panic("attempt to ensure allocation on nil *Scalar") 122 | } 123 | if s.int == nil { 124 | s.int = mod.NewInt64(0, order) 125 | } 126 | } 127 | 128 | func (s *Scalar) SetInt64(v int64) kyber.Scalar { 129 | s.ensureAllocation() 130 | _ = s.int.SetInt64(v) 131 | return s 132 | } 133 | 134 | func (s *Scalar) Zero() kyber.Scalar { 135 | return s.SetInt64(0) 136 | } 137 | 138 | func (s *Scalar) Add(a kyber.Scalar, b kyber.Scalar) kyber.Scalar { 139 | aScalar := mustScalar(s, a, "sum") 140 | bScalar := mustScalar(s, b, "sum") 141 | s.ensureAllocation() 142 | s.int.Add(aScalar.int, bScalar.int) 143 | return s 144 | } 145 | 146 | func (s *Scalar) Sub(a kyber.Scalar, b kyber.Scalar) kyber.Scalar { 147 | aScalar := mustScalar(s, a, "subtract") 148 | bScalar := mustScalar(s, b, "subtract") 149 | s.ensureAllocation() 150 | _ = s.int.Sub(aScalar.int, bScalar.int) 151 | return s 152 | } 153 | 154 | func (s *Scalar) Neg(a kyber.Scalar) kyber.Scalar { 155 | panic("not implemented") 156 | } 157 | 158 | func (s *Scalar) One() kyber.Scalar { 159 | s.ensureAllocation() 160 | _ = s.int.SetInt64(1) 161 | return s 162 | } 163 | 164 | func (s *Scalar) Mul(a kyber.Scalar, b kyber.Scalar) kyber.Scalar { 165 | aScalar := mustScalar(s, a, "multiply") 166 | bScalar := mustScalar(s, b, "multiply") 167 | s.ensureAllocation() 168 | _ = s.int.Mul(aScalar.int, bScalar.int) 169 | return s 170 | } 171 | 172 | func (s *Scalar) Div(a kyber.Scalar, b kyber.Scalar) kyber.Scalar { 173 | aScalar := mustScalar(s, a, "multiply") 174 | bScalar := mustScalar(s, b, "multiply") 175 | return s.Mul(aScalar, NewScalar().Inv(bScalar)) 176 | } 177 | 178 | func (s *Scalar) Inv(a kyber.Scalar) kyber.Scalar { 179 | aScalar := mustScalar(s, a, "invert") 180 | s.ensureAllocation() 181 | _ = s.int.Inv(aScalar.int) 182 | return s 183 | } 184 | 185 | func (s *Scalar) Pick(rand cipher.Stream) kyber.Scalar { 186 | s.ensureAllocation() 187 | _ = s.int.Pick(rand) 188 | return s 189 | } 190 | 191 | func (s *Scalar) SetBytes(data []byte) kyber.Scalar { 192 | s.int.V.SetBytes(data) 193 | return s 194 | } 195 | 196 | func (s *Scalar) Big() *big.Int { 197 | return big.NewInt(0).Set(&s.int.V) 198 | } 199 | -------------------------------------------------------------------------------- /internal/crypto/change_base_group/change_base_group.go: -------------------------------------------------------------------------------- 1 | package change_base_group 2 | 3 | import ( 4 | "bytes" 5 | "crypto/cipher" 6 | "fmt" 7 | "io" 8 | "reflect" 9 | 10 | "github.com/pkg/errors" 11 | 12 | "github.com/smartcontractkit/chainlink-vrf/internal/crypto/ciphertext/schnorr" 13 | 14 | "go.dedis.ch/kyber/v3" 15 | ) 16 | 17 | func NewChangeBaseGroup(group schnorr.Suite, base kyber.Point) (*changeBaseGroup, error) { 18 | if !belongs(group, base) { 19 | return nil, errors.Errorf("point does not belong to group") 20 | } 21 | if base == nil || base.Equal(group.Point().Null()) { 22 | return nil, errors.Errorf("base point cannot be zero") 23 | } 24 | return &changeBaseGroup{group, base}, nil 25 | } 26 | 27 | type changeBaseGroup struct { 28 | schnorr.Suite 29 | base kyber.Point 30 | } 31 | 32 | var _ schnorr.Suite = (*changeBaseGroup)(nil) 33 | 34 | func (g *changeBaseGroup) equal(og *changeBaseGroup) bool { 35 | return g.Suite == og.Suite && g.base.Equal(og.base) 36 | } 37 | 38 | func (g *changeBaseGroup) String() string { 39 | return fmt.Sprintf("&changeBaseGroup{Suite: %s; base: %s}", g.Suite, g.base) 40 | } 41 | 42 | type changeBasePoint struct { 43 | point kyber.Point 44 | group *changeBaseGroup 45 | } 46 | 47 | var _ kyber.Point = (*changeBasePoint)(nil) 48 | 49 | func (g *changeBaseGroup) Point() kyber.Point { 50 | return &changeBasePoint{g.Suite.Point().Null(), g} 51 | } 52 | 53 | func (g *changeBaseGroup) Lift(p kyber.Point) (*changeBasePoint, error) { 54 | if !belongs(g.Suite, p) { 55 | return nil, errors.Errorf( 56 | "attempt to lift point when it doesn't belong to the underlying group (type %T but need %T)", 57 | p, g.Suite.Point(), 58 | ) 59 | } 60 | return &changeBasePoint{p.Clone(), g}, nil 61 | } 62 | 63 | func (p *changeBasePoint) description() string { 64 | return fmt.Sprintf("〈%s〉⊂ (%s)", p.group.base, p.group.Suite) 65 | } 66 | 67 | func (p *changeBasePoint) MarshalBinary() ([]byte, error) { 68 | rv, err := p.point.MarshalBinary() 69 | if err != nil { 70 | return nil, errors.Wrapf(err, "could not marshal underlying point") 71 | } 72 | rv = append(rv, []byte(p.description())...) 73 | return rv, nil 74 | } 75 | 76 | func (p *changeBasePoint) UnmarshalBinary(data []byte) error { 77 | if len(data) < len(p.description()) { 78 | return errors.Errorf("marshal data cannot contain description") 79 | } 80 | dstart := len(data) - len(p.description()) 81 | if !bytes.Equal(data[dstart:], []byte(p.description())) { 82 | return errors.Errorf("binary data does not contain group description") 83 | } 84 | if err := p.point.UnmarshalBinary(data[:dstart]); err != nil { 85 | return errors.Wrapf(err, "could not unmarshal underlying point") 86 | } 87 | return nil 88 | } 89 | 90 | func (p *changeBasePoint) String() string { 91 | return fmt.Sprintf("%s ∈ %s", p.point, p.description()) 92 | } 93 | 94 | func (p *changeBasePoint) MarshalSize() int { 95 | return p.point.MarshalSize() + len(p.description()) 96 | } 97 | 98 | func (p *changeBasePoint) MarshalTo(w io.Writer) (int, error) { 99 | n, err := p.point.MarshalTo(w) 100 | if err != nil { 101 | return n, errors.Wrapf(err, "could not marshal underlying point") 102 | } 103 | dn, err := w.Write([]byte(p.description())) 104 | return n + dn, errors.Wrapf(err, "could not write description for marshalling") 105 | } 106 | 107 | func (p *changeBasePoint) Equal(s2 kyber.Point) bool { 108 | s2cb, ok := s2.(*changeBasePoint) 109 | return ok && p.point.Equal(s2cb.point) && p.group.base.Equal(s2cb.group.base) 110 | } 111 | 112 | func (p *changeBasePoint) Mul(s kyber.Scalar, p2 kyber.Point) kyber.Point { 113 | if p2 == nil { 114 | p2 = p.group.base 115 | } else { 116 | if !p.group.equal(p2.(*changeBasePoint).group) { 117 | panic("attempt to multiply into different group") 118 | } 119 | p2 = p2.(*changeBasePoint).point 120 | } 121 | _ = p.point.Mul(s, p2) 122 | return p 123 | } 124 | 125 | func (p *changeBasePoint) Add(a kyber.Point, b kyber.Point) kyber.Point { 126 | if !p.group.equal(a.(*changeBasePoint).group) { 127 | panic("attempt to add into different group") 128 | } 129 | if !p.group.equal(b.(*changeBasePoint).group) { 130 | panic("attempt to add into different group") 131 | } 132 | _ = p.point.Add(a.(*changeBasePoint).point, b.(*changeBasePoint).point) 133 | return p 134 | } 135 | 136 | func (p *changeBasePoint) Null() kyber.Point { 137 | _ = p.point.Null() 138 | return p 139 | } 140 | 141 | func (p *changeBasePoint) Pick(rand cipher.Stream) kyber.Point { 142 | _ = p.point.Pick(rand) 143 | return p 144 | } 145 | 146 | func belongs(g kyber.Group, p kyber.Point) bool { 147 | return reflect.TypeOf(p) == reflect.TypeOf(g.Point()) 148 | } 149 | 150 | func (p *changeBasePoint) Set(p2 kyber.Point) kyber.Point { panic("not implemented") } 151 | func (p *changeBasePoint) EmbedLen() int { panic("not implemented") } 152 | func (p *changeBasePoint) Embed(data []byte, r cipher.Stream) kyber.Point { panic("not implemented") } 153 | func (p *changeBasePoint) Data() ([]byte, error) { panic("not implemented") } 154 | func (p *changeBasePoint) Base() kyber.Point { panic("not implemented") } 155 | func (p *changeBasePoint) Clone() kyber.Point { panic("not implemented") } 156 | func (p *changeBasePoint) UnmarshalFrom(r io.Reader) (int, error) { panic("not implemented") } 157 | func (p *changeBasePoint) Sub(a kyber.Point, b kyber.Point) kyber.Point { panic("not implemented") } 158 | func (p *changeBasePoint) Neg(a kyber.Point) kyber.Point { panic("not implemented") } 159 | -------------------------------------------------------------------------------- /internal/pvss/share_set_implementation.go: -------------------------------------------------------------------------------- 1 | package pvss 2 | 3 | import ( 4 | "bytes" 5 | 6 | "github.com/pkg/errors" 7 | 8 | "github.com/smartcontractkit/libocr/offchainreporting2plus/types" 9 | 10 | "github.com/smartcontractkit/chainlink-vrf/internal/crypto/player_idx" 11 | "github.com/smartcontractkit/chainlink-vrf/internal/crypto/point_translation" 12 | "github.com/smartcontractkit/chainlink-vrf/internal/util" 13 | 14 | "go.dedis.ch/kyber/v3" 15 | kshare "go.dedis.ch/kyber/v3/share" 16 | "go.dedis.ch/kyber/v3/sign/anon" 17 | ) 18 | 19 | var MinPlayers = 5 20 | 21 | var _ = NewShareSet 22 | 23 | func newShareSet(domainSep types.ConfigDigest, threshold player_idx.Int, dealerIdx *player_idx.PlayerIdx, 24 | group anon.Suite, translation point_translation.PubKeyTranslation, 25 | pks []kyber.Point, toxicWasteNeverUseThisParam ...interface{}, 26 | ) (*ShareSet, error) { 27 | if len(pks) < MinPlayers { 28 | return nil, errors.Errorf("%d is not enough players", len(pks)) 29 | } 30 | if int(threshold) >= len(pks) { 31 | return nil, errors.Errorf( 32 | "threshold %d cannot exceed number of players %d", threshold, len(pks), 33 | ) 34 | } 35 | players, err := player_idx.PlayerIdxs(player_idx.Int(len(pks))) 36 | if err != nil { 37 | return nil, err 38 | } 39 | secretPoly := kshare.NewPriPoly(group, int(threshold)+1, nil, group.RandomStream()) 40 | coeffCommits := secretPoly.Commit(nil) 41 | pvssKey, err := translation.TranslateKey(secretPoly.Secret()) 42 | if err != nil { 43 | return nil, errors.Wrapf(err, "could not translate dealer's additive share") 44 | } 45 | rv := &ShareSet{ 46 | dealerIdx, coeffCommits, pvssKey, make([]*share, len(pks)), translation, nil, 47 | } 48 | 49 | if len(toxicWasteNeverUseThisParam) == 1 && toxicWasteNeverUseThisParam[0] == 50 | "⚠⚠⚠☣☢️☠ Yes, please inject me with that yummy plutonium-salt solution. "+ 51 | "I long for the sweet release of a lingering death by radiation "+ 52 | "sickness ☠☢️☣️⚠⚠⚠" { 53 | rv.xXXToxicWaste = secretPoly 54 | } 55 | edomain, err := rv.domainSep(domainSep) 56 | if err != nil { 57 | return nil, err 58 | } 59 | for shareIdx, receiver := range players { 60 | pk := receiver.Index(pks).(kyber.Point) 61 | rv.shares[shareIdx], err = newShare( 62 | edomain, group, secretPoly, rv, receiver, pk, 63 | ) 64 | if err != nil { 65 | return nil, errors.Wrapf(err, "while constructing share set") 66 | } 67 | } 68 | return rv, nil 69 | } 70 | 71 | var _ = (&ShareSet{}).Verify 72 | 73 | func (s *ShareSet) verify(group anon.Suite, domainSep types.ConfigDigest, pks []kyber.Point) error { 74 | if _, commits := s.coeffCommitments.Info(); len(commits) < 1 { 75 | return errors.Errorf("need at least one coefficient commitment in a valid share") 76 | } 77 | if err := s.translation.VerifyTranslation(s.coeffCommitments.Commit(), s.pvssKey); err != nil { 78 | return errors.Wrapf(err, "bad translation of additive key share in share set") 79 | } 80 | numPlayers := len(pks) 81 | if numPlayers > int(player_idx.MaxPlayer) { 82 | return errors.Errorf("Can't handle %d players; %d is max", len(pks), player_idx.MaxPlayer) 83 | } 84 | edomain, err := s.domainSep(domainSep) 85 | if err != nil { 86 | return errors.Wrapf(err, "failed to construct domain separator for PVSS proofs") 87 | } 88 | players, err := player_idx.PlayerIdxs(player_idx.Int(numPlayers)) 89 | if err != nil { 90 | return util.WrapError(err, "could not get list of player indices in ShareSet.verify") 91 | } 92 | for shareIdx, share := range s.shares { 93 | p := players[shareIdx] 94 | if err := share.verify(group, append(edomain, p.Marshal()...), p); err != nil { 95 | return errors.Wrapf(err, "could not verify every share in share set") 96 | } 97 | } 98 | return nil 99 | } 100 | 101 | func (s *ShareSet) domainSep(domainSep types.ConfigDigest) ([]byte, error) { 102 | _, commits := s.coeffCommitments.Info() 103 | components := make([][]byte, len(commits)+2) 104 | components[0] = domainSep[:] 105 | offset := 1 106 | var err error 107 | for ci, c := range commits { 108 | components[ci+offset], err = c.MarshalBinary() 109 | if err != nil { 110 | return nil, errors.Errorf( 111 | "while including secret polynomial coefficient commits in domain separator", 112 | ) 113 | } 114 | } 115 | 116 | components[len(components)-1] = s.dealer.Marshal() 117 | return bytes.Join(components, nil), nil 118 | } 119 | 120 | func (s *ShareSet) Equal(s2 *ShareSet) bool { 121 | if s == nil || s2 == nil { 122 | return false 123 | } 124 | if !s.dealer.Equal(s2.dealer) { 125 | return false 126 | } 127 | _, scommits := s.coeffCommitments.Info() 128 | _, s2commits := s2.coeffCommitments.Info() 129 | if len(scommits) == len(s2commits) && 130 | s.coeffCommitments.Equal(s2.coeffCommitments) && 131 | s.pvssKey.Equal(s2.pvssKey) && 132 | (len(s.shares) == len(s2.shares)) { 133 | for i, sh := range s.shares { 134 | if !sh.equal(s2.shares[i]) { 135 | return false 136 | } 137 | } 138 | return true 139 | } 140 | return false 141 | } 142 | 143 | var _ = (*ShareSet)(nil).Decrypt 144 | 145 | func (s *ShareSet) decrypt( 146 | playerIdx player_idx.PlayerIdx, sk kyber.Scalar, 147 | keyGroup anon.Suite, domainSep types.ConfigDigest, sharePublicCommitment kyber.Point, 148 | ) (kshare.PriShare, error) { 149 | playerShare := playerIdx.Index(s.shares).(*share) 150 | edomain, err := s.domainSep(domainSep) 151 | if err != nil { 152 | return kshare.PriShare{}, err 153 | } 154 | return playerShare.decrypt( 155 | sk, keyGroup, edomain, sharePublicCommitment, playerIdx, 156 | ) 157 | } 158 | 159 | var _ = (*ShareSet)(nil).PublicShares 160 | 161 | func (s *ShareSet) publicShares() (rv []kyber.Point) { 162 | for _, share := range s.shares { 163 | rv = append(rv, share.subKeyTranslation) 164 | } 165 | return rv 166 | } 167 | -------------------------------------------------------------------------------- /internal/crypto/ciphertext/cipher_text_marshal.go: -------------------------------------------------------------------------------- 1 | package ciphertext 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "fmt" 7 | "io" 8 | "math" 9 | 10 | "github.com/pkg/errors" 11 | 12 | "github.com/smartcontractkit/chainlink-vrf/internal/crypto/player_idx" 13 | 14 | "go.dedis.ch/kyber/v3/sign/anon" 15 | ) 16 | 17 | const ( 18 | SUITE_DESC_LEN = "suite description length" 19 | CRYPTOGRAPHIC_SUITE_DESCRIPTION = "cryptographic suite description" 20 | RECEIVER_INDEX = "receiver index" 21 | ENCRYPTION_KEY = "encryption key" 22 | SHARE_PROOF_LEN = "share proof length" 23 | SHARE_PROOF = "proof that cipher text encodes share" 24 | NUM_PAIRS = "number of ciphertext pairs" 25 | ) 26 | 27 | func (c *cipherText) marshal() (m []byte, err error) { 28 | rv := new(bytes.Buffer) 29 | 30 | suiteName := []byte(c.suite.String()) 31 | if len(suiteName) > math.MaxUint8 { 32 | return nil, errors.Errorf("suite name too long") 33 | } 34 | 35 | if err2 := hw(rv, []byte{uint8(len(suiteName))}, SUITE_DESC_LEN); err2 != nil { 36 | return nil, err2 37 | } 38 | 39 | if err2 := hw(rv, suiteName, CRYPTOGRAPHIC_SUITE_DESCRIPTION); err2 != nil { 40 | return nil, err2 41 | } 42 | 43 | if err2 := hw(rv, c.receiver.Marshal(), RECEIVER_INDEX); err2 != nil { 44 | return nil, err2 45 | } 46 | 47 | n, err := c.encryptionKey.MarshalTo(rv) 48 | if err != nil { 49 | return nil, errors.Wrap(err, "could not marshal "+ENCRYPTION_KEY) 50 | } 51 | if n != c.suite.PointLen() { 52 | return nil, errors.Errorf("could not marshal " + ENCRYPTION_KEY + 53 | "; wrote wrong number of bytes for it") 54 | } 55 | 56 | if len(c.encodesShareProof) > math.MaxUint16 { 57 | return nil, errors.Errorf("encodesShareProof too long") 58 | } 59 | proofLen := make([]byte, 2) 60 | binary.BigEndian.PutUint16(proofLen, uint16(len(c.encodesShareProof))) 61 | if err := hw(rv, proofLen, SHARE_PROOF_LEN); err != nil { 62 | return nil, err 63 | } 64 | 65 | if err := hw(rv, c.encodesShareProof, SHARE_PROOF); err != nil { 66 | return nil, err 67 | } 68 | 69 | numPairs := len(c.cipherText) 70 | if numPairs > math.MaxUint16 { 71 | return nil, errors.Errorf("too many pairs to marshal") 72 | } 73 | bigEndNp := make([]byte, 2) 74 | binary.BigEndian.PutUint16(bigEndNp, uint16(numPairs)) 75 | if err := hw(rv, bigEndNp, NUM_PAIRS); err != nil { 76 | return nil, err 77 | } 78 | 79 | ctl := elGamalBitPairMarshalLength(c.suite) 80 | for _, ct := range c.cipherText { 81 | ctm, err := ct.marshal() 82 | if err != nil { 83 | return nil, errors.Wrap(err, "could not marshal ciphertext") 84 | } 85 | if len(ctm) != ctl { 86 | return nil, errors.Errorf("elGamalBitPair marshaled to wrong length") 87 | } 88 | if err := hw(rv, ctm, "ciphertext bit pair"); err != nil { 89 | return nil, err 90 | } 91 | } 92 | 93 | return rv.Bytes(), nil 94 | } 95 | 96 | func hw(rv io.Writer, d []byte, errmsg string) (err error) { 97 | errmsg = "could not marshal " + errmsg 98 | n, err := rv.Write(d) 99 | if err != nil { 100 | err = errors.Wrapf(err, errmsg) 101 | } else if n != len(d) { 102 | err = errors.Errorf(errmsg + ": failed to write all bytes") 103 | } 104 | return err 105 | } 106 | 107 | func unmarshal(suite anon.Suite, byteStream io.Reader) (c *cipherText, err error) { 108 | c = &cipherText{suite: suite} 109 | 110 | var strLen [1]byte 111 | if err2 := hr(byteStream, strLen[:], SUITE_DESC_LEN); err2 != nil { 112 | return nil, err2 113 | } 114 | str := make([]byte, strLen[0]) 115 | if err2 := hr(byteStream, str, CRYPTOGRAPHIC_SUITE_DESCRIPTION); err2 != nil { 116 | return nil, err2 117 | } 118 | if string(str) != suite.String() { 119 | return nil, errors.Errorf(`wrong suite for unmarshalling: need "%s", got "%s"`, suite, str) 120 | } 121 | 122 | idx := make([]byte, player_idx.MarshalLen) 123 | if err2 := hr(byteStream, idx, RECEIVER_INDEX); err2 != nil { 124 | return nil, err2 125 | } 126 | c.receiver, _, err = player_idx.Unmarshal(idx) 127 | if err != nil { 128 | return nil, errors.Wrap(err, "could not unmarshal ciphertext's "+RECEIVER_INDEX) 129 | } 130 | 131 | c.encryptionKey = suite.Point() 132 | n, err := c.encryptionKey.UnmarshalFrom(byteStream) 133 | if err != nil { 134 | return nil, errors.Wrap(err, "could not unmarshal "+ENCRYPTION_KEY) 135 | } 136 | if n != suite.PointLen() { 137 | return nil, errors.Errorf("could not unmarshal " + ENCRYPTION_KEY) 138 | } 139 | 140 | proofLen := make([]byte, 2) 141 | if err2 := hr(byteStream, proofLen[:], SHARE_PROOF_LEN); err2 != nil { 142 | return nil, err2 143 | } 144 | 145 | c.encodesShareProof = make([]byte, binary.BigEndian.Uint16(proofLen)) 146 | if err2 := hr(byteStream, c.encodesShareProof, SHARE_PROOF); err2 != nil { 147 | return nil, err2 148 | } 149 | 150 | var rawNumPairs [2]byte 151 | if err2 := hr(byteStream, rawNumPairs[:], NUM_PAIRS); err2 != nil { 152 | return nil, err2 153 | } 154 | numPairs := binary.BigEndian.Uint16(rawNumPairs[:]) 155 | 156 | ctm := make([]byte, elGamalBitPairMarshalLength(suite)) 157 | c.cipherText = make([]*elGamalBitPair, numPairs) 158 | for bpIdx := uint16(0); bpIdx < numPairs; bpIdx++ { 159 | if err2 := hr(byteStream, ctm[:], "ciphertext bit pair"); err2 != nil { 160 | return nil, err2 161 | } 162 | c.cipherText[bpIdx], err = unmarshalElGamalBitPair(suite, ctm) 163 | if err != nil { 164 | return nil, errors.Wrap(err, "could not unmarshal cipher text") 165 | } 166 | } 167 | 168 | return c, nil 169 | } 170 | 171 | func hr(byteStream io.Reader, dst []byte, errmsg string) (rv error) { 172 | errmsg = fmt.Sprint("could not read ", errmsg, " for unmarshalling") 173 | n, err := byteStream.Read(dst) 174 | if err != nil { 175 | return errors.Wrap(err, errmsg) 176 | } 177 | if n != len(dst) { 178 | return errors.Errorf(errmsg) 179 | } 180 | return nil 181 | } 182 | -------------------------------------------------------------------------------- /internal/dkg/protobuf/offchain_config.pb.go: -------------------------------------------------------------------------------- 1 | package protobuf 2 | 3 | import ( 4 | reflect "reflect" 5 | sync "sync" 6 | 7 | protoreflect "google.golang.org/protobuf/reflect/protoreflect" 8 | protoimpl "google.golang.org/protobuf/runtime/protoimpl" 9 | ) 10 | 11 | const ( 12 | _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) 13 | 14 | _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) 15 | ) 16 | 17 | type OffchainConfig struct { 18 | state protoimpl.MessageState 19 | sizeCache protoimpl.SizeCache 20 | unknownFields protoimpl.UnknownFields 21 | 22 | EncryptionPKs [][]byte `protobuf:"bytes,2,rep,name=encryptionPKs,proto3" json:"encryptionPKs,omitempty"` 23 | SignaturePKs [][]byte `protobuf:"bytes,3,rep,name=signaturePKs,proto3" json:"signaturePKs,omitempty"` 24 | EncryptionGroup string `protobuf:"bytes,4,opt,name=encryptionGroup,proto3" json:"encryptionGroup,omitempty"` 25 | Translator string `protobuf:"bytes,5,opt,name=translator,proto3" json:"translator,omitempty"` 26 | } 27 | 28 | func (x *OffchainConfig) Reset() { 29 | *x = OffchainConfig{} 30 | if protoimpl.UnsafeEnabled { 31 | mi := &file_offchain_config_proto_msgTypes[0] 32 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 33 | ms.StoreMessageInfo(mi) 34 | } 35 | } 36 | 37 | func (x *OffchainConfig) String() string { 38 | return protoimpl.X.MessageStringOf(x) 39 | } 40 | 41 | func (*OffchainConfig) ProtoMessage() {} 42 | 43 | func (x *OffchainConfig) ProtoReflect() protoreflect.Message { 44 | mi := &file_offchain_config_proto_msgTypes[0] 45 | if protoimpl.UnsafeEnabled && x != nil { 46 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 47 | if ms.LoadMessageInfo() == nil { 48 | ms.StoreMessageInfo(mi) 49 | } 50 | return ms 51 | } 52 | return mi.MessageOf(x) 53 | } 54 | 55 | func (*OffchainConfig) Descriptor() ([]byte, []int) { 56 | return file_offchain_config_proto_rawDescGZIP(), []int{0} 57 | } 58 | 59 | func (x *OffchainConfig) GetEncryptionPKs() [][]byte { 60 | if x != nil { 61 | return x.EncryptionPKs 62 | } 63 | return nil 64 | } 65 | 66 | func (x *OffchainConfig) GetSignaturePKs() [][]byte { 67 | if x != nil { 68 | return x.SignaturePKs 69 | } 70 | return nil 71 | } 72 | 73 | func (x *OffchainConfig) GetEncryptionGroup() string { 74 | if x != nil { 75 | return x.EncryptionGroup 76 | } 77 | return "" 78 | } 79 | 80 | func (x *OffchainConfig) GetTranslator() string { 81 | if x != nil { 82 | return x.Translator 83 | } 84 | return "" 85 | } 86 | 87 | var File_offchain_config_proto protoreflect.FileDescriptor 88 | 89 | var file_offchain_config_proto_rawDesc = []byte{ 90 | 0x0a, 0x15, 0x6f, 0x66, 0x66, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 91 | 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x05, 0x74, 0x79, 0x70, 0x65, 0x73, 0x22, 0xa4, 92 | 0x01, 0x0a, 0x0e, 0x6f, 0x66, 0x66, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 93 | 0x67, 0x12, 0x24, 0x0a, 0x0d, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x50, 94 | 0x4b, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x0d, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 95 | 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x4b, 0x73, 0x12, 0x22, 0x0a, 0x0c, 0x73, 0x69, 0x67, 0x6e, 0x61, 96 | 0x74, 0x75, 0x72, 0x65, 0x50, 0x4b, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x0c, 0x73, 97 | 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x50, 0x4b, 0x73, 0x12, 0x28, 0x0a, 0x0f, 0x65, 98 | 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x18, 0x04, 99 | 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e, 100 | 0x47, 0x72, 0x6f, 0x75, 0x70, 0x12, 0x1e, 0x0a, 0x0a, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x6c, 0x61, 101 | 0x74, 0x6f, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x74, 0x72, 0x61, 0x6e, 0x73, 102 | 0x6c, 0x61, 0x74, 0x6f, 0x72, 0x42, 0x0c, 0x5a, 0x0a, 0x2e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 103 | 0x62, 0x75, 0x66, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, 104 | } 105 | 106 | var ( 107 | file_offchain_config_proto_rawDescOnce sync.Once 108 | file_offchain_config_proto_rawDescData = file_offchain_config_proto_rawDesc 109 | ) 110 | 111 | func file_offchain_config_proto_rawDescGZIP() []byte { 112 | file_offchain_config_proto_rawDescOnce.Do(func() { 113 | file_offchain_config_proto_rawDescData = protoimpl.X.CompressGZIP(file_offchain_config_proto_rawDescData) 114 | }) 115 | return file_offchain_config_proto_rawDescData 116 | } 117 | 118 | var file_offchain_config_proto_msgTypes = make([]protoimpl.MessageInfo, 1) 119 | var file_offchain_config_proto_goTypes = []interface{}{ 120 | (*OffchainConfig)(nil), 121 | } 122 | var file_offchain_config_proto_depIdxs = []int32{ 123 | 0, 124 | 0, 125 | 0, 126 | 0, 127 | 0, 128 | } 129 | 130 | func init() { file_offchain_config_proto_init() } 131 | func file_offchain_config_proto_init() { 132 | if File_offchain_config_proto != nil { 133 | return 134 | } 135 | if !protoimpl.UnsafeEnabled { 136 | file_offchain_config_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { 137 | switch v := v.(*OffchainConfig); i { 138 | case 0: 139 | return &v.state 140 | case 1: 141 | return &v.sizeCache 142 | case 2: 143 | return &v.unknownFields 144 | default: 145 | return nil 146 | } 147 | } 148 | } 149 | type x struct{} 150 | out := protoimpl.TypeBuilder{ 151 | File: protoimpl.DescBuilder{ 152 | GoPackagePath: reflect.TypeOf(x{}).PkgPath(), 153 | RawDescriptor: file_offchain_config_proto_rawDesc, 154 | NumEnums: 0, 155 | NumMessages: 1, 156 | NumExtensions: 0, 157 | NumServices: 0, 158 | }, 159 | GoTypes: file_offchain_config_proto_goTypes, 160 | DependencyIndexes: file_offchain_config_proto_depIdxs, 161 | MessageInfos: file_offchain_config_proto_msgTypes, 162 | }.Build() 163 | File_offchain_config_proto = out.File 164 | file_offchain_config_proto_rawDesc = nil 165 | file_offchain_config_proto_goTypes = nil 166 | file_offchain_config_proto_depIdxs = nil 167 | } 168 | -------------------------------------------------------------------------------- /internal/dkg/dkg.go: -------------------------------------------------------------------------------- 1 | package dkg 2 | 3 | import ( 4 | "context" 5 | "io" 6 | "reflect" 7 | "sync" 8 | 9 | "github.com/pkg/errors" 10 | 11 | "github.com/smartcontractkit/libocr/commontypes" 12 | "github.com/smartcontractkit/libocr/offchainreporting2plus/types" 13 | 14 | "github.com/smartcontractkit/chainlink-vrf/internal/crypto/player_idx" 15 | "github.com/smartcontractkit/chainlink-vrf/internal/crypto/point_translation" 16 | "github.com/smartcontractkit/chainlink-vrf/internal/dkg/contract" 17 | "github.com/smartcontractkit/chainlink-vrf/internal/pvss" 18 | dkg_types "github.com/smartcontractkit/chainlink-vrf/types" 19 | 20 | "go.dedis.ch/kyber/v3" 21 | "go.dedis.ch/kyber/v3/group/edwards25519" 22 | kshare "go.dedis.ch/kyber/v3/share" 23 | "go.dedis.ch/kyber/v3/sign/anon" 24 | ) 25 | 26 | type dkg struct { 27 | t player_idx.Int 28 | 29 | lock sync.RWMutex 30 | 31 | selfIdx *player_idx.PlayerIdx 32 | 33 | cfgDgst types.ConfigDigest 34 | 35 | keyID contract.KeyID 36 | 37 | keyConsumer KeyConsumer 38 | 39 | shareSets shareRecords 40 | 41 | myShareRecord *shareRecord 42 | 43 | esk kyber.Scalar 44 | epks []kyber.Point 45 | ssk kyber.Scalar 46 | spks []kyber.Point 47 | 48 | encryptionGroup anon.Suite 49 | 50 | translationGroup kyber.Group 51 | 52 | translator point_translation.PubKeyTranslation 53 | 54 | lastKeyData *KeyData 55 | 56 | contract onchainContract 57 | 58 | completed bool 59 | dkgComplete func() 60 | 61 | db dkg_types.DKGSharePersistence 62 | 63 | logger commontypes.Logger 64 | 65 | randomness io.Reader 66 | 67 | ctx context.Context 68 | cancelFunc context.CancelFunc 69 | } 70 | 71 | var _ types.ReportingPlugin = (*dkg)(nil) 72 | 73 | func (a *NewDKGArgs) SanityCheckArgs() error { 74 | if a.encryptionGroup == nil { 75 | return errors.Errorf("encryption group not set") 76 | } 77 | if a.esk == nil { 78 | return errors.Errorf("encryption secret key not set") 79 | } 80 | if a.ssk == nil { 81 | return errors.Errorf("signing secret key not set") 82 | } 83 | n := len(a.epks) 84 | if n > int(player_idx.MaxPlayer) { 85 | return errors.Errorf("too many players") 86 | } 87 | if int(a.t) > n { 88 | return errors.Errorf("threshold can't exceed number of players") 89 | } 90 | if _, err := a.selfIdx.Check(); err != nil { 91 | return errors.Wrap(err, "self index invalid") 92 | } 93 | if !a.selfIdx.AtMost(player_idx.Int(n)) { 94 | return errors.Errorf("player index can't exceed number of players") 95 | } 96 | if reflect.TypeOf(a.esk) != reflect.TypeOf(a.encryptionGroup.Scalar()) { 97 | return errors.Errorf("encryption secret key must come from encryption group") 98 | } 99 | exampleEncryptionPK := a.encryptionGroup.Point() 100 | for _, epk := range a.epks { 101 | if reflect.TypeOf(epk) != reflect.TypeOf(exampleEncryptionPK) { 102 | return errors.Errorf( 103 | "encryption public key must be of type returned by encryptionGroup %v, %T", 104 | a.encryptionGroup, exampleEncryptionPK, 105 | ) 106 | } 107 | } 108 | if !a.selfIdx.Index(a.epks).(kyber.Point).Equal(a.encryptionGroup.Point().Mul(a.esk, nil)) { 109 | return errors.Errorf("secret encryption key does not match public encryption key") 110 | } 111 | if len(a.spks) != n { 112 | return errors.Errorf("must be exactly one signing key for each player") 113 | } 114 | if reflect.TypeOf(a.ssk) != reflect.TypeOf(a.signingGroup().Scalar()) { 115 | return errors.Errorf("signing secret key must be of type %T", a.signingGroup().Scalar()) 116 | } 117 | exampleSigningPK := a.signingGroup().Point() 118 | for _, spk := range a.spks { 119 | if reflect.TypeOf(spk) != reflect.TypeOf(exampleSigningPK) { 120 | return errors.Errorf("signing public keys must be of type %T", exampleSigningPK) 121 | } 122 | } 123 | if !a.selfIdx.Index(a.spks).(kyber.Point).Equal(a.signingGroup().Point().Mul(a.ssk, nil)) { 124 | return errors.Errorf("secret signing key does not match public signing key") 125 | } 126 | if n < pvss.MinPlayers { 127 | return errors.Errorf("not enough players (need at least %d)", pvss.MinPlayers) 128 | } 129 | return nil 130 | } 131 | 132 | func (a *NewDKGArgs) signingGroup() anon.Suite { 133 | if a.xxxTestingOnlySigningGroup != nil { 134 | return a.xxxTestingOnlySigningGroup 135 | } 136 | return SigningGroup 137 | } 138 | 139 | func (d *dkg) keyReportedOnchain(ctx context.Context) bool { 140 | kd, err := d.contract.KeyData(ctx, d.keyID, d.cfgDgst) 141 | return err == nil && kd.PublicKey != nil && len(kd.Hashes) > 0 142 | } 143 | 144 | func (d *dkg) recoverDistributedKeyShare(ctx context.Context) (err error) { 145 | kd, err := d.contract.KeyData(ctx, d.keyID, d.cfgDgst) 146 | if err != nil { 147 | return errors.Wrap(err, "could not get key data while recovering key shares") 148 | } 149 | if d.shareSets.allKeysPresent(kd.Hashes) { 150 | 151 | finalShare, err := d.shareSets.recoverDistributedKeyShare( 152 | d.esk, *d.selfIdx, &kd, d.encryptionGroup, d.cfgDgst, 153 | ) 154 | if err != nil { 155 | return errors.Wrap(err, "could not recover distribute key from shares") 156 | } 157 | 158 | shares, err := d.shareSets.recoverPublicShares(&kd) 159 | if err != nil { 160 | return errors.Wrap(err, "could not get public shares to report to consumer") 161 | } 162 | players, err := player_idx.PlayerIdxs(player_idx.Int(len(shares))) 163 | if err != nil { 164 | return errors.Wrap(err, "could not construct players for pubshares") 165 | } 166 | pubShares := make([]kshare.PubShare, len(players)) 167 | for i, playerIdx := range players { 168 | 169 | pubShares[i] = playerIdx.PubShare(shares[i]) 170 | } 171 | 172 | keyData := &KeyData{ 173 | kd.PublicKey, 174 | pubShares, 175 | &SecretShare{*d.selfIdx, finalShare.V}, 176 | d.t, 177 | true, 178 | } 179 | 180 | d.keyConsumer.NewKey(d.keyID, keyData) 181 | d.completed = true 182 | d.dkgComplete() 183 | return nil 184 | } 185 | return errors.Errorf( 186 | "do not yet have all shares required for reconstruction of given key", 187 | ) 188 | } 189 | 190 | var SigningGroup anon.Suite = edwards25519.NewBlakeSHA256Ed25519() 191 | 192 | type onchainContract interface { 193 | KeyData(context.Context, contract.KeyID, types.ConfigDigest) (contract.KeyData, error) 194 | } 195 | -------------------------------------------------------------------------------- /internal/vrf/ethereum_serialization.go: -------------------------------------------------------------------------------- 1 | package vrf 2 | 3 | import ( 4 | "math/big" 5 | "strings" 6 | 7 | "github.com/ethereum/go-ethereum/accounts/abi" 8 | bn256 "github.com/ethereum/go-ethereum/crypto/bn256/cloudflare" 9 | 10 | "github.com/pkg/errors" 11 | 12 | "github.com/smartcontractkit/chainlink-vrf/altbn_128" 13 | "github.com/smartcontractkit/chainlink-vrf/gethwrappers/vrfbeacon" 14 | "github.com/smartcontractkit/chainlink-vrf/internal/util" 15 | vrf_types "github.com/smartcontractkit/chainlink-vrf/types" 16 | 17 | "go.dedis.ch/kyber/v3" 18 | "go.dedis.ch/kyber/v3/group/mod" 19 | ) 20 | 21 | type EthereumReportSerializer struct { 22 | G kyber.Group 23 | } 24 | 25 | func (e *EthereumReportSerializer) DeserializeReport( 26 | rawReport []byte, 27 | ) (vrf_types.AbstractReport, error) { 28 | arguments := vrfABI().Methods["exposeType"].Inputs 29 | parsedReport, err := arguments.Unpack(rawReport) 30 | if err != nil { 31 | return vrf_types.AbstractReport{}, err 32 | } 33 | type reportType = vrfbeacon.VRFBeaconReportReport 34 | report := reportType{} 35 | abi.ConvertType(parsedReport[0], &report) 36 | abstractReport, err := e.ConvertToAbstractReport(report) 37 | if err != nil { 38 | return vrf_types.AbstractReport{}, err 39 | } 40 | return abstractReport, err 41 | } 42 | 43 | func (e *EthereumReportSerializer) MaxReportLength() uint { 44 | 45 | panic("implement me") 46 | } 47 | 48 | func (e *EthereumReportSerializer) ReportLength(abstractReport vrf_types.AbstractReport) uint { 49 | 50 | panic("implement me") 51 | } 52 | 53 | var _ vrf_types.ReportSerializer = &EthereumReportSerializer{} 54 | 55 | func (e *EthereumReportSerializer) SerializeReport( 56 | report vrf_types.AbstractReport, 57 | ) ([]byte, error) { 58 | beaconReport, err := e.ConvertToBeaconReport(report) 59 | if err != nil { 60 | return nil, err 61 | } 62 | arguments := vrfABI().Methods["exposeType"].Inputs 63 | serializedReport, err := arguments.Pack(beaconReport) 64 | if err != nil { 65 | return nil, err 66 | } 67 | return serializedReport, err 68 | } 69 | 70 | func (e *EthereumReportSerializer) ConvertToBeaconReport( 71 | report vrf_types.AbstractReport, 72 | ) (vrfbeacon.VRFBeaconReportReport, error) { 73 | vrfOutputs := make( 74 | []vrfbeacon.VRFBeaconTypesVRFOutput, 0, len(report.Outputs), 75 | ) 76 | emptyReport := vrfbeacon.VRFBeaconReportReport{} 77 | for _, output := range report.Outputs { 78 | p := e.G.Point() 79 | if err := p.UnmarshalBinary(output.VRFProof[:]); err != nil { 80 | return emptyReport, errors.Wrap(err, "while unmarshalling vrf proof") 81 | } 82 | x, y := big.NewInt(0), big.NewInt(0) 83 | if !p.Equal(e.G.Point().Null()) { 84 | x, y = affineCoordinates(p) 85 | } 86 | vrfProof := vrfbeacon.ECCArithmeticG1Point{P: [2]*big.Int{x, y}} 87 | type callbackType = vrfbeacon.VRFBeaconTypesCostedCallback 88 | callbacks := make([]callbackType, 0, len(output.Callbacks)) 89 | for _, callback := range output.Callbacks { 90 | beaconCostedCallback := callbackType{ 91 | Callback: vrfbeacon.VRFBeaconTypesCallback{ 92 | RequestID: callback.RequestID, 93 | NumWords: callback.NumWords, 94 | Requester: callback.Requester, 95 | Arguments: callback.Arguments, 96 | GasAllowance: callback.GasAllowance, 97 | SubID: callback.SubscriptionID, 98 | GasPrice: callback.GasPrice, 99 | WeiPerUnitLink: callback.WeiPerUnitLink, 100 | }, 101 | Price: callback.Price, 102 | } 103 | callbacks = append(callbacks, beaconCostedCallback) 104 | } 105 | vrfOutput := vrfbeacon.VRFBeaconTypesVRFOutput{ 106 | BlockHeight: output.BlockHeight, 107 | ConfirmationDelay: big.NewInt(int64(output.ConfirmationDelay)), 108 | VrfOutput: vrfProof, 109 | Callbacks: callbacks, 110 | ShouldStore: output.ShouldStore, 111 | } 112 | vrfOutputs = append(vrfOutputs, vrfOutput) 113 | } 114 | onchainReport := vrfbeacon.VRFBeaconReportReport{ 115 | Outputs: vrfOutputs, 116 | JuelsPerFeeCoin: report.JuelsPerFeeCoin, 117 | ReasonableGasPrice: report.ReasonableGasPrice, 118 | RecentBlockHeight: report.RecentBlockHeight, 119 | RecentBlockHash: report.RecentBlockHash, 120 | } 121 | return onchainReport, nil 122 | } 123 | 124 | func (e *EthereumReportSerializer) ConvertToAbstractReport( 125 | report vrfbeacon.VRFBeaconReportReport, 126 | ) (vrf_types.AbstractReport, error) { 127 | abstractOutputs := make([]vrf_types.AbstractVRFOutput, 0, len(report.Outputs)) 128 | for _, out := range report.Outputs { 129 | xCoordinate := mod.NewInt(out.VrfOutput.P[0], bn256.P) 130 | yCoordinate := mod.NewInt(out.VrfOutput.P[1], bn256.P) 131 | vrfG1Point, err := altbn_128.CoordinatesToG1(xCoordinate, yCoordinate) 132 | if err != nil { 133 | return vrf_types.AbstractReport{}, 134 | util.WrapErrorf( 135 | err, 136 | "could not parse VRF proof (0x%x, 0x%x)", 137 | xCoordinate, 138 | yCoordinate, 139 | ) 140 | } 141 | vrfG1PointBinary, err := vrfG1Point.MarshalBinary() 142 | var vrfProof [32]byte 143 | copy(vrfProof[:], vrfG1PointBinary[:]) 144 | 145 | if err != nil { 146 | errMsg := "while unmarshalling vrf proof" 147 | return vrf_types.AbstractReport{}, util.WrapError(err, errMsg) 148 | } 149 | var abstractCallbacks []vrf_types.AbstractCostedCallbackRequest 150 | for _, c := range out.Callbacks { 151 | abstractCallback := vrf_types.AbstractCostedCallbackRequest{ 152 | out.BlockHeight, 153 | uint32(out.ConfirmationDelay.Uint64()), 154 | c.Callback.SubID, 155 | c.Price, 156 | c.Callback.RequestID, 157 | c.Callback.NumWords, 158 | c.Callback.Requester, 159 | c.Callback.Arguments, 160 | c.Callback.GasAllowance, 161 | c.Callback.GasPrice, 162 | c.Callback.WeiPerUnitLink, 163 | } 164 | abstractCallbacks = append(abstractCallbacks, abstractCallback) 165 | } 166 | abstractOutput := vrf_types.AbstractVRFOutput{ 167 | out.BlockHeight, 168 | uint32(out.ConfirmationDelay.Uint64()), 169 | vrfProof, 170 | abstractCallbacks, 171 | out.ShouldStore, 172 | } 173 | abstractOutputs = append(abstractOutputs, abstractOutput) 174 | } 175 | abstractReport := vrf_types.AbstractReport{ 176 | abstractOutputs, 177 | report.JuelsPerFeeCoin, 178 | report.ReasonableGasPrice, 179 | report.RecentBlockHeight, 180 | report.RecentBlockHash, 181 | } 182 | return abstractReport, nil 183 | } 184 | 185 | func vrfABI() *abi.ABI { 186 | rv, err := abi.JSON( 187 | strings.NewReader(vrfbeacon.VRFBeaconReportMetaData.ABI), 188 | ) 189 | if err != nil { 190 | panic(err) 191 | } 192 | return &rv 193 | } 194 | -------------------------------------------------------------------------------- /altbn_128/g1_point.go: -------------------------------------------------------------------------------- 1 | package altbn_128 2 | 3 | import ( 4 | "bytes" 5 | "crypto/cipher" 6 | "fmt" 7 | "io" 8 | "math/big" 9 | 10 | "github.com/pkg/errors" 11 | "go.dedis.ch/kyber/v3" 12 | "go.dedis.ch/kyber/v3/group/mod" 13 | 14 | bn256 "github.com/ethereum/go-ethereum/crypto/bn256/cloudflare" 15 | 16 | "github.com/smartcontractkit/chainlink-vrf/altbn_128/scalar" 17 | ) 18 | 19 | type g1Point struct{ G1 g1Interface } 20 | 21 | var _ kyber.Point = (*g1Point)(nil) 22 | 23 | func newG1Point() (rv *g1Point) { 24 | rv = &g1Point{new(bn256.G1)} 25 | rv.G1.Set(null.G1.(*bn256.G1)) 26 | return 27 | } 28 | 29 | var Order = bn256.Order 30 | 31 | func (p *g1Point) ensureP() *g1Point { 32 | if p == nil { 33 | panic("cannot assign to nil point") 34 | } 35 | if p.G1 == nil { 36 | p.G1 = new(bn256.G1) 37 | p.G1.Set(null.G1.(*bn256.G1)) 38 | } 39 | return p 40 | } 41 | 42 | func bytesZeroThroughPMinusOne(b []byte) bool { 43 | return big.NewInt(0).SetBytes(b).Cmp(bn256.P) < 0 44 | } 45 | 46 | func (p *g1Point) MarshalBinary() (data []byte, err error) { 47 | rawData := p.G1.Marshal() 48 | if len(rawData) != 64 { 49 | return nil, errors.Errorf("wrong format from bn256 marshalling logic") 50 | } 51 | rawX, rawY := rawData[:32], rawData[32:] 52 | if !bytesZeroThroughPMinusOne(rawX) { 53 | return nil, errors.Errorf("x ordinate 0x%x too large", rawX) 54 | } 55 | if !bytesZeroThroughPMinusOne(rawY) { 56 | return nil, errors.Errorf("y ordinate 0x%x too large", rawY) 57 | } 58 | rawX[0] |= (rawY[31] & 1) << 7 59 | return rawX, nil 60 | } 61 | 62 | func i(x int64) *scalar.Scalar { return scalar.NewScalarInt64(x) } 63 | func m(x int64) *mod.Int { return mod.NewInt64(x, bn256.P) } 64 | 65 | var three = m(3) 66 | 67 | func (p *g1Point) UnmarshalBinary(data []byte) error { 68 | if len(data) != 32 { 69 | return errors.Errorf("attempt to unmarshal g1Point data of wrong length") 70 | } 71 | if bytes.Equal(data, uncompressedZero[:32]) { 72 | 73 | p.Null() 74 | return nil 75 | } 76 | 77 | xData := make([]byte, len(data)) 78 | copy(xData, data) 79 | xData[0] &= 0x7F 80 | if !bytesZeroThroughPMinusOne(xData) { 81 | return errors.Errorf("x ordinate 0x%x too large", xData) 82 | } 83 | x := mod.NewIntBytes(xData, bn256.P, mod.BigEndian) 84 | tmp := m(0).Mul(x, x) 85 | _ = tmp.Mul(tmp, x) 86 | ySq := tmp.Add(tmp, three) 87 | y := m(0) 88 | if !y.Sqrt(ySq) { 89 | return errors.Errorf("no point on curve with given x ordinate 0x%s", x) 90 | } 91 | yParity := (data[0] & 0x80) == 0x80 92 | yData, err := y.MarshalBinary() 93 | if err != nil { 94 | return errors.Wrapf(err, "while marshalling y data") 95 | } 96 | currentParity := (yData[31] & 1) == 1 97 | if yParity != currentParity { 98 | 99 | _ = y.Neg(y) 100 | } 101 | yData, err = y.MarshalBinary() 102 | if err != nil { 103 | return errors.Wrap(err, "could not re-marshal y after possible negation") 104 | } 105 | if (yData[31]&1 == 1) != yParity { 106 | panic("failed to set correct parity for y") 107 | } 108 | p.ensureP() 109 | if _, err := p.G1.Unmarshal(append(xData, yData...)); err != nil { 110 | 111 | return errors.Wrap(err, "while unmarshalling to bn256 point") 112 | } 113 | return nil 114 | } 115 | 116 | var uncompressedZero = make([]byte, 64) 117 | 118 | func (p *g1Point) String() string { 119 | if p == nil { 120 | return "(*g1Point)(nil)" 121 | } 122 | b := p.G1.Marshal() 123 | if bytes.Equal(b, uncompressedZero) { 124 | return "g1Point{∞}" 125 | } 126 | return fmt.Sprintf("g1Point{0x%x,0x%x}", b[:32], b[32:]) 127 | } 128 | 129 | func (p *g1Point) Equal(p2 kyber.Point) bool { 130 | if p == nil || p2 == nil { 131 | return false 132 | } 133 | p2G1, ok := p2.(*g1Point) 134 | return p != nil && ok && bytes.Equal(p.G1.Marshal(), p2G1.G1.Marshal()) 135 | } 136 | 137 | func (p *g1Point) Null() kyber.Point { 138 | p.ensureP() 139 | p.G1.ScalarBaseMult(big.NewInt(0)) 140 | return p 141 | } 142 | 143 | func (p *g1Point) Base() kyber.Point { 144 | p.ensureP() 145 | p.G1.ScalarBaseMult(big.NewInt(1)) 146 | return p 147 | } 148 | 149 | func (p *g1Point) Pick(rand cipher.Stream) kyber.Point { 150 | p.ensureP() 151 | 152 | _ = p.G1.ScalarBaseMult(i(0).Pick(rand).(*scalar.Scalar).Big()) 153 | return p 154 | } 155 | 156 | func (p *g1Point) Set(p2 kyber.Point) kyber.Point { 157 | p.ensureP() 158 | p.G1.Set(p2.(*g1Point).G1.(*bn256.G1)) 159 | return p 160 | } 161 | 162 | func (p *g1Point) Clone() kyber.Point { 163 | rv := newG1Point() 164 | rv.Set(p) 165 | return rv 166 | } 167 | 168 | func (p *g1Point) EmbedLen() int { panic("not implemented") } 169 | func (p *g1Point) Embed(data []byte, r cipher.Stream) kyber.Point { panic("not implemented") } 170 | func (p *g1Point) Data() ([]byte, error) { panic("not implemented") } 171 | 172 | func (p *g1Point) Add(a kyber.Point, b kyber.Point) kyber.Point { 173 | p.ensureP() 174 | ap := a.(*g1Point).G1 175 | bp := b.(*g1Point).G1 176 | p.G1.Add(ap.(*bn256.G1), bp.(*bn256.G1)) 177 | return p 178 | } 179 | 180 | func (p *g1Point) Sub(a kyber.Point, b kyber.Point) kyber.Point { 181 | return p.Add(a, newG1Point().Neg(b)) 182 | } 183 | 184 | func (p *g1Point) Neg(a kyber.Point) kyber.Point { 185 | aG1 := a.(*g1Point) 186 | p.ensureP() 187 | p.G1.Neg(aG1.G1.(*bn256.G1)) 188 | return p 189 | } 190 | 191 | func (p *g1Point) Mul(s kyber.Scalar, p2 kyber.Point) kyber.Point { 192 | sm := s.(*scalar.Scalar) 193 | p.ensureP() 194 | if p2 == nil { 195 | p2 = newG1Point().Base() 196 | } 197 | p.G1.ScalarMult(p2.(*g1Point).G1.(*bn256.G1), sm.Big()) 198 | return p 199 | } 200 | 201 | func (p *g1Point) MarshalSize() int { return g1PointLength } 202 | 203 | func (p *g1Point) MarshalTo(w io.Writer) (numBytesWritten int, err error) { 204 | data, err := p.MarshalBinary() 205 | if err != nil { 206 | return 0, errors.Wrapf(err, "while marshalling for writing") 207 | } 208 | n, err := w.Write(data) 209 | return n, errors.Wrapf(err, "while writing marshaled value 0x%x", data) 210 | } 211 | 212 | func (p *g1Point) UnmarshalFrom(r io.Reader) (numBytesRead int, err error) { 213 | if strm, ok := r.(cipher.Stream); ok { 214 | p.Pick(strm) 215 | return -1, nil 216 | } 217 | buf := make([]byte, p.MarshalSize()) 218 | n, err := io.ReadFull(r, buf) 219 | if err != nil { 220 | return n, errors.Wrap(err, "while reading for unmarshalling") 221 | } 222 | return n, errors.Wrapf(p.UnmarshalBinary(buf), "while unmarshalling 0x%x", buf) 223 | } 224 | 225 | func LongMarshal(p kyber.Point) (rv [64]byte) { 226 | m := p.(*g1Point).G1.Marshal() 227 | if len(m) != 64 { 228 | panic(fmt.Errorf("wrong length for serialized G1 point 0x%x from %s", m, p)) 229 | } 230 | copy(rv[:], m) 231 | return 232 | } 233 | 234 | func IsAltBN128G1Point(p kyber.Point) bool { 235 | _, ok := p.(*g1Point) 236 | return ok 237 | } 238 | -------------------------------------------------------------------------------- /altbn_128/g1_hash_to_curve.go: -------------------------------------------------------------------------------- 1 | package altbn_128 2 | 3 | import ( 4 | "fmt" 5 | "math/big" 6 | 7 | "github.com/ethereum/go-ethereum/common/hexutil" 8 | "github.com/ethereum/go-ethereum/crypto" 9 | 10 | "github.com/smartcontractkit/chainlink-vrf/gethwrappers/vrf" 11 | "github.com/smartcontractkit/chainlink-vrf/internal/util" 12 | 13 | bn256 "github.com/ethereum/go-ethereum/crypto/bn256/cloudflare" 14 | 15 | "go.dedis.ch/kyber/v3" 16 | "go.dedis.ch/kyber/v3/group/mod" 17 | ) 18 | 19 | var ( 20 | curve = (&PairingSuite{}).G1() 21 | p = bn256.P 22 | curveB = m(3) 23 | 24 | j = big.NewInt 25 | uint256Bound = j(0).Lsh(j(1), 256) 26 | uint256ModP = j(0).Mod(uint256Bound, p) 27 | maxP = j(0).Sub(uint256Bound, uint256ModP) 28 | 29 | sqrMinus3, sqrMinus3Valid = m(0), sqrMinus3.Sqrt(m(-3)) 30 | thirdModP = m(0).Exp(m(3), j(-1)) 31 | sqrpwr = j(0).Rsh(j(0).Add(p, j(1)), 2) 32 | 33 | vConst, _ = m(0), vConst.Div(vConst.Add(m(-1), sqrMinus3), m(2)) 34 | ) 35 | 36 | type fProof struct { 37 | point kyber.Point 38 | t *big.Int 39 | interimValues vrf.HashToCurveFProof 40 | } 41 | 42 | var zeroHashPoint = hexutil.MustDecode( 43 | "0x000000000000000059e26bcea0d48bacd4f263f1acdb5c4f5763473177fffffe", 44 | ) 45 | 46 | func newFProof(t *big.Int) *fProof { 47 | if t.Cmp(j(0)) == 0 { 48 | return &fProof{point: curve.Point().Null(), t: t} 49 | } 50 | if t.Cmp(maxP) >= 0 { 51 | 52 | panic("input must be less than maxP") 53 | } 54 | it := mod.NewInt(t, p) 55 | tmp := m(0) 56 | iTSquared := m(0).Mul(it, it) 57 | denom := tmp.Add(m(1), tmp.Add(curveB, iTSquared)) 58 | denomInv := m(0).Inv(denom) 59 | tmp = m(0) 60 | v := tmp.Sub(vConst, tmp.Mul(tmp.Mul(sqrMinus3, iTSquared), denomInv)) 61 | pseudoSqrtY := func(x *mod.Int) (isActualSquareRoot bool, psqrt *mod.Int) { 62 | tmp := m(0) 63 | ySquare := tmp.Add(tmp.Mul(tmp.Mul(x, x), x), curveB).(*mod.Int) 64 | 65 | pseudoY := m(0).Exp(ySquare, sqrpwr) 66 | pseudoYSquare := m(0).Mul(pseudoY, pseudoY) 67 | valid := pseudoYSquare.Equal(ySquare) 68 | if !valid { 69 | negPseudoYSquare := m(0).Neg(pseudoYSquare) 70 | if !negPseudoYSquare.Equal(ySquare) { 71 | panic(fmt.Sprintln("failed to compute correct pseudo square root of", ySquare)) 72 | } 73 | } 74 | return valid, pseudoY.(*mod.Int) 75 | } 76 | rv := &fProof{ 77 | t: t, 78 | interimValues: vrf.HashToCurveFProof{ 79 | DenomInv: &denomInv.(*mod.Int).V, 80 | 81 | TInvSquared: j(0), Y1: j(0), Y2: j(0), Y3: j(0), 82 | }, 83 | } 84 | 85 | x1 := v.Clone().(*mod.Int) 86 | valid, y1 := pseudoSqrtY(x1) 87 | rv.interimValues.Y1 = &y1.V 88 | if valid { 89 | rv.point = coordinatesToG1(x1, y1, it) 90 | return rv 91 | } 92 | 93 | x2 := m(0).Neg(m(0).Add(x1, m(1))).(*mod.Int) 94 | valid, y2 := pseudoSqrtY(x2) 95 | rv.interimValues.Y2 = &y2.V 96 | if valid { 97 | rv.point = coordinatesToG1(x2, y2, it) 98 | return rv 99 | } 100 | 101 | tInvSquared := m(0).Exp(it, j(-2)).(*mod.Int) 102 | numSquared := m(0).Mul(denom, denom) 103 | x3 := m(0).Sub(m(1), m(0).Mul(numSquared, m(0).Mul(tInvSquared, thirdModP))).(*mod.Int) 104 | valid, y3 := pseudoSqrtY(x3) 105 | rv.interimValues.Y3 = &y3.V 106 | rv.interimValues.TInvSquared = &tInvSquared.V 107 | if valid { 108 | rv.point = coordinatesToG1(x3, y3, it) 109 | return rv 110 | } 111 | 112 | panic( 113 | "one of x1, x2, x3 should have been the x ordinate of a point on G1: " + 114 | rv.String(), 115 | ) 116 | } 117 | 118 | func coordinatesToG1(x, y, t *mod.Int) *g1Point { 119 | if x.M.Cmp(p) != 0 || y.M.Cmp(p) != 0 || t.M.Cmp(p) != 0 { 120 | panic("inputs are not in base field") 121 | } 122 | xBin, err := x.MarshalBinary() 123 | if err != nil { 124 | panic(err) 125 | } 126 | targetParity := t.V.Bit(0) 127 | if y.V.Bit(0) != targetParity { 128 | _ = y.Neg(y) 129 | if y.V.Bit(0) != targetParity { 130 | panic("failed to set target parity for output") 131 | } 132 | } 133 | yBin, err := y.MarshalBinary() 134 | if err != nil { 135 | panic(err) 136 | } 137 | pt := newG1Point() 138 | if _, err := pt.G1.Unmarshal(append(xBin, yBin...)); err != nil { 139 | panic(err) 140 | } 141 | return pt 142 | } 143 | 144 | func CoordinatesToG1(x, y *mod.Int) (kyber.Point, error) { 145 | if x.M.Cmp(p) != 0 { 146 | return nil, fmt.Errorf("x ordinate %+v is not in base field", x) 147 | } 148 | if y.M.Cmp(p) != 0 { 149 | return nil, fmt.Errorf("y ordinate %+v is not in base field", y) 150 | } 151 | xBin, err := x.MarshalBinary() 152 | if err != nil { 153 | return nil, util.WrapError(err, "could not marshal x ordinate") 154 | } 155 | yBin, err := y.MarshalBinary() 156 | if err != nil { 157 | return nil, util.WrapError(err, "could not marshal y ordinate") 158 | } 159 | pt := newG1Point() 160 | combinedOrdinates := append(xBin, yBin...) 161 | if _, err := pt.G1.Unmarshal(combinedOrdinates); err != nil { 162 | return nil, util.WrapErrorf( 163 | err, 164 | "could not unmarshal combined ordinates 0x%x", 165 | combinedOrdinates, 166 | ) 167 | } 168 | return pt, nil 169 | } 170 | 171 | type HashProof struct { 172 | msg [32]byte 173 | HashPoint kyber.Point 174 | SummandProofs [2]*fProof 175 | } 176 | 177 | func NewHashProof(msg [32]byte) *HashProof { 178 | rv := &HashProof{msg: msg, HashPoint: newG1Point().Null()} 179 | for hashCount := 0; hashCount < 2; { 180 | nmsg := crypto.Keccak256(msg[:]) 181 | copy(msg[:], nmsg) 182 | t := j(0).SetBytes(msg[:]) 183 | if t.Cmp(maxP) < 0 { 184 | t = t.Mod(t, p) 185 | rv.SummandProofs[hashCount] = newFProof(t) 186 | rv.HashPoint = newG1Point().Add(rv.HashPoint, rv.SummandProofs[hashCount].point) 187 | hashCount++ 188 | } 189 | } 190 | return rv 191 | } 192 | 193 | func init() { 194 | if !sqrMinus3Valid { 195 | panic("-3 is not a square in ℤ/pℤ") 196 | } 197 | if j(0).Lsh(sqrpwr, 2).Cmp(j(0).Add(p, j(1))) != 0 { 198 | panic("p ≢ 3 mod 4") 199 | } 200 | } 201 | 202 | func SolidityVRFProof(pubKey, output kyber.Point, hp HashProof) (*vrf.VRFProof, error) { 203 | pubKeyB, err := pubKey.MarshalBinary() 204 | if err != nil { 205 | return nil, err 206 | } 207 | var pubKeySol vrf.ECCArithmeticG2Point 208 | for i := range pubKeySol.P { 209 | pubKeySol.P[i] = big.NewInt(0).SetBytes(pubKeyB[i*32 : (i+1)*32]) 210 | } 211 | outputB := LongMarshal(output) 212 | var outputSol vrf.ECCArithmeticG1Point 213 | for i := range outputSol.P { 214 | outputSol.P[i] = big.NewInt(0).SetBytes(outputB[i*32 : (i+1)*32]) 215 | } 216 | proof := vrf.VRFProof{ 217 | pubKeySol, 218 | outputSol, 219 | hp.SummandProofs[0].interimValues, 220 | hp.SummandProofs[1].interimValues, 221 | } 222 | return &proof, nil 223 | } 224 | 225 | func (hp *HashProof) EqualFProofs(f1, f2 vrf.HashToCurveFProof) bool { 226 | s := hp.SummandProofs 227 | return f1.DenomInv.Cmp(s[0].interimValues.DenomInv) == 0 && 228 | f1.TInvSquared.Cmp(s[0].interimValues.TInvSquared) == 0 && 229 | f1.Y1.Cmp(s[0].interimValues.Y1) == 0 && 230 | f1.Y2.Cmp(s[0].interimValues.Y2) == 0 && 231 | f1.Y3.Cmp(s[0].interimValues.Y3) == 0 && 232 | f2.DenomInv.Cmp(s[1].interimValues.DenomInv) == 0 && 233 | f2.TInvSquared.Cmp(s[1].interimValues.TInvSquared) == 0 && 234 | f2.Y1.Cmp(s[1].interimValues.Y1) == 0 && 235 | f2.Y2.Cmp(s[1].interimValues.Y2) == 0 && 236 | f2.Y3.Cmp(s[1].interimValues.Y3) == 0 237 | } 238 | 239 | func (f *fProof) String() string { 240 | i := f.interimValues 241 | return fmt.Sprintf( 242 | "&fProof{point: %s, t: 0x%x, interimValues: "+ 243 | "{DenomInv: 0x%x, TInvSquared: 0x%x, Y1: 0x%x, Y2: 0x%x, Y3: 0x%x}"+ 244 | "}", 245 | f.point, f.t, i.DenomInv, i.TInvSquared, i.Y1, i.Y2, i.Y3, 246 | ) 247 | } 248 | --------------------------------------------------------------------------------