diff --git a/bridge/pkg/signatures/ethdss/ethdss.go b/bridge/pkg/signatures/ethdss/ethdss.go deleted file mode 100644 index 92800d03f..000000000 --- a/bridge/pkg/signatures/ethdss/ethdss.go +++ /dev/null @@ -1,304 +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 ethdss - -import ( - "bytes" - "errors" - "math/big" - - "github.com/certusone/wormhole/bridge/pkg/signatures/ethschnorr" - "github.com/certusone/wormhole/bridge/pkg/signatures/secp256k1" - - "go.dedis.ch/kyber/v3" - "go.dedis.ch/kyber/v3/share" -) - -// 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/pkg/signatures/ethdss/ethdss_test.go b/bridge/pkg/signatures/ethdss/ethdss_test.go deleted file mode 100644 index 68e9df616..000000000 --- a/bridge/pkg/signatures/ethdss/ethdss_test.go +++ /dev/null @@ -1,290 +0,0 @@ -package ethdss - -import ( - "crypto/rand" - "fmt" - "math/big" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "github.com/certusone/wormhole/bridge/pkg/signatures/cryptotest" - "github.com/certusone/wormhole/bridge/pkg/signatures/ethschnorr" - "github.com/certusone/wormhole/bridge/pkg/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/pkg/signatures/ethschnorr/ethschnorr.go b/bridge/pkg/signatures/ethschnorr/ethschnorr.go deleted file mode 100644 index 773d99d4e..000000000 --- a/bridge/pkg/signatures/ethschnorr/ethschnorr.go +++ /dev/null @@ -1,154 +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" - "fmt" - "go.dedis.ch/kyber/v3" - "math/big" - - "github.com/certusone/wormhole/bridge/pkg/signatures/secp256k1" -) - -var secp256k1Suite = secp256k1.NewBlakeKeccackSecp256k1() -var secp256k1Group kyber.Group = secp256k1Suite - -type signature = struct { - CommitmentPublicAddress [20]byte - R string - 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, commitmentPublicKey.String(), 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 - } - println("hash", challenge.String()) - } - 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)) - println("commpoint", maybeCommitmentPublicKey.String()) - maybeCommitmentPublicAddress := secp256k1.EthereumAddress(maybeCommitmentPublicKey) - if !bytes.Equal(s.CommitmentPublicAddress[:], - maybeCommitmentPublicAddress[:]) { - return fmt.Errorf("signature mismatch") - } - return nil -} diff --git a/bridge/pkg/signatures/ethschnorr/ethschnorr_test.go b/bridge/pkg/signatures/ethschnorr/ethschnorr_test.go deleted file mode 100644 index 61b47e253..000000000 --- a/bridge/pkg/signatures/ethschnorr/ethschnorr_test.go +++ /dev/null @@ -1,119 +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" - "encoding/hex" - "fmt" - "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/certusone/wormhole/bridge/pkg/signatures/cryptotest" - "github.com/certusone/wormhole/bridge/pkg/signatures/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(2)) - 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") - println("msg", hex.EncodeToString(msg.Bytes())) - println("sig", hex.EncodeToString(sig.Signature.Bytes())) - println("addr", hex.EncodeToString(sig.CommitmentPublicAddress[:])) - println("pub", kp.Public.String()) - 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/pkg/signatures/secp256k1/curve.go b/bridge/pkg/signatures/secp256k1/curve.go deleted file mode 100644 index be3e89cb1..000000000 --- a/bridge/pkg/signatures/secp256k1/curve.go +++ /dev/null @@ -1,44 +0,0 @@ -// 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/pkg/signatures/secp256k1/curve_test.go b/bridge/pkg/signatures/secp256k1/curve_test.go deleted file mode 100644 index b28ce2f0b..000000000 --- a/bridge/pkg/signatures/secp256k1/curve_test.go +++ /dev/null @@ -1,20 +0,0 @@ -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/pkg/signatures/secp256k1/field.go b/bridge/pkg/signatures/secp256k1/field.go deleted file mode 100644 index 0d1a922c1..000000000 --- a/bridge/pkg/signatures/secp256k1/field.go +++ /dev/null @@ -1,170 +0,0 @@ -// 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 -var halfQ = new(big.Int).Div(q, big.NewInt(2)) - -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/pkg/signatures/secp256k1/field_test.go b/bridge/pkg/signatures/secp256k1/field_test.go deleted file mode 100644 index 0287d667f..000000000 --- a/bridge/pkg/signatures/secp256k1/field_test.go +++ /dev/null @@ -1,159 +0,0 @@ -package secp256k1 - -import ( - "encoding/hex" - "math/big" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "github.com/certusone/wormhole/bridge/pkg/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/pkg/signatures/secp256k1/point.go b/bridge/pkg/signatures/secp256k1/point.go deleted file mode 100644 index ccd2a5c97..000000000 --- a/bridge/pkg/signatures/secp256k1/point.go +++ /dev/null @@ -1,386 +0,0 @@ -// 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 - } - - // Verify that X < HALF_Q so it can be used for optimized on-chain verification - if P.X.Int().Cmp(halfQ) == 1 { - return false - } - - // Verify that the pub key is a valid curve point - 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/pkg/signatures/secp256k1/point_test.go b/bridge/pkg/signatures/secp256k1/point_test.go deleted file mode 100644 index 98156b1ee..000000000 --- a/bridge/pkg/signatures/secp256k1/point_test.go +++ /dev/null @@ -1,232 +0,0 @@ -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/certusone/wormhole/bridge/pkg/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/pkg/signatures/secp256k1/scalar.go b/bridge/pkg/signatures/secp256k1/scalar.go deleted file mode 100644 index 599ba10aa..000000000 --- a/bridge/pkg/signatures/secp256k1/scalar.go +++ /dev/null @@ -1,228 +0,0 @@ -// 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/pkg/signatures/secp256k1/scalar_test.go b/bridge/pkg/signatures/secp256k1/scalar_test.go deleted file mode 100644 index 5e9ff98af..000000000 --- a/bridge/pkg/signatures/secp256k1/scalar_test.go +++ /dev/null @@ -1,189 +0,0 @@ -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/certusone/wormhole/bridge/pkg/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/pkg/signatures/secp256k1/suite.go b/bridge/pkg/signatures/secp256k1/suite.go deleted file mode 100644 index 8c87a1621..000000000 --- a/bridge/pkg/signatures/secp256k1/suite.go +++ /dev/null @@ -1,89 +0,0 @@ -// 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/pkg/signatures/secp256k1/suite_test.go b/bridge/pkg/signatures/secp256k1/suite_test.go deleted file mode 100644 index 64dd2b076..000000000 --- a/bridge/pkg/signatures/secp256k1/suite_test.go +++ /dev/null @@ -1,16 +0,0 @@ -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() -} diff --git a/bridge/pkg/vaa/structs.go b/bridge/pkg/vaa/structs.go index f6405fb74..a87eb3a4a 100644 --- a/bridge/pkg/vaa/structs.go +++ b/bridge/pkg/vaa/structs.go @@ -4,9 +4,12 @@ import ( "bytes" "encoding/binary" "fmt" - "github.com/certusone/wormhole/bridge/pkg/signatures/secp256k1" + "github.com/certusone/wormhole/bridge/third_party/chainlink/ethschnorr" + "github.com/certusone/wormhole/bridge/third_party/chainlink/secp256k1" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" "go.dedis.ch/kyber/v3" + "go.dedis.ch/kyber/v3/util/key" "io" "math" "math/big" @@ -150,11 +153,66 @@ func ParseVAA(data []byte) (*VAA, error) { return v, nil } -// SigningBody returns the binary representation of the data that is relevant for signing and verifying the VAA -func (v *VAA) SigningBody() ([]byte, error) { +// signingBody returns the binary representation of the data that is relevant for signing and verifying the VAA +func (v *VAA) signingBody() ([]byte, error) { return v.serializeBody() } +// SigningMsg returns the hash of the signing body. This is used for signature generation and verification +func (v *VAA) SigningMsg() (*big.Int, error) { + body, err := v.signingBody() + if err != nil { + // Should never happen on a successfully parsed VAA + return nil, fmt.Errorf("failed to serialize signing body: %w", err) + } + + hash := crypto.Keccak256Hash(body) + return hash.Big(), nil +} + +// VerifySignature verifies the signature of the VAA given a public key +func (v *VAA) VerifySignature(pubKey kyber.Point) bool { + if v.Signature == nil { + return false + } + + msg, err := v.SigningMsg() + if err != nil { + return false + } + + sig := ethschnorr.NewSignature() + sig.Signature = new(big.Int).SetBytes(v.Signature.Sig[:]) + sig.CommitmentPublicAddress = v.Signature.Address + + err = ethschnorr.Verify(pubKey, msg, sig) + return err == nil +} + +// Sign signs the VAA, setting it's signature field +func (v *VAA) Sign(key *key.Pair) error { + if v.Signature != nil { + return fmt.Errorf("VAA has already been signed") + } + + hash, err := v.SigningMsg() + if err != nil { + return fmt.Errorf("failed to get signing message: %w", err) + } + + sig, err := ethschnorr.Sign(key.Private, hash) + if err != nil { + return fmt.Errorf("failed to sign: %w", err) + } + + // Set fields + v.Signature = &Signature{} + copy(v.Signature.Sig[:], common.LeftPadBytes(sig.Signature.Bytes(), 32)) + v.Signature.Address = sig.CommitmentPublicAddress + + return nil +} + // Serialize returns the binary representation of the VAA func (v *VAA) Serialize() ([]byte, error) { buf := new(bytes.Buffer) diff --git a/bridge/pkg/vaa/types_test.go b/bridge/pkg/vaa/types_test.go index cc76252ed..05add6402 100644 --- a/bridge/pkg/vaa/types_test.go +++ b/bridge/pkg/vaa/types_test.go @@ -1,8 +1,8 @@ package vaa import ( - "github.com/certusone/wormhole/bridge/pkg/signatures/cryptotest" - "github.com/certusone/wormhole/bridge/pkg/signatures/secp256k1" + "github.com/certusone/wormhole/bridge/third_party/chainlink/cryptotest" + "github.com/certusone/wormhole/bridge/third_party/chainlink/secp256k1" "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/require" "math/big" @@ -69,3 +69,26 @@ func TestSerializeDeserialize(t *testing.T) { }) } } + +func TestVerifySignature(t *testing.T) { + key := secp256k1.Generate(randomStream) + + v := &VAA{ + Version: 8, + GuardianSetIndex: 9, + Timestamp: time.Unix(2837, 0), + Payload: &BodyTransfer{ + SourceChain: 2, + TargetChain: 1, + TargetAddress: Address{2, 1, 3}, + Asset: &AssetMeta{ + Chain: 9, + Address: Address{9, 2, 4}, + }, + Amount: big.NewInt(29), + }, + } + + require.NoError(t, v.Sign(key)) + require.True(t, v.VerifySignature(key.Public)) +} diff --git a/bridge/pkg/signatures/cryptotest/cryptotest.go b/bridge/third_party/chainlink/cryptotest/cryptotest.go similarity index 100% rename from bridge/pkg/signatures/cryptotest/cryptotest.go rename to bridge/third_party/chainlink/cryptotest/cryptotest.go diff --git a/bridge/third_party/chainlink/secp256k1/field.go b/bridge/third_party/chainlink/secp256k1/field.go index 0cd2f8500..0d1a922c1 100644 --- a/bridge/third_party/chainlink/secp256k1/field.go +++ b/bridge/third_party/chainlink/secp256k1/field.go @@ -21,6 +21,7 @@ import ( // q is the field characteristic (cardinality) of the secp256k1 base field. All // arithmetic operations on the field are modulo this. var q = s256.P +var halfQ = new(big.Int).Div(q, big.NewInt(2)) type fieldElt big.Int @@ -28,17 +29,17 @@ type fieldElt big.Int 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) } +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 { + 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) + f.Int().Mod(f.Int(), q) } return f } @@ -55,7 +56,7 @@ var bigZero = big.NewInt(0) // String returns the string representation of f func (f *fieldElt) String() string { - return fmt.Sprintf("fieldElt{%x}", f.int()) + return fmt.Sprintf("fieldElt{%x}", f.Int()) } // Equal returns true iff f=g, i.e. the backing big.Ints satisfy f ≡ g mod q @@ -69,30 +70,30 @@ func (f *fieldElt) Equal(g *fieldElt) bool { if g == (*fieldElt)(nil) { // g is nil, f is not return false } - return bigZero.Cmp(newFieldZero().Sub(f, g).modQ().int()) == 0 + 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()) + 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()) + 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()) + 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) + f.Int().Set(v) return f.modQ() } @@ -103,7 +104,7 @@ func (f *fieldElt) Pick(rand cipher.Stream) *fieldElt { // 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()) + f.Int().Neg(g.Int()) return f.modQ() } @@ -113,13 +114,13 @@ 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[:]) + 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() + bytes := f.modQ().Int().Bytes() if len(bytes) > 32 { panic("field element longer than 256 bits") } @@ -132,7 +133,7 @@ var two = big.NewInt(2) // square returns y² mod q func fieldSquare(y *fieldElt) *fieldElt { - return fieldEltFromBigInt(newFieldZero().int().Exp(y.int(), two, q)) + 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 @@ -146,7 +147,7 @@ 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) + s.Int().Exp(v.Int(), sqrtPower, q) if !fieldSquare(s).Equal(v) { return nil } @@ -159,11 +160,11 @@ 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) + 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 + 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 index 2839a3dba..2051de44a 100644 --- a/bridge/third_party/chainlink/secp256k1/field_test.go +++ b/bridge/third_party/chainlink/secp256k1/field_test.go @@ -74,7 +74,7 @@ func TestField_SmokeTestPick(t *testing.T) { f := newFieldZero() f.Pick(randomStream) observedFieldElt(t, f) - assert.True(t, f.int().Cmp(big.NewInt(1000000000)) == 1, + assert.True(t, f.Int().Cmp(big.NewInt(1000000000)) == 1, "should be greater than 1000000000, with very high probability") } @@ -132,9 +132,9 @@ func TestField_MaybeSquareRootInField(t *testing.T) { 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") + 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)) @@ -142,11 +142,11 @@ func TestField_MaybeSquareRootInField(t *testing.T) { 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) + 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) + 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") diff --git a/bridge/third_party/chainlink/secp256k1/point.go b/bridge/third_party/chainlink/secp256k1/point.go index 1273250b9..f50d985b8 100644 --- a/bridge/third_party/chainlink/secp256k1/point.go +++ b/bridge/third_party/chainlink/secp256k1/point.go @@ -31,7 +31,7 @@ type secp256k1Point struct { Y *fieldElt } -func newPoint() *secp256k1Point { +func NewPoint() *secp256k1Point { return &secp256k1Point{newFieldZero(), newFieldZero()} } @@ -148,8 +148,8 @@ func (P *secp256k1Point) Data() ([]byte, error) { // 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()) + 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 @@ -158,9 +158,9 @@ func (P *secp256k1Point) Add(a, b kyber.Point) kyber.Point { // 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 + 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 @@ -185,8 +185,8 @@ func (P *secp256k1Point) Mul(s kyber.Scalar, a kyber.Point) kyber.Point { 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) + X, Y = s256.ScalarMult(a.(*secp256k1Point).X.Int(), + a.(*secp256k1Point).Y.Int(), sBytes) } P.X.SetInt(X) P.Y.SetInt(Y) @@ -309,7 +309,7 @@ func IsSecp256k1Point(p kyber.Point) bool { // Coordinates returns the coordinates of p func Coordinates(p kyber.Point) (*big.Int, *big.Int) { - return p.(*secp256k1Point).X.int(), p.(*secp256k1Point).Y.int() + return p.(*secp256k1Point).X.Int(), p.(*secp256k1Point).Y.Int() } // ValidPublicKey returns true iff p can be used in the optimized on-chain @@ -322,6 +322,13 @@ func ValidPublicKey(p kyber.Point) bool { if !ok { return false } + + // Verify that X < HALF_Q so it can be used for optimized on-chain verification + if P.X.Int().Cmp(halfQ) == 1 { + return false + } + + // Verify that the pub key is a valid curve point maybeY := maybeSqrtInField(rightHandSide(P.X)) return maybeY != nil && (P.Y.Equal(maybeY) || P.Y.Equal(maybeY.Neg(maybeY))) } @@ -352,7 +359,7 @@ func LongUnmarshal(m []byte) (kyber.Point, error) { "0x%x does not represent an uncompressed secp256k1Point. Should be length 64, but is length %d", m, len(m)) } - p := newPoint() + p := NewPoint() p.X.SetInt(big.NewInt(0).SetBytes(m[:32])) p.Y.SetInt(big.NewInt(0).SetBytes(m[32:])) if !ValidPublicKey(p) { @@ -369,7 +376,7 @@ func ScalarToPublicPoint(s kyber.Scalar) kyber.Point { // SetCoordinates returns the point (x,y), or panics if an invalid secp256k1Point func SetCoordinates(x, y *big.Int) kyber.Point { - rv := newPoint() + rv := NewPoint() rv.X.SetInt(x) rv.Y.SetInt(y) if !ValidPublicKey(rv) { diff --git a/bridge/third_party/chainlink/secp256k1/point_test.go b/bridge/third_party/chainlink/secp256k1/point_test.go index f8f1b4d00..3e3cc05ae 100644 --- a/bridge/third_party/chainlink/secp256k1/point_test.go +++ b/bridge/third_party/chainlink/secp256k1/point_test.go @@ -20,12 +20,12 @@ var numPointSamples = 10 var randomStreamPoint = cryptotest.NewStream(&testing.T{}, 0) func TestPoint_String(t *testing.T) { - require.Equal(t, newPoint().String(), + require.Equal(t, NewPoint().String(), "Secp256k1{X: fieldElt{0}, Y: fieldElt{0}}") } func TestPoint_CloneAndEqual(t *testing.T) { - f := newPoint() + f := NewPoint() for i := 0; i < numPointSamples; i++ { g := f.Clone() f.Pick(randomStreamPoint) @@ -40,7 +40,7 @@ func TestPoint_CloneAndEqual(t *testing.T) { } func TestPoint_NullAndAdd(t *testing.T) { - f, g := newPoint(), newPoint() + f, g := NewPoint(), NewPoint() for i := 0; i < numPointSamples; i++ { g.Null() f.Pick(randomStreamPoint) @@ -50,21 +50,21 @@ func TestPoint_NullAndAdd(t *testing.T) { } func TestPoint_Set(t *testing.T) { - p := newPoint() - base := newPoint().Base() + 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() + 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()), + 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) @@ -94,8 +94,8 @@ func TestPoint_Embed(t *testing.T) { } func TestPoint_AddSubAndNeg(t *testing.T) { - zero := newPoint().Null() - p := newPoint() + zero := NewPoint().Null() + p := NewPoint() for i := 0; i < numPointSamples; i++ { p.Pick(randomStreamPoint) q := p.Clone() @@ -104,20 +104,20 @@ func TestPoint_AddSubAndNeg(t *testing.T) { "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) + 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) + 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() + zero := NewPoint().Null() multiplier := newScalar(bigZero) one := newScalar(big.NewInt(int64(1))) var p *secp256k1Point @@ -125,12 +125,12 @@ func TestPoint_Mul(t *testing.T) { if i%20 == 0 { p = nil // Test default to generator point } else { - p = newPoint() + p = NewPoint() p.Pick(randomStreamPoint) } multiplier.Pick(randomStreamPoint) - q := newPoint().Mul(one, p) - comparee := newPoint() + q := NewPoint().Mul(one, p) + comparee := NewPoint() if p == (*secp256k1Point)(nil) { comparee.Base() } else { @@ -140,8 +140,8 @@ func TestPoint_Mul(t *testing.T) { comparee, q) q.Mul(multiplier, p) negMultiplier := newScalar(bigZero).Neg(multiplier) - r := newPoint().Mul(negMultiplier, p) - s := newPoint().Add(q, r) + 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, ) @@ -149,12 +149,12 @@ func TestPoint_Mul(t *testing.T) { } func TestPoint_Marshal(t *testing.T) { - p := newPoint() + p := NewPoint() for i := 0; i < numPointSamples; i++ { p.Pick(randomStreamPoint) serialized, err := p.MarshalBinary() require.Nil(t, err) - q := newPoint() + q := NewPoint() err = q.UnmarshalBinary(serialized) require.Nil(t, err) require.True(t, p.Equal(q), "%v marshalled to %x, which "+ @@ -186,9 +186,9 @@ func TestPoint_Marshal(t *testing.T) { } func TestPoint_BaseTakesCopy(t *testing.T) { - p := newPoint().Base() + p := NewPoint().Base() p.Add(p, p) - q := newPoint().Base() + q := NewPoint().Base() assert.False(t, p.Equal(q), "modifying output from Base changes S256.G{x,y}") } @@ -200,7 +200,7 @@ func TestPoint_EthereumAddress(t *testing.T) { 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) + public := NewPoint().Mul(private, nil) address := EthereumAddress(public) assert.Equal(t, fmt.Sprintf("%x", address), "c2d7cf95645d33006175b78989035c7c9061d3f9") @@ -209,18 +209,18 @@ func TestPoint_EthereumAddress(t *testing.T) { func TestIsSecp256k1Point(t *testing.T) { p := curve25519.NewBlakeSHA256Curve25519(false).Point() require.False(t, IsSecp256k1Point(p)) - require.True(t, IsSecp256k1Point(newPoint())) + require.True(t, IsSecp256k1Point(NewPoint())) } func TestCoordinates(t *testing.T) { - x, y := Coordinates(newPoint()) + 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())) + require.False(t, ValidPublicKey(NewPoint()), "zero is not a valid key") + require.True(t, ValidPublicKey(NewPoint().Base())) } func TestGenerate(t *testing.T) {