Skip to content

Commit

Permalink
feat: add ECDSA using emulated SW
Browse files Browse the repository at this point in the history
  • Loading branch information
ivokub committed Nov 15, 2022
1 parent 352a387 commit 0e752ac
Show file tree
Hide file tree
Showing 2 changed files with 196 additions and 0 deletions.
65 changes: 65 additions & 0 deletions std/signature/ecdsa/ecdsa.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/*
Package ecdsa implements ECDSA signature verification over any elliptic curve.
The package depends on the [weierstrass] package for elliptic curve group
operations using non-native arithmetic. Thus we can verify ECDSA signatures over
any curve. The cost for a single secp256k1 signature verification is
approximately 4M constraints in R1CS and 10M constraints in PLONKish.
See [ECDSA] for the signature verification algorithm.
[ECDSA]:
https://en.wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm
*/
package ecdsa

import (
"github.com/consensys/gnark/frontend"
"github.com/consensys/gnark/std/algebra/weierstrass"
"github.com/consensys/gnark/std/math/emulated"
)

// Signature represents the signature for some message.
type Signature[Scalar emulated.FieldParams] struct {
R, S emulated.Element[Scalar]
}

// PublicKey represents the public key to verify the signature for.
type PublicKey[Base, Scalar emulated.FieldParams] weierstrass.AffinePoint[Base]

// Verify asserts that the signature sig verifies for the message msg and public
// key pk. The curve parameters params define the elliptic curve.
//
// We assume that the message msg is already hashed to the scalar field.
func (pk PublicKey[T, S]) Verify(api frontend.API, params weierstrass.CurveParams, msg *emulated.Element[S], sig *Signature[S]) {
cr, err := weierstrass.New[T, S](api, params)
if err != nil {
// TODO: softer handling.
panic(err)
}
scalarApi, err := emulated.NewField[S](api)
if err != nil {
panic(err)
}
baseApi, err := emulated.NewField[T](api)
if err != nil {
panic(err)
}
pkpt := weierstrass.AffinePoint[T](pk)
sInv := scalarApi.Inverse(&sig.S)
msInv := scalarApi.MulMod(msg, sInv)
rsInv := scalarApi.MulMod(&sig.R, sInv)

qa := cr.ScalarMul(cr.Generator(), msInv)
qb := cr.ScalarMul(&pkpt, rsInv)
q := cr.Add(qa, qb)
qx := baseApi.Reduce(&q.X)
qxBits := baseApi.ToBits(qx)
rbits := scalarApi.ToBits(&sig.R)
if len(rbits) != len(qxBits) {
panic("non-equal lengths")
}
for i := range rbits {
api.AssertIsEqual(rbits[i], qxBits[i])
}
}
131 changes: 131 additions & 0 deletions std/signature/ecdsa/ecdsa_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
package ecdsa

import (
"math/big"
"testing"

"github.com/consensys/gnark-crypto/ecc"
"github.com/consensys/gnark/frontend"
"github.com/consensys/gnark/std/algebra/weierstrass"
"github.com/consensys/gnark/std/math/emulated"
"github.com/consensys/gnark/test"
"github.com/ethereum/go-ethereum/crypto"
)

var testPrivHex = "289c2857d4598e37fb9647507e47a309d6133539bf21a8b9cb6df88fd5232032"

func sign(t *testing.T) ([]byte, []byte, error) {
t.Helper()
key, _ := crypto.HexToECDSA(testPrivHex)
msg := crypto.Keccak256([]byte("foo"))
sig, err := crypto.Sign(msg, key)
if err != nil {
t.Errorf("Sign error: %s", err)
}
return sig, msg, nil
}

type EcdsaCircuit[T, S emulated.FieldParams] struct {
Sig Signature[S]
Msg emulated.Element[S]
Pub PublicKey[T, S]
}

func (c *EcdsaCircuit[T, S]) Define(api frontend.API) error {
c.Pub.Verify(api, weierstrass.GetCurveParams[T](), &c.Msg, &c.Sig)
return nil
}

