arnaucube
arnaucube/go-blindsecp256k1Public Self-hosted repository
Migrate to geth/secp256k1, add checks

Migrate from btcd/btcec to go-ethereum/crypto/secp256k1
Abstract calls on secp256k1.S256()
Change newRand approach, use ecdsa.GenerateKey underneath
Add check of size of mBlinded & k when blind signing
arnaucube root@arnaucube.com
Mon, 15 Nov 2021 17:25:58 +0100
commit

0e9f71e07e49cb99a0c8699a745784701936f36c

parent

ccaa6313370dddc0923271bd1844baa4750672ba

M README.md → README.md

@@ -1,8 +1,10 @@

# 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

@@ -13,23 +15,22 @@ "github.com/arnaucube/go-blindsecp256k1"

) [...] +// 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)

@@ -37,6 +38,32 @@

// signature can be verified with signer PublicKey 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
M blindsecp256k1.go → 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 @@ // WARNING: WIP code

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 @@ }

// 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 @@ }

// 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) 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 @@ return p, nil

} // 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 @@ // PublicKey represents the signer's public key

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 @@ }

// 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 @@ return (*Point)(pk)

} // 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 @@ }

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 @@ if err := signerR.isValid(); err != nil {

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 @@ h := new(big.Int).SetBytes(hBytes)

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)
M blindsecp256k1_test.go → blindsecp256k1_test.go

@@ -12,15 +12,18 @@ )

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)

@@ -38,6 +41,18 @@

// signature can be verified with signer PublicKey verified := Verify(msg, sig, signerPubK) 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) {

@@ -122,7 +137,11 @@ sig2, err := DecompressSignature(b)

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,
M go.mod → go.mod

@@ -3,7 +3,6 @@

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 )
M go.sum → go.sum

@@ -80,8 +80,10 @@ github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=

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:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=

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:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=

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=
M v0/blindsecp256k1v0.go → v0/blindsecp256k1v0.go

@@ -11,31 +11,34 @@ // WARNING: WIP code

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 @@ return &pk

} // 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 @@ }

// 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 @@ br := new(big.Int).Mul(u.B, r)

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
M v0/blindsecp256k1v0_test.go → v0/blindsecp256k1v0_test.go

@@ -5,20 +5,24 @@ "math/big"

"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)
M wasm/blindsecp256k1-wasm.go → wasm/blindsecp256k1-wasm.go

@@ -66,7 +66,10 @@ X: signerRx,

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()