feat: secp256k1 public key constant time (#18026)

Signed-off-by: bizk <santiago.yanzon1999@gmail.com>
This commit is contained in:
Carlos Santiago Yanzon
2023-12-04 19:04:02 +00:00
committed by GitHub
parent d3b30e946d
commit b6f9c705f7
59 changed files with 226 additions and 42 deletions
+7 -1
View File
@@ -2,6 +2,7 @@ package hd
import (
"github.com/cosmos/go-bip39"
"gitlab.com/yawning/secp256k1-voi/secec"
"github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1"
"github.com/cosmos/cosmos-sdk/crypto/types"
@@ -65,6 +66,11 @@ func (s secp256k1Algo) Generate() GenerateFn {
bzArr := make([]byte, secp256k1.PrivKeySize)
copy(bzArr, bz)
return &secp256k1.PrivKey{Key: bzArr}
privKeyObj, err := secec.NewPrivateKey(bz)
if err != nil {
panic(err)
}
return &secp256k1.PrivKey{Key: privKeyObj.Bytes()}
}
}
+1 -1
View File
@@ -11,7 +11,7 @@ import (
func BenchmarkKeyGeneration(b *testing.B) {
b.ReportAllocs()
benchmarkKeygenWrapper := func(reader io.Reader) types.PrivKey {
priv := genPrivKey(reader)
priv := genPrivKey()
return &PrivKey{Key: priv}
}
benchmarking.BenchmarkKeyGeneration(b, benchmarkKeygenWrapper)
@@ -8,11 +8,6 @@
// nolint // this nolint lets us use this file in its original and unmodified form.
package secp256k1
import (
"math/big"
"unsafe"
)
/*
#include "libsecp256k1/include/secp256k1.h"
@@ -22,6 +17,11 @@ extern int secp256k1_ext_scalar_mul(const secp256k1_context* ctx, const unsigned
*/
import "C"
import (
"math/big"
"unsafe"
)
func (BitCurve *BitCurve) ScalarMult(Bx, By *big.Int, scalar []byte) (*big.Int, *big.Int) {
// Ensure scalar is exactly 32 bytes. We pad always, even if
// scalar is 32 bytes long, to avoid a timing side channel.
@@ -24,6 +24,7 @@ func generateKeyPair() (pubkey, privkey []byte) {
if err != nil {
panic(err)
}
pubkey = elliptic.Marshal(S256(), key.X, key.Y) //nolint:staticcheck // crypto will be refactored soon.
privkey = make([]byte, 32)
blob := key.D.Bytes()
+5 -3
View File
@@ -5,12 +5,14 @@ package secp256k1
import (
fmt "fmt"
_ "github.com/cosmos/cosmos-sdk/types/tx/amino"
_ "github.com/cosmos/gogoproto/gogoproto"
proto "github.com/cosmos/gogoproto/proto"
io "io"
math "math"
math_bits "math/bits"
_ "github.com/cosmos/gogoproto/gogoproto"
proto "github.com/cosmos/gogoproto/proto"
_ "github.com/cosmos/cosmos-sdk/types/tx/amino"
)
// Reference imports to suppress errors if they are not otherwise used.
+19 -6
View File
@@ -10,6 +10,7 @@ import (
"github.com/cometbft/cometbft/crypto"
secp256k1dcrd "github.com/decred/dcrd/dcrec/secp256k1/v4"
"gitlab.com/yawning/secp256k1-voi/secec"
"golang.org/x/crypto/ripemd160" //nolint: staticcheck // keep around for backwards compatibility
errorsmod "cosmossdk.io/errors"
@@ -39,10 +40,12 @@ func (privKey *PrivKey) Bytes() []byte {
// PubKey performs the point-scalar multiplication from the privKey on the
// generator point to get the pubkey.
func (privKey *PrivKey) PubKey() cryptotypes.PubKey {
pubkeyObject := secp256k1dcrd.PrivKeyFromBytes(privKey.Key).PubKey()
privateKeyObject, err := secec.NewPrivateKey(privKey.Key)
if err != nil {
panic(err)
}
pk := pubkeyObject.SerializeCompressed()
return &PubKey{Key: pk}
return &PubKey{Key: privateKeyObject.PublicKey().CompressedBytes()}
}
// Equals - you probably don't need to use this.
@@ -85,11 +88,21 @@ func (privKey *PrivKey) UnmarshalAminoJSON(bz []byte) error {
// GenPrivKey generates a new ECDSA private key on curve secp256k1 private key.
// It uses OS randomness to generate the private key.
func GenPrivKey() *PrivKey {
return &PrivKey{Key: genPrivKey(crypto.CReader())}
return &PrivKey{Key: genPrivKey()}
}
// genPrivKey generates a new secp256k1 private key using the provided reader.
func genPrivKey(rand io.Reader) []byte {
// genPrivKey generates a new secp256k1 private key.
func genPrivKey() []byte {
privateKeyObject, err := secec.GenerateKey()
if err != nil {
panic(err)
}
return privateKeyObject.Bytes()
}
// genPrivKeyLegacy generates a new secp256k1 private key using the provided reader.
func genPrivKeyLegacy(rand io.Reader) []byte {
var privKeyBytes [PrivKeySize]byte
d := new(big.Int)
for {
@@ -7,7 +7,6 @@ import (
"testing"
"github.com/magiconair/properties/assert"
"github.com/stretchr/testify/require"
)
@@ -31,11 +31,11 @@ func Test_genPrivKey(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
if tt.shouldPanic {
require.Panics(t, func() {
genPrivKey(bytes.NewReader(tt.notSoRand))
genPrivKeyLegacy(bytes.NewReader(tt.notSoRand))
})
return
}
got := genPrivKey(bytes.NewReader(tt.notSoRand))
got := genPrivKeyLegacy(bytes.NewReader(tt.notSoRand))
fe := new(big.Int).SetBytes(got)
require.True(t, fe.Cmp(secp.S256().N) < 0)
require.True(t, fe.Sign() > 0)
+22 -5
View File
@@ -6,7 +6,7 @@ package secp256k1
import (
"testing"
secp256k1 "github.com/decred/dcrd/dcrec/secp256k1/v4"
secp256k1_dcrd "github.com/decred/dcrd/dcrec/secp256k1/v4"
"github.com/stretchr/testify/require"
)
@@ -19,9 +19,9 @@ func TestSignatureVerificationAndRejectUpperS(t *testing.T) {
priv := GenPrivKey()
sigStr, err := priv.Sign(msg)
require.NoError(t, err)
var r secp256k1.ModNScalar
var r secp256k1_dcrd.ModNScalar
r.SetByteSlice(sigStr[:32])
var s secp256k1.ModNScalar
var s secp256k1_dcrd.ModNScalar
s.SetByteSlice(sigStr[32:64])
require.False(t, s.IsOverHalfOrder())
@@ -29,8 +29,8 @@ func TestSignatureVerificationAndRejectUpperS(t *testing.T) {
require.True(t, pub.VerifySignature(msg, sigStr))
// malleate:
var S256 secp256k1.ModNScalar
S256.SetByteSlice(secp256k1.S256().N.Bytes())
var S256 secp256k1_dcrd.ModNScalar
S256.SetByteSlice(secp256k1_dcrd.S256().N.Bytes())
s.Negate().Add(&S256)
require.True(t, s.IsOverHalfOrder())
@@ -46,3 +46,20 @@ func TestSignatureVerificationAndRejectUpperS(t *testing.T) {
)
}
}
func TestConstantTimePubKeyGeneration(t *testing.T) {
for i := 0; i < 500; i++ {
pk := GenPrivKey().PubKey()
require.NotNil(t, pk)
}
}
// Legacy generation code
func TestNonConstantTimePubKeyGeneration(t *testing.T) {
for i := 0; i < 500; i++ {
privKey := GenPrivKey()
nonConstantTimePk := secp256k1_dcrd.PrivKeyFromBytes(privKey.Key).PubKey().SerializeCompressed() // Legacy functionability from pubkey
pk := &PubKey{Key: nonConstantTimePk}
require.NotNil(t, pk)
}
}
+11
View File
@@ -450,3 +450,14 @@ func TestMarshalAmino_BackwardsCompatibility(t *testing.T) {
})
}
}
func TestLegacyKeyGenerationAgainstConstantTime(t *testing.T) {
privKey := secp256k1.GenPrivKey()
pubKey := privKey.PubKey()
nonConstantTimePk := secp.PrivKeyFromBytes(privKey.Key).PubKey().SerializeCompressed() // Legacy functionability from pubkey
legacyPubKey := &secp256k1.PubKey{Key: nonConstantTimePk}
require.Equal(t, legacyPubKey, pubKey)
}
+5 -4
View File
@@ -8,6 +8,7 @@ import (
secp "github.com/decred/dcrd/dcrec/secp256k1/v4"
"github.com/decred/dcrd/dcrec/secp256k1/v4/ecdsa"
"gitlab.com/yawning/secp256k1-voi/secec"
"github.com/cosmos/cosmos-sdk/crypto/hd"
"github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1"
@@ -320,13 +321,13 @@ func getPubKeyUnsafe(device SECP256K1, path hd.BIP44Params) (types.PubKey, error
}
// re-serialize in the 33-byte compressed format
cmp, err := secp.ParsePubKey(publicKey)
cmp, err := secec.NewPublicKey(publicKey)
if err != nil {
return nil, fmt.Errorf("error parsing public key: %w", err)
}
compressedPublicKey := make([]byte, secp256k1.PubKeySize)
copy(compressedPublicKey, cmp.SerializeCompressed())
copy(compressedPublicKey, cmp.CompressedBytes())
return options.createPubkey(compressedPublicKey), nil
}
@@ -344,13 +345,13 @@ func getPubKeyAddrSafe(device SECP256K1, path hd.BIP44Params, hrp string) (types
}
// re-serialize in the 33-byte compressed format
cmp, err := secp.ParsePubKey(publicKey)
cmp, err := secec.NewPublicKey(publicKey)
if err != nil {
return nil, "", fmt.Errorf("error parsing public key: %w", err)
}
compressedPublicKey := make([]byte, secp256k1.PubKeySize)
copy(compressedPublicKey, cmp.SerializeCompressed())
copy(compressedPublicKey, cmp.CompressedBytes())
return options.createPubkey(compressedPublicKey), addr, nil
}