diff --git a/bridge/go.mod b/bridge/go.mod index 10adadc9f..9197e319d 100644 --- a/bridge/go.mod +++ b/bridge/go.mod @@ -3,8 +3,9 @@ module github.com/certusone/wormhole/bridge go 1.14 require ( + github.com/btcsuite/btcd v0.20.1-beta github.com/cenkalti/backoff/v4 v4.0.2 - github.com/ipfs/go-log v1.0.4 + github.com/ethereum/go-ethereum v1.9.18 github.com/ipfs/go-log/v2 v2.1.1 github.com/libp2p/go-libp2p v0.10.2 github.com/libp2p/go-libp2p-connmgr v0.2.4 @@ -14,6 +15,11 @@ require ( github.com/libp2p/go-libp2p-quic-transport v0.7.1 github.com/libp2p/go-libp2p-tls v0.1.3 github.com/multiformats/go-multiaddr v0.2.2 + github.com/smartcontractkit/chainlink v0.8.11 + github.com/stretchr/testify v1.6.1 + go.dedis.ch/fixbuf v1.0.3 + go.dedis.ch/kyber/v3 v3.0.12 go.uber.org/zap v1.15.0 + golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 google.golang.org/grpc v1.31.0 ) diff --git a/bridge/third_party/chainlink/LICENSE b/bridge/third_party/chainlink/LICENSE new file mode 100644 index 000000000..1fa3822f5 --- /dev/null +++ b/bridge/third_party/chainlink/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2018 SmartContract ChainLink, Ltd. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/bridge/third_party/chainlink/NOTICE b/bridge/third_party/chainlink/NOTICE new file mode 100644 index 000000000..ad59c1490 --- /dev/null +++ b/bridge/third_party/chainlink/NOTICE @@ -0,0 +1,2 @@ +The Distributed Schnorr Signature Go implementation and the corresponding Solidity smart contract +have been imported from https://hub.docker.com/r/smartcontract/chainlink (which is based ob EPFL DEDIS/kyber). diff --git a/bridge/third_party/chainlink/ethdss/ethdss.go b/bridge/third_party/chainlink/ethdss/ethdss.go new file mode 100644 index 000000000..3a79c295d --- /dev/null +++ b/bridge/third_party/chainlink/ethdss/ethdss.go @@ -0,0 +1,304 @@ +// 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 ethdss + +import ( + "bytes" + "errors" + "math/big" + + "go.dedis.ch/kyber/v3" + "go.dedis.ch/kyber/v3/share" + + "github.com/certusone/wormhole/bridge/third_party/chainlink/ethschnorr" + "github.com/certusone/wormhole/bridge/third_party/chainlink/secp256k1" +) + +// 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/bridge/third_party/chainlink/ethdss/ethdss_test.go b/bridge/third_party/chainlink/ethdss/ethdss_test.go new file mode 100644 index 000000000..79a658ea6 --- /dev/null +++ b/bridge/third_party/chainlink/ethdss/ethdss_test.go @@ -0,0 +1,290 @@ +package ethdss + +import ( + "crypto/rand" + "fmt" + "math/big" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/smartcontractkit/chainlink/core/services/signatures/cryptotest" + "github.com/smartcontractkit/chainlink/core/services/signatures/ethschnorr" + "github.com/smartcontractkit/chainlink/core/services/signatures/secp256k1" + + "go.dedis.ch/kyber/v3" + dkg "go.dedis.ch/kyber/v3/share/dkg/rabin" +) + +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 := 0; i < nbParticipants; i++ { + 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.Nil(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.Nil(t, err) + assert.NotNil(t, ps0) + assert.Len(t, dss0.partials, 1) + // second time should not affect list + ps0, err = dss0.PartialSig() + assert.Nil(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.Nil(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.Nil(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.Nil(t, e) + require.Nil(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) + fmt.Printf(" ['%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 := 0; i < nbParticipants; i++ { + dsss[i] = getDSS(i) + ps, err := dsss[i].PartialSig() + require.Nil(t, err) + require.NotNil(t, ps) + pss[i] = ps + } + for i, dss := range dsss { + for j, ps := range pss { + if i == j { + continue + } + require.Nil(t, dss.ProcessPartialSig(ps)) + } + } + // issue and verify signature + dss0 := dsss[0] + sig, err := dss0.Signature() + assert.NotNil(t, sig) + assert.Nil(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 := 0; i < nbParticipants; i++ { + 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 := 0; i < nbParticipants; i++ { + 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/bridge/third_party/chainlink/ethschnorr/ethschnorr.go b/bridge/third_party/chainlink/ethschnorr/ethschnorr.go new file mode 100644 index 000000000..c89edc3e7 --- /dev/null +++ b/bridge/third_party/chainlink/ethschnorr/ethschnorr.go @@ -0,0 +1,152 @@ +// 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" + "fmt" + "math/big" + + "go.dedis.ch/kyber/v3" + + "github.com/certusone/wormhole/bridge/third_party/chainlink/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: %s", herr) + } + if err != nil && (msg.BitLen() > 256 || msg.Cmp(zero) == -1) { + err = fmt.Errorf("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: %s", herr) + } + } + if err == nil { + if _, herr := h.Write(rAddress[:]); herr != nil { + err = fmt.Errorf("failed to hash r for signature: %s", 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, fmt.Errorf("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 = fmt.Errorf("s is not a valid signature") + } + if err == nil && !secp256k1.IsSecp256k1Point(public) { + err = fmt.Errorf("public key is not a secp256k1 point") + } + if err == nil && !secp256k1.ValidPublicKey(public) { + err = fmt.Errorf("`public` is not a valid public key") + } + if err == nil && (msg.Cmp(zero) == -1 || msg.Cmp(maxUint256) == 1) { + err = fmt.Errorf("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 fmt.Errorf("signature mismatch") + } + return nil +} diff --git a/bridge/third_party/chainlink/ethschnorr/ethschnorr_test.go b/bridge/third_party/chainlink/ethschnorr/ethschnorr_test.go new file mode 100644 index 000000000..159466abd --- /dev/null +++ b/bridge/third_party/chainlink/ethschnorr/ethschnorr_test.go @@ -0,0 +1,114 @@ +package ethschnorr + +// This code is largely based on go.dedis.ch/kyber/sign/schnorr_test from +// EPFL's DEDIS + +import ( + crand "crypto/rand" + "fmt" + "math/big" + mrand "math/rand" + "testing" + + "github.com/smartcontractkit/chainlink/core/services/signatures/cryptotest" + "github.com/stretchr/testify/require" + + "go.dedis.ch/kyber/v3" + "go.dedis.ch/kyber/v3/group/curve25519" + + "github.com/certusone/wormhole/bridge/third_party/chainlink/secp256k1" +) + +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.Nil(t, err) + pX, pY := secp256k1.Coordinates(public) + fmt.Printf(" ['%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 { + fmt.Printf("tests = [\n") + } + for i := 0; i < numSignatures; i++ { + 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 { + fmt.Println("]") + } + // 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/bridge/third_party/chainlink/secp256k1/curve.go b/bridge/third_party/chainlink/secp256k1/curve.go new file mode 100644 index 000000000..5717db8ea --- /dev/null +++ b/bridge/third_party/chainlink/secp256k1/curve.go @@ -0,0 +1,44 @@ +// Package secp256k1 is an implementation of the kyber.{Group,Point,Scalar} +//////////////////////////////////////////////////////////////////////////////// +// XXX: Do not use in production until this code has been audited. +//////////////////////////////////////////////////////////////////////////////// +// interfaces, based on btcd/btcec and kyber/group/mod +// +// XXX: NOT CONSTANT TIME! +package secp256k1 + +import ( + "math/big" + + secp256k1BTCD "github.com/btcsuite/btcd/btcec" + + "go.dedis.ch/kyber/v3" +) + +// Secp256k1 represents the secp256k1 group. +// There are no parameters and no initialization is required +// because it supports only this one specific curve. +type Secp256k1 struct{} + +// s256 is the btcec representation of secp256k1. +var s256 *secp256k1BTCD.KoblitzCurve = secp256k1BTCD.S256() + +// String returns the name of the curve +func (*Secp256k1) String() string { return "Secp256k1" } + +var egScalar kyber.Scalar = newScalar(big.NewInt(0)) +var egPoint kyber.Point = &secp256k1Point{newFieldZero(), newFieldZero()} + +// ScalarLen returns the length of a marshalled Scalar +func (*Secp256k1) ScalarLen() int { return egScalar.MarshalSize() } + +// Scalar creates a new Scalar for the prime-order group on the secp256k1 curve +func (*Secp256k1) Scalar() kyber.Scalar { return newScalar(big.NewInt(0)) } + +// PointLen returns the length of a marshalled Point +func (*Secp256k1) PointLen() int { return egPoint.MarshalSize() } + +// Point returns a new secp256k1 point +func (*Secp256k1) Point() kyber.Point { + return &secp256k1Point{newFieldZero(), newFieldZero()} +} diff --git a/bridge/third_party/chainlink/secp256k1/curve_test.go b/bridge/third_party/chainlink/secp256k1/curve_test.go new file mode 100644 index 000000000..e4802ea3d --- /dev/null +++ b/bridge/third_party/chainlink/secp256k1/curve_test.go @@ -0,0 +1,20 @@ +package secp256k1 + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +var group = &Secp256k1{} + +func TestSecp256k1_String(t *testing.T) { + require.Equal(t, group.String(), "Secp256k1") +} + +func TestSecp256k1_Constructors(t *testing.T) { + require.Equal(t, group.ScalarLen(), 32) + require.Equal(t, ToInt(group.Scalar()), bigZero) + require.Equal(t, group.PointLen(), 33) + require.Equal(t, group.Point(), &secp256k1Point{fieldZero, fieldZero}) +} diff --git a/bridge/third_party/chainlink/secp256k1/field.go b/bridge/third_party/chainlink/secp256k1/field.go new file mode 100644 index 000000000..0cd2f8500 --- /dev/null +++ b/bridge/third_party/chainlink/secp256k1/field.go @@ -0,0 +1,169 @@ +// Package secp256k1 is an implementation of the kyber.{Group,Point,Scalar} +//////////////////////////////////////////////////////////////////////////////// +// XXX: Do not use in production until this code has been audited. +//////////////////////////////////////////////////////////////////////////////// +// interfaces, based on btcd/btcec and kyber/group/mod +// +// XXX: NOT CONSTANT TIME! +package secp256k1 + +// Arithmetic operations in the base field of secp256k1, i.e. ℤ/qℤ, where q is +// the base field characteristic. + +import ( + "crypto/cipher" + "fmt" + "math/big" + + "go.dedis.ch/kyber/v3/util/random" +) + +// q is the field characteristic (cardinality) of the secp256k1 base field. All +// arithmetic operations on the field are modulo this. +var q = s256.P + +type fieldElt big.Int + +// newFieldZero returns a newly allocated field element. +func newFieldZero() *fieldElt { return (*fieldElt)(big.NewInt(0)) } + +// Int returns f as a big.Int +func (f *fieldElt) int() *big.Int { return (*big.Int)(f) } + +// modQ reduces f's underlying big.Int modulo q, and returns it +func (f *fieldElt) modQ() *fieldElt { + if f.int().Cmp(q) != -1 || f.int().Cmp(bigZero) == -1 { + // f ∉ {0, ..., q-1}. Find the representative of f+qℤ in that set. + // + // Per Mod docstring, "Mod implements Euclidean modulus", meaning that after + // this, f will be the smallest non-negative representative of its + // equivalence class in ℤ/qℤ. TODO(alx): Make this faster + f.int().Mod(f.int(), q) + } + return f +} + +// This differs from SetInt below, in that it does not take a copy of v. +func fieldEltFromBigInt(v *big.Int) *fieldElt { return (*fieldElt)(v).modQ() } + +func fieldEltFromInt(v int64) *fieldElt { + return fieldEltFromBigInt(big.NewInt(int64(v))).modQ() +} + +var fieldZero = fieldEltFromInt(0) +var bigZero = big.NewInt(0) + +// String returns the string representation of f +func (f *fieldElt) String() string { + return fmt.Sprintf("fieldElt{%x}", f.int()) +} + +// Equal returns true iff f=g, i.e. the backing big.Ints satisfy f ≡ g mod q +func (f *fieldElt) Equal(g *fieldElt) bool { + if f == (*fieldElt)(nil) && g == (*fieldElt)(nil) { + return true + } + if f == (*fieldElt)(nil) { // f is nil, g is not + return false + } + if g == (*fieldElt)(nil) { // g is nil, f is not + return false + } + return bigZero.Cmp(newFieldZero().Sub(f, g).modQ().int()) == 0 +} + +// Add sets f to the sum of a and b modulo q, and returns it. +func (f *fieldElt) Add(a, b *fieldElt) *fieldElt { + f.int().Add(a.int(), b.int()) + return f.modQ() +} + +// Sub sets f to a-b mod q, and returns it. +func (f *fieldElt) Sub(a, b *fieldElt) *fieldElt { + f.int().Sub(a.int(), b.int()) + return f.modQ() +} + +// Set sets f's value to v, and returns f. +func (f *fieldElt) Set(v *fieldElt) *fieldElt { + f.int().Set(v.int()) + return f.modQ() +} + +// SetInt sets f's value to v mod q, and returns f. +func (f *fieldElt) SetInt(v *big.Int) *fieldElt { + f.int().Set(v) + return f.modQ() +} + +// Pick samples uniformly from {0, ..., q-1}, assigns sample to f, and returns f +func (f *fieldElt) Pick(rand cipher.Stream) *fieldElt { + return f.SetInt(random.Int(q, rand)) // random.Int safe because q≅2²⁵⁶, q<2²⁵⁶ +} + +// Neg sets f to the negation of g modulo q, and returns it +func (f *fieldElt) Neg(g *fieldElt) *fieldElt { + f.int().Neg(g.int()) + return f.modQ() +} + +// Clone returns a new fieldElt, backed by a clone of f +func (f *fieldElt) Clone() *fieldElt { return newFieldZero().Set(f.modQ()) } + +// SetBytes sets f to the 32-byte big-endian value represented by buf, reduces +// it, and returns it. +func (f *fieldElt) SetBytes(buf [32]byte) *fieldElt { + f.int().SetBytes(buf[:]) + return f.modQ() +} + +// Bytes returns the 32-byte big-endian representation of f +func (f *fieldElt) Bytes() [32]byte { + bytes := f.modQ().int().Bytes() + if len(bytes) > 32 { + panic("field element longer than 256 bits") + } + var rv [32]byte + copy(rv[32-len(bytes):], bytes) // leftpad w zeros + return rv +} + +var two = big.NewInt(2) + +// square returns y² mod q +func fieldSquare(y *fieldElt) *fieldElt { + return fieldEltFromBigInt(newFieldZero().int().Exp(y.int(), two, q)) +} + +// sqrtPower is s.t. n^sqrtPower≡sqrt(n) mod q, if n has a root at all. See +// https://math.stackexchange.com/a/1816280, for instance +// +// What I'm calling sqrtPower is called q on the s256 struct. (See +// btcec.initS256), which is confusing because the "Q" in "QPlus1Div4" refers to +// the field characteristic +var sqrtPower = s256.QPlus1Div4() + +// maybeSqrtInField returns a square root of v, if it has any, else nil +func maybeSqrtInField(v *fieldElt) *fieldElt { + s := newFieldZero() + s.int().Exp(v.int(), sqrtPower, q) + if !fieldSquare(s).Equal(v) { + return nil + } + return s +} + +var three = big.NewInt(3) +var seven = fieldEltFromInt(7) + +// rightHandSide returns the RHS of the secp256k1 equation, x³+7 mod q, given x +func rightHandSide(x *fieldElt) *fieldElt { + xCubed := newFieldZero() + xCubed.int().Exp(x.int(), three, q) + return xCubed.Add(xCubed, seven) +} + +// isEven returns true if f is even, false otherwise +func (f *fieldElt) isEven() bool { + return big.NewInt(0).Mod(f.int(), two).Cmp(big.NewInt(0)) == 0 +} diff --git a/bridge/third_party/chainlink/secp256k1/field_test.go b/bridge/third_party/chainlink/secp256k1/field_test.go new file mode 100644 index 000000000..2839a3dba --- /dev/null +++ b/bridge/third_party/chainlink/secp256k1/field_test.go @@ -0,0 +1,159 @@ +package secp256k1 + +import ( + "encoding/hex" + "math/big" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/smartcontractkit/chainlink/core/services/signatures/cryptotest" +) + +var numFieldSamples = 10 + +var observedFieldElts map[string]bool + +func init() { + observedFieldElts = make(map[string]bool) +} + +// observedFieldElt ensures that novel scalars are being picked. +func observedFieldElt(t *testing.T, s *fieldElt) { + elt := s.Bytes() + data := hex.Dump(elt[:]) + require.False(t, observedFieldElts[data]) + observedFieldElts[data] = true +} + +var randomStream = cryptotest.NewStream(&testing.T{}, 0) + +func TestField_SetIntAndEqual(t *testing.T) { + tests := []int64{5, 67108864, 67108865, 4294967295} + g := newFieldZero() + for _, test := range tests { + f := fieldEltFromInt(test) + i := big.NewInt(test) + g.SetInt(i) + assert.Equal(t, f, g, + "different values obtained for same input, using "+ + "SetInt vs fieldEltFromInt") + i.Add(i, big.NewInt(1)) + assert.Equal(t, f, g, + "SetInt should take a copy of the backing big.Int") + } +} + +func TestField_String(t *testing.T) { + require.Equal(t, fieldZero.String(), "fieldElt{0}") +} + +func TestField_Equal(t *testing.T) { + require.True(t, (*fieldElt)(nil).Equal((*fieldElt)(nil))) + require.False(t, (*fieldElt)(nil).Equal(fieldZero)) + require.False(t, fieldZero.Equal((*fieldElt)(nil))) +} + +func TestField_Set(t *testing.T) { + f := fieldEltFromInt(1) + g := newFieldZero() + g.Set(f) + g.Add(g, fieldEltFromInt(1)) + assert.Equal(t, f, fieldEltFromInt(1), + "Set takes a copy of the backing big.Int") +} + +func TestFieldEltFromInt(t *testing.T) { + assert.Equal(t, fieldEltFromInt(1), // Also tests fieldElt.modQ + fieldEltFromBigInt(new(big.Int).Add(q, big.NewInt(1))), + "only one representation of a ℤ/qℤ element should be used") +} + +func TestField_SmokeTestPick(t *testing.T) { + f := newFieldZero() + f.Pick(randomStream) + observedFieldElt(t, f) + assert.True(t, f.int().Cmp(big.NewInt(1000000000)) == 1, + "should be greater than 1000000000, with very high probability") +} + +func TestField_Neg(t *testing.T) { + f := newFieldZero() + for i := 0; i < numFieldSamples; i++ { + f.Pick(randomStream) + observedFieldElt(t, f) + g := f.Clone() + g.Neg(g) + require.True(t, g.Add(f, g).Equal(fieldZero), + "adding something to its negative should give zero: "+ + "failed with %s", f) + } +} + +func TestField_Sub(t *testing.T) { + f := newFieldZero() + for i := 0; i < numFieldSamples; i++ { + f.Pick(randomStream) + observedFieldElt(t, f) + require.True(t, f.Sub(f, f).Equal(fieldZero), + "subtracting something from itself should give zero: "+ + "failed with %s", f) + } +} + +func TestField_Clone(t *testing.T) { + f := fieldEltFromInt(1) + g := f.Clone() + h := f.Clone() + assert.Equal(t, f, g, "clone output does not equal original") + g.Add(f, f) + assert.Equal(t, f, h, "clone does not make a copy") + +} + +func TestField_SetBytesAndBytes(t *testing.T) { + f := newFieldZero() + g := newFieldZero() + for i := 0; i < numFieldSamples; i++ { + f.Pick(randomStream) + observedFieldElt(t, f) + g.SetBytes(f.Bytes()) + require.True(t, g.Equal(f), + "roundtrip through serialization should give same "+ + "result back: failed with %s", f) + } +} + +func TestField_MaybeSquareRootInField(t *testing.T) { + f := newFieldZero() + minusOne := fieldEltFromInt(-1) + assert.Nil(t, maybeSqrtInField(minusOne), "-1 is not a square, in this field") + for i := 0; i < numFieldSamples; i++ { + f.Pick(randomStream) + observedFieldElt(t, f) + require.True(t, f.int().Cmp(q) == -1, "picked larger value than q: %s", f) + require.True(t, f.int().Cmp(big.NewInt(-1)) != -1, + "backing int must be non-negative") + s := fieldSquare(f) + g := maybeSqrtInField(s) + require.NotEqual(t, g, (*fieldElt)(nil)) + ng := newFieldZero().Neg(g) + require.True(t, f.Equal(g) || f.Equal(ng), "squaring something and "+ + "taking the square root should give ± the original: failed with %s", f) + bigIntSqrt := newFieldZero() // Cross-check against big.ModSqrt + rv := bigIntSqrt.int().ModSqrt(s.int(), q) + require.NotNil(t, rv) + require.True(t, bigIntSqrt.Equal(g) || bigIntSqrt.Equal(ng)) + nonSquare := newFieldZero().Neg(s) + rv = bigIntSqrt.int().ModSqrt(nonSquare.int(), q) + require.Nil(t, rv, "ModSqrt indicates nonSquare is square") + require.Nil(t, maybeSqrtInField(nonSquare), "the negative of square "+ + "should not be a square") + } +} + +func TestField_RightHandSide(t *testing.T) { + assert.Equal(t, rightHandSide(fieldEltFromInt(1)), fieldEltFromInt(8)) + assert.Equal(t, rightHandSide(fieldEltFromInt(2)), fieldEltFromInt(15)) +} diff --git a/bridge/third_party/chainlink/secp256k1/point.go b/bridge/third_party/chainlink/secp256k1/point.go new file mode 100644 index 000000000..1273250b9 --- /dev/null +++ b/bridge/third_party/chainlink/secp256k1/point.go @@ -0,0 +1,379 @@ +// Package secp256k1 is an implementation of the kyber.{Group,Point,Scalar} +//////////////////////////////////////////////////////////////////////////////// +// XXX: Do not use in production until this code has been audited. +//////////////////////////////////////////////////////////////////////////////// +// interfaces, based on btcd/btcec and kyber/group/mod +// +// XXX: NOT CONSTANT TIME! +package secp256k1 + +// Implementation of kyber.Point interface for elliptic-curve arithmetic +// operations on secpk256k1. +// +// This is mostly a wrapper of the functionality provided by btcec + +import ( + "crypto/cipher" + "fmt" + "io" + "math/big" + + "go.dedis.ch/kyber/v3" + "go.dedis.ch/kyber/v3/util/key" + "golang.org/x/crypto/sha3" +) + +// btcec's public interface uses this affine representation for points on the +// curve. This does not naturally accommodate the point at infinity. btcec +// represents it as (0, 0), which is not a point on {y²=x³+7}. +type secp256k1Point struct { + X *fieldElt + Y *fieldElt +} + +func newPoint() *secp256k1Point { + return &secp256k1Point{newFieldZero(), newFieldZero()} +} + +// String returns a string representation of P +func (P *secp256k1Point) String() string { + return fmt.Sprintf("Secp256k1{X: %s, Y: %s}", P.X, P.Y) +} + +// Equal returns true if p and pPrime represent the same point, false otherwise. +func (P *secp256k1Point) Equal(pPrime kyber.Point) bool { + return P.X.Equal(pPrime.(*secp256k1Point).X) && + P.Y.Equal(pPrime.(*secp256k1Point).Y) +} + +// Null sets p to the group-identity value, and returns it. +func (P *secp256k1Point) Null() kyber.Point { + P.X = fieldEltFromInt(0) // btcec representation of null point is (0,0) + P.Y = fieldEltFromInt(0) + return P +} + +// Base sets p to a copy of the standard group generator, and returns it. +func (P *secp256k1Point) Base() kyber.Point { + P.X.SetInt(s256.Gx) + P.Y.SetInt(s256.Gy) + return P +} + +// Pick sets P to a random point sampled from rand, and returns it. +func (P *secp256k1Point) Pick(rand cipher.Stream) kyber.Point { + for { // Keep trying X's until one fits the curve (~50% probability of + // success each iteration + P.X.Set(newFieldZero().Pick(rand)) + maybeRHS := rightHandSide(P.X) + if maybeY := maybeSqrtInField(maybeRHS); maybeY != (*fieldElt)(nil) { + P.Y.Set(maybeY) + // Take the negative with 50% probability + b := make([]byte, 1) + rand.XORKeyStream(b[:], b[:]) + if b[0]&1 == 0 { + P.Y.Neg(P.Y) + } + return P + } + } +} + +// Set sets P to copies of pPrime's values, and returns it. +func (P *secp256k1Point) Set(pPrime kyber.Point) kyber.Point { + P.X.Set(pPrime.(*secp256k1Point).X) + P.Y.Set(pPrime.(*secp256k1Point).Y) + return P +} + +// Clone returns a copy of P. +func (P *secp256k1Point) Clone() kyber.Point { + return &secp256k1Point{X: P.X.Clone(), Y: P.Y.Clone()} +} + +// EmbedLen returns the number of bytes of data which can be embedded in a point. +func (*secp256k1Point) EmbedLen() int { + // Reserve the most-significant 8 bits for pseudo-randomness. + // Reserve the least-significant 8 bits for embedded data length. + return (255 - 8 - 8) / 8 +} + +// Embed encodes a limited amount of specified data in the Point, using r as a +// source of cryptographically secure random data. Implementations only embed +// the first EmbedLen bytes of the given data. +func (P *secp256k1Point) Embed(data []byte, r cipher.Stream) kyber.Point { + numEmbedBytes := P.EmbedLen() + if len(data) > numEmbedBytes { + panic("too much data to embed in a point") + } + numEmbedBytes = len(data) + var x [32]byte + randStart := 1 // First byte to fill with random data + if data != nil { + x[0] = byte(numEmbedBytes) // Encode length in low 8 bits + copy(x[1:1+numEmbedBytes], data) // Copy in data to embed + randStart = 1 + numEmbedBytes + } + maxAttempts := 10000 + // Try random x ordinates satisfying the constraints, until one provides + // a point on secp256k1 + for numAttempts := 0; numAttempts < maxAttempts; numAttempts++ { + // Fill the rest of the x ordinate with random data + r.XORKeyStream(x[randStart:], x[randStart:]) + xOrdinate := newFieldZero().SetBytes(x) + // RHS of secp256k1 equation is x³+7 mod p. Success if square. + // We optimistically don't use btcec.IsOnCurve, here, because we + // hope to assign the intermediate result maybeY to P.Y + secp256k1RHS := rightHandSide(xOrdinate) + if maybeY := maybeSqrtInField(secp256k1RHS); maybeY != (*fieldElt)(nil) { + P.X = xOrdinate // success: found (x,y) s.t. y²=x³+7 + P.Y = maybeY + return P + } + } + // Probability 2^{-maxAttempts}, under correct operation. + panic("failed to find point satisfying all constraints") +} + +// Data returns data embedded in P, or an error if inconsistent with encoding +func (P *secp256k1Point) Data() ([]byte, error) { + b := P.X.Bytes() + dataLength := int(b[0]) + if dataLength > P.EmbedLen() { + return nil, fmt.Errorf("point specifies too much data") + } + return b[1 : dataLength+1], nil +} + +// Add sets P to a+b (secp256k1 group operation) and returns it. +func (P *secp256k1Point) Add(a, b kyber.Point) kyber.Point { + X, Y := s256.Add( + a.(*secp256k1Point).X.int(), a.(*secp256k1Point).Y.int(), + b.(*secp256k1Point).X.int(), b.(*secp256k1Point).Y.int()) + P.X.SetInt(X) + P.Y.SetInt(Y) + return P +} + +// Add sets P to a-b (secp256k1 group operation), and returns it. +func (P *secp256k1Point) Sub(a, b kyber.Point) kyber.Point { + X, Y := s256.Add( + a.(*secp256k1Point).X.int(), a.(*secp256k1Point).Y.int(), + b.(*secp256k1Point).X.int(), + newFieldZero().Neg(b.(*secp256k1Point).Y).int()) // -b_y + P.X.SetInt(X) + P.Y.SetInt(Y) + return P +} + +// Neg sets P to -a (in the secp256k1 group), and returns it. +func (P *secp256k1Point) Neg(a kyber.Point) kyber.Point { + P.X = a.(*secp256k1Point).X.Clone() + P.Y = newFieldZero().Neg(a.(*secp256k1Point).Y) + return P +} + +// Mul sets P to s*a (in the secp256k1 group, i.e. adding a to itself s times), +// and returns it. If a is nil, it is replaced by the secp256k1 generator. +func (P *secp256k1Point) Mul(s kyber.Scalar, a kyber.Point) kyber.Point { + sBytes, err := s.(*secp256k1Scalar).MarshalBinary() + if err != nil { + panic(fmt.Errorf("failure while marshaling multiplier: %s", + err)) + } + var X, Y *big.Int + if a == (*secp256k1Point)(nil) || a == nil { + X, Y = s256.ScalarBaseMult(sBytes) + } else { + X, Y = s256.ScalarMult(a.(*secp256k1Point).X.int(), + a.(*secp256k1Point).Y.int(), sBytes) + } + P.X.SetInt(X) + P.Y.SetInt(Y) + return P +} + +// MarshalBinary returns the concatenated big-endian representation of the X +// ordinate and a byte which is 0 if Y is even, 1 if it's odd. Or it returns an +// error on failure. +func (P *secp256k1Point) MarshalBinary() ([]byte, error) { + maybeSqrt := maybeSqrtInField(rightHandSide(P.X)) + if maybeSqrt == (*fieldElt)(nil) { + return nil, fmt.Errorf("x³+7 not a square") + } + minusMaybeSqrt := newFieldZero().Neg(maybeSqrt) + if !P.Y.Equal(maybeSqrt) && !P.Y.Equal(minusMaybeSqrt) { + return nil, fmt.Errorf( + "y ≠ ±maybeSqrt(x³+7), so not a point on the curve") + } + rv := make([]byte, P.MarshalSize()) + signByte := P.MarshalSize() - 1 // Last byte contains sign of Y. + xordinate := P.X.Bytes() + copyLen := copy(rv[:signByte], xordinate[:]) + if copyLen != P.MarshalSize()-1 { + return []byte{}, fmt.Errorf("marshal of x ordinate too short") + } + if P.Y.isEven() { + rv[signByte] = 0 + } else { + rv[signByte] = 1 + } + return rv, nil +} + +// MarshalSize returns the length of the byte representation of P +func (P *secp256k1Point) MarshalSize() int { return 33 } + +// MarshalID returns the ID for a secp256k1 point +func (P *secp256k1Point) MarshalID() [8]byte { + return [8]byte{'s', 'p', '2', '5', '6', '.', 'p', 'o'} +} + +// UnmarshalBinary sets P to the point represented by contents of buf, or +// returns an non-nil error +func (P *secp256k1Point) UnmarshalBinary(buf []byte) error { + var err error + if len(buf) != P.MarshalSize() { + err = fmt.Errorf("wrong length for marshaled point") + } + if err == nil && !(buf[32] == 0 || buf[32] == 1) { + err = fmt.Errorf("bad sign byte (the last one)") + } + if err != nil { + return err + } + var xordinate [32]byte + copy(xordinate[:], buf[:32]) + P.X = newFieldZero().SetBytes(xordinate) + secp256k1RHS := rightHandSide(P.X) + maybeY := maybeSqrtInField(secp256k1RHS) + if maybeY == (*fieldElt)(nil) { + return fmt.Errorf("x ordinate does not correspond to a curve point") + } + isEven := maybeY.isEven() + P.Y.Set(maybeY) + if (buf[32] == 0 && !isEven) || (buf[32] == 1 && isEven) { + P.Y.Neg(P.Y) + } else { + if buf[32] != 0 && buf[32] != 1 { + return fmt.Errorf("parity byte must be 0 or 1") + } + } + return nil +} + +// MarshalTo writes the serialized P to w, and returns the number of bytes +// written, or an error on failure. +func (P *secp256k1Point) MarshalTo(w io.Writer) (int, error) { + buf, err := P.MarshalBinary() + if err != nil { + return 0, err + } + return w.Write(buf) +} + +// UnmarshalFrom sets P to the secp256k1 point represented by bytes read from r, +// and returns the number of bytes read, or an error on failure. +func (P *secp256k1Point) UnmarshalFrom(r io.Reader) (int, error) { + buf := make([]byte, P.MarshalSize()) + n, err := io.ReadFull(r, buf) + if err != nil { + return 0, err + } + return n, P.UnmarshalBinary(buf) +} + +// EthereumAddress returns the 160-bit address corresponding to p as public key. +func EthereumAddress(p kyber.Point) (rv [20]byte) { + // The Ethereum address of P is the bottom 160 bits of keccak256(P.X‖P.Y), + // where P.X and P.Y are represented in 32 bytes as big-endian. See equations + // (277, 284) of Ethereum Yellow Paper version 3e36772, or go-ethereum's + // crypto.PubkeyToAddress. + h := sha3.NewLegacyKeccak256() + if _, err := h.Write(LongMarshal(p)); err != nil { + panic(err) + } + copy(rv[:], h.Sum(nil)[12:]) + return rv +} + +// IsSecp256k1Point returns true if p is a secp256k1Point +func IsSecp256k1Point(p kyber.Point) bool { + switch p.(type) { + case *secp256k1Point: + return true + default: + return false + } +} + +// Coordinates returns the coordinates of p +func Coordinates(p kyber.Point) (*big.Int, *big.Int) { + return p.(*secp256k1Point).X.int(), p.(*secp256k1Point).Y.int() +} + +// ValidPublicKey returns true iff p can be used in the optimized on-chain +// Schnorr-signature verification. See SchnorrSECP256K1.sol for details. +func ValidPublicKey(p kyber.Point) bool { + if p == (*secp256k1Point)(nil) || p == nil { + return false + } + P, ok := p.(*secp256k1Point) + if !ok { + return false + } + maybeY := maybeSqrtInField(rightHandSide(P.X)) + return maybeY != nil && (P.Y.Equal(maybeY) || P.Y.Equal(maybeY.Neg(maybeY))) +} + +// Generate generates a public/private key pair, which can be verified cheaply +// on-chain +func Generate(random cipher.Stream) *key.Pair { + p := key.Pair{} + for !ValidPublicKey(p.Public) { + p.Private = (&Secp256k1{}).Scalar().Pick(random) + p.Public = (&Secp256k1{}).Point().Mul(p.Private, nil) + } + return &p +} + +// LongMarshal returns the concatenated coordinates serialized as uint256's +func LongMarshal(p kyber.Point) []byte { + xMarshal := p.(*secp256k1Point).X.Bytes() + yMarshal := p.(*secp256k1Point).Y.Bytes() + return append(xMarshal[:], yMarshal[:]...) +} + +// LongUnmarshal returns the secp256k1 point represented by m, as a concatenated +// pair of uint256's +func LongUnmarshal(m []byte) (kyber.Point, error) { + if len(m) != 64 { + return nil, fmt.Errorf( + "0x%x does not represent an uncompressed secp256k1Point. Should be length 64, but is length %d", + m, len(m)) + } + p := newPoint() + p.X.SetInt(big.NewInt(0).SetBytes(m[:32])) + p.Y.SetInt(big.NewInt(0).SetBytes(m[32:])) + if !ValidPublicKey(p) { + return nil, fmt.Errorf("%s is not a valid secp256k1 point", p) + } + return p, nil +} + +// ScalarToPublicPoint returns the public secp256k1 point associated to s +func ScalarToPublicPoint(s kyber.Scalar) kyber.Point { + publicPoint := (&Secp256k1{}).Point() + return publicPoint.Mul(s, nil) +} + +// SetCoordinates returns the point (x,y), or panics if an invalid secp256k1Point +func SetCoordinates(x, y *big.Int) kyber.Point { + rv := newPoint() + rv.X.SetInt(x) + rv.Y.SetInt(y) + if !ValidPublicKey(rv) { + panic("point requested from invalid coordinates") + } + return rv +} diff --git a/bridge/third_party/chainlink/secp256k1/point_test.go b/bridge/third_party/chainlink/secp256k1/point_test.go new file mode 100644 index 000000000..f8f1b4d00 --- /dev/null +++ b/bridge/third_party/chainlink/secp256k1/point_test.go @@ -0,0 +1,232 @@ +package secp256k1 + +import ( + "bytes" + "crypto/rand" + "fmt" + "math/big" + "testing" + + "go.dedis.ch/kyber/v3/group/curve25519" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/smartcontractkit/chainlink/core/services/signatures/cryptotest" +) + +var numPointSamples = 10 + +var randomStreamPoint = cryptotest.NewStream(&testing.T{}, 0) + +func TestPoint_String(t *testing.T) { + require.Equal(t, newPoint().String(), + "Secp256k1{X: fieldElt{0}, Y: fieldElt{0}}") +} + +func TestPoint_CloneAndEqual(t *testing.T) { + f := newPoint() + for i := 0; i < numPointSamples; i++ { + g := f.Clone() + f.Pick(randomStreamPoint) + assert.NotEqual(t, f, g, + "modifying original shouldn't change clone") + g, h := f.Clone(), f.Clone() + assert.Equal(t, f, g, "clones should be equal") + g.Add(g, f) + assert.Equal(t, h, f, + "modifying a clone shouldn't change originial") + } +} + +func TestPoint_NullAndAdd(t *testing.T) { + f, g := newPoint(), newPoint() + for i := 0; i < numPointSamples; i++ { + g.Null() + f.Pick(randomStreamPoint) + g.Add(f, g) + assert.Equal(t, f, g, "adding zero should have no effect") + } +} + +func TestPoint_Set(t *testing.T) { + p := newPoint() + base := newPoint().Base() + assert.NotEqual(t, p, base, "generator should not be zero") + p.Set(base) + assert.Equal(t, p, base, "setting to generator should yield generator") +} + +func TestPoint_Embed(t *testing.T) { + p := newPoint() + for i := 0; i < numPointSamples; i++ { + data := make([]byte, p.EmbedLen()) + _, err := rand.Read(data) + require.Nil(t, err) + p.Embed(data, randomStreamPoint) + require.True(t, s256.IsOnCurve(p.X.int(), p.Y.int()), + "should embed to a secp256k1 point") + output, err := p.Data() + require.NoError(t, err) + require.True(t, bytes.Equal(data, output), + "should get same value back after round-trip "+ + "embedding, got %v, then %v", data, output) + } + var uint256Bytes [32]byte + uint256Bytes[0] = 30 + p.X.SetBytes(uint256Bytes) + _, err := p.Data() + require.Error(t, err) + require.Contains(t, err.Error(), "specifies too much data") + var b bytes.Buffer + p.Pick(randomStreamPoint) + _, err = p.MarshalTo(&b) + require.NoError(t, err) + _, err = p.UnmarshalFrom(&b) + require.NoError(t, err) + data := make([]byte, p.EmbedLen()+1) // Check length validation. This test + defer func() { // comes last, because it triggers panic + r := recover() + require.NotNil(t, r, "calling embed with too much data should panic") + require.Contains(t, r, "too much data to embed in a point") + }() + p.Embed(data, randomStreamPoint) +} + +func TestPoint_AddSubAndNeg(t *testing.T) { + zero := newPoint().Null() + p := newPoint() + for i := 0; i < numPointSamples; i++ { + p.Pick(randomStreamPoint) + q := p.Clone() + p.Sub(p, q) + require.True(t, p.Equal(zero), + "subtracting a point from itself should give zero, "+ + "got %v - %v = %v ≠ %v", q, q, p, zero) + p.Neg(q) + r := newPoint().Add(p, q) + require.True(t, r.Equal(zero), + "adding a point to its negative should give zero"+ + " got %v+%v=%v≠%v", q, p, r, zero) + r.Neg(q) + p.Sub(q, r) + s := newPoint().Add(q, q) + require.True(t, p.Equal(s), "q-(-q)=q+q?"+ + " got %v-%v=%v≠%v", q, r, p, s) + } +} + +func TestPoint_Mul(t *testing.T) { + zero := newPoint().Null() + multiplier := newScalar(bigZero) + one := newScalar(big.NewInt(int64(1))) + var p *secp256k1Point + for i := 0; i < numPointSamples/5; i++ { + if i%20 == 0 { + p = nil // Test default to generator point + } else { + p = newPoint() + p.Pick(randomStreamPoint) + } + multiplier.Pick(randomStreamPoint) + q := newPoint().Mul(one, p) + comparee := newPoint() + if p == (*secp256k1Point)(nil) { + comparee.Base() + } else { + comparee = p.Clone().(*secp256k1Point) + } + require.True(t, comparee.Equal(q), "1*p=p? %v * %v ≠ %v", one, + comparee, q) + q.Mul(multiplier, p) + negMultiplier := newScalar(bigZero).Neg(multiplier) + r := newPoint().Mul(negMultiplier, p) + s := newPoint().Add(q, r) + require.True(t, s.Equal(zero), "s*p+(-s)*p=0? got "+ + "%v*%v + %v*%v = %v + %v = %v ≠ %v", multiplier, p, + ) + } +} + +func TestPoint_Marshal(t *testing.T) { + p := newPoint() + for i := 0; i < numPointSamples; i++ { + p.Pick(randomStreamPoint) + serialized, err := p.MarshalBinary() + require.Nil(t, err) + q := newPoint() + err = q.UnmarshalBinary(serialized) + require.Nil(t, err) + require.True(t, p.Equal(q), "%v marshalled to %x, which "+ + "unmarshalled to %v", p, serialized, q) + } + p.X.SetInt(big.NewInt(0)) // 0³+7 is not a square in the base field. + _, err := p.MarshalBinary() + require.Error(t, err) + require.Contains(t, err.Error(), "not a square") + p.X.SetInt(big.NewInt(1)) + _, err = p.MarshalBinary() + require.Error(t, err) + require.Contains(t, err.Error(), "not a point on the curve") + id := p.MarshalID() + require.Equal(t, string(id[:]), "sp256.po") + data := make([]byte, 34) + err = p.UnmarshalBinary(data) + require.Error(t, err) + require.Contains(t, err.Error(), "wrong length for marshaled point") + require.Contains(t, p.UnmarshalBinary(data[:32]).Error(), + "wrong length for marshaled point") + data[32] = 2 + require.Contains(t, p.UnmarshalBinary(data[:33]).Error(), + "bad sign byte") + data[32] = 0 + data[31] = 5 // I.e., x-ordinate is now 5 + require.Contains(t, p.UnmarshalBinary(data[:33]).Error(), + "does not correspond to a curve point") +} + +func TestPoint_BaseTakesCopy(t *testing.T) { + p := newPoint().Base() + p.Add(p, p) + q := newPoint().Base() + assert.False(t, p.Equal(q), + "modifying output from Base changes S256.G{x,y}") +} + +func TestPoint_EthereumAddress(t *testing.T) { + // Example taken from + // https://theethereum.wiki/w/index.php/Accounts,_Addresses,_Public_And_Private_Keys,_And_Tokens + pString := "3a1076bf45ab87712ad64ccb3b10217737f7faacbf2872e88fdd9a537d8fe266" + pInt, ok := big.NewInt(0).SetString(pString, 16) + require.True(t, ok, "failed to parse private key") + private := newScalar(pInt) + public := newPoint().Mul(private, nil) + address := EthereumAddress(public) + assert.Equal(t, fmt.Sprintf("%x", address), + "c2d7cf95645d33006175b78989035c7c9061d3f9") +} + +func TestIsSecp256k1Point(t *testing.T) { + p := curve25519.NewBlakeSHA256Curve25519(false).Point() + require.False(t, IsSecp256k1Point(p)) + require.True(t, IsSecp256k1Point(newPoint())) +} + +func TestCoordinates(t *testing.T) { + x, y := Coordinates(newPoint()) + require.Equal(t, x, bigZero) + require.Equal(t, y, bigZero) +} + +func TestValidPublicKey(t *testing.T) { + require.False(t, ValidPublicKey(newPoint()), "zero is not a valid key") + require.True(t, ValidPublicKey(newPoint().Base())) +} + +func TestGenerate(t *testing.T) { + for { + if ValidPublicKey(Generate(randomStreamPoint).Public) { + break + } + } +} diff --git a/bridge/third_party/chainlink/secp256k1/scalar.go b/bridge/third_party/chainlink/secp256k1/scalar.go new file mode 100644 index 000000000..599ba10aa --- /dev/null +++ b/bridge/third_party/chainlink/secp256k1/scalar.go @@ -0,0 +1,228 @@ +// Package secp256k1 is an implementation of the kyber.{Group,Point,Scalcar} +//////////////////////////////////////////////////////////////////////////////// +// XXX: Do not use in production until this code has been audited. +//////////////////////////////////////////////////////////////////////////////// +// interfaces, based on btcd/btcec and kyber/group/mod +// +// XXX: NOT CONSTANT TIME! +package secp256k1 + +// Implementation of kyber.Scalar interface for arithmetic operations mod the +// order of the secpk256k1 group (i.e. hex value +// 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141.) + +import ( + "crypto/cipher" + "fmt" + "io" + "math/big" + + secp256k1BTCD "github.com/btcsuite/btcd/btcec" + "github.com/ethereum/go-ethereum/common" + + "go.dedis.ch/kyber/v3" + "go.dedis.ch/kyber/v3/util/random" +) + +var GroupOrder = secp256k1BTCD.S256().N + +type secp256k1Scalar big.Int + +// AllowVarTime, if passed true indicates that variable-time operations may be +// used on s. +func (s *secp256k1Scalar) AllowVarTime(varTimeAllowed bool) { + // Since constant-time operations are unimplemented for secp256k1, a + // value of false panics. + if !varTimeAllowed { + panic("implementation is not constant-time!") + } +} + +// newScalar returns a secpk256k1 scalar, with value v modulo GroupOrder. +func newScalar(v *big.Int) kyber.Scalar { + return (*secp256k1Scalar)(zero().Mod(v, GroupOrder)) +} + +func zero() *big.Int { return big.NewInt(0) } + +func ToInt(s kyber.Scalar) *big.Int { return (*big.Int)(s.(*secp256k1Scalar)) } + +func (s *secp256k1Scalar) int() *big.Int { return (*big.Int)(s) } + +func (s *secp256k1Scalar) modG() kyber.Scalar { + // TODO(alx): Make this faster + s.int().Mod(s.int(), GroupOrder) + return s +} + +func (s *secp256k1Scalar) String() string { + return fmt.Sprintf("scalar{%x}", (*big.Int)(s)) +} + +var scalarZero = zero() + +// Equal returns true if s and sPrime represent the same value modulo the group +// order, false otherwise +func (s *secp256k1Scalar) Equal(sPrime kyber.Scalar) bool { + difference := zero().Sub(s.int(), ToInt(sPrime)) + return scalarZero.Cmp(difference.Mod(difference, GroupOrder)) == 0 +} + +// Set copies sPrime's value (modulo GroupOrder) to s, and returns it +func (s *secp256k1Scalar) Set(sPrime kyber.Scalar) kyber.Scalar { + return (*secp256k1Scalar)(s.int().Mod(ToInt(sPrime), GroupOrder)) +} + +// Clone returns a copy of s mod GroupOrder +func (s *secp256k1Scalar) Clone() kyber.Scalar { + return (*secp256k1Scalar)(zero().Mod(s.int(), GroupOrder)) +} + +// SetInt64 returns s with value set to v modulo GroupOrder +func (s *secp256k1Scalar) SetInt64(v int64) kyber.Scalar { + return (*secp256k1Scalar)(s.int().SetInt64(v)).modG() +} + +// Zero sets s to 0 mod GroupOrder, and returns it +func (s *secp256k1Scalar) Zero() kyber.Scalar { + return s.SetInt64(0) +} + +// Add sets s to a+b mod GroupOrder, and returns it +func (s *secp256k1Scalar) Add(a, b kyber.Scalar) kyber.Scalar { + s.int().Add(ToInt(a), ToInt(b)) + return s.modG() +} + +// Sub sets s to a-b mod GroupOrder, and returns it +func (s *secp256k1Scalar) Sub(a, b kyber.Scalar) kyber.Scalar { + s.int().Sub(ToInt(a), ToInt(b)) + return s.modG() +} + +// Neg sets s to -a mod GroupOrder, and returns it +func (s *secp256k1Scalar) Neg(a kyber.Scalar) kyber.Scalar { + s.int().Neg(ToInt(a)) + return s.modG() +} + +// One sets s to 1 mod GroupOrder, and returns it +func (s *secp256k1Scalar) One() kyber.Scalar { + return s.SetInt64(1) +} + +// Mul sets s to a*b mod GroupOrder, and returns it +func (s *secp256k1Scalar) Mul(a, b kyber.Scalar) kyber.Scalar { + // TODO(alx): Make this faster + s.int().Mul(ToInt(a), ToInt(b)) + return s.modG() +} + +// Div sets s to a*b⁻¹ mod GroupOrder, and returns it +func (s *secp256k1Scalar) Div(a, b kyber.Scalar) kyber.Scalar { + if ToInt(b).Cmp(scalarZero) == 0 { + panic("attempt to divide by zero") + } + // TODO(alx): Make this faster + s.int().Mul(ToInt(a), zero().ModInverse(ToInt(b), GroupOrder)) + return s.modG() +} + +// Inv sets s to s⁻¹ mod GroupOrder, and returns it +func (s *secp256k1Scalar) Inv(a kyber.Scalar) kyber.Scalar { + if ToInt(a).Cmp(scalarZero) == 0 { + panic("attempt to divide by zero") + } + s.int().ModInverse(ToInt(a), GroupOrder) + return s +} + +// Pick sets s to a random value mod GroupOrder sampled from rand, and returns +// it +func (s *secp256k1Scalar) Pick(rand cipher.Stream) kyber.Scalar { + return s.Set((*secp256k1Scalar)(random.Int(GroupOrder, rand))) +} + +// MarshalBinary returns the big-endian byte representation of s, or an error on +// failure +func (s *secp256k1Scalar) MarshalBinary() ([]byte, error) { + b := ToInt(s.modG()).Bytes() + // leftpad with zeros + rv := append(make([]byte, s.MarshalSize()-len(b)), b...) + if len(rv) != s.MarshalSize() { + return nil, fmt.Errorf("marshalled scalar to wrong length") + } + return rv, nil +} + +// MarshalSize returns the length of the byte representation of s +func (s *secp256k1Scalar) MarshalSize() int { return 32 } + +// MarshalID returns the ID for a secp256k1 scalar +func (s *secp256k1Scalar) MarshalID() [8]byte { + return [8]byte{'s', 'p', '2', '5', '6', '.', 's', 'c'} +} + +// UnmarshalBinary sets s to the scalar represented by the contents of buf, +// returning error on failure. +func (s *secp256k1Scalar) UnmarshalBinary(buf []byte) error { + if len(buf) != s.MarshalSize() { + return fmt.Errorf("cannot unmarshal to scalar: wrong length") + } + s.int().Mod(s.int().SetBytes(buf), GroupOrder) + return nil +} + +// MarshalTo writes the serialized s to w, and returns the number of bytes +// written, or an error on failure. +func (s *secp256k1Scalar) MarshalTo(w io.Writer) (int, error) { + buf, err := s.MarshalBinary() + if err != nil { + return 0, fmt.Errorf("cannot marshal binary: %s", err) + } + return w.Write(buf) +} + +// UnmarshalFrom sets s to the scalar represented by bytes read from r, and +// returns the number of bytes read, or an error on failure. +func (s *secp256k1Scalar) UnmarshalFrom(r io.Reader) (int, error) { + buf := make([]byte, s.MarshalSize()) + n, err := io.ReadFull(r, buf) + if err != nil { + return n, err + } + return n, s.UnmarshalBinary(buf) +} + +// SetBytes sets s to the number with big-endian representation a mod +// GroupOrder, and returns it +func (s *secp256k1Scalar) SetBytes(a []byte) kyber.Scalar { + return ((*secp256k1Scalar)(s.int().SetBytes(a))).modG() +} + +// IsSecp256k1Scalar returns true if p is a secp256k1Scalar +func IsSecp256k1Scalar(s kyber.Scalar) bool { + switch s := s.(type) { + case *secp256k1Scalar: + s.modG() + return true + default: + return false + } +} + +// IntToScalar returns i wrapped as a big.Int. +// +// May modify i to reduce mod GroupOrder +func IntToScalar(i *big.Int) kyber.Scalar { + return ((*secp256k1Scalar)(i)).modG() +} + +func ScalarToHash(s kyber.Scalar) common.Hash { + return common.BigToHash(ToInt(s.(*secp256k1Scalar))) +} + +// RepresentsScalar returns true iff i is in the right range to be a scalar +func RepresentsScalar(i *big.Int) bool { + return i.Cmp(GroupOrder) == -1 +} diff --git a/bridge/third_party/chainlink/secp256k1/scalar_test.go b/bridge/third_party/chainlink/secp256k1/scalar_test.go new file mode 100644 index 000000000..b08dc01ad --- /dev/null +++ b/bridge/third_party/chainlink/secp256k1/scalar_test.go @@ -0,0 +1,189 @@ +package secp256k1 + +import ( + "bytes" + "encoding/hex" + "math/big" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "go.dedis.ch/kyber/v3" + "go.dedis.ch/kyber/v3/group/curve25519" + + "github.com/smartcontractkit/chainlink/core/services/signatures/cryptotest" +) + +var numScalarSamples = 10 + +var observedScalars map[string]bool + +func init() { + observedScalars = make(map[string]bool) +} + +// observedScalar ensures that novel scalars are being picked. +func observedScalar(t *testing.T, s kyber.Scalar) { + data, err := s.(*secp256k1Scalar).modG().MarshalBinary() + require.NoError(t, err) + scalar := hex.Dump(data) + require.False(t, observedScalars[scalar]) + observedScalars[scalar] = true +} + +var randomStreamScalar = cryptotest.NewStream(&testing.T{}, 0) + +func TestScalar_SetAndEqual(t *testing.T) { + tests := []int64{5, 67108864, 67108865, 4294967295} + g := newScalar(scalarZero) + for _, test := range tests { + f := newScalar(big.NewInt(test)) + g.Set(f) + assert.Equal(t, f, g, + "the method Set should give the same value to receiver") + f.Add(f, newScalar(big.NewInt(1))) + assert.NotEqual(t, f, g, + "SetInt should take a copy of the backing big.Int") + } +} + +func TestNewScalar(t *testing.T) { + one := newScalar(big.NewInt(1)) + assert.Equal(t, ToInt(one), + ToInt(newScalar(big.NewInt(0).Add(ToInt(one), GroupOrder))), + "equivalence classes mod GroupOrder not equal") +} + +func TestScalar_SmokeTestPick(t *testing.T) { + f := newScalar(scalarZero).Clone() + for i := 0; i < numScalarSamples; i++ { + f.Pick(randomStreamScalar) + observedScalar(t, f) + require.True(t, ToInt(f).Cmp(big.NewInt(1000000000)) == 1, + "implausibly low value returned from Pick: %v", f) + } +} + +func TestScalar_Neg(t *testing.T) { + f := newScalar(scalarZero).Clone() + for i := 0; i < numScalarSamples; i++ { + f.Pick(randomStreamScalar) + observedScalar(t, f) + g := f.Clone() + g.Neg(g) + require.True(t, g.Add(f, g).Equal(newScalar(scalarZero))) + } +} + +func TestScalar_Sub(t *testing.T) { + f := newScalar(scalarZero).Clone() + for i := 0; i < numScalarSamples; i++ { + f.Pick(randomStreamScalar) + observedScalar(t, f) + require.True(t, f.Sub(f, f).Equal(newScalar(scalarZero)), + "subtracting something from itself should give zero") + } +} + +func TestScalar_Clone(t *testing.T) { + f := newScalar(big.NewInt(1)) + g := f.Clone() + h := f.Clone() + assert.Equal(t, f, g, "clone output does not equal input") + g.Add(f, f) + assert.Equal(t, f, h, "clone does not make a copy") +} + +func TestScalar_Marshal(t *testing.T) { + f := newScalar(scalarZero) + g := newScalar(scalarZero) + for i := 0; i < numFieldSamples; i++ { + f.Pick(randomStreamScalar) + observedScalar(t, f) + data, err := f.MarshalBinary() + require.Nil(t, err) + err = g.UnmarshalBinary(data) + require.Nil(t, err) + require.True(t, g.Equal(f), + "roundtrip through serialization should give same "+ + "result back: failed with %s", f) + } + marshalID := f.(*secp256k1Scalar).MarshalID() + require.Equal(t, string(marshalID[:]), "sp256.sc") + data := make([]byte, 33) + require.Contains(t, f.UnmarshalBinary(data).Error(), "wrong length") + var buf bytes.Buffer + _, err := f.MarshalTo(&buf) + require.NoError(t, err) + _, err = f.UnmarshalFrom(&buf) + require.NoError(t, err) +} + +func TestScalar_MulDivInv(t *testing.T) { + f := newScalar(scalarZero) + g := newScalar(scalarZero) + h := newScalar(scalarZero) + j := newScalar(scalarZero) + k := newScalar(scalarZero) + for i := 0; i < numFieldSamples; i++ { + f.Pick(randomStreamScalar) + observedScalar(t, f) + g.Inv(f) + h.Mul(f, g) + require.True(t, h.Equal(newScalar(big.NewInt(1)))) + h.Div(f, f) + require.True(t, h.Equal(newScalar(big.NewInt(1)))) + h.Div(newScalar(big.NewInt(1)), f) + require.True(t, h.Equal(g)) + h.Pick(randomStreamScalar) + observedScalar(t, h) + j.Neg(j.Mul(h, f)) + k.Mul(h, k.Neg(f)) + require.True(t, j.Equal(k), "-(h*f) != h*(-f)") + } +} + +func TestScalar_AllowVarTime(t *testing.T) { + defer func() { require.Contains(t, recover(), "not constant-time!") }() + newScalar(bigZero).(*secp256k1Scalar).AllowVarTime(false) +} + +func TestScalar_String(t *testing.T) { + require.Equal(t, newScalar(bigZero).String(), "scalar{0}") +} + +func TestScalar_SetInt64(t *testing.T) { + require.Equal(t, newScalar(bigZero).SetInt64(1), newScalar(big.NewInt(1))) + require.True(t, newScalar(big.NewInt(1)).Zero().Equal(newScalar(bigZero))) + require.Equal(t, newScalar(bigZero).One(), newScalar(big.NewInt(1))) +} + +func TestScalar_DivPanicsOnZeroDivisor(t *testing.T) { + defer func() { require.Contains(t, recover(), "divide by zero") }() + newScalar(bigZero).Div(newScalar(bigZero).One(), newScalar(bigZero)) +} + +func TestScalar_InvPanicsOnZero(t *testing.T) { + defer func() { require.Contains(t, recover(), "divide by zero") }() + newScalar(bigZero).Inv(newScalar(bigZero)) +} + +func TestScalar_SetBytes(t *testing.T) { + u256Cardinality := zero().Lsh(big.NewInt(1), 256) + newScalar(bigZero).(*secp256k1Scalar).int().Cmp( + zero().Sub(u256Cardinality, GroupOrder)) +} + +func TestScalar_IsSecp256k1Scalar(t *testing.T) { + c := curve25519.NewBlakeSHA256Curve25519(true) + require.False(t, IsSecp256k1Scalar(c.Scalar())) + require.True(t, IsSecp256k1Scalar(newScalar(bigZero))) +} + +func TestScalar_IntToScalar(t *testing.T) { + u256Cardinality := zero().Lsh(big.NewInt(1), 256) + IntToScalar(u256Cardinality) + require.Equal(t, u256Cardinality, zero().Sub(zero().Lsh(big.NewInt(1), 256), + GroupOrder)) +} diff --git a/bridge/third_party/chainlink/secp256k1/suite.go b/bridge/third_party/chainlink/secp256k1/suite.go new file mode 100644 index 000000000..8c87a1621 --- /dev/null +++ b/bridge/third_party/chainlink/secp256k1/suite.go @@ -0,0 +1,89 @@ +// Package secp256k1 is an implementation of the kyber.{Group,Point,Scalar} +//////////////////////////////////////////////////////////////////////////////// +// XXX: Do not use in production until this code has been audited. +//////////////////////////////////////////////////////////////////////////////// +// interfaces, based on btcd/btcec and kyber/group/mod +// +// XXX: NOT CONSTANT TIME! +package secp256k1 + +import ( + "crypto/cipher" + "hash" + "io" + "reflect" + + "golang.org/x/crypto/sha3" + + "go.dedis.ch/fixbuf" + "go.dedis.ch/kyber/v3" + "go.dedis.ch/kyber/v3/util/random" + "go.dedis.ch/kyber/v3/xof/blake2xb" +) + +// SuiteSecp256k1 implements some basic functionalities such as Group, HashFactory, +// and XOFFactory. +type SuiteSecp256k1 struct { + Secp256k1 + r cipher.Stream +} + +// Hash returns a newly instantiated keccak hash function. +func (s *SuiteSecp256k1) Hash() hash.Hash { + return sha3.NewLegacyKeccak256() +} + +// XOF returns an XOR function, implemented via the Blake2b hash. +// +// This should only be used for generating secrets, so there is no need to make +// it cheap to compute on-chain. +func (s *SuiteSecp256k1) XOF(key []byte) kyber.XOF { + return blake2xb.New(key) +} + +// Read implements the Encoding interface function, and reads a series of objs from r +// The objs must all be pointers +func (s *SuiteSecp256k1) Read(r io.Reader, objs ...interface{}) error { + return fixbuf.Read(r, s, objs...) +} + +// Write implements the Encoding interface, and writes the objs to r using their +// built-in binary serializations. Supports Points, Scalars, fixed-length data +// types supported by encoding/binary/Write(), and structs, arrays, and slices +// containing these types. +func (s *SuiteSecp256k1) Write(w io.Writer, objs ...interface{}) error { + return fixbuf.Write(w, objs) +} + +var aScalar kyber.Scalar +var tScalar = reflect.TypeOf(aScalar) +var aPoint kyber.Point +var tPoint = reflect.TypeOf(aPoint) + +// New implements the kyber.Encoding interface, and returns a new element of +// type t, which can be a Point or a Scalar +func (s *SuiteSecp256k1) New(t reflect.Type) interface{} { + switch t { + case tScalar: + return s.Scalar() + case tPoint: + return s.Point() + } + return nil +} + +// RandomStream returns a cipher.Stream that returns a key stream +// from crypto/rand. +func (s *SuiteSecp256k1) RandomStream() cipher.Stream { + if s.r != nil { + return s.r + } + return random.New() +} + +// NewBlakeKeccackSecp256k1 returns a cipher suite based on package +// go.dedis.ch/kyber/xof/blake2xb, SHA-256, and the secp256k1 curve. It +// produces cryptographically secure random numbers via package crypto/rand. +func NewBlakeKeccackSecp256k1() *SuiteSecp256k1 { + return new(SuiteSecp256k1) +} diff --git a/bridge/third_party/chainlink/secp256k1/suite_test.go b/bridge/third_party/chainlink/secp256k1/suite_test.go new file mode 100644 index 000000000..64dd2b076 --- /dev/null +++ b/bridge/third_party/chainlink/secp256k1/suite_test.go @@ -0,0 +1,16 @@ +package secp256k1 + +import ( + "encoding/hex" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestSuite(t *testing.T) { + s := NewBlakeKeccackSecp256k1() + emptyHashAsHex := hex.EncodeToString(s.Hash().Sum(nil)) + require.Equal(t, emptyHashAsHex, + "c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470") + _ = s.RandomStream() +}