Consensys/gnark-crypto

Question: is it possible to generate secp256k1 ecdsa Private Key from a secret string?

Teja2045 opened this issue · 3 comments

I want to check if secp256k1 implementation of cosmosSDK is compatable with gnark-crypto. Let me know if I'm missing something

package accounts

import (
	"bytes"
	"crypto/sha256"
	"fmt"

	"github.com/consensys/gnark-crypto/ecc/secp256k1/ecdsa"
	"github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1"
)

func Account() {
	// generate private from a secret phrase
	secret := "This is a very very big secret, dont share it"
	privkey := secp256k1.GenPrivKeyFromSecret([]byte(secret))

	// generate gnark private key from a secret phrase
	buf := bytes.NewBuffer([]byte{})
	buf.Write([]byte(secret))
	gnarkPrivkey, err := ecdsa.GenerateKey(buf)
	if err != nil {
		panic(err)
	}

	// pubkeys
	pubkey := privkey.PubKey()
	gnarkPubkey := gnarkPrivkey.PublicKey

	// msg to sign
	msg := []byte("message")

	sign, err := privkey.Sign(msg)
	if err != nil {
		panic(err)
	}

	// verify with sdk pubkey
	isVerified := pubkey.VerifySignature(msg, sign)
	fmt.Println("normal verification:", isVerified)

	// verify with gnark pubkey
	isVerified, err = gnarkPubkey.Verify(sign, msg, sha256.New())
	if err != nil {
		panic(err)
	}
	fmt.Println("gnark verification:", isVerified)

}

currently the only way in gnark-crypto to generate a private key is through crypto/rand.Reader this follows https://pkg.go.dev/crypto/ecdsa#GenerateKey and FIPS 186-4, Appendix B.5.1. Citing crypto/ecdsa here:

Most applications should use crypto/rand.Reader as rand. Note that the returned key does not depend deterministically on the bytes read from rand, and may change between calls and/or between versions.

To achieve gnark-crypto and cosmos-sdk compatibility I would generate the private key in either one and transform it to the format of the other library.

Something like this works on my machine:

package accounts

import (
	"bytes"
	"crypto/sha256"
	"fmt"
	"math/big"
	"unsafe"

	curve "github.com/consensys/gnark-crypto/ecc/secp256k1"
	"github.com/consensys/gnark-crypto/ecc/secp256k1/ecdsa"
	"github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1"
)

func byte32(s []byte) (a *[32]byte) {
	if len(a) <= len(s) {
		a = (*[len(a)]byte)(unsafe.Pointer(&s[0]))
	}
	return a
}

func Account() {
	// generate private from a secret phrase
	secret := "This is a very very big secret, dont share it"
	privkey := secp256k1.GenPrivKeyFromSecret([]byte(secret))

	// translate cosmos-sdk privKey to gnark privKey
	var gnarkPrivkey ecdsa.PrivateKey
	gnarkPrivkey.Scalar = *byte32(bytes.Clone(privkey.Key))

	// pubkeys
	pubkey := privkey.PubKey()

	var k big.Int
	k.SetBytes(gnarkPrivkey.Scalar[:32])
	fmt.Println(&k)
	_, g := curve.Generators()
	gnarkPrivkey.PublicKey.A.ScalarMultiplication(&g, &k)
	gnarkPubkey := gnarkPrivkey.PublicKey

	// msg to sign
	msg := []byte("message")

	sign, err := privkey.Sign(msg)
	if err != nil {
		panic(err)
	}

	// verify with sdk pubkey
	isVerified := pubkey.VerifySignature(msg, sign)
	fmt.Println("normal verification:", isVerified)

	// verify with gnark pubkey
	isVerified, err = gnarkPubkey.Verify(sign, msg, sha256.New())
	if err != nil {
		panic(err)
	}
	fmt.Println("gnark verification:", isVerified)

}

but we need to expose scalar in

scalar [sizeFr]byte // secret scalar, in big Endian

