diff --git a/README.md b/README.md index 1f5930f..08702c2 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,11 @@ # go-blindsecp256k1 [![GoDoc](https://godoc.org/github.com/arnaucube/go-blindsecp256k1?status.svg)](https://godoc.org/github.com/arnaucube/go-blindsecp256k1) [![Go Report Card](https://goreportcard.com/badge/github.com/arnaucube/go-blindsecp256k1)](https://goreportcard.com/report/github.com/arnaucube/go-blindsecp256k1) [![Test](https://github.com/arnaucube/go-blindsecp256k1/workflows/Test/badge.svg)](https://github.com/arnaucube/go-blindsecp256k1/actions?query=workflow%3ATest) -Blind signature over [secp256k1](https://en.bitcoin.it/wiki/Secp256k1), based on *"[New Blind Signature Schemes Based on the (Elliptic Curve) Discrete Logarithm Problem](https://sci-hub.do/10.1109/ICCKE.2013.6682844)"* paper by Hamid Mala & Nafiseh Nezhadansari. +Blind signature over [secp256k1](https://en.bitcoin.it/wiki/Secp256k1), based on *"[New Blind Signature Schemes Based on the (Elliptic Curve) Discrete Logarithm Problem](https://sci-hub.st/10.1109/iccke.2013.6682844)"* paper by Hamid Mala & Nafiseh Nezhadansari. **WARNING**: this repo is experimental, do not use in production. +The implementation of this repo is compatible with https://github.com/arnaucube/blindsecp256k1-js + ## Usage ```go @@ -13,23 +15,22 @@ import ( ) [...] +// errors are not handled for simplicity of the example // signer: create new signer key pair -sk := blindsecp256k1.NewPrivateKey() +sk, _ := blindsecp256k1.NewPrivateKey() signerPubK := sk.Public() // signer: when user requests new R parameter to blind a new msg, // create new signerR (public) with its secret k -k, signerR := blindsecp256k1.NewRequestParameters() +k, signerR, _ := blindsecp256k1.NewRequestParameters() // user: blinds the msg using signer's R msg := new(big.Int).SetBytes([]byte("test")) -msgBlinded, userSecretData, err := blindsecp256k1.Blind(msg, signerR) -require.Nil(t, err) +msgBlinded, userSecretData, _ := blindsecp256k1.Blind(msg, signerR) // signer: signs the blinded message using its private key & secret k -sBlind, err := sk.BlindSign(msgBlinded, k) -require.Nil(t, err) +sBlind, _ := sk.BlindSign(msgBlinded, k) // user: unblinds the blinded signature sig := blindsecp256k1.Unblind(sBlind, userSecretData) @@ -39,5 +40,31 @@ verified := blindsecp256k1.Verify(msg, sig, signerPubK) assert.True(t, verified) ``` +Compression & decompression (allows to compress a point & public key (64 bytes) into 33 bytes, and a signature (96 bytes) into 65 bytes): +```go +p := blindsecp256k1.G // take the generator point as an example + +// also, instead from G, we can start from a PublicKey, which can be converted +// into a Point with +p = pk.Point() + +// compress point +b := p.Compress() +fmt.Println(hex.EncodeToString(b[:])) + +// decompress point (recovering the original point) +p2, _ := blindsecp256k1.DecompressPoint(b) +assert.Equal(t, p, p2) + + +// compress signature +b = sig.Compress() +fmt.Println(hex.EncodeToString(b[:])) // 65 bytes + +// decompress signature +sig2, _ := DecompressSignature(b) +assert.Equal(t, sig, sig2) +``` + ## WASM usage WASM wrappers for browser usage can be found at the [wasm](https://github.com/arnaucube/go-blindsecp256k1/tree/master/wasm/) directory with an example in html&js. diff --git a/blindsecp256k1.go b/blindsecp256k1.go index 587dffb..ee9626d 100644 --- a/blindsecp256k1.go +++ b/blindsecp256k1.go @@ -1,7 +1,7 @@ // Package blindsecp256k1 implements the Blind signature scheme explained at // "New Blind Signature Schemes Based on the (Elliptic Curve) Discrete // Logarithm Problem", by Hamid Mala & Nafiseh Nezhadansari -// https://sci-hub.do/10.1109/ICCKE.2013.6682844 +// https://sci-hub.st/10.1109/ICCKE.2013.6682844 // // LICENSE can be found at https://github.com/arnaucube/go-blindsecp256k1/blob/master/LICENSE // @@ -11,42 +11,33 @@ package blindsecp256k1 import ( "bytes" + "crypto/ecdsa" "crypto/rand" "fmt" "math/big" - "github.com/btcsuite/btcd/btcec" "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/crypto/secp256k1" ) -// TMP -// const ( -// // MinBigIntBytesLen defines the minimum bytes length of the minimum -// // accepted value for the checked *big.Int -// MinBigIntBytesLen = 20 * 8 -// ) - var ( - zero *big.Int = big.NewInt(0) + s256 *secp256k1.BitCurve = secp256k1.S256() + zero *big.Int = big.NewInt(0) // B (from y^2 = x^3 + B) - B *big.Int = btcec.S256().B + B *big.Int = s256.B // P represents the secp256k1 finite field - P *big.Int = btcec.S256().P - - // Q = (P+1)/4 - Q = new(big.Int).Div(new(big.Int).Add(P, - big.NewInt(1)), big.NewInt(4)) // nolint:gomnd + P *big.Int = s256.P // G represents the base point of secp256k1 G *Point = &Point{ - X: btcec.S256().Gx, - Y: btcec.S256().Gy, + X: s256.Gx, + Y: s256.Gy, } // N represents the order of G of secp256k1 - N *big.Int = btcec.S256().N + N *big.Int = s256.N ) // Point represents a point on the secp256k1 curve @@ -57,7 +48,7 @@ type Point struct { // Add performs the Point addition func (p *Point) Add(q *Point) *Point { - x, y := btcec.S256().Add(p.X, p.Y, q.X, q.Y) + x, y := s256.Add(p.X, p.Y, q.X, q.Y) return &Point{ X: x, Y: y, @@ -66,7 +57,7 @@ func (p *Point) Add(q *Point) *Point { // Mul performs the Point scalar multiplication func (p *Point) Mul(scalar *big.Int) *Point { - x, y := btcec.S256().ScalarMult(p.X, p.Y, scalar.Bytes()) + x, y := s256.ScalarMult(p.X, p.Y, scalar.Bytes()) return &Point{ X: x, Y: y, @@ -74,7 +65,7 @@ func (p *Point) Mul(scalar *big.Int) *Point { } func (p *Point) isValid() error { - if !btcec.S256().IsOnCurve(p.X, p.Y) { + if !s256.IsOnCurve(p.X, p.Y) { return fmt.Errorf("Point is not on secp256k1") } @@ -147,14 +138,12 @@ func DecompressPoint(b [33]byte) (*Point, error) { } // WIP -func newRand() *big.Int { - var b [32]byte - _, err := rand.Read(b[:]) +func newRand() (*big.Int, error) { + pk, err := ecdsa.GenerateKey(s256, rand.Reader) if err != nil { - panic(err) + return nil, err } - bi := new(big.Int).SetBytes(b[:]) - return new(big.Int).Mod(bi, N) + return pk.D, nil } // PrivateKey represents the signer's private key @@ -164,10 +153,16 @@ type PrivateKey big.Int type PublicKey Point // NewPrivateKey returns a new random private key -func NewPrivateKey() *PrivateKey { - k := newRand() +func NewPrivateKey() (*PrivateKey, error) { + k, err := newRand() + if err != nil { + return nil, err + } + if err := checkBigIntSize(k); err != nil { + return nil, fmt.Errorf("k error: %s", err) + } sk := PrivateKey(*k) - return &sk + return &sk, nil } // BigInt returns a *big.Int representation of the PrivateKey @@ -177,8 +172,8 @@ func (sk *PrivateKey) BigInt() *big.Int { // Public returns the PublicKey from the PrivateKey func (sk *PrivateKey) Public() *PublicKey { - Q := G.Mul(sk.BigInt()) - pk := PublicKey(*Q) + q := G.Mul(sk.BigInt()) + pk := PublicKey(*q) return &pk } @@ -188,13 +183,26 @@ func (pk *PublicKey) Point() *Point { } // NewRequestParameters returns a new random k (secret) & R (public) parameters -func NewRequestParameters() (*big.Int, *Point) { - k := newRand() - return k, G.Mul(k) // R = kG +func NewRequestParameters() (*big.Int, *Point, error) { + k, err := newRand() + if err != nil { + return nil, nil, err + } + // k, R = kG + return k, G.Mul(k), nil +} + +func checkBigIntSize(b *big.Int) error { + // check b.Bytes()==32, as go returns big-endian representation of the + // bigint, so if length is not 32 we have a smaller value than expected + if len(b.Bytes()) != 32 { //nolint:gomnd + return fmt.Errorf("invalid length, need 32 bytes") + } + return nil } // BlindSign performs the blind signature on the given mBlinded using the -// PrivateKey and the secret k values +// PrivateKey and the secret k values. func (sk *PrivateKey) BlindSign(mBlinded *big.Int, k *big.Int) (*big.Int, error) { // TODO add pending checks if mBlinded.Cmp(N) != -1 { @@ -203,10 +211,12 @@ func (sk *PrivateKey) BlindSign(mBlinded *big.Int, k *big.Int) (*big.Int, error) if bytes.Equal(mBlinded.Bytes(), big.NewInt(0).Bytes()) { return nil, fmt.Errorf("mBlinded can not be 0") } - // TMP - // if mBlinded.BitLen() < MinBigIntBytesLen { - // return nil, fmt.Errorf("mBlinded too small") - // } + if err := checkBigIntSize(mBlinded); err != nil { + return nil, fmt.Errorf("mBlinded error: %s", err) + } + if err := checkBigIntSize(k); err != nil { + return nil, fmt.Errorf("k error: %s", err) + } // s' = dm' + k sBlind := new(big.Int).Add( @@ -231,16 +241,22 @@ func Blind(m *big.Int, signerR *Point) (*big.Int, *UserSecretData, error) { return nil, nil, fmt.Errorf("signerR %s", err) } + var err error u := &UserSecretData{} - u.A = newRand() - u.B = newRand() + u.A, err = newRand() + if err != nil { + return nil, nil, err + } + u.B, err = newRand() + if err != nil { + return nil, nil, err + } // (R) F = aR' + bG aR := signerR.Mul(u.A) bG := G.Mul(u.B) u.F = aR.Add(bG) - // TODO check that F != O (point at infinity) if err := u.F.isValid(); err != nil { return nil, nil, fmt.Errorf("u.F %s", err) } @@ -319,8 +335,10 @@ func Verify(m *big.Int, s *Signature, q *PublicKey) bool { rx := new(big.Int).Mod(s.F.X, N) rxh := new(big.Int).Mul(rx, h) + // do mod, as go-ethereum/crypto/secp256k1 can not handle scalars > 256 bits + rxhMod := new(big.Int).Mod(rxh, N) // rxhG := G.Mul(rxh) // originally the paper uses G - rxhG := q.Point().Mul(rxh) + rxhG := q.Point().Mul(rxhMod) right := s.F.Add(rxhG) diff --git a/blindsecp256k1_test.go b/blindsecp256k1_test.go index 49aedef..17151a5 100644 --- a/blindsecp256k1_test.go +++ b/blindsecp256k1_test.go @@ -12,15 +12,18 @@ import ( func TestFlow(t *testing.T) { // signer: create new signer key pair - sk := NewPrivateKey() + sk, err := NewPrivateKey() + require.Nil(t, err) signerPubK := sk.Public() // signer: when user requests new R parameter to blind a new msg, // create new signerR (public) with its secret k - k, signerR := NewRequestParameters() + k, signerR, err := NewRequestParameters() + require.Nil(t, err) // user: blinds the msg using signer's R - msg := new(big.Int).SetBytes([]byte("test")) + // msg := new(big.Int).SetBytes([]byte("test")) + msg := new(big.Int).SetBytes(crypto.Keccak256([]byte("test"))) msgBlinded, userSecretData, err := Blind(msg, signerR) require.Nil(t, err) @@ -40,6 +43,18 @@ func TestFlow(t *testing.T) { assert.True(t, verified) } +func TestSmallBlindedMsg(t *testing.T) { + sk, err := NewPrivateKey() + require.Nil(t, err) + k := big.NewInt(1) + smallMsgBlinded := big.NewInt(1) + + // try to BlindSign a small value + _, err = sk.BlindSign(smallMsgBlinded, k) + require.NotNil(t, err) + require.Equal(t, "mBlinded error: invalid length, need 32 bytes", err.Error()) +} + func TestHashMOddBytes(t *testing.T) { // This test is made with same values than // https://github.com/arnaucube/blindsecp256k1-js to ensure @@ -122,7 +137,11 @@ func TestSignatureCompressDecompress(t *testing.T) { require.Nil(t, err) assert.Equal(t, sig, sig2) + // Q = (P+1)/4 + Q := new(big.Int).Div(new(big.Int).Add(P, + big.NewInt(1)), big.NewInt(4)) // nolint:gomnd f = G + sig = &Signature{ S: Q, F: f, diff --git a/go.mod b/go.mod index 0c15b13..7273c65 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,6 @@ module github.com/arnaucube/go-blindsecp256k1 go 1.14 require ( - github.com/btcsuite/btcd v0.0.0-20171128150713-2e60448ffcc6 github.com/ethereum/go-ethereum v1.9.25 github.com/stretchr/testify v1.6.1 ) diff --git a/go.sum b/go.sum index da58442..890264e 100644 --- a/go.sum +++ b/go.sum @@ -80,8 +80,10 @@ github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht github.com/julienschmidt/httprouter v1.1.1-0.20170430222011-975b5c4c7c21/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/karalabe/usb v0.0.0-20190919080040-51dc0efba356/go.mod h1:Od972xHfMJowv7NGVDiWVxk2zxnWgjLlJzE+F4F7AGU= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/mattn/go-colorable v0.1.0/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= @@ -169,6 +171,7 @@ golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200824131525-c12d262b63d8 h1:AvbQYmiaaaza3cW3QXRyPo5kYgpFIzOAfeAAN7m3qQ4= golang.org/x/sys v0.0.0-20200824131525-c12d262b63d8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= @@ -190,6 +193,7 @@ google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzi google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce/go.mod h1:5AcXVHNjg+BDxry382+8OKon8SEWiKktQR07RKPsv1c= diff --git a/v0/blindsecp256k1v0.go b/v0/blindsecp256k1v0.go index bda1558..b3dd883 100644 --- a/v0/blindsecp256k1v0.go +++ b/v0/blindsecp256k1v0.go @@ -11,31 +11,34 @@ package blindsecp256k1v0 import ( "bytes" + "crypto/ecdsa" "crypto/rand" "math/big" "github.com/arnaucube/go-blindsecp256k1" + "github.com/ethereum/go-ethereum/crypto/secp256k1" ) // WIP -func newRand() *big.Int { - var b [32]byte - _, err := rand.Read(b[:]) +func newRand() (*big.Int, error) { + pk, err := ecdsa.GenerateKey(secp256k1.S256(), rand.Reader) if err != nil { - panic(err) + return nil, err } - bi := new(big.Int).SetBytes(b[:]) - return new(big.Int).Mod(bi, blindsecp256k1.N) + return pk.D, nil } // PrivateKey represents the signer's private key type PrivateKey big.Int // NewPrivateKey returns a new random private key -func NewPrivateKey() *PrivateKey { - k := newRand() +func NewPrivateKey() (*PrivateKey, error) { + k, err := newRand() + if err != nil { + return nil, err + } sk := PrivateKey(*k) - return &sk + return &sk, nil } // BigInt returns a *big.Int representation of the PrivateKey @@ -51,14 +54,20 @@ func (sk *PrivateKey) Public() *blindsecp256k1.PublicKey { } // NewRequestParameters returns a new random k (secret) & R (public) parameters -func NewRequestParameters() (*big.Int, *blindsecp256k1.Point) { - k := newRand() - return k, blindsecp256k1.G.Mul(k) // R = kG +func NewRequestParameters() (*big.Int, *blindsecp256k1.Point, error) { + k, err := newRand() + if err != nil { + return nil, nil, err + } + // k, R = kG + return k, blindsecp256k1.G.Mul(k), nil } // BlindSign performs the blind signature on the given mBlinded using // SignerPrivateData values func (sk *PrivateKey) BlindSign(mBlinded *big.Int, k *big.Int) *big.Int { + // WARNING missing checks, this package is not updated, use + // blindsecp256k1 instead // TODO add pending checks // s' = d(m') + k sBlind := new(big.Int).Add( @@ -79,11 +88,21 @@ type UserSecretData struct { // Blind performs the blinding operation on m using SignerPublicData parameters func Blind(m *big.Int, signerPubK *blindsecp256k1.PublicKey, - signerR *blindsecp256k1.Point) (*big.Int, *UserSecretData) { + signerR *blindsecp256k1.Point) (*big.Int, *UserSecretData, error) { + var err error u := &UserSecretData{} - u.A = newRand() - u.B = newRand() - u.C = newRand() + u.A, err = newRand() + if err != nil { + return nil, nil, err + } + u.B, err = newRand() + if err != nil { + return nil, nil, err + } + u.C, err = newRand() + if err != nil { + return nil, nil, err + } binv := new(big.Int).ModInverse(u.B, blindsecp256k1.N) // F = b^-1 R + a b^-1 Q + c G @@ -102,7 +121,7 @@ func Blind(m *big.Int, signerPubK *blindsecp256k1.PublicKey, brm := new(big.Int).Mul(br, m) mBlinded := new(big.Int).Add(brm, u.A) mBlinded = new(big.Int).Mod(mBlinded, blindsecp256k1.N) - return mBlinded, u + return mBlinded, u, nil } // Signature contains the signature values S & F diff --git a/v0/blindsecp256k1v0_test.go b/v0/blindsecp256k1v0_test.go index 20e7e75..9735b06 100644 --- a/v0/blindsecp256k1v0_test.go +++ b/v0/blindsecp256k1v0_test.go @@ -5,20 +5,24 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestFlow(t *testing.T) { // signer: create new signer key pair - sk := NewPrivateKey() + sk, err := NewPrivateKey() + require.Nil(t, err) signerPubK := sk.Public() // signer: when user requests new R parameter to blind a new msg, // create new signerR (public) with its secret k - k, signerR := NewRequestParameters() + k, signerR, err := NewRequestParameters() + require.Nil(t, err) // user: blinds the msg using signer's R msg := new(big.Int).SetBytes([]byte("test")) - msgBlinded, userSecretData := Blind(msg, signerPubK, signerR) + msgBlinded, userSecretData, err := Blind(msg, signerPubK, signerR) + require.Nil(t, err) // signer: signs the blinded message using its private key & secret k sBlind := sk.BlindSign(msgBlinded, k) diff --git a/wasm/blindsecp256k1-wasm.go b/wasm/blindsecp256k1-wasm.go index 92e301f..c8bce8d 100644 --- a/wasm/blindsecp256k1-wasm.go +++ b/wasm/blindsecp256k1-wasm.go @@ -66,7 +66,10 @@ func blindv0(this js.Value, values []js.Value) interface{} { Y: signerRy, } - mBlinded, user := blindsecp256k1v0.Blind(m, signerQ, signerR) + mBlinded, user, err := blindsecp256k1v0.Blind(m, signerQ, signerR) + if err != nil { + panic(err) + } r := make(map[string]interface{}) r["mBlinded"] = mBlinded.String()