Browse Source

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
master
arnaucube 2 years ago
parent
commit
0e9f71e07e
8 changed files with 170 additions and 77 deletions
  1. +34
    -7
      README.md
  2. +63
    -45
      blindsecp256k1.go
  3. +22
    -3
      blindsecp256k1_test.go
  4. +0
    -1
      go.mod
  5. +4
    -0
      go.sum
  6. +36
    -17
      v0/blindsecp256k1v0.go
  7. +7
    -3
      v0/blindsecp256k1v0_test.go
  8. +4
    -1
      wasm/blindsecp256k1-wasm.go

+ 34
- 7
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.

+ 63
- 45
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)

+ 22
- 3
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,

+ 0
- 1
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
)

+ 4
- 0
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=

+ 36
- 17
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

+ 7
- 3
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)

+ 4
- 1
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()

Loading…
Cancel
Save