diff --git a/ecc/secp256k1/ecdsa/ecdsa.go b/ecc/secp256k1/ecdsa/ecdsa.go
index 689bc5003..77efdd352 100644
--- a/ecc/secp256k1/ecdsa/ecdsa.go
+++ b/ecc/secp256k1/ecdsa/ecdsa.go
@@ -59,7 +59,7 @@ type PublicKey struct {
 // PrivateKey represents an ECDSA private key
 type PrivateKey struct {
 	PublicKey PublicKey
-	scalar    [sizeFr]byte // secret scalar, in big Endian
+	Scalar    [sizeFr]byte // secret scalar, in big Endian
 }

 // Signature represents an ECDSA signature
@@ -96,7 +96,7 @@ func GenerateKey(rand io.Reader) (*PrivateKey, error) {
 	_, g := secp256k1.Generators()

 	privateKey := new(PrivateKey)
-	k.FillBytes(privateKey.scalar[:sizeFr])
+	k.FillBytes(privateKey.Scalar[:sizeFr])
 	privateKey.PublicKey.A.ScalarMultiplication(&g, k)
 	return privateKey, nil
 }
@@ -178,7 +178,7 @@ const (
 func nonce(privateKey *PrivateKey, hash []byte) (csprng *cipher.StreamReader, err error) {
 	// This implementation derives the nonce from an AES-CTR CSPRNG keyed by:
 	//
-	//    SHA2-512(privateKey.scalar ∥ entropy ∥ hash)[:32]
+	//    SHA2-512(privateKey.Scalar ∥ entropy ∥ hash)[:32]
 	//
 	// The CSPRNG key is indifferentiable from a random oracle as shown in
 	// [Coron], the AES-CTR stream is indifferentiable from a random oracle
@@ -197,7 +197,7 @@ func nonce(privateKey *PrivateKey, hash []byte) (csprng *cipher.StreamReader, er

 	// Initialize an SHA-512 hash context; digest...
 	md := sha512.New()
-	md.Write(privateKey.scalar[:sizeFr]) // the private key,
+	md.Write(privateKey.Scalar[:sizeFr]) // the private key,
 	md.Write(entropy)                    // the entropy,
 	md.Write(hash)                       // and the input hash;
 	key := md.Sum(nil)[:32]              // and compute ChopMD-256(SHA-512),
@@ -247,7 +247,7 @@ func (privKey *PrivateKey) SignForRecover(message []byte, hFunc hash.Hash) (v ui
 	r, s = new(big.Int), new(big.Int)

 	scalar, kInv := new(big.Int), new(big.Int)
-	scalar.SetBytes(privKey.scalar[:sizeFr])
+	scalar.SetBytes(privKey.Scalar[:sizeFr])
 	for {
 		for {
 			csprng, err := nonce(privKey, message)
diff --git a/ecc/secp256k1/ecdsa/marshal.go b/ecc/secp256k1/ecdsa/marshal.go
index bc2be013c..1341e2576 100644
--- a/ecc/secp256k1/ecdsa/marshal.go
+++ b/ecc/secp256k1/ecdsa/marshal.go
@@ -98,7 +98,7 @@ func (privKey *PrivateKey) Bytes() []byte {
 	var res [sizePrivateKey]byte
 	pubkBin := privKey.PublicKey.A.RawBytes()
 	subtle.ConstantTimeCopy(1, res[:sizePublicKey], pubkBin[:])
-	subtle.ConstantTimeCopy(1, res[sizePublicKey:sizePrivateKey], privKey.scalar[:])
+	subtle.ConstantTimeCopy(1, res[sizePublicKey:sizePrivateKey], privKey.Scalar[:])
 	return res[:]
 }

@@ -116,7 +116,7 @@ func (privKey *PrivateKey) SetBytes(buf []byte) (int, error) {
 		return 0, err
 	}
 	n += sizePublicKey
-	subtle.ConstantTimeCopy(1, privKey.scalar[:], buf[sizePublicKey:sizePrivateKey])
+	subtle.ConstantTimeCopy(1, privKey.Scalar[:], buf[sizePublicKey:sizePrivateKey])
 	n += sizeFr
 	return n, nil
 }
diff --git a/ecc/secp256k1/ecdsa/marshal_test.go b/ecc/secp256k1/ecdsa/marshal_test.go
index a880e6301..7a6b6c776 100644
--- a/ecc/secp256k1/ecdsa/marshal_test.go
+++ b/ecc/secp256k1/ecdsa/marshal_test.go
@@ -55,7 +55,7 @@ func TestSerialization(t *testing.T) {
 				return false
 			}

-			return end.PublicKey.Equal(&privKey.PublicKey) && subtle.ConstantTimeCompare(end.scalar[:], privKey.scalar[:]) == 1
+			return end.PublicKey.Equal(&privKey.PublicKey) && subtle.ConstantTimeCompare(end.Scalar[:], privKey.Scalar[:]) == 1

 		},
 	))

thanks for the reply @yelhousni. It's working.

It would be great if I can check compatibility of sha256 and merkleproofs. Can you cite some examples if possible? thank you
#508