From f84bacb9ba01060784e09416332addd0e65fd973 Mon Sep 17 00:00:00 2001 From: arnaucube Date: Sat, 13 Nov 2021 18:00:33 +0100 Subject: [PATCH] Abstract curve from impl to use elliptic.Curve interface --- blindsecp256k1.go | 144 +++++++++++++++++++++++------------------ blindsecp256k1_test.go | 68 ++++++++++++++----- parsers_test.go | 21 +++++- 3 files changed, 150 insertions(+), 83 deletions(-) diff --git a/blindsecp256k1.go b/blindsecp256k1.go index 587dffb..7031063 100644 --- a/blindsecp256k1.go +++ b/blindsecp256k1.go @@ -11,11 +11,11 @@ package blindsecp256k1 import ( "bytes" + "crypto/elliptic" "crypto/rand" "fmt" "math/big" - "github.com/btcsuite/btcd/btcec" "github.com/ethereum/go-ethereum/crypto" ) @@ -28,27 +28,13 @@ import ( var ( zero *big.Int = big.NewInt(0) - - // B (from y^2 = x^3 + B) - B *big.Int = btcec.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 - - // G represents the base point of secp256k1 - G *Point = &Point{ - X: btcec.S256().Gx, - Y: btcec.S256().Gy, - } - - // N represents the order of G of secp256k1 - N *big.Int = btcec.S256().N ) +// Curve is a curve wrapper that works with Point structs +type Curve struct { + c elliptic.Curve +} + // Point represents a point on the secp256k1 curve type Point struct { X *big.Int @@ -56,8 +42,8 @@ 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) +func (c Curve) Add(p, q *Point) *Point { + x, y := c.c.Add(p.X, p.Y, q.X, q.Y) return &Point{ X: x, Y: y, @@ -65,17 +51,17 @@ 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()) +func (c Curve) Mul(p *Point, scalar *big.Int) *Point { + x, y := c.c.ScalarMult(p.X, p.Y, scalar.Bytes()) return &Point{ X: x, Y: y, } } -func (p *Point) isValid() error { - if !btcec.S256().IsOnCurve(p.X, p.Y) { - return fmt.Errorf("Point is not on secp256k1") +func (c Curve) isValid(p *Point) error { + if !c.c.IsOnCurve(p.X, p.Y) { + return fmt.Errorf("Point is not on curve %s", c.c.Params().Name) } if bytes.Equal(p.X.Bytes(), zero.Bytes()) && @@ -105,7 +91,7 @@ func isOdd(b *big.Int) bool { // DecompressPoint unpacks a Point from the given byte array of 33 bytes // https://bitcointalk.org/index.php?topic=162805.msg1712294#msg1712294 -func DecompressPoint(b [33]byte) (*Point, error) { +func DecompressPoint(curv elliptic.Curve, b [33]byte) (*Point, error) { x := new(big.Int).SetBytes(b[:32]) var odd bool if b[32] == byte(1) { @@ -113,6 +99,9 @@ func DecompressPoint(b [33]byte) (*Point, error) { } // secp256k1: y2 = x3+ ax2 + b (where A==0, B==7) + params := curv.Params() + B := params.B + P := params.P // compute x^3 + B mod p x3 := new(big.Int).Mul(x, x) @@ -147,14 +136,14 @@ func DecompressPoint(b [33]byte) (*Point, error) { } // WIP -func newRand() *big.Int { +func newRand(curv elliptic.Curve) *big.Int { var b [32]byte _, err := rand.Read(b[:]) if err != nil { panic(err) } bi := new(big.Int).SetBytes(b[:]) - return new(big.Int).Mod(bi, N) + return new(big.Int).Mod(bi, curv.Params().N) } // PrivateKey represents the signer's private key @@ -164,8 +153,8 @@ type PrivateKey big.Int type PublicKey Point // NewPrivateKey returns a new random private key -func NewPrivateKey() *PrivateKey { - k := newRand() +func NewPrivateKey(curv elliptic.Curve) *PrivateKey { + k := newRand(curv) sk := PrivateKey(*k) return &sk } @@ -176,9 +165,16 @@ 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) +func (sk *PrivateKey) Public(curv elliptic.Curve) *PublicKey { + // TODO change impl to use directly X, Y instead + // of Point wrapper. In order to have the impl more close to go interface + c := Curve{curv} + G := &Point{ + X: c.c.Params().Gx, + Y: c.c.Params().Gy, + } + q := c.Mul(G, sk.BigInt()) + pk := PublicKey{X: q.X, Y: q.Y} return &pk } @@ -188,16 +184,24 @@ 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(curv elliptic.Curve) (*big.Int, *Point) { + k := newRand(curv) + G := &Point{ + X: curv.Params().Gx, + Y: curv.Params().Gy, + } + // R = kG + r := Curve{curv}.Mul(G, k) + return k, r } // BlindSign performs the blind signature on the given mBlinded using the // PrivateKey and the secret k values -func (sk *PrivateKey) BlindSign(mBlinded *big.Int, k *big.Int) (*big.Int, error) { +func (sk *PrivateKey) BlindSign(curv elliptic.Curve, mBlinded *big.Int, k *big.Int) (*big.Int, error) { + c := Curve{curv} + n := c.c.Params().N // TODO add pending checks - if mBlinded.Cmp(N) != -1 { + if mBlinded.Cmp(n) != -1 { return nil, fmt.Errorf("mBlinded not inside the finite field") } if bytes.Equal(mBlinded.Bytes(), big.NewInt(0).Bytes()) { @@ -212,7 +216,7 @@ func (sk *PrivateKey) BlindSign(mBlinded *big.Int, k *big.Int) (*big.Int, error) sBlind := new(big.Int).Add( new(big.Int).Mul(sk.BigInt(), mBlinded), k) - sBlind = new(big.Int).Mod(sBlind, N) + sBlind = new(big.Int).Mod(sBlind, n) return sBlind, nil } @@ -226,34 +230,43 @@ type UserSecretData struct { } // Blind performs the blinding operation on m using signerR parameter -func Blind(m *big.Int, signerR *Point) (*big.Int, *UserSecretData, error) { - if err := signerR.isValid(); err != nil { +func Blind(curv elliptic.Curve, m *big.Int, signerR *Point) (*big.Int, *UserSecretData, error) { + c := Curve{curv} + if err := c.isValid(signerR); err != nil { return nil, nil, fmt.Errorf("signerR %s", err) } + // TODO check if curv==signerR.curv + // TODO (once the Point abstraction is removed) check that signerR is + // in the curve + G := &Point{ + X: curv.Params().Gx, + Y: curv.Params().Gy, + } + u := &UserSecretData{} - u.A = newRand() - u.B = newRand() + u.A = newRand(curv) + u.B = newRand(curv) // (R) F = aR' + bG - aR := signerR.Mul(u.A) - bG := G.Mul(u.B) - u.F = aR.Add(bG) + aR := c.Mul(signerR, u.A) + bG := c.Mul(G, u.B) + u.F = c.Add(aR, bG) // TODO check that F != O (point at infinity) - if err := u.F.isValid(); err != nil { + if err := c.isValid(u.F); err != nil { return nil, nil, fmt.Errorf("u.F %s", err) } - rx := new(big.Int).Mod(u.F.X, N) + rx := new(big.Int).Mod(u.F.X, curv.Params().N) // m' = a^-1 rx h(m) - ainv := new(big.Int).ModInverse(u.A, N) + ainv := new(big.Int).ModInverse(u.A, curv.Params().N) ainvrx := new(big.Int).Mul(ainv, rx) hBytes := crypto.Keccak256(m.Bytes()) h := new(big.Int).SetBytes(hBytes) mBlinded := new(big.Int).Mul(ainvrx, h) - mBlinded = new(big.Int).Mod(mBlinded, N) + mBlinded = new(big.Int).Mod(mBlinded, curv.Params().N) return mBlinded, u, nil } @@ -276,11 +289,11 @@ func (s *Signature) Compress() [65]byte { } // DecompressSignature unpacks a Signature from the given byte array of 65 bytes -func DecompressSignature(b [65]byte) (*Signature, error) { +func DecompressSignature(curve elliptic.Curve, b [65]byte) (*Signature, error) { s := new(big.Int).SetBytes(swapEndianness(b[:32])) var fBytes [33]byte copy(fBytes[:], b[32:]) - f, err := DecompressPoint(fBytes) + f, err := DecompressPoint(curve, fBytes) if err != nil { return nil, err } @@ -290,11 +303,11 @@ func DecompressSignature(b [65]byte) (*Signature, error) { // Unblind performs the unblinding operation of the blinded signature for the // given the UserSecretData -func Unblind(sBlind *big.Int, u *UserSecretData) *Signature { +func Unblind(curv elliptic.Curve, sBlind *big.Int, u *UserSecretData) *Signature { // s = a s' + b as := new(big.Int).Mul(u.A, sBlind) s := new(big.Int).Add(as, u.B) - s = new(big.Int).Mod(s, N) + s = new(big.Int).Mod(s, curv.Params().N) return &Signature{ S: s, @@ -303,26 +316,31 @@ func Unblind(sBlind *big.Int, u *UserSecretData) *Signature { } // Verify checks the signature of the message m for the given PublicKey -func Verify(m *big.Int, s *Signature, q *PublicKey) bool { +func Verify(curv elliptic.Curve, m *big.Int, s *Signature, q *PublicKey) bool { // TODO add pending checks - if err := s.F.isValid(); err != nil { + c := Curve{curv} + if err := c.isValid(s.F); err != nil { return false } - if err := q.Point().isValid(); err != nil { + if err := c.isValid(q.Point()); err != nil { return false } - sG := G.Mul(s.S) // sG + G := &Point{ + X: curv.Params().Gx, + Y: curv.Params().Gy, + } + sG := c.Mul(G, s.S) // sG hBytes := crypto.Keccak256(m.Bytes()) h := new(big.Int).SetBytes(hBytes) - rx := new(big.Int).Mod(s.F.X, N) + rx := new(big.Int).Mod(s.F.X, curv.Params().N) rxh := new(big.Int).Mul(rx, h) // rxhG := G.Mul(rxh) // originally the paper uses G - rxhG := q.Point().Mul(rxh) + rxhG := c.Mul(q.Point(), rxh) - right := s.F.Add(rxhG) + right := c.Add(s.F, rxhG) // check sG == R + rx h(m) Q (where R in this code is F) if bytes.Equal(sG.X.Bytes(), right.X.Bytes()) && diff --git a/blindsecp256k1_test.go b/blindsecp256k1_test.go index 49aedef..e0f6289 100644 --- a/blindsecp256k1_test.go +++ b/blindsecp256k1_test.go @@ -5,38 +5,44 @@ import ( "math/big" "testing" + "github.com/btcsuite/btcd/btcec" "github.com/ethereum/go-ethereum/crypto" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) +// TODO abstract the test to work with any curve, and be +// called from a test that testes all the tests with the +// main curves + func TestFlow(t *testing.T) { + curv := btcec.S256() // signer: create new signer key pair - sk := NewPrivateKey() - signerPubK := sk.Public() + sk := NewPrivateKey(curv) + signerPubK := sk.Public(curv) // 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 := NewRequestParameters(curv) // user: blinds the msg using signer's R msg := new(big.Int).SetBytes([]byte("test")) - msgBlinded, userSecretData, err := Blind(msg, signerR) + msgBlinded, userSecretData, err := Blind(curv, msg, signerR) require.Nil(t, err) // signer: signs the blinded message using its private key & secret k - sBlind, err := sk.BlindSign(msgBlinded, k) + sBlind, err := sk.BlindSign(curv, msgBlinded, k) require.Nil(t, err) // user: unblinds the blinded signature - sig := Unblind(sBlind, userSecretData) + sig := Unblind(curv, sBlind, userSecretData) sigB := sig.Bytes() sig2, err := NewSignatureFromBytes(sigB) assert.Nil(t, err) assert.Equal(t, sig, sig2) // signature can be verified with signer PublicKey - verified := Verify(msg, sig, signerPubK) + verified := Verify(curv, msg, sig, signerPubK) assert.True(t, verified) } @@ -87,27 +93,44 @@ func TestHashMOddBytes(t *testing.T) { // } func TestPointCompressDecompress(t *testing.T) { - p := G + curv := btcec.S256() + c := Curve{curv} + + G := &Point{ + X: curv.Gx, + Y: curv.Gy, + } + p := &Point{ + X: curv.Gx, + Y: curv.Gy, + } b := p.Compress() assert.Equal(t, "79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f8179800", hex.EncodeToString(b[:])) - p2, err := DecompressPoint(b) + p2, err := DecompressPoint(curv, b) require.Nil(t, err) assert.Equal(t, p, p2) for i := 2; i < 1000; i++ { - p := G.Mul(big.NewInt(int64(i))) + p := c.Mul(G, big.NewInt(int64(i))) b := p.Compress() assert.Equal(t, 33, len(b)) - p2, err := DecompressPoint(b) + p2, err := DecompressPoint(curv, b) require.Nil(t, err) assert.Equal(t, p, p2) } } func TestSignatureCompressDecompress(t *testing.T) { + curv := btcec.S256() + c := Curve{curv} + + G := &Point{ + X: curv.Gx, + Y: curv.Gy, + } f := G sig := &Signature{ S: big.NewInt(1), @@ -118,11 +141,15 @@ func TestSignatureCompressDecompress(t *testing.T) { "01000000000000000000000000000000000000000000000000000000000000007"+ "9be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f8179800", hex.EncodeToString(b[:])) - sig2, err := DecompressSignature(b) + sig2, err := DecompressSignature(curv, b) require.Nil(t, err) assert.Equal(t, sig, sig2) f = G + P := curv.Params().P + // Q = (P+1)/4 + Q := new(big.Int).Div(new(big.Int).Add(P, + big.NewInt(1)), big.NewInt(4)) // nolint:gomnd sig = &Signature{ S: Q, F: f, @@ -132,13 +159,13 @@ func TestSignatureCompressDecompress(t *testing.T) { "0cffffbfffffffffffffffffffffffffffffffffffffffffffffffffffffff3f7"+ "9be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f8179800", hex.EncodeToString(b[:])) - sig2, err = DecompressSignature(b) + sig2, err = DecompressSignature(curv, b) require.Nil(t, err) require.Equal(t, sig, sig2) for i := 2; i < 10; i++ { s := new(big.Int).Mod(new(big.Int).Mul(Q, big.NewInt(int64(i))), P) - f := G.Mul(big.NewInt(int64(i))) + f := c.Mul(G, big.NewInt(int64(i))) sig := &Signature{ S: s, F: f, @@ -146,19 +173,26 @@ func TestSignatureCompressDecompress(t *testing.T) { b := sig.Compress() assert.Equal(t, 65, len(b)) - sig2, err := DecompressSignature(b) + sig2, err := DecompressSignature(curv, b) require.Nil(t, err) assert.Equal(t, sig, sig2) } } func BenchmarkCompressDecompress(b *testing.B) { + curv := btcec.S256() + c := Curve{curv} + const n = 256 var points [n]*Point var compPoints [n][33]byte + G := &Point{ + X: curv.Gx, + Y: curv.Gy, + } for i := 0; i < n; i++ { - points[i] = G.Mul(big.NewInt(int64(i))) + points[i] = c.Mul(G, big.NewInt(int64(i))) } for i := 0; i < n; i++ { compPoints[i] = points[i].Compress() @@ -171,7 +205,7 @@ func BenchmarkCompressDecompress(b *testing.B) { }) b.Run("DecompressPoint", func(b *testing.B) { for i := 0; i < b.N; i++ { - _, _ = DecompressPoint(compPoints[i%n]) + _, _ = DecompressPoint(curv, compPoints[i%n]) } }) } diff --git a/parsers_test.go b/parsers_test.go index def538c..2493707 100644 --- a/parsers_test.go +++ b/parsers_test.go @@ -6,14 +6,21 @@ import ( "math/big" "testing" + "github.com/btcsuite/btcd/btcec" "github.com/ethereum/go-ethereum/crypto" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestMarshalers(t *testing.T) { + curv := btcec.S256() + G := &Point{ + X: curv.Params().Gx, + Y: curv.Params().Gy, + } + // Point - p := G.Mul(big.NewInt(1234)) + p := Curve{curv}.Mul(G, big.NewInt(1234)) b, err := json.Marshal(p) require.Nil(t, err) assert.Equal(t, @@ -56,6 +63,12 @@ func TestMarshalers(t *testing.T) { } func TestBytes(t *testing.T) { + curv := btcec.S256() + G := &Point{ + X: curv.Params().Gx, + Y: curv.Params().Gy, + } + // Point p := &Point{ X: big.NewInt(3), @@ -67,7 +80,7 @@ func TestBytes(t *testing.T) { assert.Nil(t, err) assert.Equal(t, p, p2) - p = G.Mul(big.NewInt(1234)) + p = Curve{curv}.Mul(G, big.NewInt(1234)) b = p.Bytes() assert.Equal(t, "f258163f65f65865a79a4279e2ebabb5a57b85501dd4b381d1dc605c434876e34c308bd3f18f062d5cc07f34948ced82f9a76f9c3e65ae64f158412da8e92e6d", hex.EncodeToString(b)) //nolint:lll p2, err = NewPointFromBytes(b) @@ -112,6 +125,8 @@ func TestBytes(t *testing.T) { } func TestImportECDSApubKey(t *testing.T) { + curv := btcec.S256() + // Generate an ECDSA key k, err := crypto.GenerateKey() assert.Nil(t, err) @@ -121,5 +136,5 @@ func TestImportECDSApubKey(t *testing.T) { // Set the ECDSA Private key point as a blindsecp256k1 PrivateKey type bk := PrivateKey(*k.D) // Compare both public keys - assert.Equal(t, bk.Public().Bytes(), pk.Bytes()) + assert.Equal(t, bk.Public(curv).Bytes(), pk.Bytes()) }