Remove duplicate CL libraries, add signing logic to VAA
This commit is contained in:
parent
90e75b3d22
commit
a1ba223361
|
@ -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)
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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")
|
|
||||||
}
|
|
|
@ -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()}
|
|
||||||
}
|
|
|
@ -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})
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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))
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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))
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
}
|
|
|
@ -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()
|
|
||||||
}
|
|
|
@ -4,9 +4,12 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"fmt"
|
"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/common"
|
||||||
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
"go.dedis.ch/kyber/v3"
|
"go.dedis.ch/kyber/v3"
|
||||||
|
"go.dedis.ch/kyber/v3/util/key"
|
||||||
"io"
|
"io"
|
||||||
"math"
|
"math"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
@ -150,11 +153,66 @@ func ParseVAA(data []byte) (*VAA, error) {
|
||||||
return v, nil
|
return v, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// SigningBody returns the binary representation of the data that is relevant for signing and verifying the VAA
|
// signingBody returns the binary representation of the data that is relevant for signing and verifying the VAA
|
||||||
func (v *VAA) SigningBody() ([]byte, error) {
|
func (v *VAA) signingBody() ([]byte, error) {
|
||||||
return v.serializeBody()
|
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
|
// Serialize returns the binary representation of the VAA
|
||||||
func (v *VAA) Serialize() ([]byte, error) {
|
func (v *VAA) Serialize() ([]byte, error) {
|
||||||
buf := new(bytes.Buffer)
|
buf := new(bytes.Buffer)
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
package vaa
|
package vaa
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/certusone/wormhole/bridge/pkg/signatures/cryptotest"
|
"github.com/certusone/wormhole/bridge/third_party/chainlink/cryptotest"
|
||||||
"github.com/certusone/wormhole/bridge/pkg/signatures/secp256k1"
|
"github.com/certusone/wormhole/bridge/third_party/chainlink/secp256k1"
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"math/big"
|
"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))
|
||||||
|
}
|
||||||
|
|
|
@ -21,6 +21,7 @@ import (
|
||||||
// q is the field characteristic (cardinality) of the secp256k1 base field. All
|
// q is the field characteristic (cardinality) of the secp256k1 base field. All
|
||||||
// arithmetic operations on the field are modulo this.
|
// arithmetic operations on the field are modulo this.
|
||||||
var q = s256.P
|
var q = s256.P
|
||||||
|
var halfQ = new(big.Int).Div(q, big.NewInt(2))
|
||||||
|
|
||||||
type fieldElt big.Int
|
type fieldElt big.Int
|
||||||
|
|
||||||
|
@ -28,17 +29,17 @@ type fieldElt big.Int
|
||||||
func newFieldZero() *fieldElt { return (*fieldElt)(big.NewInt(0)) }
|
func newFieldZero() *fieldElt { return (*fieldElt)(big.NewInt(0)) }
|
||||||
|
|
||||||
// Int returns f as a big.Int
|
// 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
|
// modQ reduces f's underlying big.Int modulo q, and returns it
|
||||||
func (f *fieldElt) modQ() *fieldElt {
|
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.
|
// f ∉ {0, ..., q-1}. Find the representative of f+qℤ in that set.
|
||||||
//
|
//
|
||||||
// Per Mod docstring, "Mod implements Euclidean modulus", meaning that after
|
// Per Mod docstring, "Mod implements Euclidean modulus", meaning that after
|
||||||
// this, f will be the smallest non-negative representative of its
|
// this, f will be the smallest non-negative representative of its
|
||||||
// equivalence class in ℤ/qℤ. TODO(alx): Make this faster
|
// equivalence class in ℤ/qℤ. TODO(alx): Make this faster
|
||||||
f.int().Mod(f.int(), q)
|
f.Int().Mod(f.Int(), q)
|
||||||
}
|
}
|
||||||
return f
|
return f
|
||||||
}
|
}
|
||||||
|
@ -55,7 +56,7 @@ var bigZero = big.NewInt(0)
|
||||||
|
|
||||||
// String returns the string representation of f
|
// String returns the string representation of f
|
||||||
func (f *fieldElt) String() string {
|
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
|
// 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
|
if g == (*fieldElt)(nil) { // g is nil, f is not
|
||||||
return false
|
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.
|
// Add sets f to the sum of a and b modulo q, and returns it.
|
||||||
func (f *fieldElt) Add(a, b *fieldElt) *fieldElt {
|
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()
|
return f.modQ()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sub sets f to a-b mod q, and returns it.
|
// Sub sets f to a-b mod q, and returns it.
|
||||||
func (f *fieldElt) Sub(a, b *fieldElt) *fieldElt {
|
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()
|
return f.modQ()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set sets f's value to v, and returns f.
|
// Set sets f's value to v, and returns f.
|
||||||
func (f *fieldElt) Set(v *fieldElt) *fieldElt {
|
func (f *fieldElt) Set(v *fieldElt) *fieldElt {
|
||||||
f.int().Set(v.int())
|
f.Int().Set(v.Int())
|
||||||
return f.modQ()
|
return f.modQ()
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetInt sets f's value to v mod q, and returns f.
|
// SetInt sets f's value to v mod q, and returns f.
|
||||||
func (f *fieldElt) SetInt(v *big.Int) *fieldElt {
|
func (f *fieldElt) SetInt(v *big.Int) *fieldElt {
|
||||||
f.int().Set(v)
|
f.Int().Set(v)
|
||||||
return f.modQ()
|
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
|
// Neg sets f to the negation of g modulo q, and returns it
|
||||||
func (f *fieldElt) Neg(g *fieldElt) *fieldElt {
|
func (f *fieldElt) Neg(g *fieldElt) *fieldElt {
|
||||||
f.int().Neg(g.int())
|
f.Int().Neg(g.Int())
|
||||||
return f.modQ()
|
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
|
// SetBytes sets f to the 32-byte big-endian value represented by buf, reduces
|
||||||
// it, and returns it.
|
// it, and returns it.
|
||||||
func (f *fieldElt) SetBytes(buf [32]byte) *fieldElt {
|
func (f *fieldElt) SetBytes(buf [32]byte) *fieldElt {
|
||||||
f.int().SetBytes(buf[:])
|
f.Int().SetBytes(buf[:])
|
||||||
return f.modQ()
|
return f.modQ()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bytes returns the 32-byte big-endian representation of f
|
// Bytes returns the 32-byte big-endian representation of f
|
||||||
func (f *fieldElt) Bytes() [32]byte {
|
func (f *fieldElt) Bytes() [32]byte {
|
||||||
bytes := f.modQ().int().Bytes()
|
bytes := f.modQ().Int().Bytes()
|
||||||
if len(bytes) > 32 {
|
if len(bytes) > 32 {
|
||||||
panic("field element longer than 256 bits")
|
panic("field element longer than 256 bits")
|
||||||
}
|
}
|
||||||
|
@ -132,7 +133,7 @@ var two = big.NewInt(2)
|
||||||
|
|
||||||
// square returns y² mod q
|
// square returns y² mod q
|
||||||
func fieldSquare(y *fieldElt) *fieldElt {
|
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
|
// 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
|
// maybeSqrtInField returns a square root of v, if it has any, else nil
|
||||||
func maybeSqrtInField(v *fieldElt) *fieldElt {
|
func maybeSqrtInField(v *fieldElt) *fieldElt {
|
||||||
s := newFieldZero()
|
s := newFieldZero()
|
||||||
s.int().Exp(v.int(), sqrtPower, q)
|
s.Int().Exp(v.Int(), sqrtPower, q)
|
||||||
if !fieldSquare(s).Equal(v) {
|
if !fieldSquare(s).Equal(v) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -159,11 +160,11 @@ var seven = fieldEltFromInt(7)
|
||||||
// rightHandSide returns the RHS of the secp256k1 equation, x³+7 mod q, given x
|
// rightHandSide returns the RHS of the secp256k1 equation, x³+7 mod q, given x
|
||||||
func rightHandSide(x *fieldElt) *fieldElt {
|
func rightHandSide(x *fieldElt) *fieldElt {
|
||||||
xCubed := newFieldZero()
|
xCubed := newFieldZero()
|
||||||
xCubed.int().Exp(x.int(), three, q)
|
xCubed.Int().Exp(x.Int(), three, q)
|
||||||
return xCubed.Add(xCubed, seven)
|
return xCubed.Add(xCubed, seven)
|
||||||
}
|
}
|
||||||
|
|
||||||
// isEven returns true if f is even, false otherwise
|
// isEven returns true if f is even, false otherwise
|
||||||
func (f *fieldElt) isEven() bool {
|
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
|
||||||
}
|
}
|
||||||
|
|
|
@ -74,7 +74,7 @@ func TestField_SmokeTestPick(t *testing.T) {
|
||||||
f := newFieldZero()
|
f := newFieldZero()
|
||||||
f.Pick(randomStream)
|
f.Pick(randomStream)
|
||||||
observedFieldElt(t, f)
|
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")
|
"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++ {
|
for i := 0; i < numFieldSamples; i++ {
|
||||||
f.Pick(randomStream)
|
f.Pick(randomStream)
|
||||||
observedFieldElt(t, f)
|
observedFieldElt(t, f)
|
||||||
require.True(t, f.int().Cmp(q) == -1, "picked larger value than q: %s", 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,
|
require.True(t, f.Int().Cmp(big.NewInt(-1)) != -1,
|
||||||
"backing int must be non-negative")
|
"backing Int must be non-negative")
|
||||||
s := fieldSquare(f)
|
s := fieldSquare(f)
|
||||||
g := maybeSqrtInField(s)
|
g := maybeSqrtInField(s)
|
||||||
require.NotEqual(t, g, (*fieldElt)(nil))
|
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 "+
|
require.True(t, f.Equal(g) || f.Equal(ng), "squaring something and "+
|
||||||
"taking the square root should give ± the original: failed with %s", f)
|
"taking the square root should give ± the original: failed with %s", f)
|
||||||
bigIntSqrt := newFieldZero() // Cross-check against big.ModSqrt
|
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.NotNil(t, rv)
|
||||||
require.True(t, bigIntSqrt.Equal(g) || bigIntSqrt.Equal(ng))
|
require.True(t, bigIntSqrt.Equal(g) || bigIntSqrt.Equal(ng))
|
||||||
nonSquare := newFieldZero().Neg(s)
|
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, rv, "ModSqrt indicates nonSquare is square")
|
||||||
require.Nil(t, maybeSqrtInField(nonSquare), "the negative of square "+
|
require.Nil(t, maybeSqrtInField(nonSquare), "the negative of square "+
|
||||||
"should not be a square")
|
"should not be a square")
|
||||||
|
|
|
@ -31,7 +31,7 @@ type secp256k1Point struct {
|
||||||
Y *fieldElt
|
Y *fieldElt
|
||||||
}
|
}
|
||||||
|
|
||||||
func newPoint() *secp256k1Point {
|
func NewPoint() *secp256k1Point {
|
||||||
return &secp256k1Point{newFieldZero(), newFieldZero()}
|
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.
|
// Add sets P to a+b (secp256k1 group operation) and returns it.
|
||||||
func (P *secp256k1Point) Add(a, b kyber.Point) kyber.Point {
|
func (P *secp256k1Point) Add(a, b kyber.Point) kyber.Point {
|
||||||
X, Y := s256.Add(
|
X, Y := s256.Add(
|
||||||
a.(*secp256k1Point).X.int(), a.(*secp256k1Point).Y.int(),
|
a.(*secp256k1Point).X.Int(), a.(*secp256k1Point).Y.Int(),
|
||||||
b.(*secp256k1Point).X.int(), b.(*secp256k1Point).Y.int())
|
b.(*secp256k1Point).X.Int(), b.(*secp256k1Point).Y.Int())
|
||||||
P.X.SetInt(X)
|
P.X.SetInt(X)
|
||||||
P.Y.SetInt(Y)
|
P.Y.SetInt(Y)
|
||||||
return P
|
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.
|
// Add sets P to a-b (secp256k1 group operation), and returns it.
|
||||||
func (P *secp256k1Point) Sub(a, b kyber.Point) kyber.Point {
|
func (P *secp256k1Point) Sub(a, b kyber.Point) kyber.Point {
|
||||||
X, Y := s256.Add(
|
X, Y := s256.Add(
|
||||||
a.(*secp256k1Point).X.int(), a.(*secp256k1Point).Y.int(),
|
a.(*secp256k1Point).X.Int(), a.(*secp256k1Point).Y.Int(),
|
||||||
b.(*secp256k1Point).X.int(),
|
b.(*secp256k1Point).X.Int(),
|
||||||
newFieldZero().Neg(b.(*secp256k1Point).Y).int()) // -b_y
|
newFieldZero().Neg(b.(*secp256k1Point).Y).Int()) // -b_y
|
||||||
P.X.SetInt(X)
|
P.X.SetInt(X)
|
||||||
P.Y.SetInt(Y)
|
P.Y.SetInt(Y)
|
||||||
return P
|
return P
|
||||||
|
@ -185,8 +185,8 @@ func (P *secp256k1Point) Mul(s kyber.Scalar, a kyber.Point) kyber.Point {
|
||||||
if a == (*secp256k1Point)(nil) || a == nil {
|
if a == (*secp256k1Point)(nil) || a == nil {
|
||||||
X, Y = s256.ScalarBaseMult(sBytes)
|
X, Y = s256.ScalarBaseMult(sBytes)
|
||||||
} else {
|
} else {
|
||||||
X, Y = s256.ScalarMult(a.(*secp256k1Point).X.int(),
|
X, Y = s256.ScalarMult(a.(*secp256k1Point).X.Int(),
|
||||||
a.(*secp256k1Point).Y.int(), sBytes)
|
a.(*secp256k1Point).Y.Int(), sBytes)
|
||||||
}
|
}
|
||||||
P.X.SetInt(X)
|
P.X.SetInt(X)
|
||||||
P.Y.SetInt(Y)
|
P.Y.SetInt(Y)
|
||||||
|
@ -309,7 +309,7 @@ func IsSecp256k1Point(p kyber.Point) bool {
|
||||||
|
|
||||||
// Coordinates returns the coordinates of p
|
// Coordinates returns the coordinates of p
|
||||||
func Coordinates(p kyber.Point) (*big.Int, *big.Int) {
|
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
|
// 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 {
|
if !ok {
|
||||||
return false
|
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))
|
maybeY := maybeSqrtInField(rightHandSide(P.X))
|
||||||
return maybeY != nil && (P.Y.Equal(maybeY) || P.Y.Equal(maybeY.Neg(maybeY)))
|
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",
|
"0x%x does not represent an uncompressed secp256k1Point. Should be length 64, but is length %d",
|
||||||
m, len(m))
|
m, len(m))
|
||||||
}
|
}
|
||||||
p := newPoint()
|
p := NewPoint()
|
||||||
p.X.SetInt(big.NewInt(0).SetBytes(m[:32]))
|
p.X.SetInt(big.NewInt(0).SetBytes(m[:32]))
|
||||||
p.Y.SetInt(big.NewInt(0).SetBytes(m[32:]))
|
p.Y.SetInt(big.NewInt(0).SetBytes(m[32:]))
|
||||||
if !ValidPublicKey(p) {
|
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
|
// SetCoordinates returns the point (x,y), or panics if an invalid secp256k1Point
|
||||||
func SetCoordinates(x, y *big.Int) kyber.Point {
|
func SetCoordinates(x, y *big.Int) kyber.Point {
|
||||||
rv := newPoint()
|
rv := NewPoint()
|
||||||
rv.X.SetInt(x)
|
rv.X.SetInt(x)
|
||||||
rv.Y.SetInt(y)
|
rv.Y.SetInt(y)
|
||||||
if !ValidPublicKey(rv) {
|
if !ValidPublicKey(rv) {
|
||||||
|
|
|
@ -20,12 +20,12 @@ var numPointSamples = 10
|
||||||
var randomStreamPoint = cryptotest.NewStream(&testing.T{}, 0)
|
var randomStreamPoint = cryptotest.NewStream(&testing.T{}, 0)
|
||||||
|
|
||||||
func TestPoint_String(t *testing.T) {
|
func TestPoint_String(t *testing.T) {
|
||||||
require.Equal(t, newPoint().String(),
|
require.Equal(t, NewPoint().String(),
|
||||||
"Secp256k1{X: fieldElt{0}, Y: fieldElt{0}}")
|
"Secp256k1{X: fieldElt{0}, Y: fieldElt{0}}")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPoint_CloneAndEqual(t *testing.T) {
|
func TestPoint_CloneAndEqual(t *testing.T) {
|
||||||
f := newPoint()
|
f := NewPoint()
|
||||||
for i := 0; i < numPointSamples; i++ {
|
for i := 0; i < numPointSamples; i++ {
|
||||||
g := f.Clone()
|
g := f.Clone()
|
||||||
f.Pick(randomStreamPoint)
|
f.Pick(randomStreamPoint)
|
||||||
|
@ -40,7 +40,7 @@ func TestPoint_CloneAndEqual(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPoint_NullAndAdd(t *testing.T) {
|
func TestPoint_NullAndAdd(t *testing.T) {
|
||||||
f, g := newPoint(), newPoint()
|
f, g := NewPoint(), NewPoint()
|
||||||
for i := 0; i < numPointSamples; i++ {
|
for i := 0; i < numPointSamples; i++ {
|
||||||
g.Null()
|
g.Null()
|
||||||
f.Pick(randomStreamPoint)
|
f.Pick(randomStreamPoint)
|
||||||
|
@ -50,21 +50,21 @@ func TestPoint_NullAndAdd(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPoint_Set(t *testing.T) {
|
func TestPoint_Set(t *testing.T) {
|
||||||
p := newPoint()
|
p := NewPoint()
|
||||||
base := newPoint().Base()
|
base := NewPoint().Base()
|
||||||
assert.NotEqual(t, p, base, "generator should not be zero")
|
assert.NotEqual(t, p, base, "generator should not be zero")
|
||||||
p.Set(base)
|
p.Set(base)
|
||||||
assert.Equal(t, p, base, "setting to generator should yield generator")
|
assert.Equal(t, p, base, "setting to generator should yield generator")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPoint_Embed(t *testing.T) {
|
func TestPoint_Embed(t *testing.T) {
|
||||||
p := newPoint()
|
p := NewPoint()
|
||||||
for i := 0; i < numPointSamples; i++ {
|
for i := 0; i < numPointSamples; i++ {
|
||||||
data := make([]byte, p.EmbedLen())
|
data := make([]byte, p.EmbedLen())
|
||||||
_, err := rand.Read(data)
|
_, err := rand.Read(data)
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
p.Embed(data, randomStreamPoint)
|
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")
|
"should embed to a secp256k1 point")
|
||||||
output, err := p.Data()
|
output, err := p.Data()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
@ -94,8 +94,8 @@ func TestPoint_Embed(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPoint_AddSubAndNeg(t *testing.T) {
|
func TestPoint_AddSubAndNeg(t *testing.T) {
|
||||||
zero := newPoint().Null()
|
zero := NewPoint().Null()
|
||||||
p := newPoint()
|
p := NewPoint()
|
||||||
for i := 0; i < numPointSamples; i++ {
|
for i := 0; i < numPointSamples; i++ {
|
||||||
p.Pick(randomStreamPoint)
|
p.Pick(randomStreamPoint)
|
||||||
q := p.Clone()
|
q := p.Clone()
|
||||||
|
@ -104,20 +104,20 @@ func TestPoint_AddSubAndNeg(t *testing.T) {
|
||||||
"subtracting a point from itself should give zero, "+
|
"subtracting a point from itself should give zero, "+
|
||||||
"got %v - %v = %v ≠ %v", q, q, p, zero)
|
"got %v - %v = %v ≠ %v", q, q, p, zero)
|
||||||
p.Neg(q)
|
p.Neg(q)
|
||||||
r := newPoint().Add(p, q)
|
r := NewPoint().Add(p, q)
|
||||||
require.True(t, r.Equal(zero),
|
require.True(t, r.Equal(zero),
|
||||||
"adding a point to its negative should give zero"+
|
"adding a point to its negative should give zero"+
|
||||||
" got %v+%v=%v≠%v", q, p, r, zero)
|
" got %v+%v=%v≠%v", q, p, r, zero)
|
||||||
r.Neg(q)
|
r.Neg(q)
|
||||||
p.Sub(q, r)
|
p.Sub(q, r)
|
||||||
s := newPoint().Add(q, q)
|
s := NewPoint().Add(q, q)
|
||||||
require.True(t, p.Equal(s), "q-(-q)=q+q?"+
|
require.True(t, p.Equal(s), "q-(-q)=q+q?"+
|
||||||
" got %v-%v=%v≠%v", q, r, p, s)
|
" got %v-%v=%v≠%v", q, r, p, s)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPoint_Mul(t *testing.T) {
|
func TestPoint_Mul(t *testing.T) {
|
||||||
zero := newPoint().Null()
|
zero := NewPoint().Null()
|
||||||
multiplier := newScalar(bigZero)
|
multiplier := newScalar(bigZero)
|
||||||
one := newScalar(big.NewInt(int64(1)))
|
one := newScalar(big.NewInt(int64(1)))
|
||||||
var p *secp256k1Point
|
var p *secp256k1Point
|
||||||
|
@ -125,12 +125,12 @@ func TestPoint_Mul(t *testing.T) {
|
||||||
if i%20 == 0 {
|
if i%20 == 0 {
|
||||||
p = nil // Test default to generator point
|
p = nil // Test default to generator point
|
||||||
} else {
|
} else {
|
||||||
p = newPoint()
|
p = NewPoint()
|
||||||
p.Pick(randomStreamPoint)
|
p.Pick(randomStreamPoint)
|
||||||
}
|
}
|
||||||
multiplier.Pick(randomStreamPoint)
|
multiplier.Pick(randomStreamPoint)
|
||||||
q := newPoint().Mul(one, p)
|
q := NewPoint().Mul(one, p)
|
||||||
comparee := newPoint()
|
comparee := NewPoint()
|
||||||
if p == (*secp256k1Point)(nil) {
|
if p == (*secp256k1Point)(nil) {
|
||||||
comparee.Base()
|
comparee.Base()
|
||||||
} else {
|
} else {
|
||||||
|
@ -140,8 +140,8 @@ func TestPoint_Mul(t *testing.T) {
|
||||||
comparee, q)
|
comparee, q)
|
||||||
q.Mul(multiplier, p)
|
q.Mul(multiplier, p)
|
||||||
negMultiplier := newScalar(bigZero).Neg(multiplier)
|
negMultiplier := newScalar(bigZero).Neg(multiplier)
|
||||||
r := newPoint().Mul(negMultiplier, p)
|
r := NewPoint().Mul(negMultiplier, p)
|
||||||
s := newPoint().Add(q, r)
|
s := NewPoint().Add(q, r)
|
||||||
require.True(t, s.Equal(zero), "s*p+(-s)*p=0? got "+
|
require.True(t, s.Equal(zero), "s*p+(-s)*p=0? got "+
|
||||||
"%v*%v + %v*%v = %v + %v = %v ≠ %v", multiplier, p,
|
"%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) {
|
func TestPoint_Marshal(t *testing.T) {
|
||||||
p := newPoint()
|
p := NewPoint()
|
||||||
for i := 0; i < numPointSamples; i++ {
|
for i := 0; i < numPointSamples; i++ {
|
||||||
p.Pick(randomStreamPoint)
|
p.Pick(randomStreamPoint)
|
||||||
serialized, err := p.MarshalBinary()
|
serialized, err := p.MarshalBinary()
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
q := newPoint()
|
q := NewPoint()
|
||||||
err = q.UnmarshalBinary(serialized)
|
err = q.UnmarshalBinary(serialized)
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
require.True(t, p.Equal(q), "%v marshalled to %x, which "+
|
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) {
|
func TestPoint_BaseTakesCopy(t *testing.T) {
|
||||||
p := newPoint().Base()
|
p := NewPoint().Base()
|
||||||
p.Add(p, p)
|
p.Add(p, p)
|
||||||
q := newPoint().Base()
|
q := NewPoint().Base()
|
||||||
assert.False(t, p.Equal(q),
|
assert.False(t, p.Equal(q),
|
||||||
"modifying output from Base changes S256.G{x,y}")
|
"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)
|
pInt, ok := big.NewInt(0).SetString(pString, 16)
|
||||||
require.True(t, ok, "failed to parse private key")
|
require.True(t, ok, "failed to parse private key")
|
||||||
private := newScalar(pInt)
|
private := newScalar(pInt)
|
||||||
public := newPoint().Mul(private, nil)
|
public := NewPoint().Mul(private, nil)
|
||||||
address := EthereumAddress(public)
|
address := EthereumAddress(public)
|
||||||
assert.Equal(t, fmt.Sprintf("%x", address),
|
assert.Equal(t, fmt.Sprintf("%x", address),
|
||||||
"c2d7cf95645d33006175b78989035c7c9061d3f9")
|
"c2d7cf95645d33006175b78989035c7c9061d3f9")
|
||||||
|
@ -209,18 +209,18 @@ func TestPoint_EthereumAddress(t *testing.T) {
|
||||||
func TestIsSecp256k1Point(t *testing.T) {
|
func TestIsSecp256k1Point(t *testing.T) {
|
||||||
p := curve25519.NewBlakeSHA256Curve25519(false).Point()
|
p := curve25519.NewBlakeSHA256Curve25519(false).Point()
|
||||||
require.False(t, IsSecp256k1Point(p))
|
require.False(t, IsSecp256k1Point(p))
|
||||||
require.True(t, IsSecp256k1Point(newPoint()))
|
require.True(t, IsSecp256k1Point(NewPoint()))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCoordinates(t *testing.T) {
|
func TestCoordinates(t *testing.T) {
|
||||||
x, y := Coordinates(newPoint())
|
x, y := Coordinates(NewPoint())
|
||||||
require.Equal(t, x, bigZero)
|
require.Equal(t, x, bigZero)
|
||||||
require.Equal(t, y, bigZero)
|
require.Equal(t, y, bigZero)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestValidPublicKey(t *testing.T) {
|
func TestValidPublicKey(t *testing.T) {
|
||||||
require.False(t, ValidPublicKey(newPoint()), "zero is not a valid key")
|
require.False(t, ValidPublicKey(NewPoint()), "zero is not a valid key")
|
||||||
require.True(t, ValidPublicKey(newPoint().Base()))
|
require.True(t, ValidPublicKey(NewPoint().Base()))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGenerate(t *testing.T) {
|
func TestGenerate(t *testing.T) {
|
||||||
|
|
Loading…
Reference in New Issue