feat: secp256k1 public key constant time (#18026)
Signed-off-by: bizk <santiago.yanzon1999@gmail.com>
This commit is contained in:
+7
-1
@@ -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()}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,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.
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user