func TestEcdsa(t *testing.T) {
// generate a valid signature
sig, msg, err := sign(t)
if err != nil {
t.Fatal(err)
}

// check that the signature is correct
pub, err := crypto.Ecrecover(msg, sig)
if err != nil {
t.Fatal(err)
}
sig = sig[:len(sig)-1]
if !crypto.VerifySignature(pub, msg, sig) {
t.Errorf("can't verify signature with uncompressed key")
}

r := new(big.Int).SetBytes(sig[:32])
s := new(big.Int).SetBytes(sig[32:])
m := new(big.Int).SetBytes(msg)

_pub, err := crypto.UnmarshalPubkey(pub)
if err != nil {
t.Fatal(err)
}

circuit := EcdsaCircuit[emulated.Secp256k1Fp, emulated.Secp256k1Fr]{}
witness := EcdsaCircuit[emulated.Secp256k1Fp, emulated.Secp256k1Fr]{
Sig: Signature[emulated.Secp256k1Fr]{
R: emulated.NewElement[emulated.Secp256k1Fr](r),
S: emulated.NewElement[emulated.Secp256k1Fr](s),
},
Msg: emulated.NewElement[emulated.Secp256k1Fr](m),
Pub: PublicKey[emulated.Secp256k1Fp, emulated.Secp256k1Fr]{
X: emulated.NewElement[emulated.Secp256k1Fp](_pub.X),
Y: emulated.NewElement[emulated.Secp256k1Fp](_pub.Y),
},
}
assert := test.NewAssert(t)
err = test.IsSolved(&circuit, &witness, ecc.BN254.ScalarField())
assert.NoError(err)
// _, err = frontend.Compile(ecc.BN254.ScalarField(), r1cs.NewBuilder, &circuit)
// assert.NoError(err)
}

// Example how to verify the signature inside the circuit.
func ExamplePublicKey_Verify() {
api := frontend.API(nil) // provider by the builder
r, s := 0x01, 0x02 // usually given in the witness
pubx, puby := 0x03, 0x04 // usually given in the witness
m := 0x1337 // usually given in the witness

// can be done in or out-circuit.
Sig := Signature[emulated.Secp256k1Fr]{
R: emulated.NewElement[emulated.Secp256k1Fr](r),
S: emulated.NewElement[emulated.Secp256k1Fr](s),
}
Msg := emulated.NewElement[emulated.Secp256k1Fr](m)
Pub := PublicKey[emulated.Secp256k1Fp, emulated.Secp256k1Fr]{
X: emulated.NewElement[emulated.Secp256k1Fp](pubx),
Y: emulated.NewElement[emulated.Secp256k1Fp](puby),
}
// signature verification assertion is done in-circuit
Pub.Verify(api, weierstrass.GetCurveParams[emulated.Secp256k1Fp](), &Msg, &Sig)
}

// Example how to create a valid signature for secp256k1
func ExamplePublicKey_Verify_create() {
testPrivHex := "289c2857d4598e37fb9647507e47a309d6133539bf21a8b9cb6df88fd5232032"
key, _ := crypto.HexToECDSA(testPrivHex)
msg := crypto.Keccak256([]byte("foo"))
sig, err := crypto.Sign(msg, key)
if err != nil {
panic("sign")
}
_pub, err := crypto.Ecrecover(msg, sig)
if err != nil {
panic("ecrecover")
}
sig = sig[:len(sig)-1]

pub, err := crypto.UnmarshalPubkey(_pub)
if err != nil {
panic("unmarshal")
}
r := new(big.Int).SetBytes(sig[:32])
s := new(big.Int).SetBytes(sig[32:])
m := new(big.Int).SetBytes(msg)
pubx := pub.X
puby := pub.Y
// can continue in the PublicKey Verify example
_, _, _, _, _ = r, s, m, pubx, puby
}

0 comments on commit 0e752ac

Please sign in to comment.