Skip to content

Commit

Permalink
feat: implement NIST P-256 and P-384 curves (#697)
Browse files Browse the repository at this point in the history
* refactor: use goldilocks modulus from gnark-crypto

* refactor: use secp256k1 moduli from gnark-crypto

* refactor: move emulation parameters to separate package

* chore: godoc compatible emulation parms docs

* docs: add package documentation for emulation params

* refactor: use type embedding for common params

* docs: expand FieldParams doc

* feat: add P384 emulation params

* feat: add P256 field emulation params

* refactor: rename to emparams

* feat: add P256 and P384 curves

* chore: refer to moduli in curve parameter getter

* fix: wrong precomputation

* feat: merge contribution

commit cbf25b5
Author: SherLzp <lzp.sher@gmail.com>
Date:   Thu Mar 30 13:57:51 2023 +0800

    secp256r1: ecdsa

* refactor: use type alias
  • Loading branch information
ivokub authored May 25, 2023
1 parent c1bddbf commit c617c9f
Show file tree
Hide file tree
Showing 4 changed files with 250 additions and 5 deletions.
51 changes: 46 additions & 5 deletions std/algebra/emulated/sw_emulated/params.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package sw_emulated

import (
"crypto/elliptic"
"math/big"

bls12381 "github.com/consensys/gnark-crypto/ecc/bls12-381"
Expand Down Expand Up @@ -65,16 +66,52 @@ func GetBLS12381Params() CurveParams {
}
}

// GetCurveParams returns suitable curve parameters given the parametric type Base as base field.
// GetP256Params returns the curve parameters for the curve P-256 (also
// SECP256r1). When initialising new curve, use the base field
// [emulated.P256Fp] and scalar field [emulated.P256Fr].
func GetP256Params() CurveParams {
pr := elliptic.P256().Params()
a := new(big.Int).Sub(pr.P, big.NewInt(3))
return CurveParams{
A: a,
B: pr.B,
Gx: pr.Gx,
Gy: pr.Gy,
Gm: computeP256Table(),
}
}

// GetP384Params returns the curve parameters for the curve P-384 (also
// SECP384r1). When initialising new curve, use the base field
// [emulated.P384Fp] and scalar field [emulated.P384Fr].
func GetP384Params() CurveParams {
pr := elliptic.P384().Params()
a := new(big.Int).Sub(pr.P, big.NewInt(3))
return CurveParams{
A: a,
B: pr.B,
Gx: pr.Gx,
Gy: pr.Gy,
Gm: computeP384Table(),
}
}

// GetCurveParams returns suitable curve parameters given the parametric type
// Base as base field. It caches the parameters and modifying the values in the
// parameters struct leads to undefined behaviour.
func GetCurveParams[Base emulated.FieldParams]() CurveParams {
var t Base
switch t.Modulus().Text(16) {
case "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f":
switch t.Modulus().String() {
case emulated.Secp256k1Fp{}.Modulus().String():
return secp256k1Params
case "30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47":
case emulated.BN254Fp{}.Modulus().String():
return bn254Params
case "1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab":
case emulated.BLS12381Fp{}.Modulus().String():
return bls12381Params
case emulated.P256Fp{}.Modulus().String():
return p256Params
case emulated.P384Fp{}.Modulus().String():
return p384Params
default:
panic("no stored parameters")
}
Expand All @@ -84,10 +121,14 @@ var (
secp256k1Params CurveParams
bn254Params CurveParams
bls12381Params CurveParams
p256Params CurveParams
p384Params CurveParams
)

func init() {
secp256k1Params = GetSecp256k1Params()
bn254Params = GetBN254Params()
bls12381Params = GetBLS12381Params()
p256Params = GetP256Params()
p384Params = GetP384Params()
}
45 changes: 45 additions & 0 deletions std/algebra/emulated/sw_emulated/params_compute.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package sw_emulated

import (
"crypto/elliptic"
"math/big"

bls12381 "github.com/consensys/gnark-crypto/ecc/bls12-381"
Expand Down Expand Up @@ -85,3 +86,47 @@ func computeBLS12381Table() [][2]*big.Int {
}
return table
}

func computeP256Table() [][2]*big.Int {
table := make([][2]*big.Int, 256)
p256 := elliptic.P256()
gx, gy := p256.Params().Gx, p256.Params().Gy
tmpx, tmpy := new(big.Int).Set(gx), new(big.Int).Set(gy)
for i := 1; i < 256; i++ {
tmpx, tmpy = p256.Double(tmpx, tmpy)
switch i {
case 1, 2:
xx, yy := p256.Add(tmpx, tmpy, gx, gy)
table[i-1] = [2]*big.Int{xx, yy}
case 3:
xx, yy := p256.Add(tmpx, tmpy, gx, new(big.Int).Sub(p256.Params().P, gy))
table[i-1] = [2]*big.Int{xx, yy}
fallthrough
default:
table[i] = [2]*big.Int{tmpx, tmpy}
}
}
return table
}

func computeP384Table() [][2]*big.Int {
table := make([][2]*big.Int, 384)
p384 := elliptic.P384()
gx, gy := p384.Params().Gx, p384.Params().Gy
tmpx, tmpy := new(big.Int).Set(gx), new(big.Int).Set(gy)
for i := 1; i < 384; i++ {
tmpx, tmpy = p384.Double(tmpx, tmpy)
switch i {
case 1, 2:
xx, yy := p384.Add(tmpx, tmpy, gx, gy)
table[i-1] = [2]*big.Int{xx, yy}
case 3:
xx, yy := p384.Add(tmpx, tmpy, gx, new(big.Int).Sub(p384.Params().P, gy))
table[i-1] = [2]*big.Int{xx, yy}
fallthrough
default:
table[i] = [2]*big.Int{tmpx, tmpy}
}
}
return table
}
48 changes: 48 additions & 0 deletions std/algebra/emulated/sw_emulated/point_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package sw_emulated

import (
"crypto/elliptic"
"crypto/rand"
"math/big"
"testing"

Expand Down Expand Up @@ -569,6 +571,52 @@ func TestScalarMul3(t *testing.T) {
assert.NoError(err)
}

func TestScalarMul4(t *testing.T) {
assert := test.NewAssert(t)
p256 := elliptic.P256()
s, err := rand.Int(rand.Reader, p256.Params().N)
assert.NoError(err)
px, py := p256.ScalarBaseMult(s.Bytes())

circuit := ScalarMulTest[emulated.P256Fp, emulated.P256Fr]{}
witness := ScalarMulTest[emulated.P256Fp, emulated.P256Fr]{
S: emulated.ValueOf[emulated.P256Fr](s),
P: AffinePoint[emulated.P256Fp]{
X: emulated.ValueOf[emulated.P256Fp](p256.Params().Gx),
Y: emulated.ValueOf[emulated.P256Fp](p256.Params().Gy),
},
Q: AffinePoint[emulated.P256Fp]{
X: emulated.ValueOf[emulated.P256Fp](px),
Y: emulated.ValueOf[emulated.P256Fp](py),
},
}
err = test.IsSolved(&circuit, &witness, testCurve.ScalarField())
assert.NoError(err)
}

func TestScalarMul5(t *testing.T) {
assert := test.NewAssert(t)
p384 := elliptic.P384()
s, err := rand.Int(rand.Reader, p384.Params().N)
assert.NoError(err)
px, py := p384.ScalarBaseMult(s.Bytes())

circuit := ScalarMulTest[emulated.P384Fp, emulated.P384Fr]{}
witness := ScalarMulTest[emulated.P384Fp, emulated.P384Fr]{
S: emulated.ValueOf[emulated.P384Fr](s),
P: AffinePoint[emulated.P384Fp]{
X: emulated.ValueOf[emulated.P384Fp](p384.Params().Gx),
Y: emulated.ValueOf[emulated.P384Fp](p384.Params().Gy),
},
Q: AffinePoint[emulated.P384Fp]{
X: emulated.ValueOf[emulated.P384Fp](px),
Y: emulated.ValueOf[emulated.P384Fp](py),
},
}
err = test.IsSolved(&circuit, &witness, testCurve.ScalarField())
assert.NoError(err)
}

type IsOnCurveTest[T, S emulated.FieldParams] struct {
Q AffinePoint[T]
}
Expand Down
111 changes: 111 additions & 0 deletions std/signature/ecdsa/ecdsa_secpr_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
package ecdsa

import (
cryptoecdsa "crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/sha256"
"crypto/sha512"
"math/big"
"testing"

"github.com/consensys/gnark-crypto/ecc"
"github.com/consensys/gnark/std/math/emulated"
"github.com/consensys/gnark/test"
"golang.org/x/crypto/cryptobyte"
"golang.org/x/crypto/cryptobyte/asn1"
)

func TestEcdsaP256PreHashed(t *testing.T) {

// generate parameters
privKey, _ := cryptoecdsa.GenerateKey(elliptic.P256(), rand.Reader)
publicKey := privKey.PublicKey

// sign
msg := []byte("testing ECDSA (pre-hashed)")
msgHash := sha256.Sum256(msg)
sigBin, _ := privKey.Sign(rand.Reader, msgHash[:], nil)

// check that the signature is correct
var (
r, s = &big.Int{}, &big.Int{}
inner cryptobyte.String
)
input := cryptobyte.String(sigBin)
if !input.ReadASN1(&inner, asn1.SEQUENCE) ||
!input.Empty() ||
!inner.ReadASN1Integer(r) ||
!inner.ReadASN1Integer(s) ||
!inner.Empty() {
panic("invalid sig")
}
flag := cryptoecdsa.Verify(&publicKey, msgHash[:], r, s)
if !flag {
t.Errorf("can't verify signature")
}

circuit := EcdsaCircuit[emulated.P256Fp, emulated.P256Fr]{}
witness := EcdsaCircuit[emulated.P256Fp, emulated.P256Fr]{
Sig: Signature[emulated.P256Fr]{
R: emulated.ValueOf[emulated.P256Fr](r),
S: emulated.ValueOf[emulated.P256Fr](s),
},
Msg: emulated.ValueOf[emulated.P256Fr](msgHash[:]),
Pub: PublicKey[emulated.P256Fp, emulated.P256Fr]{
X: emulated.ValueOf[emulated.P256Fp](privKey.PublicKey.X),
Y: emulated.ValueOf[emulated.P256Fp](privKey.PublicKey.Y),
},
}
assert := test.NewAssert(t)
err := test.IsSolved(&circuit, &witness, ecc.BN254.ScalarField())
assert.NoError(err)

}

func TestEcdsaP384PreHashed(t *testing.T) {

// generate parameters
privKey, _ := cryptoecdsa.GenerateKey(elliptic.P384(), rand.Reader)
publicKey := privKey.PublicKey

// sign
msg := []byte("testing ECDSA (pre-hashed)")
msgHash := sha512.Sum384(msg)
sigBin, _ := privKey.Sign(rand.Reader, msgHash[:], nil)

// check that the signature is correct
var (
r, s = &big.Int{}, &big.Int{}
inner cryptobyte.String
)
input := cryptobyte.String(sigBin)
if !input.ReadASN1(&inner, asn1.SEQUENCE) ||
!input.Empty() ||
!inner.ReadASN1Integer(r) ||
!inner.ReadASN1Integer(s) ||
!inner.Empty() {
panic("invalid sig")
}
flag := cryptoecdsa.Verify(&publicKey, msgHash[:], r, s)
if !flag {
t.Errorf("can't verify signature")
}

circuit := EcdsaCircuit[emulated.P384Fp, emulated.P384Fr]{}
witness := EcdsaCircuit[emulated.P384Fp, emulated.P384Fr]{
Sig: Signature[emulated.P384Fr]{
R: emulated.ValueOf[emulated.P384Fr](r),
S: emulated.ValueOf[emulated.P384Fr](s),
},
Msg: emulated.ValueOf[emulated.P384Fr](msgHash[:]),
Pub: PublicKey[emulated.P384Fp, emulated.P384Fr]{
X: emulated.ValueOf[emulated.P384Fp](privKey.PublicKey.X),
Y: emulated.ValueOf[emulated.P384Fp](privKey.PublicKey.Y),
},
}
assert := test.NewAssert(t)
err := test.IsSolved(&circuit, &witness, ecc.BN254.ScalarField())
assert.NoError(err)

}

0 comments on commit c617c9f

Please sign in to comment.