diff --git a/core/services/signatures/cryptotest/cryptotest.go b/core/services/signatures/cryptotest/cryptotest.go deleted file mode 100644 index d788af56daf..00000000000 --- a/core/services/signatures/cryptotest/cryptotest.go +++ /dev/null @@ -1,33 +0,0 @@ -// Package cryptotest provides convenience functions for kyber-based APIs. -// -// It is separate from cltest to prevent an import cycle. -package cryptotest - -import ( - "math/rand" - "testing" -) - -// randomStream implements cipher.Stream, but with a deterministic output. -type randomStream rand.Rand - -// NewStream returns a randomStream seeded from seed, for deterministic -// randomness in tests of random outputs, and for small property-based tests. -// -// This API is deliberately awkward to prevent it from being used outside of -// tests. -// -// The testing framework runs the tests in a file in series, unless you -// explicitly request otherwise with testing.T.Parallel(). So one such stream -// per file is enough, most of the time. -func NewStream(t *testing.T, seed int64) *randomStream { - return (*randomStream)(rand.New(rand.NewSource(seed))) -} - -// XORKeyStream dumps the output from a math/rand PRNG on dst. -// -// It gives no consideration for the contents of src, and is named so -// misleadingly purely to satisfy the cipher.Stream interface. -func (s *randomStream) XORKeyStream(dst, src []byte) { - (*rand.Rand)(s).Read(dst) -} diff --git a/core/services/signatures/ethdss/ethdss.go b/core/services/signatures/ethdss/ethdss.go deleted file mode 100644 index 0f98b46b127..00000000000 --- a/core/services/signatures/ethdss/ethdss.go +++ /dev/null @@ -1,306 +0,0 @@ -// Package ethdss implements the Distributed Schnorr Signature protocol from the -// ////////////////////////////////////////////////////////////////////////////// -// -// XXX: Do not use in production until this code has been audited. -// -// ////////////////////////////////////////////////////////////////////////////// -// paper "Provably Secure Distributed Schnorr Signatures and a (t, n) -// Threshold Scheme for Implicit Certificates". -// https://dl.acm.org/citation.cfm?id=678297 -// To generate a distributed signature from a group of participants, the group -// must first generate one longterm distributed secret with the share/dkg -// package, and then one random secret to be used only once. -// Each participant then creates a DSS struct, that can issue partial signatures -// with `dss.PartialSignature()`. These partial signatures can be broadcasted to -// the whole group or to a trusted combiner. Once one has collected enough -// partial signatures, it is possible to compute the distributed signature with -// the `Signature` method. -// -// This is mostly copied from the sign/dss package, with minor adjustments for -// use with ethschnorr. -package clientdss - -import ( - "bytes" - "errors" - "math/big" - - "go.dedis.ch/kyber/v3" - "go.dedis.ch/kyber/v3/share" - - "github.com/smartcontractkit/chainlink-common/keystore/corekeys/vrfkey/secp256k1" - "github.com/smartcontractkit/chainlink/v2/core/services/signatures/ethschnorr" -) - -// Suite represents the functionalities needed by the dss package -type Suite interface { - kyber.Group - kyber.HashFactory - kyber.Random -} - -var secp256k1Suite = secp256k1.NewBlakeKeccackSecp256k1() -var secp256k1Group kyber.Group = secp256k1Suite - -// DistKeyShare is an abstraction to allow one to use distributed key share -// from different schemes easily into this distributed threshold Schnorr -// signature framework. -type DistKeyShare interface { - PriShare() *share.PriShare - Commitments() []kyber.Point -} - -// DSS holds the information used to issue partial signatures as well as to -// compute the distributed schnorr signature. -type DSS struct { - // Keypair for this participant in the signing process (i.e., the one where - // this struct is stored.) This is not the keypair for full signing key; that - // would defeat the point. - secret kyber.Scalar - public kyber.Point - // Index value of this participant in the signing process. The index is shared - // across participants. - index int - // Public keys of potential participants in the signing process - participants []kyber.Point - // Number of participants needed to construct a signature - T int - // Shares of the distributed long-term signing keypair - long DistKeyShare - // Shares of the distributed ephemeral nonce keypair - random DistKeyShare - // Pedersen commitments to the coefficients of the polynomial implicitly used - // to share the long-term signing public/private keypair. - longPoly *share.PubPoly - // Pedersen commitments to the coefficients of the polynomial implicitly used - // to share the ephemeral nonce keypair. - randomPoly *share.PubPoly - // Message to be signed - msg *big.Int - // The partial signatures collected so far. - partials []*share.PriShare - // Indices for the participants who have provided their partial signatures to - // this participant. - partialsIdx map[int]bool - // True iff the partial signature for this dss has been signed by its owner. - signed bool - // String which uniquely identifies this signature, shared by all - // participants. - sessionID []byte -} - -// DSSArgs is the arguments to NewDSS, as a struct. See NewDSS for details. -type DSSArgs = struct { - secret kyber.Scalar - participants []kyber.Point - long DistKeyShare - random DistKeyShare - msg *big.Int - T int -} - -// PartialSig is partial representation of the final distributed signature. It -// must be sent to each of the other participants. -type PartialSig struct { - Partial *share.PriShare - SessionID []byte - Signature ethschnorr.Signature -} - -// NewDSS returns a DSS struct out of the suite, the longterm secret of this -// node, the list of participants, the longterm and random distributed key -// (generated by the dkg package), the message to sign and finally the T -// threshold. It returns an error if the public key of the secret can't be found -// in the list of participants. -func NewDSS(args DSSArgs) (*DSS, error) { - public := secp256k1Group.Point().Mul(args.secret, nil) - var i int - var found bool - for j, p := range args.participants { - if p.Equal(public) { - found = true - i = j - break - } - } - if !found { - return nil, errors.New("dss: public key not found in list of participants") - } - return &DSS{ - secret: args.secret, - public: public, - index: i, - participants: args.participants, - long: args.long, - longPoly: share.NewPubPoly(secp256k1Suite, - secp256k1Group.Point().Base(), args.long.Commitments()), - random: args.random, - randomPoly: share.NewPubPoly(secp256k1Suite, - secp256k1Group.Point().Base(), args.random.Commitments()), - msg: args.msg, - T: args.T, - partialsIdx: make(map[int]bool), - sessionID: sessionID(secp256k1Suite, args.long, args.random), - }, nil -} - -// PartialSig generates the partial signature related to this DSS. This -// PartialSig can be broadcasted to every other participant or only to a -// trusted combiner as described in the paper. -// The signature format is compatible with EdDSA verification implementations. -// -// Corresponds to section 4.2, step 2 the Stinson 2001 paper. -func (d *DSS) PartialSig() (*PartialSig, error) { - secretPartialLongTermKey := d.long.PriShare().V // ɑᵢ, in the paper - secretPartialCommitmentKey := d.random.PriShare().V // βᵢ, in the paper - fullChallenge := d.hashSig() // h(m‖V), in the paper - secretChallengeMultiple := secp256k1Suite.Scalar().Mul( - fullChallenge, secretPartialLongTermKey) // ɑᵢh(m‖V)G, in the paper - // Corresponds to ɣᵢG=βᵢG+ɑᵢh(m‖V)G in the paper, but NB, in its notation, we - // use ɣᵢG=βᵢG-ɑᵢh(m‖V)G. (Subtract instead of add.) - partialSignature := secp256k1Group.Scalar().Sub( - secretPartialCommitmentKey, secretChallengeMultiple) - ps := &PartialSig{ - Partial: &share.PriShare{V: partialSignature, I: d.index}, - SessionID: d.sessionID, - } - var err error - ps.Signature, err = ethschnorr.Sign(d.secret, ps.Hash()) // sign share - if !d.signed { - d.partialsIdx[d.index] = true - d.partials = append(d.partials, ps.Partial) - d.signed = true - } - return ps, err -} - -// ProcessPartialSig takes a PartialSig from another participant and stores it -// for generating the distributed signature. It returns an error if the index is -// wrong, or the signature is invalid or if a partial signature has already been -// received by the same peer. To know whether the distributed signature can be -// computed after this call, one can use the `EnoughPartialSigs` method. -// -// Corresponds to section 4.3, step 3 of the paper -func (d *DSS) ProcessPartialSig(ps *PartialSig) error { - var err error - public, ok := findPub(d.participants, ps.Partial.I) - if !ok { - err = errors.New("dss: partial signature with invalid index") - } - // nothing secret here - if err == nil && !bytes.Equal(ps.SessionID, d.sessionID) { - err = errors.New("dss: session id do not match") - } - if err == nil { - if vrr := ethschnorr.Verify(public, ps.Hash(), ps.Signature); vrr != nil { - err = vrr - } - } - if err == nil { - if _, ok := d.partialsIdx[ps.Partial.I]; ok { - err = errors.New("dss: partial signature already received from peer") - } - } - if err != nil { - return err - } - hash := d.hashSig() // h(m‖V), in the paper's notation - idx := ps.Partial.I - // βᵢG=sum(cₖi^kG), in the paper, defined as sᵢ in step 2 of section 2.4 - randShare := d.randomPoly.Eval(idx) - // ɑᵢG=sum(bₖi^kG), defined as sᵢ in step 2 of section 2.4 - longShare := d.longPoly.Eval(idx) - // h(m‖V)(Y+...) term from equation (3) of the paper. AKA h(m‖V)ɑᵢG - challengeSummand := secp256k1Group.Point().Mul(hash, longShare.V) - // RHS of equation (3), except we subtract the second term instead of adding. - // AKA (βᵢ-ɑᵢh(m‖V))G, which should equal ɣᵢG, according to equation (3) - maybePartialSigCommitment := secp256k1Group.Point().Sub(randShare.V, - challengeSummand) - // Check that equation (3) holds (ɣᵢ is represented as ps.Partial.V, here.) - partialSigCommitment := secp256k1Group.Point().Mul(ps.Partial.V, nil) - if !partialSigCommitment.Equal(maybePartialSigCommitment) { - return errors.New("dss: partial signature not valid") - } - d.partialsIdx[ps.Partial.I] = true - d.partials = append(d.partials, ps.Partial) - return nil -} - -// EnoughPartialSig returns true if there are enough partial signature to compute -// the distributed signature. It returns false otherwise. If there are enough -// partial signatures, one can issue the signature with `Signature()`. -func (d *DSS) EnoughPartialSig() bool { - return len(d.partials) >= d.T -} - -// Signature computes the distributed signature from the list of partial -// signatures received. It returns an error if there are not enough partial -// signatures. -// -// Corresponds to section 4.2, step 4 of Stinson, 2001 paper -func (d *DSS) Signature() (ethschnorr.Signature, error) { - if !d.EnoughPartialSig() { - return nil, errors.New("dkg: not enough partial signatures to sign") - } - // signature corresponds to σ in step 4 of section 4.2 - signature, err := share.RecoverSecret(secp256k1Suite, d.partials, d.T, - len(d.participants)) - if err != nil { - return nil, err - } - rv := ethschnorr.NewSignature() - rv.Signature = secp256k1.ToInt(signature) - // commitmentPublicKey corresponds to V in step 4 of section 4.2 - commitmentPublicKey := d.random.Commitments()[0] - rv.CommitmentPublicAddress = secp256k1.EthereumAddress(commitmentPublicKey) - return rv, nil -} - -// hashSig returns, in the paper's notation, h(m‖V). It is the challenge hash -// for the signature. (Actually, the hash also includes the public key, but that -// has no effect on the correctness or robustness arguments from the paper.) -func (d *DSS) hashSig() kyber.Scalar { - v := d.random.Commitments()[0] // Public-key commitment, in signature from d - vAddress := secp256k1.EthereumAddress(v) - publicKey := d.long.Commitments()[0] - rv, err := ethschnorr.ChallengeHash(publicKey, vAddress, d.msg) - if err != nil { - panic(err) - } - return rv -} - -// Verify takes a public key, a message and a signature and returns an error if -// the signature is invalid. -func Verify(public kyber.Point, msg *big.Int, sig ethschnorr.Signature) error { - return ethschnorr.Verify(public, msg, sig) -} - -// Hash returns the hash representation of this PartialSig to be used in a -// signature. -func (ps *PartialSig) Hash() *big.Int { - h := secp256k1Suite.Hash() - _, _ = h.Write(ps.Partial.Hash(secp256k1Suite)) - _, _ = h.Write(ps.SessionID) - return (&big.Int{}).SetBytes(h.Sum(nil)) -} - -func findPub(list []kyber.Point, i int) (kyber.Point, bool) { - if i >= len(list) { - return nil, false - } - return list[i], true -} - -func sessionID(s Suite, a, b DistKeyShare) []byte { - h := s.Hash() - for _, p := range a.Commitments() { - _, _ = p.MarshalTo(h) - } - - for _, p := range b.Commitments() { - _, _ = p.MarshalTo(h) - } - - return h.Sum(nil) -} diff --git a/core/services/signatures/ethdss/ethdss_test.go b/core/services/signatures/ethdss/ethdss_test.go deleted file mode 100644 index a9adcf65bc2..00000000000 --- a/core/services/signatures/ethdss/ethdss_test.go +++ /dev/null @@ -1,288 +0,0 @@ -package clientdss - -import ( - "crypto/rand" - "math/big" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "go.dedis.ch/kyber/v3" - dkg "go.dedis.ch/kyber/v3/share/dkg/rabin" - - "github.com/smartcontractkit/chainlink-common/keystore/corekeys/vrfkey/secp256k1" - "github.com/smartcontractkit/chainlink/v2/core/services/signatures/cryptotest" - "github.com/smartcontractkit/chainlink/v2/core/services/signatures/ethschnorr" -) - -var suite = secp256k1.NewBlakeKeccackSecp256k1() - -var nbParticipants = 7 -var t = nbParticipants/2 + 1 - -var partPubs []kyber.Point -var partSec []kyber.Scalar - -var longterms []*dkg.DistKeyShare -var randoms []*dkg.DistKeyShare - -var msg *big.Int - -var randomStream = cryptotest.NewStream(&testing.T{}, 0) - -func init() { - partPubs = make([]kyber.Point, nbParticipants) - partSec = make([]kyber.Scalar, nbParticipants) - for i := range nbParticipants { - kp := secp256k1.Generate(randomStream) - partPubs[i] = kp.Public - partSec[i] = kp.Private - } - // Corresponds to section 4.2, step 1 of Stinson, 2001 paper - longterms = genDistSecret(true) // Keep trying until valid public key - randoms = genDistSecret(false) - - var err error - msg, err = rand.Int(rand.Reader, big.NewInt(0).Lsh(big.NewInt(1), 256)) - if err != nil { - panic(err) - } -} - -func TestDSSNew(t *testing.T) { - dssArgs := DSSArgs{secret: partSec[0], participants: partPubs, - long: longterms[0], random: randoms[0], msg: msg, T: 4} - dss, err := NewDSS(dssArgs) - assert.NotNil(t, dss) - assert.NoError(t, err) - dssArgs.secret = suite.Scalar().Zero() - dss, err = NewDSS(dssArgs) - assert.Nil(t, dss) - assert.Error(t, err) -} - -func TestDSSPartialSigs(t *testing.T) { - dss0 := getDSS(0) - dss1 := getDSS(1) - ps0, err := dss0.PartialSig() - assert.NoError(t, err) - assert.NotNil(t, ps0) - assert.Len(t, dss0.partials, 1) - // second time should not affect list - ps0, err = dss0.PartialSig() - assert.NoError(t, err) - assert.NotNil(t, ps0) - assert.Len(t, dss0.partials, 1) - - // wrong index - goodI := ps0.Partial.I - ps0.Partial.I = 100 - err = dss1.ProcessPartialSig(ps0) - assert.Error(t, err) - assert.Contains(t, err.Error(), "invalid index") - ps0.Partial.I = goodI - - // wrong sessionID - goodSessionID := ps0.SessionID - ps0.SessionID = []byte("ahhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh") - err = dss1.ProcessPartialSig(ps0) - assert.Error(t, err) - assert.Contains(t, err.Error(), "dss: session id") - ps0.SessionID = goodSessionID - - // wrong Signature - goodSig := ps0.Signature - ps0.Signature = ethschnorr.NewSignature() - copy(ps0.Signature.CommitmentPublicAddress[:], randomBytes(20)) - badSig := secp256k1.ToInt(suite.Scalar().Pick(randomStream)) - ps0.Signature.Signature.Set(badSig) - assert.Error(t, dss1.ProcessPartialSig(ps0)) - ps0.Signature = goodSig - - // invalid partial sig - goodV := ps0.Partial.V - ps0.Partial.V = suite.Scalar().Zero() - ps0.Signature, err = ethschnorr.Sign(dss0.secret, ps0.Hash()) - require.NoError(t, err) - err = dss1.ProcessPartialSig(ps0) - assert.Error(t, err) - assert.Contains(t, err.Error(), "not valid") - ps0.Partial.V = goodV - ps0.Signature = goodSig - - // fine - err = dss1.ProcessPartialSig(ps0) - assert.NoError(t, err) - - // already received - assert.Error(t, dss1.ProcessPartialSig(ps0)) - - // if not enough partial signatures, can't generate signature - sig, err := dss1.Signature() - assert.Nil(t, sig) // XXX: Should also check err is nil? - assert.Error(t, err) - assert.Contains(t, err.Error(), "not enough") - - // enough partial sigs ? - for i := 2; i < nbParticipants; i++ { - dss := getDSS(i) - ps, e := dss.PartialSig() - require.NoError(t, e) - require.NoError(t, dss1.ProcessPartialSig(ps)) - } - assert.True(t, dss1.EnoughPartialSig()) - sig, err = dss1.Signature() - assert.NoError(t, err) - assert.NoError(t, Verify(dss1.long.Commitments()[0], msg, sig)) -} - -var printTests = false - -func printTest(t *testing.T, msg *big.Int, public kyber.Point, - signature ethschnorr.Signature) { - pX, pY := secp256k1.Coordinates(public) - t.Logf(" ['%064x',\n '%064x',\n '%064x',\n '%064x',\n '%040x'],\n", - msg, pX, pY, signature.Signature, - signature.CommitmentPublicAddress) -} - -func TestDSSSignature(t *testing.T) { - dsss := make([]*DSS, nbParticipants) - pss := make([]*PartialSig, nbParticipants) - for i := range nbParticipants { - dsss[i] = getDSS(i) - ps, err := dsss[i].PartialSig() - require.NoError(t, err) - require.NotNil(t, ps) - pss[i] = ps - } - for i, dss := range dsss { - for j, ps := range pss { - if i == j { - continue - } - require.NoError(t, dss.ProcessPartialSig(ps)) - } - } - // issue and verify signature - dss0 := dsss[0] - sig, err := dss0.Signature() - assert.NotNil(t, sig) - assert.NoError(t, err) - assert.NoError(t, ethschnorr.Verify(longterms[0].Public(), dss0.msg, sig)) - // Original contains this second check. Unclear why. - assert.NoError(t, ethschnorr.Verify(longterms[0].Public(), dss0.msg, sig)) - if printTests { - printTest(t, dss0.msg, dss0.long.Commitments()[0], sig) - } -} - -func TestPartialSig_Hash(t *testing.T) { - observedHashes := make(map[*big.Int]bool) - for i := range nbParticipants { - psig, err := getDSS(i).PartialSig() - require.NoError(t, err) - hash := psig.Hash() - require.False(t, observedHashes[hash]) - observedHashes[hash] = true - } -} - -func getDSS(i int) *DSS { - dss, err := NewDSS(DSSArgs{secret: partSec[i], participants: partPubs, - long: longterms[i], random: randoms[i], msg: msg, T: t}) - if dss == nil || err != nil { - panic("nil dss") - } - return dss -} - -func _genDistSecret() []*dkg.DistKeyShare { - dkgs := make([]*dkg.DistKeyGenerator, nbParticipants) - for i := range nbParticipants { - dkg, err := dkg.NewDistKeyGenerator(suite, partSec[i], partPubs, nbParticipants/2+1) - if err != nil { - panic(err) - } - dkgs[i] = dkg - } - // full secret sharing exchange - // 1. broadcast deals - resps := make([]*dkg.Response, 0, nbParticipants*nbParticipants) - for _, dkg := range dkgs { - deals, err := dkg.Deals() - if err != nil { - panic(err) - } - for i, d := range deals { - resp, err := dkgs[i].ProcessDeal(d) - if err != nil { - panic(err) - } - if !resp.Response.Approved { - panic("wrong approval") - } - resps = append(resps, resp) - } - } - // 2. Broadcast responses - for _, resp := range resps { - for h, dkg := range dkgs { - // ignore all messages from ourself - if resp.Response.Index == uint32(h) { - continue - } - j, err := dkg.ProcessResponse(resp) - if err != nil || j != nil { - panic("wrongProcessResponse") - } - } - } - // 4. Broadcast secret commitment - for i, dkg := range dkgs { - scs, err := dkg.SecretCommits() - if err != nil { - panic("wrong SecretCommits") - } - for j, dkg2 := range dkgs { - if i == j { - continue - } - cc, err := dkg2.ProcessSecretCommits(scs) - if err != nil || cc != nil { - panic("wrong ProcessSecretCommits") - } - } - } - - // 5. reveal shares - dkss := make([]*dkg.DistKeyShare, len(dkgs)) - for i, dkg := range dkgs { - dks, err := dkg.DistKeyShare() - if err != nil { - panic(err) - } - dkss[i] = dks - } - return dkss -} - -func genDistSecret(checkValidPublicKey bool) []*dkg.DistKeyShare { - rv := _genDistSecret() - if checkValidPublicKey { - // Because of the trick we're using to verify the signatures on-chain, we - // need to make sure that the ordinate of this distributed public key is - // in the lower half of {0,...,} - for !secp256k1.ValidPublicKey(rv[0].Public()) { - rv = _genDistSecret() // Keep trying until valid distributed public key. - } - } - return rv -} - -func randomBytes(n int) []byte { - var buff = make([]byte, n) - _, _ = rand.Read(buff) - return buff -} diff --git a/core/services/signatures/ethschnorr/ethschnorr.go b/core/services/signatures/ethschnorr/ethschnorr.go deleted file mode 100644 index 3d4a5337f5c..00000000000 --- a/core/services/signatures/ethschnorr/ethschnorr.go +++ /dev/null @@ -1,155 +0,0 @@ -// Package ethschnorr implements a version of the Schnorr signature which is -// ////////////////////////////////////////////////////////////////////////////// -// -// XXX: Do not use in production until this code has been audited. -// -// ////////////////////////////////////////////////////////////////////////////// -// cheap to verify on-chain. -// -// See https://en.wikipedia.org/wiki/Schnorr_signature For vanilla Schnorr. -// -// Since we are targeting ethereum specifically, there is no need to abstract -// away the group operations, as original kyber Schnorr code does. Thus, these -// functions only work with secp256k1 objects, even though they are expressed in -// terms of the abstract kyber Group interfaces. -// -// This code is largely based on EPFL-DEDIS's go.dedis.ch/kyber/sign/schnorr -package ethschnorr - -import ( - "bytes" - "errors" - "fmt" - "math/big" - - "go.dedis.ch/kyber/v3" - - "github.com/smartcontractkit/chainlink-common/keystore/corekeys/vrfkey/secp256k1" -) - -var secp256k1Suite = secp256k1.NewBlakeKeccackSecp256k1() -var secp256k1Group kyber.Group = secp256k1Suite - -type signature = struct { - CommitmentPublicAddress [20]byte - Signature *big.Int -} - -// Signature is a representation of the Schnorr signature generated and verified -// by this library. -type Signature = *signature - -func i() *big.Int { return big.NewInt(0) } - -var one = big.NewInt(1) -var u256Cardinality = i().Lsh(one, 256) -var maxUint256 = i().Sub(u256Cardinality, one) - -// NewSignature allocates space for a Signature, and returns it -func NewSignature() Signature { return &signature{Signature: i()} } - -var zero = i() - -// ValidSignature(s) is true iff s.Signature represents an element of secp256k1 -func ValidSignature(s Signature) bool { - return s.Signature.Cmp(secp256k1.GroupOrder) == -1 && - s.Signature.Cmp(zero) != -1 -} - -// ChallengeHash returns the value the signer must use to demonstrate knowledge -// of the secret key -// -// NB: for parity with the on-chain hash, it's important that public and r -// marshall to the big-endian x ordinate, followed by a byte which is 0 if the y -// ordinate is even, 1 if it's odd. See evm/contracts/SchnorrSECP256K1.sol and -// evm/test/schnorr_test.js -func ChallengeHash(public kyber.Point, rAddress [20]byte, msg *big.Int) ( - kyber.Scalar, error) { - var err error - h := secp256k1Suite.Hash() - if _, herr := public.MarshalTo(h); herr != nil { - err = fmt.Errorf("failed to hash public key for signature: %w", herr) - } - if err != nil && (msg.BitLen() > 256 || msg.Cmp(zero) == -1) { - err = errors.New("msg must be a uint256") - } - if err == nil { - if _, herr := h.Write(msg.Bytes()); herr != nil { - err = fmt.Errorf("failed to hash message for signature: %w", herr) - } - } - if err == nil { - if _, herr := h.Write(rAddress[:]); herr != nil { - err = fmt.Errorf("failed to hash r for signature: %w", herr) - } - } - if err != nil { - return nil, err - } - return secp256k1Suite.Scalar().SetBytes(h.Sum(nil)), nil -} - -// Sign creates a signature from a msg and a private key. Verify with the -// function Verify, or on-chain with SchnorrSECP256K1.sol. -func Sign(private kyber.Scalar, msg *big.Int) (Signature, error) { - if !secp256k1.IsSecp256k1Scalar(private) { - return nil, errors.New("private key is not a secp256k1 scalar") - } - // create random secret and public commitment to it - commitmentSecretKey := secp256k1Group.Scalar().Pick( - secp256k1Suite.RandomStream()) - commitmentPublicKey := secp256k1Group.Point().Mul(commitmentSecretKey, nil) - commitmentPublicAddress := secp256k1.EthereumAddress(commitmentPublicKey) - - public := secp256k1Group.Point().Mul(private, nil) - challenge, err := ChallengeHash(public, commitmentPublicAddress, msg) - if err != nil { - return nil, err - } - // commitmentSecretKey-private*challenge - s := secp256k1Group.Scalar().Sub(commitmentSecretKey, - secp256k1Group.Scalar().Mul(private, challenge)) - rv := signature{commitmentPublicAddress, secp256k1.ToInt(s)} - return &rv, nil -} - -// Verify verifies the given Schnorr signature. It returns true iff the -// signature is valid. -func Verify(public kyber.Point, msg *big.Int, s Signature) error { - var err error - if !ValidSignature(s) { - err = errors.New("s is not a valid signature") - } - if err == nil && !secp256k1.IsSecp256k1Point(public) { - err = errors.New("public key is not a secp256k1 point") - } - if err == nil && !secp256k1.ValidPublicKey(public) { - err = errors.New("`public` is not a valid public key") - } - if err == nil && (msg.Cmp(zero) == -1 || msg.Cmp(maxUint256) == 1) { - err = errors.New("msg is not a uint256") - } - var challenge kyber.Scalar - var herr error - if err == nil { - challenge, herr = ChallengeHash(public, s.CommitmentPublicAddress, msg) - if herr != nil { - err = herr - } - } - if err != nil { - return err - } - sigScalar := secp256k1.IntToScalar(s.Signature) - // s*g + challenge*public = s*g + challenge*(secretKey*g) = - // commitmentSecretKey*g = commitmentPublicKey - maybeCommitmentPublicKey := secp256k1Group.Point().Add( - secp256k1Group.Point().Mul(sigScalar, nil), - secp256k1Group.Point().Mul(challenge, public)) - maybeCommitmentPublicAddress := secp256k1.EthereumAddress(maybeCommitmentPublicKey) - if !bytes.Equal(s.CommitmentPublicAddress[:], - maybeCommitmentPublicAddress[:]) { - return errors.New("signature mismatch") - } - return nil -} diff --git a/core/services/signatures/ethschnorr/ethschnorr_test.go b/core/services/signatures/ethschnorr/ethschnorr_test.go deleted file mode 100644 index d64c720a871..00000000000 --- a/core/services/signatures/ethschnorr/ethschnorr_test.go +++ /dev/null @@ -1,113 +0,0 @@ -package ethschnorr - -// This code is largely based on go.dedis.ch/kyber/sign/schnorr_test from -// EPFL's DEDIS - -import ( - crand "crypto/rand" - "math/big" - mrand "math/rand" - "testing" - - "github.com/stretchr/testify/require" - - "go.dedis.ch/kyber/v3" - "go.dedis.ch/kyber/v3/group/curve25519" - - "github.com/smartcontractkit/chainlink-common/keystore/corekeys/vrfkey/secp256k1" - "github.com/smartcontractkit/chainlink/v2/core/services/signatures/cryptotest" -) - -var numSignatures = 5 - -var randomStream = cryptotest.NewStream(&testing.T{}, 0) - -var printTests = false - -func printTest(t *testing.T, msg *big.Int, private kyber.Scalar, - public kyber.Point, signature Signature) { - privateBytes, err := private.MarshalBinary() - require.NoError(t, err) - pX, pY := secp256k1.Coordinates(public) - t.Logf(" ['%064x',\n '%064x',\n '%064x',\n '%064x',\n "+ - "'%064x',\n '%040x'],\n", - msg, privateBytes, pX, pY, signature.Signature, - signature.CommitmentPublicAddress) -} - -func TestShortSchnorr_SignAndVerify(t *testing.T) { - if printTests { - t.Log("tests = [\n") - } - for range numSignatures { - rand := mrand.New(mrand.NewSource(0)) - msg, err := crand.Int(rand, maxUint256) - require.NoError(t, err) - kp := secp256k1.Generate(randomStream) - sig, err := Sign(kp.Private, msg) - require.NoError(t, err, "failed to sign message") - require.NoError(t, Verify(kp.Public, msg, sig), - "failed to validate own signature") - require.Error(t, Verify(kp.Public, u256Cardinality, sig), - "failed to abort on too large a message") - require.Error(t, Verify(kp.Public, big.NewInt(0).Neg(big.NewInt(1)), sig), - "failed to abort on negative message") - if printTests { - printTest(t, msg, kp.Private, kp.Public, sig) - } - wrongMsg := big.NewInt(0).Add(msg, big.NewInt(1)) - require.Error(t, Verify(kp.Public, wrongMsg, sig), - "failed to reject signature with bad message") - wrongPublic := secp256k1Group.Point().Add(kp.Public, kp.Public) - require.Error(t, Verify(wrongPublic, msg, sig), - "failed to reject signature with bad public key") - wrongSignature := &signature{ - CommitmentPublicAddress: sig.CommitmentPublicAddress, - Signature: big.NewInt(0).Add(sig.Signature, one), - } - require.Error(t, Verify(kp.Public, msg, wrongSignature), - "failed to reject bad signature") - badPublicCommitmentAddress := &signature{Signature: sig.Signature} - copy(badPublicCommitmentAddress.CommitmentPublicAddress[:], - sig.CommitmentPublicAddress[:]) - badPublicCommitmentAddress.CommitmentPublicAddress[0] ^= 1 // Corrupt it - require.Error(t, Verify(kp.Public, msg, badPublicCommitmentAddress), - "failed to reject signature with bad public commitment") - } - if printTests { - t.Log("]") - } - // Check other validations - edSuite := curve25519.NewBlakeSHA256Curve25519(false) - badScalar := edSuite.Scalar() - _, err := Sign(badScalar, i()) - require.Error(t, err) - require.Contains(t, err.Error(), "not a secp256k1 scalar") - err = Verify(edSuite.Point(), i(), NewSignature()) - require.Error(t, err) - require.Contains(t, err.Error(), "not a secp256k1 point") - err = Verify(secp256k1Suite.Point(), i(), &signature{Signature: big.NewInt(-1)}) - require.Error(t, err) - require.Contains(t, err.Error(), "not a valid signature") - err = Verify(secp256k1Suite.Point(), i(), &signature{Signature: u256Cardinality}) - require.Error(t, err) - require.Contains(t, err.Error(), "not a valid signature") -} - -func TestShortSchnorr_NewSignature(t *testing.T) { - s := NewSignature() - require.Equal(t, s.Signature, big.NewInt(0)) -} - -func TestShortSchnorr_ChallengeHash(t *testing.T) { - point := secp256k1Group.Point() - var hash [20]byte - h, err := ChallengeHash(point, hash, big.NewInt(-1)) - require.Nil(t, h) - require.Error(t, err) - require.Contains(t, err.Error(), "msg must be a uint256") - h, err = ChallengeHash(point, hash, u256Cardinality) - require.Nil(t, h) - require.Error(t, err) - require.Contains(t, err.Error(), "msg must be a uint256") -} diff --git a/go.mod b/go.mod index 04295d568a4..d493e6b836c 100644 --- a/go.mod +++ b/go.mod @@ -398,7 +398,6 @@ require ( github.com/zondax/hid v0.9.2 // indirect github.com/zondax/ledger-go v0.14.3 // indirect go.dedis.ch/fixbuf v1.0.3 // indirect - go.dedis.ch/protobuf v1.0.11 // indirect go.etcd.io/bbolt v1.4.2 // indirect go.mongodb.org/mongo-driver v1.17.2 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect diff --git a/go.sum b/go.sum index 953646897a5..7590b4f68ae 100644 --- a/go.sum +++ b/go.sum @@ -1488,7 +1488,6 @@ go.dedis.ch/kyber/v3 v3.1.0 h1:ghu+kiRgM5JyD9TJ0hTIxTLQlJBR/ehjWvWwYW3XsC0= go.dedis.ch/kyber/v3 v3.1.0/go.mod h1:kXy7p3STAurkADD+/aZcsznZGKVHEqbtmdIzvPfrs1U= go.dedis.ch/protobuf v1.0.5/go.mod h1:eIV4wicvi6JK0q/QnfIEGeSFNG0ZeB24kzut5+HaRLo= go.dedis.ch/protobuf v1.0.7/go.mod h1:pv5ysfkDX/EawiPqcW3ikOxsL5t+BqnV6xHSmE79KI4= -go.dedis.ch/protobuf v1.0.11 h1:FTYVIEzY/bfl37lu3pR4lIj+F9Vp1jE8oh91VmxKgLo= go.dedis.ch/protobuf v1.0.11/go.mod h1:97QR256dnkimeNdfmURz0wAMNVbd1VmLXhG1CrTYrJ4= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.4.2 h1:IrUHp260R8c+zYx/Tm8QZr04CX+qWS5PGfPdevhdm1I=