Import Chainlink's Distributed Schnorr implementation

Unmodified except for imports and addition of license files.
This commit is contained in:
Leo 2020-08-04 19:46:27 +02:00
parent 71c0e29dcf
commit 2a81b445b1
17 changed files with 2415 additions and 1 deletions

View File

@ -3,8 +3,9 @@ module github.com/certusone/wormhole/bridge
go 1.14
require (
github.com/btcsuite/btcd v0.20.1-beta
github.com/cenkalti/backoff/v4 v4.0.2
github.com/ipfs/go-log v1.0.4
github.com/ethereum/go-ethereum v1.9.18
github.com/ipfs/go-log/v2 v2.1.1
github.com/libp2p/go-libp2p v0.10.2
github.com/libp2p/go-libp2p-connmgr v0.2.4
@ -14,6 +15,11 @@ require (
github.com/libp2p/go-libp2p-quic-transport v0.7.1
github.com/libp2p/go-libp2p-tls v0.1.3
github.com/multiformats/go-multiaddr v0.2.2
github.com/smartcontractkit/chainlink v0.8.11
github.com/stretchr/testify v1.6.1
go.dedis.ch/fixbuf v1.0.3
go.dedis.ch/kyber/v3 v3.0.12
go.uber.org/zap v1.15.0
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9
google.golang.org/grpc v1.31.0
)

21
bridge/third_party/chainlink/LICENSE vendored Normal file
View File

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2018 SmartContract ChainLink, Ltd.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

2
bridge/third_party/chainlink/NOTICE vendored Normal file
View File

@ -0,0 +1,2 @@
The Distributed Schnorr Signature Go implementation and the corresponding Solidity smart contract
have been imported from https://hub.docker.com/r/smartcontract/chainlink (which is based ob EPFL DEDIS/kyber).

View File

@ -0,0 +1,304 @@
// Package ethdss implements the Distributed Schnorr Signature protocol from the
////////////////////////////////////////////////////////////////////////////////
// XXX: Do not use in production until this code has been audited.
////////////////////////////////////////////////////////////////////////////////
// paper "Provably Secure Distributed Schnorr Signatures and a (t, n)
// Threshold Scheme for Implicit Certificates".
// https://dl.acm.org/citation.cfm?id=678297
// To generate a distributed signature from a group of participants, the group
// must first generate one longterm distributed secret with the share/dkg
// package, and then one random secret to be used only once.
// Each participant then creates a DSS struct, that can issue partial signatures
// with `dss.PartialSignature()`. These partial signatures can be broadcasted to
// the whole group or to a trusted combiner. Once one has collected enough
// partial signatures, it is possible to compute the distributed signature with
// the `Signature` method.
//
// This is mostly copied from the sign/dss package, with minor adjustments for
// use with ethschnorr.
package ethdss
import (
"bytes"
"errors"
"math/big"
"go.dedis.ch/kyber/v3"
"go.dedis.ch/kyber/v3/share"
"github.com/certusone/wormhole/bridge/third_party/chainlink/ethschnorr"
"github.com/certusone/wormhole/bridge/third_party/chainlink/secp256k1"
)
// Suite represents the functionalities needed by the dss package
type Suite interface {
kyber.Group
kyber.HashFactory
kyber.Random
}
var secp256k1Suite = secp256k1.NewBlakeKeccackSecp256k1()
var secp256k1Group kyber.Group = secp256k1Suite
// DistKeyShare is an abstraction to allow one to use distributed key share
// from different schemes easily into this distributed threshold Schnorr
// signature framework.
type DistKeyShare interface {
PriShare() *share.PriShare
Commitments() []kyber.Point
}
// DSS holds the information used to issue partial signatures as well as to
// compute the distributed schnorr signature.
type DSS struct {
// Keypair for this participant in the signing process (i.e., the one where
// this struct is stored.) This is not the keypair for full signing key; that
// would defeat the point.
secret kyber.Scalar
public kyber.Point
// Index value of this participant in the signing process. The index is shared
// across participants.
index int
// Public keys of potential participants in the signing process
participants []kyber.Point
// Number of participants needed to construct a signature
T int
// Shares of the distributed long-term signing keypair
long DistKeyShare
// Shares of the distributed ephemeral nonce keypair
random DistKeyShare
// Pedersen commitments to the coefficients of the polynomial implicitly used
// to share the long-term signing public/private keypair.
longPoly *share.PubPoly
// Pedersen commitments to the coefficients of the polynomial implicitly used
// to share the ephemeral nonce keypair.
randomPoly *share.PubPoly
// Message to be signed
msg *big.Int
// The partial signatures collected so far.
partials []*share.PriShare
// Indices for the participants who have provided their partial signatures to
// this participant.
partialsIdx map[int]bool
// True iff the partial signature for this dss has been signed by its owner.
signed bool
// String which uniquely identifies this signature, shared by all
// participants.
sessionID []byte
}
// DSSArgs is the arguments to NewDSS, as a struct. See NewDSS for details.
type DSSArgs = struct {
secret kyber.Scalar
participants []kyber.Point
long DistKeyShare
random DistKeyShare
msg *big.Int
T int
}
// PartialSig is partial representation of the final distributed signature. It
// must be sent to each of the other participants.
type PartialSig struct {
Partial *share.PriShare
SessionID []byte
Signature ethschnorr.Signature
}
// NewDSS returns a DSS struct out of the suite, the longterm secret of this
// node, the list of participants, the longterm and random distributed key
// (generated by the dkg package), the message to sign and finally the T
// threshold. It returns an error if the public key of the secret can't be found
// in the list of participants.
func NewDSS(args DSSArgs) (*DSS, error) {
public := secp256k1Group.Point().Mul(args.secret, nil)
var i int
var found bool
for j, p := range args.participants {
if p.Equal(public) {
found = true
i = j
break
}
}
if !found {
return nil, errors.New("dss: public key not found in list of participants")
}
return &DSS{
secret: args.secret,
public: public,
index: i,
participants: args.participants,
long: args.long,
longPoly: share.NewPubPoly(secp256k1Suite,
secp256k1Group.Point().Base(), args.long.Commitments()),
random: args.random,
randomPoly: share.NewPubPoly(secp256k1Suite,
secp256k1Group.Point().Base(), args.random.Commitments()),
msg: args.msg,
T: args.T,
partialsIdx: make(map[int]bool),
sessionID: sessionID(secp256k1Suite, args.long, args.random),
}, nil
}
// PartialSig generates the partial signature related to this DSS. This
// PartialSig can be broadcasted to every other participant or only to a
// trusted combiner as described in the paper.
// The signature format is compatible with EdDSA verification implementations.
//
// Corresponds to section 4.2, step 2 the Stinson 2001 paper.
func (d *DSS) PartialSig() (*PartialSig, error) {
secretPartialLongTermKey := d.long.PriShare().V // ɑᵢ, in the paper
secretPartialCommitmentKey := d.random.PriShare().V // βᵢ, in the paper
fullChallenge := d.hashSig() // h(m‖V), in the paper
secretChallengeMultiple := secp256k1Suite.Scalar().Mul(
fullChallenge, secretPartialLongTermKey) // ɑᵢh(m‖V)G, in the paper
// Corresponds to ɣᵢG=βᵢG+ɑᵢh(m‖V)G in the paper, but NB, in its notation, we
// use ɣᵢG=βᵢG-ɑᵢh(m‖V)G. (Subtract instead of add.)
partialSignature := secp256k1Group.Scalar().Sub(
secretPartialCommitmentKey, secretChallengeMultiple)
ps := &PartialSig{
Partial: &share.PriShare{V: partialSignature, I: d.index},
SessionID: d.sessionID,
}
var err error
ps.Signature, err = ethschnorr.Sign(d.secret, ps.Hash()) // sign share
if !d.signed {
d.partialsIdx[d.index] = true
d.partials = append(d.partials, ps.Partial)
d.signed = true
}
return ps, err
}
// ProcessPartialSig takes a PartialSig from another participant and stores it
// for generating the distributed signature. It returns an error if the index is
// wrong, or the signature is invalid or if a partial signature has already been
// received by the same peer. To know whether the distributed signature can be
// computed after this call, one can use the `EnoughPartialSigs` method.
//
// Corresponds to section 4.3, step 3 of the paper
func (d *DSS) ProcessPartialSig(ps *PartialSig) error {
var err error
public, ok := findPub(d.participants, ps.Partial.I)
if !ok {
err = errors.New("dss: partial signature with invalid index")
}
// nothing secret here
if err == nil && !bytes.Equal(ps.SessionID, d.sessionID) {
err = errors.New("dss: session id do not match")
}
if err == nil {
if vrr := ethschnorr.Verify(public, ps.Hash(), ps.Signature); vrr != nil {
err = vrr
}
}
if err == nil {
if _, ok := d.partialsIdx[ps.Partial.I]; ok {
err = errors.New("dss: partial signature already received from peer")
}
}
if err != nil {
return err
}
hash := d.hashSig() // h(m‖V), in the paper's notation
idx := ps.Partial.I
// βᵢG=sum(cₖi^kG), in the paper, defined as sᵢ in step 2 of section 2.4
randShare := d.randomPoly.Eval(idx)
// ɑᵢG=sum(bₖi^kG), defined as sᵢ in step 2 of section 2.4
longShare := d.longPoly.Eval(idx)
// h(m‖V)(Y+...) term from equation (3) of the paper. AKA h(m‖V)ɑᵢG
challengeSummand := secp256k1Group.Point().Mul(hash, longShare.V)
// RHS of equation (3), except we subtract the second term instead of adding.
// AKA (βᵢ-ɑᵢh(m‖V))G, which should equal ɣᵢG, according to equation (3)
maybePartialSigCommitment := secp256k1Group.Point().Sub(randShare.V,
challengeSummand)
// Check that equation (3) holds (ɣᵢ is represented as ps.Partial.V, here.)
partialSigCommitment := secp256k1Group.Point().Mul(ps.Partial.V, nil)
if !partialSigCommitment.Equal(maybePartialSigCommitment) {
return errors.New("dss: partial signature not valid")
}
d.partialsIdx[ps.Partial.I] = true
d.partials = append(d.partials, ps.Partial)
return nil
}
// EnoughPartialSig returns true if there are enough partial signature to compute
// the distributed signature. It returns false otherwise. If there are enough
// partial signatures, one can issue the signature with `Signature()`.
func (d *DSS) EnoughPartialSig() bool {
return len(d.partials) >= d.T
}
// Signature computes the distributed signature from the list of partial
// signatures received. It returns an error if there are not enough partial
// signatures.
//
// Corresponds to section 4.2, step 4 of Stinson, 2001 paper
func (d *DSS) Signature() (ethschnorr.Signature, error) {
if !d.EnoughPartialSig() {
return nil, errors.New("dkg: not enough partial signatures to sign")
}
// signature corresponds to σ in step 4 of section 4.2
signature, err := share.RecoverSecret(secp256k1Suite, d.partials, d.T,
len(d.participants))
if err != nil {
return nil, err
}
rv := ethschnorr.NewSignature()
rv.Signature = secp256k1.ToInt(signature)
// commitmentPublicKey corresponds to V in step 4 of section 4.2
commitmentPublicKey := d.random.Commitments()[0]
rv.CommitmentPublicAddress = secp256k1.EthereumAddress(commitmentPublicKey)
return rv, nil
}
// hashSig returns, in the paper's notation, h(m‖V). It is the challenge hash
// for the signature. (Actually, the hash also includes the public key, but that
// has no effect on the correctness or robustness arguments from the paper.)
func (d *DSS) hashSig() kyber.Scalar {
v := d.random.Commitments()[0] // Public-key commitment, in signature from d
vAddress := secp256k1.EthereumAddress(v)
publicKey := d.long.Commitments()[0]
rv, err := ethschnorr.ChallengeHash(publicKey, vAddress, d.msg)
if err != nil {
panic(err)
}
return rv
}
// Verify takes a public key, a message and a signature and returns an error if
// the signature is invalid.
func Verify(public kyber.Point, msg *big.Int, sig ethschnorr.Signature) error {
return ethschnorr.Verify(public, msg, sig)
}
// Hash returns the hash representation of this PartialSig to be used in a
// signature.
func (ps *PartialSig) Hash() *big.Int {
h := secp256k1Suite.Hash()
_, _ = h.Write(ps.Partial.Hash(secp256k1Suite))
_, _ = h.Write(ps.SessionID)
return (&big.Int{}).SetBytes(h.Sum(nil))
}
func findPub(list []kyber.Point, i int) (kyber.Point, bool) {
if i >= len(list) {
return nil, false
}
return list[i], true
}
func sessionID(s Suite, a, b DistKeyShare) []byte {
h := s.Hash()
for _, p := range a.Commitments() {
_, _ = p.MarshalTo(h)
}
for _, p := range b.Commitments() {
_, _ = p.MarshalTo(h)
}
return h.Sum(nil)
}

View File

@ -0,0 +1,290 @@
package ethdss
import (
"crypto/rand"
"fmt"
"math/big"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/smartcontractkit/chainlink/core/services/signatures/cryptotest"
"github.com/smartcontractkit/chainlink/core/services/signatures/ethschnorr"
"github.com/smartcontractkit/chainlink/core/services/signatures/secp256k1"
"go.dedis.ch/kyber/v3"
dkg "go.dedis.ch/kyber/v3/share/dkg/rabin"
)
var suite = secp256k1.NewBlakeKeccackSecp256k1()
var nbParticipants = 7
var t = nbParticipants/2 + 1
var partPubs []kyber.Point
var partSec []kyber.Scalar
var longterms []*dkg.DistKeyShare
var randoms []*dkg.DistKeyShare
var msg *big.Int
var randomStream = cryptotest.NewStream(&testing.T{}, 0)
func init() {
partPubs = make([]kyber.Point, nbParticipants)
partSec = make([]kyber.Scalar, nbParticipants)
for i := 0; i < nbParticipants; i++ {
kp := secp256k1.Generate(randomStream)
partPubs[i] = kp.Public
partSec[i] = kp.Private
}
// Corresponds to section 4.2, step 1 of Stinson, 2001 paper
longterms = genDistSecret(true) // Keep trying until valid public key
randoms = genDistSecret(false)
var err error
msg, err = rand.Int(rand.Reader, big.NewInt(0).Lsh(big.NewInt(1), 256))
if err != nil {
panic(err)
}
}
func TestDSSNew(t *testing.T) {
dssArgs := DSSArgs{secret: partSec[0], participants: partPubs,
long: longterms[0], random: randoms[0], msg: msg, T: 4}
dss, err := NewDSS(dssArgs)
assert.NotNil(t, dss)
assert.Nil(t, err)
dssArgs.secret = suite.Scalar().Zero()
dss, err = NewDSS(dssArgs)
assert.Nil(t, dss)
assert.Error(t, err)
}
func TestDSSPartialSigs(t *testing.T) {
dss0 := getDSS(0)
dss1 := getDSS(1)
ps0, err := dss0.PartialSig()
assert.Nil(t, err)
assert.NotNil(t, ps0)
assert.Len(t, dss0.partials, 1)
// second time should not affect list
ps0, err = dss0.PartialSig()
assert.Nil(t, err)
assert.NotNil(t, ps0)
assert.Len(t, dss0.partials, 1)
// wrong index
goodI := ps0.Partial.I
ps0.Partial.I = 100
err = dss1.ProcessPartialSig(ps0)
assert.Error(t, err)
assert.Contains(t, err.Error(), "invalid index")
ps0.Partial.I = goodI
// wrong sessionID
goodSessionID := ps0.SessionID
ps0.SessionID = []byte("ahhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh")
err = dss1.ProcessPartialSig(ps0)
assert.Error(t, err)
assert.Contains(t, err.Error(), "dss: session id")
ps0.SessionID = goodSessionID
// wrong Signature
goodSig := ps0.Signature
ps0.Signature = ethschnorr.NewSignature()
copy(ps0.Signature.CommitmentPublicAddress[:], randomBytes(20))
badSig := secp256k1.ToInt(suite.Scalar().Pick(randomStream))
ps0.Signature.Signature.Set(badSig)
assert.Error(t, dss1.ProcessPartialSig(ps0))
ps0.Signature = goodSig
// invalid partial sig
goodV := ps0.Partial.V
ps0.Partial.V = suite.Scalar().Zero()
ps0.Signature, err = ethschnorr.Sign(dss0.secret, ps0.Hash())
require.Nil(t, err)
err = dss1.ProcessPartialSig(ps0)
assert.Error(t, err)
assert.Contains(t, err.Error(), "not valid")
ps0.Partial.V = goodV
ps0.Signature = goodSig
// fine
err = dss1.ProcessPartialSig(ps0)
assert.Nil(t, err)
// already received
assert.Error(t, dss1.ProcessPartialSig(ps0))
// if not enough partial signatures, can't generate signature
sig, err := dss1.Signature()
assert.Nil(t, sig) // XXX: Should also check err is nil?
assert.Error(t, err)
assert.Contains(t, err.Error(), "not enough")
// enough partial sigs ?
for i := 2; i < nbParticipants; i++ {
dss := getDSS(i)
ps, e := dss.PartialSig()
require.Nil(t, e)
require.Nil(t, dss1.ProcessPartialSig(ps))
}
assert.True(t, dss1.EnoughPartialSig())
sig, err = dss1.Signature()
assert.NoError(t, err)
assert.NoError(t, Verify(dss1.long.Commitments()[0], msg, sig))
}
var printTests = false
func printTest(t *testing.T, msg *big.Int, public kyber.Point,
signature ethschnorr.Signature) {
pX, pY := secp256k1.Coordinates(public)
fmt.Printf(" ['%064x',\n '%064x',\n '%064x',\n '%064x',\n '%040x'],\n",
msg, pX, pY, signature.Signature,
signature.CommitmentPublicAddress)
}
func TestDSSSignature(t *testing.T) {
dsss := make([]*DSS, nbParticipants)
pss := make([]*PartialSig, nbParticipants)
for i := 0; i < nbParticipants; i++ {
dsss[i] = getDSS(i)
ps, err := dsss[i].PartialSig()
require.Nil(t, err)
require.NotNil(t, ps)
pss[i] = ps
}
for i, dss := range dsss {
for j, ps := range pss {
if i == j {
continue
}
require.Nil(t, dss.ProcessPartialSig(ps))
}
}
// issue and verify signature
dss0 := dsss[0]
sig, err := dss0.Signature()
assert.NotNil(t, sig)
assert.Nil(t, err)
assert.NoError(t, ethschnorr.Verify(longterms[0].Public(), dss0.msg, sig))
// Original contains this second check. Unclear why.
assert.NoError(t, ethschnorr.Verify(longterms[0].Public(), dss0.msg, sig))
if printTests {
printTest(t, dss0.msg, dss0.long.Commitments()[0], sig)
}
}
func TestPartialSig_Hash(t *testing.T) {
observedHashes := make(map[*big.Int]bool)
for i := 0; i < nbParticipants; i++ {
psig, err := getDSS(i).PartialSig()
require.NoError(t, err)
hash := psig.Hash()
require.False(t, observedHashes[hash])
observedHashes[hash] = true
}
}
func getDSS(i int) *DSS {
dss, err := NewDSS(DSSArgs{secret: partSec[i], participants: partPubs,
long: longterms[i], random: randoms[i], msg: msg, T: t})
if dss == nil || err != nil {
panic("nil dss")
}
return dss
}
func _genDistSecret() []*dkg.DistKeyShare {
dkgs := make([]*dkg.DistKeyGenerator, nbParticipants)
for i := 0; i < nbParticipants; i++ {
dkg, err := dkg.NewDistKeyGenerator(suite, partSec[i], partPubs, nbParticipants/2+1)
if err != nil {
panic(err)
}
dkgs[i] = dkg
}
// full secret sharing exchange
// 1. broadcast deals
resps := make([]*dkg.Response, 0, nbParticipants*nbParticipants)
for _, dkg := range dkgs {
deals, err := dkg.Deals()
if err != nil {
panic(err)
}
for i, d := range deals {
resp, err := dkgs[i].ProcessDeal(d)
if err != nil {
panic(err)
}
if !resp.Response.Approved {
panic("wrong approval")
}
resps = append(resps, resp)
}
}
// 2. Broadcast responses
for _, resp := range resps {
for h, dkg := range dkgs {
// ignore all messages from ourself
if resp.Response.Index == uint32(h) {
continue
}
j, err := dkg.ProcessResponse(resp)
if err != nil || j != nil {
panic("wrongProcessResponse")
}
}
}
// 4. Broadcast secret commitment
for i, dkg := range dkgs {
scs, err := dkg.SecretCommits()
if err != nil {
panic("wrong SecretCommits")
}
for j, dkg2 := range dkgs {
if i == j {
continue
}
cc, err := dkg2.ProcessSecretCommits(scs)
if err != nil || cc != nil {
panic("wrong ProcessSecretCommits")
}
}
}
// 5. reveal shares
dkss := make([]*dkg.DistKeyShare, len(dkgs))
for i, dkg := range dkgs {
dks, err := dkg.DistKeyShare()
if err != nil {
panic(err)
}
dkss[i] = dks
}
return dkss
}
func genDistSecret(checkValidPublicKey bool) []*dkg.DistKeyShare {
rv := _genDistSecret()
if checkValidPublicKey {
// Because of the trick we're using to verify the signatures on-chain, we
// need to make sure that the ordinate of this distributed public key is
// in the lower half of {0,...,}
for !secp256k1.ValidPublicKey(rv[0].Public()) {
rv = _genDistSecret() // Keep trying until valid distributed public key.
}
}
return rv
}
func randomBytes(n int) []byte {
var buff = make([]byte, n)
_, _ = rand.Read(buff[:])
return buff
}

View File

@ -0,0 +1,152 @@
// Package ethschnorr implements a version of the Schnorr signature which is
////////////////////////////////////////////////////////////////////////////////
// XXX: Do not use in production until this code has been audited.
////////////////////////////////////////////////////////////////////////////////
// cheap to verify on-chain.
//
// See https://en.wikipedia.org/wiki/Schnorr_signature For vanilla Schnorr.
//
// Since we are targeting ethereum specifically, there is no need to abstract
// away the group operations, as original kyber Schnorr code does. Thus, these
// functions only work with secp256k1 objects, even though they are expressed in
// terms of the abstract kyber Group interfaces.
//
// This code is largely based on EPFL-DEDIS's go.dedis.ch/kyber/sign/schnorr
package ethschnorr
import (
"bytes"
"fmt"
"math/big"
"go.dedis.ch/kyber/v3"
"github.com/certusone/wormhole/bridge/third_party/chainlink/secp256k1"
)
var secp256k1Suite = secp256k1.NewBlakeKeccackSecp256k1()
var secp256k1Group kyber.Group = secp256k1Suite
type signature = struct {
CommitmentPublicAddress [20]byte
Signature *big.Int
}
// Signature is a representation of the Schnorr signature generated and verified
// by this library.
type Signature = *signature
func i() *big.Int { return big.NewInt(0) }
var one = big.NewInt(1)
var u256Cardinality = i().Lsh(one, 256)
var maxUint256 = i().Sub(u256Cardinality, one)
// NewSignature allocates space for a Signature, and returns it
func NewSignature() Signature { return &signature{Signature: i()} }
var zero = i()
// ValidSignature(s) is true iff s.Signature represents an element of secp256k1
func ValidSignature(s Signature) bool {
return s.Signature.Cmp(secp256k1.GroupOrder) == -1 &&
s.Signature.Cmp(zero) != -1
}
// ChallengeHash returns the value the signer must use to demonstrate knowledge
// of the secret key
//
// NB: for parity with the on-chain hash, it's important that public and r
// marshall to the big-endian x ordinate, followed by a byte which is 0 if the y
// ordinate is even, 1 if it's odd. See evm/contracts/SchnorrSECP256K1.sol and
// evm/test/schnorr_test.js
func ChallengeHash(public kyber.Point, rAddress [20]byte, msg *big.Int) (
kyber.Scalar, error) {
var err error
h := secp256k1Suite.Hash()
if _, herr := public.MarshalTo(h); herr != nil {
err = fmt.Errorf("failed to hash public key for signature: %s", herr)
}
if err != nil && (msg.BitLen() > 256 || msg.Cmp(zero) == -1) {
err = fmt.Errorf("msg must be a uint256")
}
if err == nil {
if _, herr := h.Write(msg.Bytes()); herr != nil {
err = fmt.Errorf("failed to hash message for signature: %s", herr)
}
}
if err == nil {
if _, herr := h.Write(rAddress[:]); herr != nil {
err = fmt.Errorf("failed to hash r for signature: %s", herr)
}
}
if err != nil {
return nil, err
}
return secp256k1Suite.Scalar().SetBytes(h.Sum(nil)), nil
}
// Sign creates a signature from a msg and a private key. Verify with the
// function Verify, or on-chain with SchnorrSECP256K1.sol.
func Sign(private kyber.Scalar, msg *big.Int) (Signature, error) {
if !secp256k1.IsSecp256k1Scalar(private) {
return nil, fmt.Errorf("private key is not a secp256k1 scalar")
}
// create random secret and public commitment to it
commitmentSecretKey := secp256k1Group.Scalar().Pick(
secp256k1Suite.RandomStream())
commitmentPublicKey := secp256k1Group.Point().Mul(commitmentSecretKey, nil)
commitmentPublicAddress := secp256k1.EthereumAddress(commitmentPublicKey)
public := secp256k1Group.Point().Mul(private, nil)
challenge, err := ChallengeHash(public, commitmentPublicAddress, msg)
if err != nil {
return nil, err
}
// commitmentSecretKey-private*challenge
s := secp256k1Group.Scalar().Sub(commitmentSecretKey,
secp256k1Group.Scalar().Mul(private, challenge))
rv := signature{commitmentPublicAddress, secp256k1.ToInt(s)}
return &rv, nil
}
// Verify verifies the given Schnorr signature. It returns true iff the
// signature is valid.
func Verify(public kyber.Point, msg *big.Int, s Signature) error {
var err error
if !ValidSignature(s) {
err = fmt.Errorf("s is not a valid signature")
}
if err == nil && !secp256k1.IsSecp256k1Point(public) {
err = fmt.Errorf("public key is not a secp256k1 point")
}
if err == nil && !secp256k1.ValidPublicKey(public) {
err = fmt.Errorf("`public` is not a valid public key")
}
if err == nil && (msg.Cmp(zero) == -1 || msg.Cmp(maxUint256) == 1) {
err = fmt.Errorf("msg is not a uint256")
}
var challenge kyber.Scalar
var herr error
if err == nil {
challenge, herr = ChallengeHash(public, s.CommitmentPublicAddress, msg)
if herr != nil {
err = herr
}
}
if err != nil {
return err
}
sigScalar := secp256k1.IntToScalar(s.Signature)
// s*g + challenge*public = s*g + challenge*(secretKey*g) =
// commitmentSecretKey*g = commitmentPublicKey
maybeCommitmentPublicKey := secp256k1Group.Point().Add(
secp256k1Group.Point().Mul(sigScalar, nil),
secp256k1Group.Point().Mul(challenge, public))
maybeCommitmentPublicAddress := secp256k1.EthereumAddress(maybeCommitmentPublicKey)
if !bytes.Equal(s.CommitmentPublicAddress[:],
maybeCommitmentPublicAddress[:]) {
return fmt.Errorf("signature mismatch")
}
return nil
}

View File

@ -0,0 +1,114 @@
package ethschnorr
// This code is largely based on go.dedis.ch/kyber/sign/schnorr_test from
// EPFL's DEDIS
import (
crand "crypto/rand"
"fmt"
"math/big"
mrand "math/rand"
"testing"
"github.com/smartcontractkit/chainlink/core/services/signatures/cryptotest"
"github.com/stretchr/testify/require"
"go.dedis.ch/kyber/v3"
"go.dedis.ch/kyber/v3/group/curve25519"
"github.com/certusone/wormhole/bridge/third_party/chainlink/secp256k1"
)
var numSignatures = 5
var randomStream = cryptotest.NewStream(&testing.T{}, 0)
var printTests = false
func printTest(t *testing.T, msg *big.Int, private kyber.Scalar,
public kyber.Point, signature Signature) {
privateBytes, err := private.MarshalBinary()
require.Nil(t, err)
pX, pY := secp256k1.Coordinates(public)
fmt.Printf(" ['%064x',\n '%064x',\n '%064x',\n '%064x',\n "+
"'%064x',\n '%040x'],\n",
msg, privateBytes, pX, pY, signature.Signature,
signature.CommitmentPublicAddress)
}
func TestShortSchnorr_SignAndVerify(t *testing.T) {
if printTests {
fmt.Printf("tests = [\n")
}
for i := 0; i < numSignatures; i++ {
rand := mrand.New(mrand.NewSource(0))
msg, err := crand.Int(rand, maxUint256)
require.NoError(t, err)
kp := secp256k1.Generate(randomStream)
sig, err := Sign(kp.Private, msg)
require.NoError(t, err, "failed to sign message")
require.NoError(t, Verify(kp.Public, msg, sig),
"failed to validate own signature")
require.Error(t, Verify(kp.Public, u256Cardinality, sig),
"failed to abort on too large a message")
require.Error(t, Verify(kp.Public, big.NewInt(0).Neg(big.NewInt(1)), sig),
"failed to abort on negative message")
if printTests {
printTest(t, msg, kp.Private, kp.Public, sig)
}
wrongMsg := big.NewInt(0).Add(msg, big.NewInt(1))
require.Error(t, Verify(kp.Public, wrongMsg, sig),
"failed to reject signature with bad message")
wrongPublic := secp256k1Group.Point().Add(kp.Public, kp.Public)
require.Error(t, Verify(wrongPublic, msg, sig),
"failed to reject signature with bad public key")
wrongSignature := &signature{
CommitmentPublicAddress: sig.CommitmentPublicAddress,
Signature: big.NewInt(0).Add(sig.Signature, one),
}
require.Error(t, Verify(kp.Public, msg, wrongSignature),
"failed to reject bad signature")
badPublicCommitmentAddress := &signature{Signature: sig.Signature}
copy(badPublicCommitmentAddress.CommitmentPublicAddress[:],
sig.CommitmentPublicAddress[:])
badPublicCommitmentAddress.CommitmentPublicAddress[0] ^= 1 // Corrupt it
require.Error(t, Verify(kp.Public, msg, badPublicCommitmentAddress),
"failed to reject signature with bad public commitment")
}
if printTests {
fmt.Println("]")
}
// Check other validations
edSuite := curve25519.NewBlakeSHA256Curve25519(false)
badScalar := edSuite.Scalar()
_, err := Sign(badScalar, i())
require.Error(t, err)
require.Contains(t, err.Error(), "not a secp256k1 scalar")
err = Verify(edSuite.Point(), i(), NewSignature())
require.Error(t, err)
require.Contains(t, err.Error(), "not a secp256k1 point")
err = Verify(secp256k1Suite.Point(), i(), &signature{Signature: big.NewInt(-1)})
require.Error(t, err)
require.Contains(t, err.Error(), "not a valid signature")
err = Verify(secp256k1Suite.Point(), i(), &signature{Signature: u256Cardinality})
require.Error(t, err)
require.Contains(t, err.Error(), "not a valid signature")
}
func TestShortSchnorr_NewSignature(t *testing.T) {
s := NewSignature()
require.Equal(t, s.Signature, big.NewInt(0))
}
func TestShortSchnorr_ChallengeHash(t *testing.T) {
point := secp256k1Group.Point()
var hash [20]byte
h, err := ChallengeHash(point, hash, big.NewInt(-1))
require.Nil(t, h)
require.Error(t, err)
require.Contains(t, err.Error(), "msg must be a uint256")
h, err = ChallengeHash(point, hash, u256Cardinality)
require.Nil(t, h)
require.Error(t, err)
require.Contains(t, err.Error(), "msg must be a uint256")
}

View File

@ -0,0 +1,44 @@
// Package secp256k1 is an implementation of the kyber.{Group,Point,Scalar}
////////////////////////////////////////////////////////////////////////////////
// XXX: Do not use in production until this code has been audited.
////////////////////////////////////////////////////////////////////////////////
// interfaces, based on btcd/btcec and kyber/group/mod
//
// XXX: NOT CONSTANT TIME!
package secp256k1
import (
"math/big"
secp256k1BTCD "github.com/btcsuite/btcd/btcec"
"go.dedis.ch/kyber/v3"
)
// Secp256k1 represents the secp256k1 group.
// There are no parameters and no initialization is required
// because it supports only this one specific curve.
type Secp256k1 struct{}
// s256 is the btcec representation of secp256k1.
var s256 *secp256k1BTCD.KoblitzCurve = secp256k1BTCD.S256()
// String returns the name of the curve
func (*Secp256k1) String() string { return "Secp256k1" }
var egScalar kyber.Scalar = newScalar(big.NewInt(0))
var egPoint kyber.Point = &secp256k1Point{newFieldZero(), newFieldZero()}
// ScalarLen returns the length of a marshalled Scalar
func (*Secp256k1) ScalarLen() int { return egScalar.MarshalSize() }
// Scalar creates a new Scalar for the prime-order group on the secp256k1 curve
func (*Secp256k1) Scalar() kyber.Scalar { return newScalar(big.NewInt(0)) }
// PointLen returns the length of a marshalled Point
func (*Secp256k1) PointLen() int { return egPoint.MarshalSize() }
// Point returns a new secp256k1 point
func (*Secp256k1) Point() kyber.Point {
return &secp256k1Point{newFieldZero(), newFieldZero()}
}

View File

@ -0,0 +1,20 @@
package secp256k1
import (
"testing"
"github.com/stretchr/testify/require"
)
var group = &Secp256k1{}
func TestSecp256k1_String(t *testing.T) {
require.Equal(t, group.String(), "Secp256k1")
}
func TestSecp256k1_Constructors(t *testing.T) {
require.Equal(t, group.ScalarLen(), 32)
require.Equal(t, ToInt(group.Scalar()), bigZero)
require.Equal(t, group.PointLen(), 33)
require.Equal(t, group.Point(), &secp256k1Point{fieldZero, fieldZero})
}

View File

@ -0,0 +1,169 @@
// Package secp256k1 is an implementation of the kyber.{Group,Point,Scalar}
////////////////////////////////////////////////////////////////////////////////
// XXX: Do not use in production until this code has been audited.
////////////////////////////////////////////////////////////////////////////////
// interfaces, based on btcd/btcec and kyber/group/mod
//
// XXX: NOT CONSTANT TIME!
package secp256k1
// Arithmetic operations in the base field of secp256k1, i.e. /q, where q is
// the base field characteristic.
import (
"crypto/cipher"
"fmt"
"math/big"
"go.dedis.ch/kyber/v3/util/random"
)
// q is the field characteristic (cardinality) of the secp256k1 base field. All
// arithmetic operations on the field are modulo this.
var q = s256.P
type fieldElt big.Int
// newFieldZero returns a newly allocated field element.
func newFieldZero() *fieldElt { return (*fieldElt)(big.NewInt(0)) }
// Int returns f as a big.Int
func (f *fieldElt) int() *big.Int { return (*big.Int)(f) }
// modQ reduces f's underlying big.Int modulo q, and returns it
func (f *fieldElt) modQ() *fieldElt {
if f.int().Cmp(q) != -1 || f.int().Cmp(bigZero) == -1 {
// f ∉ {0, ..., q-1}. Find the representative of f+q in that set.
//
// Per Mod docstring, "Mod implements Euclidean modulus", meaning that after
// this, f will be the smallest non-negative representative of its
// equivalence class in /q. TODO(alx): Make this faster
f.int().Mod(f.int(), q)
}
return f
}
// This differs from SetInt below, in that it does not take a copy of v.
func fieldEltFromBigInt(v *big.Int) *fieldElt { return (*fieldElt)(v).modQ() }
func fieldEltFromInt(v int64) *fieldElt {
return fieldEltFromBigInt(big.NewInt(int64(v))).modQ()
}
var fieldZero = fieldEltFromInt(0)
var bigZero = big.NewInt(0)
// String returns the string representation of f
func (f *fieldElt) String() string {
return fmt.Sprintf("fieldElt{%x}", f.int())
}
// Equal returns true iff f=g, i.e. the backing big.Ints satisfy f ≡ g mod q
func (f *fieldElt) Equal(g *fieldElt) bool {
if f == (*fieldElt)(nil) && g == (*fieldElt)(nil) {
return true
}
if f == (*fieldElt)(nil) { // f is nil, g is not
return false
}
if g == (*fieldElt)(nil) { // g is nil, f is not
return false
}
return bigZero.Cmp(newFieldZero().Sub(f, g).modQ().int()) == 0
}
// Add sets f to the sum of a and b modulo q, and returns it.
func (f *fieldElt) Add(a, b *fieldElt) *fieldElt {
f.int().Add(a.int(), b.int())
return f.modQ()
}
// Sub sets f to a-b mod q, and returns it.
func (f *fieldElt) Sub(a, b *fieldElt) *fieldElt {
f.int().Sub(a.int(), b.int())
return f.modQ()
}
// Set sets f's value to v, and returns f.
func (f *fieldElt) Set(v *fieldElt) *fieldElt {
f.int().Set(v.int())
return f.modQ()
}
// SetInt sets f's value to v mod q, and returns f.
func (f *fieldElt) SetInt(v *big.Int) *fieldElt {
f.int().Set(v)
return f.modQ()
}
// Pick samples uniformly from {0, ..., q-1}, assigns sample to f, and returns f
func (f *fieldElt) Pick(rand cipher.Stream) *fieldElt {
return f.SetInt(random.Int(q, rand)) // random.Int safe because q≅2²⁵⁶, q<2²⁵⁶
}
// Neg sets f to the negation of g modulo q, and returns it
func (f *fieldElt) Neg(g *fieldElt) *fieldElt {
f.int().Neg(g.int())
return f.modQ()
}
// Clone returns a new fieldElt, backed by a clone of f
func (f *fieldElt) Clone() *fieldElt { return newFieldZero().Set(f.modQ()) }
// SetBytes sets f to the 32-byte big-endian value represented by buf, reduces
// it, and returns it.
func (f *fieldElt) SetBytes(buf [32]byte) *fieldElt {
f.int().SetBytes(buf[:])
return f.modQ()
}
// Bytes returns the 32-byte big-endian representation of f
func (f *fieldElt) Bytes() [32]byte {
bytes := f.modQ().int().Bytes()
if len(bytes) > 32 {
panic("field element longer than 256 bits")
}
var rv [32]byte
copy(rv[32-len(bytes):], bytes) // leftpad w zeros
return rv
}
var two = big.NewInt(2)
// square returns y² mod q
func fieldSquare(y *fieldElt) *fieldElt {
return fieldEltFromBigInt(newFieldZero().int().Exp(y.int(), two, q))
}
// sqrtPower is s.t. n^sqrtPower≡sqrt(n) mod q, if n has a root at all. See
// https://math.stackexchange.com/a/1816280, for instance
//
// What I'm calling sqrtPower is called q on the s256 struct. (See
// btcec.initS256), which is confusing because the "Q" in "QPlus1Div4" refers to
// the field characteristic
var sqrtPower = s256.QPlus1Div4()
// maybeSqrtInField returns a square root of v, if it has any, else nil
func maybeSqrtInField(v *fieldElt) *fieldElt {
s := newFieldZero()
s.int().Exp(v.int(), sqrtPower, q)
if !fieldSquare(s).Equal(v) {
return nil
}
return s
}
var three = big.NewInt(3)
var seven = fieldEltFromInt(7)
// rightHandSide returns the RHS of the secp256k1 equation, x³+7 mod q, given x
func rightHandSide(x *fieldElt) *fieldElt {
xCubed := newFieldZero()
xCubed.int().Exp(x.int(), three, q)
return xCubed.Add(xCubed, seven)
}
// isEven returns true if f is even, false otherwise
func (f *fieldElt) isEven() bool {
return big.NewInt(0).Mod(f.int(), two).Cmp(big.NewInt(0)) == 0
}

View File

@ -0,0 +1,159 @@
package secp256k1
import (
"encoding/hex"
"math/big"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/smartcontractkit/chainlink/core/services/signatures/cryptotest"
)
var numFieldSamples = 10
var observedFieldElts map[string]bool
func init() {
observedFieldElts = make(map[string]bool)
}
// observedFieldElt ensures that novel scalars are being picked.
func observedFieldElt(t *testing.T, s *fieldElt) {
elt := s.Bytes()
data := hex.Dump(elt[:])
require.False(t, observedFieldElts[data])
observedFieldElts[data] = true
}
var randomStream = cryptotest.NewStream(&testing.T{}, 0)
func TestField_SetIntAndEqual(t *testing.T) {
tests := []int64{5, 67108864, 67108865, 4294967295}
g := newFieldZero()
for _, test := range tests {
f := fieldEltFromInt(test)
i := big.NewInt(test)
g.SetInt(i)
assert.Equal(t, f, g,
"different values obtained for same input, using "+
"SetInt vs fieldEltFromInt")
i.Add(i, big.NewInt(1))
assert.Equal(t, f, g,
"SetInt should take a copy of the backing big.Int")
}
}
func TestField_String(t *testing.T) {
require.Equal(t, fieldZero.String(), "fieldElt{0}")
}
func TestField_Equal(t *testing.T) {
require.True(t, (*fieldElt)(nil).Equal((*fieldElt)(nil)))
require.False(t, (*fieldElt)(nil).Equal(fieldZero))
require.False(t, fieldZero.Equal((*fieldElt)(nil)))
}
func TestField_Set(t *testing.T) {
f := fieldEltFromInt(1)
g := newFieldZero()
g.Set(f)
g.Add(g, fieldEltFromInt(1))
assert.Equal(t, f, fieldEltFromInt(1),
"Set takes a copy of the backing big.Int")
}
func TestFieldEltFromInt(t *testing.T) {
assert.Equal(t, fieldEltFromInt(1), // Also tests fieldElt.modQ
fieldEltFromBigInt(new(big.Int).Add(q, big.NewInt(1))),
"only one representation of a /q element should be used")
}
func TestField_SmokeTestPick(t *testing.T) {
f := newFieldZero()
f.Pick(randomStream)
observedFieldElt(t, f)
assert.True(t, f.int().Cmp(big.NewInt(1000000000)) == 1,
"should be greater than 1000000000, with very high probability")
}
func TestField_Neg(t *testing.T) {
f := newFieldZero()
for i := 0; i < numFieldSamples; i++ {
f.Pick(randomStream)
observedFieldElt(t, f)
g := f.Clone()
g.Neg(g)
require.True(t, g.Add(f, g).Equal(fieldZero),
"adding something to its negative should give zero: "+
"failed with %s", f)
}
}
func TestField_Sub(t *testing.T) {
f := newFieldZero()
for i := 0; i < numFieldSamples; i++ {
f.Pick(randomStream)
observedFieldElt(t, f)
require.True(t, f.Sub(f, f).Equal(fieldZero),
"subtracting something from itself should give zero: "+
"failed with %s", f)
}
}
func TestField_Clone(t *testing.T) {
f := fieldEltFromInt(1)
g := f.Clone()
h := f.Clone()
assert.Equal(t, f, g, "clone output does not equal original")
g.Add(f, f)
assert.Equal(t, f, h, "clone does not make a copy")
}
func TestField_SetBytesAndBytes(t *testing.T) {
f := newFieldZero()
g := newFieldZero()
for i := 0; i < numFieldSamples; i++ {
f.Pick(randomStream)
observedFieldElt(t, f)
g.SetBytes(f.Bytes())
require.True(t, g.Equal(f),
"roundtrip through serialization should give same "+
"result back: failed with %s", f)
}
}
func TestField_MaybeSquareRootInField(t *testing.T) {
f := newFieldZero()
minusOne := fieldEltFromInt(-1)
assert.Nil(t, maybeSqrtInField(minusOne), "-1 is not a square, in this field")
for i := 0; i < numFieldSamples; i++ {
f.Pick(randomStream)
observedFieldElt(t, f)
require.True(t, f.int().Cmp(q) == -1, "picked larger value than q: %s", f)
require.True(t, f.int().Cmp(big.NewInt(-1)) != -1,
"backing int must be non-negative")
s := fieldSquare(f)
g := maybeSqrtInField(s)
require.NotEqual(t, g, (*fieldElt)(nil))
ng := newFieldZero().Neg(g)
require.True(t, f.Equal(g) || f.Equal(ng), "squaring something and "+
"taking the square root should give ± the original: failed with %s", f)
bigIntSqrt := newFieldZero() // Cross-check against big.ModSqrt
rv := bigIntSqrt.int().ModSqrt(s.int(), q)
require.NotNil(t, rv)
require.True(t, bigIntSqrt.Equal(g) || bigIntSqrt.Equal(ng))
nonSquare := newFieldZero().Neg(s)
rv = bigIntSqrt.int().ModSqrt(nonSquare.int(), q)
require.Nil(t, rv, "ModSqrt indicates nonSquare is square")
require.Nil(t, maybeSqrtInField(nonSquare), "the negative of square "+
"should not be a square")
}
}
func TestField_RightHandSide(t *testing.T) {
assert.Equal(t, rightHandSide(fieldEltFromInt(1)), fieldEltFromInt(8))
assert.Equal(t, rightHandSide(fieldEltFromInt(2)), fieldEltFromInt(15))
}

View File

@ -0,0 +1,379 @@
// Package secp256k1 is an implementation of the kyber.{Group,Point,Scalar}
////////////////////////////////////////////////////////////////////////////////
// XXX: Do not use in production until this code has been audited.
////////////////////////////////////////////////////////////////////////////////
// interfaces, based on btcd/btcec and kyber/group/mod
//
// XXX: NOT CONSTANT TIME!
package secp256k1
// Implementation of kyber.Point interface for elliptic-curve arithmetic
// operations on secpk256k1.
//
// This is mostly a wrapper of the functionality provided by btcec
import (
"crypto/cipher"
"fmt"
"io"
"math/big"
"go.dedis.ch/kyber/v3"
"go.dedis.ch/kyber/v3/util/key"
"golang.org/x/crypto/sha3"
)
// btcec's public interface uses this affine representation for points on the
// curve. This does not naturally accommodate the point at infinity. btcec
// represents it as (0, 0), which is not a point on {y²=x³+7}.
type secp256k1Point struct {
X *fieldElt
Y *fieldElt
}
func newPoint() *secp256k1Point {
return &secp256k1Point{newFieldZero(), newFieldZero()}
}
// String returns a string representation of P
func (P *secp256k1Point) String() string {
return fmt.Sprintf("Secp256k1{X: %s, Y: %s}", P.X, P.Y)
}
// Equal returns true if p and pPrime represent the same point, false otherwise.
func (P *secp256k1Point) Equal(pPrime kyber.Point) bool {
return P.X.Equal(pPrime.(*secp256k1Point).X) &&
P.Y.Equal(pPrime.(*secp256k1Point).Y)
}
// Null sets p to the group-identity value, and returns it.
func (P *secp256k1Point) Null() kyber.Point {
P.X = fieldEltFromInt(0) // btcec representation of null point is (0,0)
P.Y = fieldEltFromInt(0)
return P
}
// Base sets p to a copy of the standard group generator, and returns it.
func (P *secp256k1Point) Base() kyber.Point {
P.X.SetInt(s256.Gx)
P.Y.SetInt(s256.Gy)
return P
}
// Pick sets P to a random point sampled from rand, and returns it.
func (P *secp256k1Point) Pick(rand cipher.Stream) kyber.Point {
for { // Keep trying X's until one fits the curve (~50% probability of
// success each iteration
P.X.Set(newFieldZero().Pick(rand))
maybeRHS := rightHandSide(P.X)
if maybeY := maybeSqrtInField(maybeRHS); maybeY != (*fieldElt)(nil) {
P.Y.Set(maybeY)
// Take the negative with 50% probability
b := make([]byte, 1)
rand.XORKeyStream(b[:], b[:])
if b[0]&1 == 0 {
P.Y.Neg(P.Y)
}
return P
}
}
}
// Set sets P to copies of pPrime's values, and returns it.
func (P *secp256k1Point) Set(pPrime kyber.Point) kyber.Point {
P.X.Set(pPrime.(*secp256k1Point).X)
P.Y.Set(pPrime.(*secp256k1Point).Y)
return P
}
// Clone returns a copy of P.
func (P *secp256k1Point) Clone() kyber.Point {
return &secp256k1Point{X: P.X.Clone(), Y: P.Y.Clone()}
}
// EmbedLen returns the number of bytes of data which can be embedded in a point.
func (*secp256k1Point) EmbedLen() int {
// Reserve the most-significant 8 bits for pseudo-randomness.
// Reserve the least-significant 8 bits for embedded data length.
return (255 - 8 - 8) / 8
}
// Embed encodes a limited amount of specified data in the Point, using r as a
// source of cryptographically secure random data. Implementations only embed
// the first EmbedLen bytes of the given data.
func (P *secp256k1Point) Embed(data []byte, r cipher.Stream) kyber.Point {
numEmbedBytes := P.EmbedLen()
if len(data) > numEmbedBytes {
panic("too much data to embed in a point")
}
numEmbedBytes = len(data)
var x [32]byte
randStart := 1 // First byte to fill with random data
if data != nil {
x[0] = byte(numEmbedBytes) // Encode length in low 8 bits
copy(x[1:1+numEmbedBytes], data) // Copy in data to embed
randStart = 1 + numEmbedBytes
}
maxAttempts := 10000
// Try random x ordinates satisfying the constraints, until one provides
// a point on secp256k1
for numAttempts := 0; numAttempts < maxAttempts; numAttempts++ {
// Fill the rest of the x ordinate with random data
r.XORKeyStream(x[randStart:], x[randStart:])
xOrdinate := newFieldZero().SetBytes(x)
// RHS of secp256k1 equation is x³+7 mod p. Success if square.
// We optimistically don't use btcec.IsOnCurve, here, because we
// hope to assign the intermediate result maybeY to P.Y
secp256k1RHS := rightHandSide(xOrdinate)
if maybeY := maybeSqrtInField(secp256k1RHS); maybeY != (*fieldElt)(nil) {
P.X = xOrdinate // success: found (x,y) s.t. y²=x³+7
P.Y = maybeY
return P
}
}
// Probability 2^{-maxAttempts}, under correct operation.
panic("failed to find point satisfying all constraints")
}
// Data returns data embedded in P, or an error if inconsistent with encoding
func (P *secp256k1Point) Data() ([]byte, error) {
b := P.X.Bytes()
dataLength := int(b[0])
if dataLength > P.EmbedLen() {
return nil, fmt.Errorf("point specifies too much data")
}
return b[1 : dataLength+1], nil
}
// Add sets P to a+b (secp256k1 group operation) and returns it.
func (P *secp256k1Point) Add(a, b kyber.Point) kyber.Point {
X, Y := s256.Add(
a.(*secp256k1Point).X.int(), a.(*secp256k1Point).Y.int(),
b.(*secp256k1Point).X.int(), b.(*secp256k1Point).Y.int())
P.X.SetInt(X)
P.Y.SetInt(Y)
return P
}
// Add sets P to a-b (secp256k1 group operation), and returns it.
func (P *secp256k1Point) Sub(a, b kyber.Point) kyber.Point {
X, Y := s256.Add(
a.(*secp256k1Point).X.int(), a.(*secp256k1Point).Y.int(),
b.(*secp256k1Point).X.int(),
newFieldZero().Neg(b.(*secp256k1Point).Y).int()) // -b_y
P.X.SetInt(X)
P.Y.SetInt(Y)
return P
}
// Neg sets P to -a (in the secp256k1 group), and returns it.
func (P *secp256k1Point) Neg(a kyber.Point) kyber.Point {
P.X = a.(*secp256k1Point).X.Clone()
P.Y = newFieldZero().Neg(a.(*secp256k1Point).Y)
return P
}
// Mul sets P to s*a (in the secp256k1 group, i.e. adding a to itself s times),
// and returns it. If a is nil, it is replaced by the secp256k1 generator.
func (P *secp256k1Point) Mul(s kyber.Scalar, a kyber.Point) kyber.Point {
sBytes, err := s.(*secp256k1Scalar).MarshalBinary()
if err != nil {
panic(fmt.Errorf("failure while marshaling multiplier: %s",
err))
}
var X, Y *big.Int
if a == (*secp256k1Point)(nil) || a == nil {
X, Y = s256.ScalarBaseMult(sBytes)
} else {
X, Y = s256.ScalarMult(a.(*secp256k1Point).X.int(),
a.(*secp256k1Point).Y.int(), sBytes)
}
P.X.SetInt(X)
P.Y.SetInt(Y)
return P
}
// MarshalBinary returns the concatenated big-endian representation of the X
// ordinate and a byte which is 0 if Y is even, 1 if it's odd. Or it returns an
// error on failure.
func (P *secp256k1Point) MarshalBinary() ([]byte, error) {
maybeSqrt := maybeSqrtInField(rightHandSide(P.X))
if maybeSqrt == (*fieldElt)(nil) {
return nil, fmt.Errorf("x³+7 not a square")
}
minusMaybeSqrt := newFieldZero().Neg(maybeSqrt)
if !P.Y.Equal(maybeSqrt) && !P.Y.Equal(minusMaybeSqrt) {
return nil, fmt.Errorf(
"y ≠ ±maybeSqrt(x³+7), so not a point on the curve")
}
rv := make([]byte, P.MarshalSize())
signByte := P.MarshalSize() - 1 // Last byte contains sign of Y.
xordinate := P.X.Bytes()
copyLen := copy(rv[:signByte], xordinate[:])
if copyLen != P.MarshalSize()-1 {
return []byte{}, fmt.Errorf("marshal of x ordinate too short")
}
if P.Y.isEven() {
rv[signByte] = 0
} else {
rv[signByte] = 1
}
return rv, nil
}
// MarshalSize returns the length of the byte representation of P
func (P *secp256k1Point) MarshalSize() int { return 33 }
// MarshalID returns the ID for a secp256k1 point
func (P *secp256k1Point) MarshalID() [8]byte {
return [8]byte{'s', 'p', '2', '5', '6', '.', 'p', 'o'}
}
// UnmarshalBinary sets P to the point represented by contents of buf, or
// returns an non-nil error
func (P *secp256k1Point) UnmarshalBinary(buf []byte) error {
var err error
if len(buf) != P.MarshalSize() {
err = fmt.Errorf("wrong length for marshaled point")
}
if err == nil && !(buf[32] == 0 || buf[32] == 1) {
err = fmt.Errorf("bad sign byte (the last one)")
}
if err != nil {
return err
}
var xordinate [32]byte
copy(xordinate[:], buf[:32])
P.X = newFieldZero().SetBytes(xordinate)
secp256k1RHS := rightHandSide(P.X)
maybeY := maybeSqrtInField(secp256k1RHS)
if maybeY == (*fieldElt)(nil) {
return fmt.Errorf("x ordinate does not correspond to a curve point")
}
isEven := maybeY.isEven()
P.Y.Set(maybeY)
if (buf[32] == 0 && !isEven) || (buf[32] == 1 && isEven) {
P.Y.Neg(P.Y)
} else {
if buf[32] != 0 && buf[32] != 1 {
return fmt.Errorf("parity byte must be 0 or 1")
}
}
return nil
}
// MarshalTo writes the serialized P to w, and returns the number of bytes
// written, or an error on failure.
func (P *secp256k1Point) MarshalTo(w io.Writer) (int, error) {
buf, err := P.MarshalBinary()
if err != nil {
return 0, err
}
return w.Write(buf)
}
// UnmarshalFrom sets P to the secp256k1 point represented by bytes read from r,
// and returns the number of bytes read, or an error on failure.
func (P *secp256k1Point) UnmarshalFrom(r io.Reader) (int, error) {
buf := make([]byte, P.MarshalSize())
n, err := io.ReadFull(r, buf)
if err != nil {
return 0, err
}
return n, P.UnmarshalBinary(buf)
}
// EthereumAddress returns the 160-bit address corresponding to p as public key.
func EthereumAddress(p kyber.Point) (rv [20]byte) {
// The Ethereum address of P is the bottom 160 bits of keccak256(P.X‖P.Y),
// where P.X and P.Y are represented in 32 bytes as big-endian. See equations
// (277, 284) of Ethereum Yellow Paper version 3e36772, or go-ethereum's
// crypto.PubkeyToAddress.
h := sha3.NewLegacyKeccak256()
if _, err := h.Write(LongMarshal(p)); err != nil {
panic(err)
}
copy(rv[:], h.Sum(nil)[12:])
return rv
}
// IsSecp256k1Point returns true if p is a secp256k1Point
func IsSecp256k1Point(p kyber.Point) bool {
switch p.(type) {
case *secp256k1Point:
return true
default:
return false
}
}
// Coordinates returns the coordinates of p
func Coordinates(p kyber.Point) (*big.Int, *big.Int) {
return p.(*secp256k1Point).X.int(), p.(*secp256k1Point).Y.int()
}
// ValidPublicKey returns true iff p can be used in the optimized on-chain
// Schnorr-signature verification. See SchnorrSECP256K1.sol for details.
func ValidPublicKey(p kyber.Point) bool {
if p == (*secp256k1Point)(nil) || p == nil {
return false
}
P, ok := p.(*secp256k1Point)
if !ok {
return false
}
maybeY := maybeSqrtInField(rightHandSide(P.X))
return maybeY != nil && (P.Y.Equal(maybeY) || P.Y.Equal(maybeY.Neg(maybeY)))
}
// Generate generates a public/private key pair, which can be verified cheaply
// on-chain
func Generate(random cipher.Stream) *key.Pair {
p := key.Pair{}
for !ValidPublicKey(p.Public) {
p.Private = (&Secp256k1{}).Scalar().Pick(random)
p.Public = (&Secp256k1{}).Point().Mul(p.Private, nil)
}
return &p
}
// LongMarshal returns the concatenated coordinates serialized as uint256's
func LongMarshal(p kyber.Point) []byte {
xMarshal := p.(*secp256k1Point).X.Bytes()
yMarshal := p.(*secp256k1Point).Y.Bytes()
return append(xMarshal[:], yMarshal[:]...)
}
// LongUnmarshal returns the secp256k1 point represented by m, as a concatenated
// pair of uint256's
func LongUnmarshal(m []byte) (kyber.Point, error) {
if len(m) != 64 {
return nil, fmt.Errorf(
"0x%x does not represent an uncompressed secp256k1Point. Should be length 64, but is length %d",
m, len(m))
}
p := newPoint()
p.X.SetInt(big.NewInt(0).SetBytes(m[:32]))
p.Y.SetInt(big.NewInt(0).SetBytes(m[32:]))
if !ValidPublicKey(p) {
return nil, fmt.Errorf("%s is not a valid secp256k1 point", p)
}
return p, nil
}
// ScalarToPublicPoint returns the public secp256k1 point associated to s
func ScalarToPublicPoint(s kyber.Scalar) kyber.Point {
publicPoint := (&Secp256k1{}).Point()
return publicPoint.Mul(s, nil)
}
// SetCoordinates returns the point (x,y), or panics if an invalid secp256k1Point
func SetCoordinates(x, y *big.Int) kyber.Point {
rv := newPoint()
rv.X.SetInt(x)
rv.Y.SetInt(y)
if !ValidPublicKey(rv) {
panic("point requested from invalid coordinates")
}
return rv
}

View File

@ -0,0 +1,232 @@
package secp256k1
import (
"bytes"
"crypto/rand"
"fmt"
"math/big"
"testing"
"go.dedis.ch/kyber/v3/group/curve25519"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/smartcontractkit/chainlink/core/services/signatures/cryptotest"
)
var numPointSamples = 10
var randomStreamPoint = cryptotest.NewStream(&testing.T{}, 0)
func TestPoint_String(t *testing.T) {
require.Equal(t, newPoint().String(),
"Secp256k1{X: fieldElt{0}, Y: fieldElt{0}}")
}
func TestPoint_CloneAndEqual(t *testing.T) {
f := newPoint()
for i := 0; i < numPointSamples; i++ {
g := f.Clone()
f.Pick(randomStreamPoint)
assert.NotEqual(t, f, g,
"modifying original shouldn't change clone")
g, h := f.Clone(), f.Clone()
assert.Equal(t, f, g, "clones should be equal")
g.Add(g, f)
assert.Equal(t, h, f,
"modifying a clone shouldn't change originial")
}
}
func TestPoint_NullAndAdd(t *testing.T) {
f, g := newPoint(), newPoint()
for i := 0; i < numPointSamples; i++ {
g.Null()
f.Pick(randomStreamPoint)
g.Add(f, g)
assert.Equal(t, f, g, "adding zero should have no effect")
}
}
func TestPoint_Set(t *testing.T) {
p := newPoint()
base := newPoint().Base()
assert.NotEqual(t, p, base, "generator should not be zero")
p.Set(base)
assert.Equal(t, p, base, "setting to generator should yield generator")
}
func TestPoint_Embed(t *testing.T) {
p := newPoint()
for i := 0; i < numPointSamples; i++ {
data := make([]byte, p.EmbedLen())
_, err := rand.Read(data)
require.Nil(t, err)
p.Embed(data, randomStreamPoint)
require.True(t, s256.IsOnCurve(p.X.int(), p.Y.int()),
"should embed to a secp256k1 point")
output, err := p.Data()
require.NoError(t, err)
require.True(t, bytes.Equal(data, output),
"should get same value back after round-trip "+
"embedding, got %v, then %v", data, output)
}
var uint256Bytes [32]byte
uint256Bytes[0] = 30
p.X.SetBytes(uint256Bytes)
_, err := p.Data()
require.Error(t, err)
require.Contains(t, err.Error(), "specifies too much data")
var b bytes.Buffer
p.Pick(randomStreamPoint)
_, err = p.MarshalTo(&b)
require.NoError(t, err)
_, err = p.UnmarshalFrom(&b)
require.NoError(t, err)
data := make([]byte, p.EmbedLen()+1) // Check length validation. This test
defer func() { // comes last, because it triggers panic
r := recover()
require.NotNil(t, r, "calling embed with too much data should panic")
require.Contains(t, r, "too much data to embed in a point")
}()
p.Embed(data, randomStreamPoint)
}
func TestPoint_AddSubAndNeg(t *testing.T) {
zero := newPoint().Null()
p := newPoint()
for i := 0; i < numPointSamples; i++ {
p.Pick(randomStreamPoint)
q := p.Clone()
p.Sub(p, q)
require.True(t, p.Equal(zero),
"subtracting a point from itself should give zero, "+
"got %v - %v = %v ≠ %v", q, q, p, zero)
p.Neg(q)
r := newPoint().Add(p, q)
require.True(t, r.Equal(zero),
"adding a point to its negative should give zero"+
" got %v+%v=%v≠%v", q, p, r, zero)
r.Neg(q)
p.Sub(q, r)
s := newPoint().Add(q, q)
require.True(t, p.Equal(s), "q-(-q)=q+q?"+
" got %v-%v=%v≠%v", q, r, p, s)
}
}
func TestPoint_Mul(t *testing.T) {
zero := newPoint().Null()
multiplier := newScalar(bigZero)
one := newScalar(big.NewInt(int64(1)))
var p *secp256k1Point
for i := 0; i < numPointSamples/5; i++ {
if i%20 == 0 {
p = nil // Test default to generator point
} else {
p = newPoint()
p.Pick(randomStreamPoint)
}
multiplier.Pick(randomStreamPoint)
q := newPoint().Mul(one, p)
comparee := newPoint()
if p == (*secp256k1Point)(nil) {
comparee.Base()
} else {
comparee = p.Clone().(*secp256k1Point)
}
require.True(t, comparee.Equal(q), "1*p=p? %v * %v ≠ %v", one,
comparee, q)
q.Mul(multiplier, p)
negMultiplier := newScalar(bigZero).Neg(multiplier)
r := newPoint().Mul(negMultiplier, p)
s := newPoint().Add(q, r)
require.True(t, s.Equal(zero), "s*p+(-s)*p=0? got "+
"%v*%v + %v*%v = %v + %v = %v ≠ %v", multiplier, p,
)
}
}
func TestPoint_Marshal(t *testing.T) {
p := newPoint()
for i := 0; i < numPointSamples; i++ {
p.Pick(randomStreamPoint)
serialized, err := p.MarshalBinary()
require.Nil(t, err)
q := newPoint()
err = q.UnmarshalBinary(serialized)
require.Nil(t, err)
require.True(t, p.Equal(q), "%v marshalled to %x, which "+
"unmarshalled to %v", p, serialized, q)
}
p.X.SetInt(big.NewInt(0)) // 0³+7 is not a square in the base field.
_, err := p.MarshalBinary()
require.Error(t, err)
require.Contains(t, err.Error(), "not a square")
p.X.SetInt(big.NewInt(1))
_, err = p.MarshalBinary()
require.Error(t, err)
require.Contains(t, err.Error(), "not a point on the curve")
id := p.MarshalID()
require.Equal(t, string(id[:]), "sp256.po")
data := make([]byte, 34)
err = p.UnmarshalBinary(data)
require.Error(t, err)
require.Contains(t, err.Error(), "wrong length for marshaled point")
require.Contains(t, p.UnmarshalBinary(data[:32]).Error(),
"wrong length for marshaled point")
data[32] = 2
require.Contains(t, p.UnmarshalBinary(data[:33]).Error(),
"bad sign byte")
data[32] = 0
data[31] = 5 // I.e., x-ordinate is now 5
require.Contains(t, p.UnmarshalBinary(data[:33]).Error(),
"does not correspond to a curve point")
}
func TestPoint_BaseTakesCopy(t *testing.T) {
p := newPoint().Base()
p.Add(p, p)
q := newPoint().Base()
assert.False(t, p.Equal(q),
"modifying output from Base changes S256.G{x,y}")
}
func TestPoint_EthereumAddress(t *testing.T) {
// Example taken from
// https://theethereum.wiki/w/index.php/Accounts,_Addresses,_Public_And_Private_Keys,_And_Tokens
pString := "3a1076bf45ab87712ad64ccb3b10217737f7faacbf2872e88fdd9a537d8fe266"
pInt, ok := big.NewInt(0).SetString(pString, 16)
require.True(t, ok, "failed to parse private key")
private := newScalar(pInt)
public := newPoint().Mul(private, nil)
address := EthereumAddress(public)
assert.Equal(t, fmt.Sprintf("%x", address),
"c2d7cf95645d33006175b78989035c7c9061d3f9")
}
func TestIsSecp256k1Point(t *testing.T) {
p := curve25519.NewBlakeSHA256Curve25519(false).Point()
require.False(t, IsSecp256k1Point(p))
require.True(t, IsSecp256k1Point(newPoint()))
}
func TestCoordinates(t *testing.T) {
x, y := Coordinates(newPoint())
require.Equal(t, x, bigZero)
require.Equal(t, y, bigZero)
}
func TestValidPublicKey(t *testing.T) {
require.False(t, ValidPublicKey(newPoint()), "zero is not a valid key")
require.True(t, ValidPublicKey(newPoint().Base()))
}
func TestGenerate(t *testing.T) {
for {
if ValidPublicKey(Generate(randomStreamPoint).Public) {
break
}
}
}

View File

@ -0,0 +1,228 @@
// Package secp256k1 is an implementation of the kyber.{Group,Point,Scalcar}
////////////////////////////////////////////////////////////////////////////////
// XXX: Do not use in production until this code has been audited.
////////////////////////////////////////////////////////////////////////////////
// interfaces, based on btcd/btcec and kyber/group/mod
//
// XXX: NOT CONSTANT TIME!
package secp256k1
// Implementation of kyber.Scalar interface for arithmetic operations mod the
// order of the secpk256k1 group (i.e. hex value
// 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141.)
import (
"crypto/cipher"
"fmt"
"io"
"math/big"
secp256k1BTCD "github.com/btcsuite/btcd/btcec"
"github.com/ethereum/go-ethereum/common"
"go.dedis.ch/kyber/v3"
"go.dedis.ch/kyber/v3/util/random"
)
var GroupOrder = secp256k1BTCD.S256().N
type secp256k1Scalar big.Int
// AllowVarTime, if passed true indicates that variable-time operations may be
// used on s.
func (s *secp256k1Scalar) AllowVarTime(varTimeAllowed bool) {
// Since constant-time operations are unimplemented for secp256k1, a
// value of false panics.
if !varTimeAllowed {
panic("implementation is not constant-time!")
}
}
// newScalar returns a secpk256k1 scalar, with value v modulo GroupOrder.
func newScalar(v *big.Int) kyber.Scalar {
return (*secp256k1Scalar)(zero().Mod(v, GroupOrder))
}
func zero() *big.Int { return big.NewInt(0) }
func ToInt(s kyber.Scalar) *big.Int { return (*big.Int)(s.(*secp256k1Scalar)) }
func (s *secp256k1Scalar) int() *big.Int { return (*big.Int)(s) }
func (s *secp256k1Scalar) modG() kyber.Scalar {
// TODO(alx): Make this faster
s.int().Mod(s.int(), GroupOrder)
return s
}
func (s *secp256k1Scalar) String() string {
return fmt.Sprintf("scalar{%x}", (*big.Int)(s))
}
var scalarZero = zero()
// Equal returns true if s and sPrime represent the same value modulo the group
// order, false otherwise
func (s *secp256k1Scalar) Equal(sPrime kyber.Scalar) bool {
difference := zero().Sub(s.int(), ToInt(sPrime))
return scalarZero.Cmp(difference.Mod(difference, GroupOrder)) == 0
}
// Set copies sPrime's value (modulo GroupOrder) to s, and returns it
func (s *secp256k1Scalar) Set(sPrime kyber.Scalar) kyber.Scalar {
return (*secp256k1Scalar)(s.int().Mod(ToInt(sPrime), GroupOrder))
}
// Clone returns a copy of s mod GroupOrder
func (s *secp256k1Scalar) Clone() kyber.Scalar {
return (*secp256k1Scalar)(zero().Mod(s.int(), GroupOrder))
}
// SetInt64 returns s with value set to v modulo GroupOrder
func (s *secp256k1Scalar) SetInt64(v int64) kyber.Scalar {
return (*secp256k1Scalar)(s.int().SetInt64(v)).modG()
}
// Zero sets s to 0 mod GroupOrder, and returns it
func (s *secp256k1Scalar) Zero() kyber.Scalar {
return s.SetInt64(0)
}
// Add sets s to a+b mod GroupOrder, and returns it
func (s *secp256k1Scalar) Add(a, b kyber.Scalar) kyber.Scalar {
s.int().Add(ToInt(a), ToInt(b))
return s.modG()
}
// Sub sets s to a-b mod GroupOrder, and returns it
func (s *secp256k1Scalar) Sub(a, b kyber.Scalar) kyber.Scalar {
s.int().Sub(ToInt(a), ToInt(b))
return s.modG()
}
// Neg sets s to -a mod GroupOrder, and returns it
func (s *secp256k1Scalar) Neg(a kyber.Scalar) kyber.Scalar {
s.int().Neg(ToInt(a))
return s.modG()
}
// One sets s to 1 mod GroupOrder, and returns it
func (s *secp256k1Scalar) One() kyber.Scalar {
return s.SetInt64(1)
}
// Mul sets s to a*b mod GroupOrder, and returns it
func (s *secp256k1Scalar) Mul(a, b kyber.Scalar) kyber.Scalar {
// TODO(alx): Make this faster
s.int().Mul(ToInt(a), ToInt(b))
return s.modG()
}
// Div sets s to a*b⁻¹ mod GroupOrder, and returns it
func (s *secp256k1Scalar) Div(a, b kyber.Scalar) kyber.Scalar {
if ToInt(b).Cmp(scalarZero) == 0 {
panic("attempt to divide by zero")
}
// TODO(alx): Make this faster
s.int().Mul(ToInt(a), zero().ModInverse(ToInt(b), GroupOrder))
return s.modG()
}
// Inv sets s to s⁻¹ mod GroupOrder, and returns it
func (s *secp256k1Scalar) Inv(a kyber.Scalar) kyber.Scalar {
if ToInt(a).Cmp(scalarZero) == 0 {
panic("attempt to divide by zero")
}
s.int().ModInverse(ToInt(a), GroupOrder)
return s
}
// Pick sets s to a random value mod GroupOrder sampled from rand, and returns
// it
func (s *secp256k1Scalar) Pick(rand cipher.Stream) kyber.Scalar {
return s.Set((*secp256k1Scalar)(random.Int(GroupOrder, rand)))
}
// MarshalBinary returns the big-endian byte representation of s, or an error on
// failure
func (s *secp256k1Scalar) MarshalBinary() ([]byte, error) {
b := ToInt(s.modG()).Bytes()
// leftpad with zeros
rv := append(make([]byte, s.MarshalSize()-len(b)), b...)
if len(rv) != s.MarshalSize() {
return nil, fmt.Errorf("marshalled scalar to wrong length")
}
return rv, nil
}
// MarshalSize returns the length of the byte representation of s
func (s *secp256k1Scalar) MarshalSize() int { return 32 }
// MarshalID returns the ID for a secp256k1 scalar
func (s *secp256k1Scalar) MarshalID() [8]byte {
return [8]byte{'s', 'p', '2', '5', '6', '.', 's', 'c'}
}
// UnmarshalBinary sets s to the scalar represented by the contents of buf,
// returning error on failure.
func (s *secp256k1Scalar) UnmarshalBinary(buf []byte) error {
if len(buf) != s.MarshalSize() {
return fmt.Errorf("cannot unmarshal to scalar: wrong length")
}
s.int().Mod(s.int().SetBytes(buf), GroupOrder)
return nil
}
// MarshalTo writes the serialized s to w, and returns the number of bytes
// written, or an error on failure.
func (s *secp256k1Scalar) MarshalTo(w io.Writer) (int, error) {
buf, err := s.MarshalBinary()
if err != nil {
return 0, fmt.Errorf("cannot marshal binary: %s", err)
}
return w.Write(buf)
}
// UnmarshalFrom sets s to the scalar represented by bytes read from r, and
// returns the number of bytes read, or an error on failure.
func (s *secp256k1Scalar) UnmarshalFrom(r io.Reader) (int, error) {
buf := make([]byte, s.MarshalSize())
n, err := io.ReadFull(r, buf)
if err != nil {
return n, err
}
return n, s.UnmarshalBinary(buf)
}
// SetBytes sets s to the number with big-endian representation a mod
// GroupOrder, and returns it
func (s *secp256k1Scalar) SetBytes(a []byte) kyber.Scalar {
return ((*secp256k1Scalar)(s.int().SetBytes(a))).modG()
}
// IsSecp256k1Scalar returns true if p is a secp256k1Scalar
func IsSecp256k1Scalar(s kyber.Scalar) bool {
switch s := s.(type) {
case *secp256k1Scalar:
s.modG()
return true
default:
return false
}
}
// IntToScalar returns i wrapped as a big.Int.
//
// May modify i to reduce mod GroupOrder
func IntToScalar(i *big.Int) kyber.Scalar {
return ((*secp256k1Scalar)(i)).modG()
}
func ScalarToHash(s kyber.Scalar) common.Hash {
return common.BigToHash(ToInt(s.(*secp256k1Scalar)))
}
// RepresentsScalar returns true iff i is in the right range to be a scalar
func RepresentsScalar(i *big.Int) bool {
return i.Cmp(GroupOrder) == -1
}

View File

@ -0,0 +1,189 @@
package secp256k1
import (
"bytes"
"encoding/hex"
"math/big"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.dedis.ch/kyber/v3"
"go.dedis.ch/kyber/v3/group/curve25519"
"github.com/smartcontractkit/chainlink/core/services/signatures/cryptotest"
)
var numScalarSamples = 10
var observedScalars map[string]bool
func init() {
observedScalars = make(map[string]bool)
}
// observedScalar ensures that novel scalars are being picked.
func observedScalar(t *testing.T, s kyber.Scalar) {
data, err := s.(*secp256k1Scalar).modG().MarshalBinary()
require.NoError(t, err)
scalar := hex.Dump(data)
require.False(t, observedScalars[scalar])
observedScalars[scalar] = true
}
var randomStreamScalar = cryptotest.NewStream(&testing.T{}, 0)
func TestScalar_SetAndEqual(t *testing.T) {
tests := []int64{5, 67108864, 67108865, 4294967295}
g := newScalar(scalarZero)
for _, test := range tests {
f := newScalar(big.NewInt(test))
g.Set(f)
assert.Equal(t, f, g,
"the method Set should give the same value to receiver")
f.Add(f, newScalar(big.NewInt(1)))
assert.NotEqual(t, f, g,
"SetInt should take a copy of the backing big.Int")
}
}
func TestNewScalar(t *testing.T) {
one := newScalar(big.NewInt(1))
assert.Equal(t, ToInt(one),
ToInt(newScalar(big.NewInt(0).Add(ToInt(one), GroupOrder))),
"equivalence classes mod GroupOrder not equal")
}
func TestScalar_SmokeTestPick(t *testing.T) {
f := newScalar(scalarZero).Clone()
for i := 0; i < numScalarSamples; i++ {
f.Pick(randomStreamScalar)
observedScalar(t, f)
require.True(t, ToInt(f).Cmp(big.NewInt(1000000000)) == 1,
"implausibly low value returned from Pick: %v", f)
}
}
func TestScalar_Neg(t *testing.T) {
f := newScalar(scalarZero).Clone()
for i := 0; i < numScalarSamples; i++ {
f.Pick(randomStreamScalar)
observedScalar(t, f)
g := f.Clone()
g.Neg(g)
require.True(t, g.Add(f, g).Equal(newScalar(scalarZero)))
}
}
func TestScalar_Sub(t *testing.T) {
f := newScalar(scalarZero).Clone()
for i := 0; i < numScalarSamples; i++ {
f.Pick(randomStreamScalar)
observedScalar(t, f)
require.True(t, f.Sub(f, f).Equal(newScalar(scalarZero)),
"subtracting something from itself should give zero")
}
}
func TestScalar_Clone(t *testing.T) {
f := newScalar(big.NewInt(1))
g := f.Clone()
h := f.Clone()
assert.Equal(t, f, g, "clone output does not equal input")
g.Add(f, f)
assert.Equal(t, f, h, "clone does not make a copy")
}
func TestScalar_Marshal(t *testing.T) {
f := newScalar(scalarZero)
g := newScalar(scalarZero)
for i := 0; i < numFieldSamples; i++ {
f.Pick(randomStreamScalar)
observedScalar(t, f)
data, err := f.MarshalBinary()
require.Nil(t, err)
err = g.UnmarshalBinary(data)
require.Nil(t, err)
require.True(t, g.Equal(f),
"roundtrip through serialization should give same "+
"result back: failed with %s", f)
}
marshalID := f.(*secp256k1Scalar).MarshalID()
require.Equal(t, string(marshalID[:]), "sp256.sc")
data := make([]byte, 33)
require.Contains(t, f.UnmarshalBinary(data).Error(), "wrong length")
var buf bytes.Buffer
_, err := f.MarshalTo(&buf)
require.NoError(t, err)
_, err = f.UnmarshalFrom(&buf)
require.NoError(t, err)
}
func TestScalar_MulDivInv(t *testing.T) {
f := newScalar(scalarZero)
g := newScalar(scalarZero)
h := newScalar(scalarZero)
j := newScalar(scalarZero)
k := newScalar(scalarZero)
for i := 0; i < numFieldSamples; i++ {
f.Pick(randomStreamScalar)
observedScalar(t, f)
g.Inv(f)
h.Mul(f, g)
require.True(t, h.Equal(newScalar(big.NewInt(1))))
h.Div(f, f)
require.True(t, h.Equal(newScalar(big.NewInt(1))))
h.Div(newScalar(big.NewInt(1)), f)
require.True(t, h.Equal(g))
h.Pick(randomStreamScalar)
observedScalar(t, h)
j.Neg(j.Mul(h, f))
k.Mul(h, k.Neg(f))
require.True(t, j.Equal(k), "-(h*f) != h*(-f)")
}
}
func TestScalar_AllowVarTime(t *testing.T) {
defer func() { require.Contains(t, recover(), "not constant-time!") }()
newScalar(bigZero).(*secp256k1Scalar).AllowVarTime(false)
}
func TestScalar_String(t *testing.T) {
require.Equal(t, newScalar(bigZero).String(), "scalar{0}")
}
func TestScalar_SetInt64(t *testing.T) {
require.Equal(t, newScalar(bigZero).SetInt64(1), newScalar(big.NewInt(1)))
require.True(t, newScalar(big.NewInt(1)).Zero().Equal(newScalar(bigZero)))
require.Equal(t, newScalar(bigZero).One(), newScalar(big.NewInt(1)))
}
func TestScalar_DivPanicsOnZeroDivisor(t *testing.T) {
defer func() { require.Contains(t, recover(), "divide by zero") }()
newScalar(bigZero).Div(newScalar(bigZero).One(), newScalar(bigZero))
}
func TestScalar_InvPanicsOnZero(t *testing.T) {
defer func() { require.Contains(t, recover(), "divide by zero") }()
newScalar(bigZero).Inv(newScalar(bigZero))
}
func TestScalar_SetBytes(t *testing.T) {
u256Cardinality := zero().Lsh(big.NewInt(1), 256)
newScalar(bigZero).(*secp256k1Scalar).int().Cmp(
zero().Sub(u256Cardinality, GroupOrder))
}
func TestScalar_IsSecp256k1Scalar(t *testing.T) {
c := curve25519.NewBlakeSHA256Curve25519(true)
require.False(t, IsSecp256k1Scalar(c.Scalar()))
require.True(t, IsSecp256k1Scalar(newScalar(bigZero)))
}
func TestScalar_IntToScalar(t *testing.T) {
u256Cardinality := zero().Lsh(big.NewInt(1), 256)
IntToScalar(u256Cardinality)
require.Equal(t, u256Cardinality, zero().Sub(zero().Lsh(big.NewInt(1), 256),
GroupOrder))
}

View File

@ -0,0 +1,89 @@
// Package secp256k1 is an implementation of the kyber.{Group,Point,Scalar}
////////////////////////////////////////////////////////////////////////////////
// XXX: Do not use in production until this code has been audited.
////////////////////////////////////////////////////////////////////////////////
// interfaces, based on btcd/btcec and kyber/group/mod
//
// XXX: NOT CONSTANT TIME!
package secp256k1
import (
"crypto/cipher"
"hash"
"io"
"reflect"
"golang.org/x/crypto/sha3"
"go.dedis.ch/fixbuf"
"go.dedis.ch/kyber/v3"
"go.dedis.ch/kyber/v3/util/random"
"go.dedis.ch/kyber/v3/xof/blake2xb"
)
// SuiteSecp256k1 implements some basic functionalities such as Group, HashFactory,
// and XOFFactory.
type SuiteSecp256k1 struct {
Secp256k1
r cipher.Stream
}
// Hash returns a newly instantiated keccak hash function.
func (s *SuiteSecp256k1) Hash() hash.Hash {
return sha3.NewLegacyKeccak256()
}
// XOF returns an XOR function, implemented via the Blake2b hash.
//
// This should only be used for generating secrets, so there is no need to make
// it cheap to compute on-chain.
func (s *SuiteSecp256k1) XOF(key []byte) kyber.XOF {
return blake2xb.New(key)
}
// Read implements the Encoding interface function, and reads a series of objs from r
// The objs must all be pointers
func (s *SuiteSecp256k1) Read(r io.Reader, objs ...interface{}) error {
return fixbuf.Read(r, s, objs...)
}
// Write implements the Encoding interface, and writes the objs to r using their
// built-in binary serializations. Supports Points, Scalars, fixed-length data
// types supported by encoding/binary/Write(), and structs, arrays, and slices
// containing these types.
func (s *SuiteSecp256k1) Write(w io.Writer, objs ...interface{}) error {
return fixbuf.Write(w, objs)
}
var aScalar kyber.Scalar
var tScalar = reflect.TypeOf(aScalar)
var aPoint kyber.Point
var tPoint = reflect.TypeOf(aPoint)
// New implements the kyber.Encoding interface, and returns a new element of
// type t, which can be a Point or a Scalar
func (s *SuiteSecp256k1) New(t reflect.Type) interface{} {
switch t {
case tScalar:
return s.Scalar()
case tPoint:
return s.Point()
}
return nil
}
// RandomStream returns a cipher.Stream that returns a key stream
// from crypto/rand.
func (s *SuiteSecp256k1) RandomStream() cipher.Stream {
if s.r != nil {
return s.r
}
return random.New()
}
// NewBlakeKeccackSecp256k1 returns a cipher suite based on package
// go.dedis.ch/kyber/xof/blake2xb, SHA-256, and the secp256k1 curve. It
// produces cryptographically secure random numbers via package crypto/rand.
func NewBlakeKeccackSecp256k1() *SuiteSecp256k1 {
return new(SuiteSecp256k1)
}

View File

@ -0,0 +1,16 @@
package secp256k1
import (
"encoding/hex"
"testing"
"github.com/stretchr/testify/require"
)
func TestSuite(t *testing.T) {
s := NewBlakeKeccackSecp256k1()
emptyHashAsHex := hex.EncodeToString(s.Hash().Sum(nil))
require.Equal(t, emptyHashAsHex,
"c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470")
_ = s.RandomStream()
}