-
Notifications
You must be signed in to change notification settings - Fork 3.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
crypto: add secp256r1 #8559
crypto: add secp256r1 #8559
Changes from 77 commits
78f7d46
d9d677b
5eb4e97
90a0a39
8394e39
48b584d
d842642
0c2a983
d4a1d02
9263dbd
0fe7b12
32710db
98cdf6d
956edf0
5f25b56
6f0acf4
abd614a
964bf10
679afbe
bba458f
fb4a512
8d22e18
baa1192
c99dce5
de2e521
3bb0700
4abcd8f
06aba10
2c80e9c
73ce143
95d9b7f
9e66ef6
ab780db
0ac339f
8d08d8b
0434d8d
6a8ada7
c414ca0
7d1f694
e122dce
1e8e455
c91e429
fcdbb1a
0593e41
567f41a
8f2108b
6dc666f
97677a4
c49e66f
45784bf
bd99f51
641b170
ba95b2f
2fee315
4f2a5cc
314dbf2
53a44a6
9d28034
be6a226
c1fd439
a119d7c
68ebb56
784eb5a
057c131
5f5e9a8
1f3c36c
6d0aaf4
8522668
e970f52
01f2b4f
db18ea9
38f7cf3
4a93f10
15b866a
c9cbbf0
52c0f90
1ceba68
3f3f8f9
594c186
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
// Package ECDSA implements Cosmos-SDK compatible ECDSA public and private key. The keys | ||
// can be serialized. | ||
package ecdsa |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
package ecdsa | ||
|
||
import ( | ||
"crypto/ecdsa" | ||
"crypto/elliptic" | ||
"crypto/rand" | ||
"crypto/sha256" | ||
"fmt" | ||
"math/big" | ||
) | ||
|
||
// GenPrivKey generates a new secp256r1 private key. It uses operating system randomness. | ||
func GenPrivKey(curve elliptic.Curve) (PrivKey, error) { | ||
key, err := ecdsa.GenerateKey(curve, rand.Reader) | ||
if err != nil { | ||
return PrivKey{}, err | ||
} | ||
return PrivKey{*key}, nil | ||
} | ||
|
||
type PrivKey struct { | ||
ecdsa.PrivateKey | ||
} | ||
|
||
// PubKey returns ECDSA public key associated with this private key. | ||
func (sk *PrivKey) PubKey() PubKey { | ||
return PubKey{sk.PublicKey, nil} | ||
} | ||
|
||
// Bytes serialize the private key using big-endian. | ||
func (sk *PrivKey) Bytes() []byte { | ||
if sk == nil { | ||
return nil | ||
} | ||
fieldSize := (sk.Curve.Params().BitSize + 7) / 8 | ||
bz := make([]byte, fieldSize) | ||
sk.D.FillBytes(bz) | ||
return bz | ||
} | ||
|
||
// Sign hashes and signs the message usign ECDSA. Implements SDK PrivKey interface. | ||
func (sk *PrivKey) Sign(msg []byte) ([]byte, error) { | ||
digest := sha256.Sum256(msg) | ||
amaury1093 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return sk.PrivateKey.Sign(rand.Reader, digest[:], nil) | ||
} | ||
|
||
// String returns a string representation of the public key based on the curveName. | ||
func (sk *PrivKey) String(name string) string { | ||
return name + "{-}" | ||
} | ||
|
||
// MarshalTo implements proto.Marshaler interface. | ||
func (sk *PrivKey) MarshalTo(dAtA []byte) (int, error) { | ||
bz := sk.Bytes() | ||
copy(dAtA, bz) | ||
return len(bz), nil | ||
} | ||
|
||
// Unmarshal implements proto.Marshaler interface. | ||
func (sk *PrivKey) Unmarshal(bz []byte, curve elliptic.Curve, expectedSize int) error { | ||
if len(bz) != expectedSize { | ||
return fmt.Errorf("wrong ECDSA SK bytes, expecting %d bytes", expectedSize) | ||
} | ||
|
||
sk.Curve = curve | ||
sk.D = new(big.Int).SetBytes(bz) | ||
sk.X, sk.Y = curve.ScalarBaseMult(bz) | ||
return nil | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
package ecdsa | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/tendermint/tendermint/crypto" | ||
|
||
"github.com/stretchr/testify/suite" | ||
) | ||
|
||
func TestSKSuite(t *testing.T) { | ||
suite.Run(t, new(SKSuite)) | ||
} | ||
|
||
type SKSuite struct{ CommonSuite } | ||
|
||
func (suite *SKSuite) TestString() { | ||
const prefix = "abc" | ||
suite.Require().Equal(prefix+"{-}", suite.sk.String(prefix)) | ||
} | ||
|
||
func (suite *SKSuite) TestPubKey() { | ||
pk := suite.sk.PubKey() | ||
suite.True(suite.sk.PublicKey.Equal(&pk.PublicKey)) | ||
} | ||
|
||
func (suite *SKSuite) Bytes() { | ||
bz := suite.sk.Bytes() | ||
suite.Len(bz, 32) | ||
var sk *PrivKey | ||
suite.Nil(sk.Bytes()) | ||
} | ||
|
||
func (suite *SKSuite) TestMarshal() { | ||
require := suite.Require() | ||
const size = 32 | ||
|
||
var buffer = make([]byte, size) | ||
suite.sk.MarshalTo(buffer) | ||
|
||
var sk = new(PrivKey) | ||
err := sk.Unmarshal(buffer, secp256r1, size) | ||
require.NoError(err) | ||
require.True(sk.Equal(&suite.sk.PrivateKey)) | ||
} | ||
|
||
func (suite *SKSuite) TestSign() { | ||
require := suite.Require() | ||
|
||
msg := crypto.CRandBytes(1000) | ||
sig, err := suite.sk.Sign(msg) | ||
require.NoError(err) | ||
sigCpy := make([]byte, len(sig)) | ||
copy(sigCpy, sig) | ||
require.True(suite.pk.VerifySignature(msg, sigCpy)) | ||
|
||
// Mutate the signature | ||
for i := range sig { | ||
sigCpy[i] ^= byte(i + 1) | ||
require.False(suite.pk.VerifySignature(msg, sigCpy)) | ||
} | ||
|
||
// Mutate the message | ||
msg[1] ^= byte(2) | ||
require.False(suite.pk.VerifySignature(msg, sig)) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
package ecdsa | ||
|
||
import ( | ||
"crypto/ecdsa" | ||
"crypto/elliptic" | ||
"crypto/sha256" | ||
"encoding/asn1" | ||
"fmt" | ||
"math/big" | ||
|
||
tmcrypto "github.com/tendermint/tendermint/crypto" | ||
|
||
"github.com/cosmos/cosmos-sdk/types/address" | ||
"github.com/cosmos/cosmos-sdk/types/errors" | ||
) | ||
|
||
// signature holds the r and s values of an ECDSA signature. | ||
type signature struct { | ||
R, S *big.Int | ||
} | ||
|
||
type PubKey struct { | ||
ecdsa.PublicKey | ||
|
||
// cache | ||
address tmcrypto.Address | ||
} | ||
|
||
// Address creates an ADR-28 address for ECDSA keys. protoName is a concrete proto structure id. | ||
func (pk *PubKey) Address(protoName string) tmcrypto.Address { | ||
if pk.address == nil { | ||
pk.address = address.Hash(protoName, pk.Bytes()) | ||
} | ||
return pk.address | ||
} | ||
|
||
// Bytes returns the byte representation of the public key using a compressed form | ||
// specified in section 4.3.6 of ANSI X9.62 with first byte being the curve type. | ||
func (pk *PubKey) Bytes() []byte { | ||
if pk == nil { | ||
return nil | ||
} | ||
return elliptic.MarshalCompressed(pk.Curve, pk.X, pk.Y) | ||
} | ||
|
||
// VerifySignature checks if sig is a valid ECDSA signature for msg. | ||
func (pk *PubKey) VerifySignature(msg []byte, sig []byte) bool { | ||
s := new(signature) | ||
if _, err := asn1.Unmarshal(sig, s); err != nil || s == nil { | ||
return false | ||
} | ||
|
||
h := sha256.Sum256(msg) | ||
return ecdsa.Verify(&pk.PublicKey, h[:], s.R, s.S) | ||
} | ||
|
||
// String returns a string representation of the public key based on the curveName. | ||
func (pk *PubKey) String(curveName string) string { | ||
return fmt.Sprintf("%s{%X}", curveName, pk.Bytes()) | ||
} | ||
|
||
// **** Proto Marshaler **** | ||
|
||
// MarshalTo implements proto.Marshaler interface. | ||
func (pk *PubKey) MarshalTo(dAtA []byte) (int, error) { | ||
bz := pk.Bytes() | ||
copy(dAtA, bz) | ||
return len(bz), nil | ||
} | ||
|
||
// Unmarshal implements proto.Marshaler interface. | ||
func (pk *PubKey) Unmarshal(bz []byte, curve elliptic.Curve, expectedSize int) error { | ||
if len(bz) != expectedSize { | ||
return errors.Wrapf(errors.ErrInvalidPubKey, "wrong ECDSA PK bytes, expecting %d bytes, got %d", expectedSize, len(bz)) | ||
} | ||
cpk := ecdsa.PublicKey{Curve: curve} | ||
cpk.X, cpk.Y = elliptic.UnmarshalCompressed(curve, bz) | ||
if cpk.X == nil || cpk.Y == nil { | ||
return errors.Wrapf(errors.ErrInvalidPubKey, "wrong ECDSA PK bytes, unknown curve type: %d", bz[0]) | ||
} | ||
pk.PublicKey = cpk | ||
return nil | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
package ecdsa | ||
|
||
import ( | ||
"crypto/elliptic" | ||
"encoding/hex" | ||
"testing" | ||
|
||
"github.com/stretchr/testify/suite" | ||
) | ||
|
||
var secp256r1 = elliptic.P256() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this test file seems duplicate, since everything tested here is also tested in Still ok to keep it though. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not much. they are testing on different levels (eg: secp marshaling goes through protobuf, but ecdsa marshaling tests only the bytes in/out without going through protobuf). The |
||
|
||
func GenSecp256r1() (PrivKey, error) { | ||
return GenPrivKey(secp256r1) | ||
} | ||
|
||
func TestPKSuite(t *testing.T) { | ||
suite.Run(t, new(PKSuite)) | ||
} | ||
|
||
type CommonSuite struct { | ||
suite.Suite | ||
pk PubKey | ||
sk PrivKey | ||
} | ||
|
||
func (suite *CommonSuite) SetupSuite() { | ||
sk, err := GenSecp256r1() | ||
suite.Require().NoError(err) | ||
suite.sk = sk | ||
suite.pk = sk.PubKey() | ||
} | ||
|
||
type PKSuite struct{ CommonSuite } | ||
|
||
func (suite *PKSuite) TestString() { | ||
assert := suite.Assert() | ||
require := suite.Require() | ||
|
||
prefix := "abc" | ||
pkStr := suite.pk.String(prefix) | ||
assert.Equal(prefix+"{", pkStr[:len(prefix)+1]) | ||
assert.EqualValues('}', pkStr[len(pkStr)-1]) | ||
|
||
bz, err := hex.DecodeString(pkStr[len(prefix)+1 : len(pkStr)-1]) | ||
require.NoError(err) | ||
assert.EqualValues(suite.pk.Bytes(), bz) | ||
} | ||
|
||
func (suite *PKSuite) TestBytes() { | ||
require := suite.Require() | ||
var pk *PubKey | ||
require.Nil(pk.Bytes()) | ||
} | ||
|
||
func (suite *PKSuite) TestMarshal() { | ||
require := suite.Require() | ||
const size = 33 // secp256r1 size | ||
|
||
var buffer = make([]byte, size) | ||
n, err := suite.pk.MarshalTo(buffer) | ||
require.NoError(err) | ||
require.Equal(size, n) | ||
|
||
var pk = new(PubKey) | ||
err = pk.Unmarshal(buffer, secp256r1, size) | ||
require.NoError(err) | ||
require.True(pk.PublicKey.Equal(&suite.pk.PublicKey)) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
// Package secp256r1 implements Cosmos-SDK compatible ECDSA public and private key. The keys | ||
// can be protobuf serialized and packed in Any. | ||
package secp256r1 | ||
|
||
import ( | ||
"crypto/elliptic" | ||
"fmt" | ||
|
||
codectypes "github.com/cosmos/cosmos-sdk/codec/types" | ||
cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" | ||
) | ||
|
||
const ( | ||
// fieldSize is the curve domain size. | ||
fieldSize = 32 | ||
pubKeySize = fieldSize + 1 | ||
|
||
name = "secp256r1" | ||
) | ||
|
||
var secp256r1 elliptic.Curve | ||
|
||
func init() { | ||
secp256r1 = elliptic.P256() | ||
// pubKeySize is ceil of field bit size + 1 for the sign | ||
expected := (secp256r1.Params().BitSize + 7) / 8 | ||
if expected != fieldSize { | ||
panic(fmt.Sprintf("Wrong secp256r1 curve fieldSize=%d, expecting=%d", fieldSize, expected)) | ||
} | ||
} | ||
|
||
// RegisterInterfaces adds secp256r1 PubKey to pubkey registry | ||
func RegisterInterfaces(registry codectypes.InterfaceRegistry) { | ||
registry.RegisterImplementations((*cryptotypes.PubKey)(nil), &PubKey{}) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I understand we're keeping the cgo version for secp256k1 because it's faster. Can we remove the custom code in the nocgo version? It should just call functions from this package.
can be done in a future PR too.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good idea - let's remove the secp256k1 custom code in other issue. -> #8766