Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add ECDSA signature verification #372

Merged
merged 4 commits into from
Jan 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ require (
github.com/blang/semver/v4 v4.0.0
github.com/consensys/bavard v0.1.13
github.com/consensys/gnark-crypto v0.8.1-0.20221220191316-4b7364bddab8
github.com/ethereum/go-ethereum v1.10.26
github.com/fxamacker/cbor/v2 v2.2.0
github.com/google/go-cmp v0.5.8
github.com/google/pprof v0.0.0-20220729232143-a41b82acbcb1
Expand All @@ -16,7 +17,9 @@ require (
)

require (
github.com/btcsuite/btcd/btcec/v2 v2.2.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect
github.com/kr/pretty v0.3.0 // indirect
github.com/mmcloughlin/addchain v0.4.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
Expand Down
11 changes: 9 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,16 +1,23 @@
github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM=
github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ=
github.com/btcsuite/btcd/btcec/v2 v2.2.0 h1:fzn1qaOt32TuLjFlkzYSsBC35Q3KUjT1SwPxiMSCF5k=
github.com/btcsuite/btcd/btcec/v2 v2.2.0/go.mod h1:U7MHm051Al6XmscBQ0BoNydpOTsFAn707034b5nY8zU=
github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 h1:q0rUy8C/TYNBQS1+CGKw68tLOFYSNEs0TFnxxnS9+4U=
github.com/consensys/bavard v0.1.13 h1:oLhMLOFGTLdlda/kma4VOJazblc7IM5y5QPd2A/YjhQ=
github.com/consensys/bavard v0.1.13/go.mod h1:9ItSMtA/dXMAiL7BG6bqW2m3NdSEObYWoH223nGHukI=
github.com/consensys/gnark-crypto v0.8.1-0.20221205155504-6b860ba21fbd h1:xpAhzOw3dZvRiQeTWmIO8KemBS5XdBsU+/jLfwibEmc=
github.com/consensys/gnark-crypto v0.8.1-0.20221205155504-6b860ba21fbd/go.mod h1:CkbdF9hbRidRJYMRzmfX8TMOr95I2pYXRHF18MzRrvA=
github.com/consensys/gnark-crypto v0.8.1-0.20221220191316-4b7364bddab8 h1:Ij6UQpKx4/Ox6L6qFPk8NhEnTsYCEXlILnh+1Hi1grY=
github.com/consensys/gnark-crypto v0.8.1-0.20221220191316-4b7364bddab8/go.mod h1:CkbdF9hbRidRJYMRzmfX8TMOr95I2pYXRHF18MzRrvA=
github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0=
github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 h1:YLtO71vCjJRCBcrPMtQ9nqBsqpA1m5sE92cU+pd5Mcc=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs=
github.com/ethereum/go-ethereum v1.10.26 h1:i/7d9RBBwiXCEuyduBQzJw/mKmnvzsN14jqBmytw72s=
github.com/ethereum/go-ethereum v1.10.26/go.mod h1:EYFyF19u3ezGLD4RqOkLq+ZCXzYbLoNDdZlMt7kyKFg=
github.com/fxamacker/cbor/v2 v2.2.0 h1:6eXqdDDe588rSYAi1HfZKbx6YYQO4mxQ9eC6xYpU/JQ=
github.com/fxamacker/cbor/v2 v2.2.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
Expand Down
33 changes: 33 additions & 0 deletions std/algebra/weierstrass/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
Package weierstrass implements elliptic curve group operations in (short)
Weierstrass form.

The elliptic curve is the set of points (X,Y) satisfying the equation:

Y² = X³ + aX + b

over some base field 𝐅p for some constants a, b ∈ 𝐅p.
Additionally, for every curve we also define its generator (base point) G. All
these parameters are stored in the variable of type [CurveParams].

The package provides a few curve parameters, see functions [GetSecp256k1Params]
and [GetBN254Params].

Unconventionally, this package uses type parameters to define the base field of
the points and variables to define the coefficients of the curve. This is due to
how the emulated elements are constructed by their type parameters. To unify the
different conventions, we provide the method [GetCurveParams] to allow resolving
a particular curve parameter depending on the type parameter defining the base
field. For now, we only have a single curve defined on every base field, but
this may change in the future with the addition of additional curves.

This package uses field emulation (unlike packages
[github.com/consensys/gnark/std/algebra/sw_bls12377] and
[github.com/consensys/gnark/std/algebra/sw_bls24315], which use 2-chains). This
allows to use any curve over any native (SNARK) field. The drawback of this
approach is the extreme cost of the operations. In R1CS, point addition on
256-bit fields is approximately 3500 constraints and doubling is approximately
4300 constraints. A full scalar multiplication is approximately 2M constraints.
It is several times more in PLONKish aritmetisation.
*/
package weierstrass
84 changes: 84 additions & 0 deletions std/algebra/weierstrass/doc_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package weierstrass_test

import (
"fmt"
"math/big"

"github.com/consensys/gnark-crypto/ecc"
"github.com/consensys/gnark/backend/groth16"
"github.com/consensys/gnark/frontend"
"github.com/consensys/gnark/frontend/cs/r1cs"
"github.com/consensys/gnark/std/algebra/weierstrass"
"github.com/consensys/gnark/std/math/emulated"
"github.com/ethereum/go-ethereum/crypto/secp256k1"
)

type ExampleCurveCircuit[Base, Scalar emulated.FieldParams] struct {
Res weierstrass.AffinePoint[Base]
}

func (c *ExampleCurveCircuit[B, S]) Define(api frontend.API) error {
curve, err := weierstrass.New[B, S](api, weierstrass.GetCurveParams[emulated.BN254Fp]())
if err != nil {
panic("initalize new curve")
}
G := curve.Generator()
scalar4 := emulated.NewElement[S](4)
g4 := curve.ScalarMul(G, &scalar4) // 4*G
scalar5 := emulated.NewElement[S](5)
g5 := curve.ScalarMul(G, &scalar5) // 5*G
g9 := curve.Add(g4, g5) // 9*G
curve.AssertIsEqual(g9, &c.Res)
return nil
}

func ExampleCurve() {
secpCurve := secp256k1.S256()
s := big.NewInt(9)
sx, sy := secpCurve.ScalarMult(secpCurve.Gx, secpCurve.Gy, s.Bytes())
fmt.Printf("result (%d, %d)", sx, sy)

circuit := ExampleCurveCircuit[emulated.Secp256k1Fp, emulated.Secp256k1Fr]{}
witness := ExampleCurveCircuit[emulated.Secp256k1Fp, emulated.Secp256k1Fr]{
Res: weierstrass.AffinePoint[emulated.Secp256k1Fp]{
X: emulated.NewElement[emulated.Secp256k1Fp](secpCurve.Gx),
Y: emulated.NewElement[emulated.Secp256k1Fp](secpCurve.Gy),
},
}
ccs, err := frontend.Compile(ecc.BN254.ScalarField(), r1cs.NewBuilder, &circuit)
if err != nil {
panic(err)
} else {
fmt.Println("compiled")
}
pk, vk, err := groth16.Setup(ccs)
if err != nil {
panic(err)
} else {
fmt.Println("setup done")
}
secretWitness, err := frontend.NewWitness(&witness, ecc.BN254.ScalarField())
if err != nil {
panic(err)
} else {
fmt.Println("secret witness")
}
publicWitness, err := secretWitness.Public()
if err != nil {
panic(err)
} else {
fmt.Println("public witness")
}
proof, err := groth16.Prove(ccs, pk, secretWitness)
if err != nil {
panic(err)
} else {
fmt.Println("proof")
}
err = groth16.Verify(proof, vk, publicWitness)
if err != nil {
panic(err)
} else {
fmt.Println("verify")
}
}
71 changes: 71 additions & 0 deletions std/algebra/weierstrass/params.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package weierstrass

import (
"math/big"

"github.com/consensys/gnark/std/math/emulated"
)

// CurveParams defines parameters of an elliptic curve in short Weierstrass form
// given by the equation
//
// Y² = X³ + aX + b
//
// The base point is defined by (Gx, Gy).
type CurveParams struct {
A *big.Int // a in curve equation
B *big.Int // b in curve equation
Gx *big.Int // base point x
Gy *big.Int // base point y
}

// GetSecp256k1Params returns curve parameters for the curve secp256k1. When
// initialising new curve, use the base field [emulated.Secp256k1Fp] and scalar
// field [emulated.Secp256k1Fr].
func GetSecp256k1Params() CurveParams {
gx, _ := new(big.Int).SetString("79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", 16)
gy, _ := new(big.Int).SetString("483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8", 16)
return CurveParams{
A: big.NewInt(0),
B: big.NewInt(7),
Gx: gx,
Gy: gy,
}
}

// GetBN254Params returns the curve parameters for the curve BN254 (alt_bn128).
// When initialising new curve, use the base field [emulated.BN254Fp] and scalar
// field [emulated.BN254Fr].
func GetBN254Params() CurveParams {
gx := big.NewInt(1)
gy := big.NewInt(2)
return CurveParams{
A: big.NewInt(0),
B: big.NewInt(3),
Gx: gx,
Gy: gy,
}
}

// GetCurveParams returns suitable curve parameters given the parametric type Base as base field.
func GetCurveParams[Base emulated.FieldParams]() CurveParams {
var t Base
switch t.Modulus().Text(16) {
case "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f":
return secp256k1Params
case "30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47":
return bn254Params
default:
panic("no stored parameters")
}
}

var (
secp256k1Params CurveParams
bn254Params CurveParams
)

func init() {
secp256k1Params = GetSecp256k1Params()
bn254Params = GetBN254Params()
}
163 changes: 163 additions & 0 deletions std/algebra/weierstrass/point.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
package weierstrass

import (
"fmt"
"math/big"

"github.com/consensys/gnark/frontend"
"github.com/consensys/gnark/std/math/emulated"
)

// New returns a new [Curve] instance over the base field Base and scalar field
// Scalars defined by the curve parameters params. It returns an error if
// initialising the field emulation fails (for example, when the native field is
// too small) or when the curve parameters are incompatible with the fields.
func New[Base, Scalars emulated.FieldParams](api frontend.API, params CurveParams) (*Curve[Base, Scalars], error) {
ba, err := emulated.NewField[Base](api)
if err != nil {
return nil, fmt.Errorf("new base api: %w", err)
}
sa, err := emulated.NewField[Scalars](api)
if err != nil {
return nil, fmt.Errorf("new scalar api: %w", err)
}
Gx := emulated.NewElement[Base](params.Gx)
Gy := emulated.NewElement[Base](params.Gy)
return &Curve[Base, Scalars]{
params: params,
api: api,
baseApi: ba,
scalarApi: sa,
g: AffinePoint[Base]{
X: Gx,
Y: Gy,
},
a: emulated.NewElement[Base](params.A),
addA: params.A.Cmp(big.NewInt(0)) != 0,
}, nil
}

// Curve is an initialised curve which allows performing group operations.
type Curve[Base, Scalars emulated.FieldParams] struct {
// params is the parameters of the curve
params CurveParams
// api is the native api, we construct it ourselves to be sure
api frontend.API
// baseApi is the api for point operations
baseApi *emulated.Field[Base]
// scalarApi is the api for scalar operations
scalarApi *emulated.Field[Scalars]

// g is the generator (base point) of the curve.
g AffinePoint[Base]

a emulated.Element[Base]
addA bool
}

// Generator returns the base point of the curve. The method does not copy and
// modifying the returned element leads to undefined behaviour!
func (c *Curve[B, S]) Generator() *AffinePoint[B] {
return &c.g
}

// AffinePoint represents a point on the elliptic curve. We do not check that
// the point is actually on the curve.
type AffinePoint[Base emulated.FieldParams] struct {
X, Y emulated.Element[Base]
}

// Neg returns an inverse of p. It doesn't modify p.
func (c *Curve[B, S]) Neg(p *AffinePoint[B]) *AffinePoint[B] {
return &AffinePoint[B]{
X: p.X,
Y: *c.baseApi.Neg(&p.Y),
}
}

// AssertIsEqual asserts that p and q are the same point.
func (c *Curve[B, S]) AssertIsEqual(p, q *AffinePoint[B]) {
c.baseApi.AssertIsEqual(&p.X, &q.X)
c.baseApi.AssertIsEqual(&p.Y, &q.Y)
}

// Add adds q and r and returns it.
func (c *Curve[B, S]) Add(q, r *AffinePoint[B]) *AffinePoint[B] {
// compute lambda = (p1.y-p.y)/(p1.x-p.x)
p1ypy := c.baseApi.Sub(&r.Y, &q.Y)
p1xpx := c.baseApi.Sub(&r.X, &q.X)
lambda := c.baseApi.Div(p1ypy, p1xpx)

// xr = lambda**2-p.x-p1.x
lambdaSq := c.baseApi.MulMod(lambda, lambda)
qxrx := c.baseApi.Add(&q.X, &r.X)
xr := c.baseApi.Sub(lambdaSq, qxrx)

// p.y = lambda(p.x-xr) - p.y
pxxr := c.baseApi.Sub(&q.X, xr)
lpxxr := c.baseApi.MulMod(lambda, pxxr)
py := c.baseApi.Sub(lpxxr, &q.Y)

return &AffinePoint[B]{
X: *c.baseApi.Reduce(xr),
Y: *c.baseApi.Reduce(py),
}
}

// Double doubles p and return it. It doesn't modify p.
func (c *Curve[B, S]) Double(p *AffinePoint[B]) *AffinePoint[B] {

// compute lambda = (3*p1.x**2+a)/2*p1.y, here we assume a=0 (j invariant 0 curve)
xSq3a := c.baseApi.MulMod(&p.X, &p.X)
xSq3a = c.baseApi.MulConst(xSq3a, big.NewInt(3))
if c.addA {
xSq3a = c.baseApi.Add(xSq3a, &c.a)
}
y2 := c.baseApi.MulConst(&p.Y, big.NewInt(2))
lambda := c.baseApi.Div(xSq3a, y2)

// xr = lambda**2-p1.x-p1.x
x2 := c.baseApi.MulConst(&p.X, big.NewInt(2))
lambdaSq := c.baseApi.MulMod(lambda, lambda)
xr := c.baseApi.Sub(lambdaSq, x2)

// p.y = lambda(p.x-xr) - p.y
pxxr := c.baseApi.Sub(&p.X, xr)
lpxxr := c.baseApi.MulMod(lambda, pxxr)
py := c.baseApi.Sub(lpxxr, &p.Y)

return &AffinePoint[B]{
X: *c.baseApi.Reduce(xr),
Y: *c.baseApi.Reduce(py),
}
}

// Select selects between p and q given the selector b. If b == 0, then returns
// p and q otherwise.
func (c *Curve[B, S]) Select(b frontend.Variable, p, q *AffinePoint[B]) *AffinePoint[B] {
x := c.baseApi.Select(b, &p.X, &q.X)
y := c.baseApi.Select(b, &p.Y, &q.Y)
return &AffinePoint[B]{
X: *x,
Y: *y,
}
}

// ScalarMul computes s * p and returns it. It doesn't modify p nor s.
func (c *Curve[B, S]) ScalarMul(p *AffinePoint[B], s *emulated.Element[S]) *AffinePoint[B] {
res := p
acc := c.Double(p)

var st S
sr := c.scalarApi.Reduce(s)
sBits := c.scalarApi.ToBits(sr)
for i := 1; i < st.Modulus().BitLen(); i++ {
tmp := c.Add(res, acc)
res = c.Select(sBits[i], tmp, res)
acc = c.Double(acc)
}

tmp := c.Add(res, c.Neg(p))
res = c.Select(sBits[0], res, tmp)
return res
}
Loading