Browse Source

Abstract curve from impl to use elliptic.Curve interface

feature/abstract-curve
arnaucube 2 years ago
parent
commit
f84bacb9ba
3 changed files with 150 additions and 83 deletions
  1. +81
    -63
      blindsecp256k1.go
  2. +51
    -17
      blindsecp256k1_test.go
  3. +18
    -3
      parsers_test.go

+ 81
- 63
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()) &&

+ 51
- 17
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])
}
})
}

+ 18
- 3
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())
}

Loading…
Cancel
Save