Skip to content
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

Merged
merged 79 commits into from
Mar 4, 2021
Merged
Show file tree
Hide file tree
Changes from 77 commits
Commits
Show all changes
79 commits
Select commit Hold shift + click to select a range
78f7d46
Optimize secp256k1 hashing
robert-zaremba Jan 21, 2021
d9d677b
Add ADR-028 related functions
robert-zaremba Jan 22, 2021
5eb4e97
Update ed25519
robert-zaremba Jan 22, 2021
90a0a39
fix errors/handle
robert-zaremba Jan 27, 2021
8394e39
fix build
robert-zaremba Jan 27, 2021
48b584d
fix build
robert-zaremba Jan 27, 2021
d842642
Add tests and update function names
robert-zaremba Jan 27, 2021
0c2a983
Merge branch 'master' into robert/adr-28-poc
robert-zaremba Jan 27, 2021
d4a1d02
wip
robert-zaremba Jan 28, 2021
9263dbd
Merge branch 'master' into robert/adr-28-poc
robert-zaremba Feb 1, 2021
0fe7b12
Use LengthPrefix for composed addresses
robert-zaremba Feb 1, 2021
32710db
add tests for NewComposed
robert-zaremba Feb 1, 2021
98cdf6d
add module hash function
robert-zaremba Feb 2, 2021
956edf0
fix append
robert-zaremba Feb 4, 2021
5f25b56
Merge branch 'master' into robert/adr-28-poc
robert-zaremba Feb 4, 2021
6f0acf4
rollback ed25519 ADR-28 update
robert-zaremba Feb 7, 2021
abd614a
rollback ed25519 ADR-28 test
robert-zaremba Feb 7, 2021
964bf10
Adding Module tests and convert tests to test suite
robert-zaremba Feb 7, 2021
679afbe
convert store_key_test.go to test suite
robert-zaremba Feb 7, 2021
bba458f
rollback test check comment
robert-zaremba Feb 7, 2021
fb4a512
Merge branch 'master' into robert/adr-28-poc
robert-zaremba Feb 7, 2021
8d22e18
any.pb.go update
robert-zaremba Feb 7, 2021
baa1192
generated proto files
robert-zaremba Feb 7, 2021
c99dce5
wip
robert-zaremba Feb 7, 2021
de2e521
renames
robert-zaremba Feb 8, 2021
3bb0700
wip2
robert-zaremba Feb 10, 2021
4abcd8f
add String method to PBBytes
robert-zaremba Feb 10, 2021
06aba10
wip3
robert-zaremba Feb 10, 2021
2c80e9c
add pubkey tests
robert-zaremba Feb 10, 2021
73ce143
adding cryptotypes.PrivKey methods
robert-zaremba Feb 11, 2021
95d9b7f
Merge branch 'master' into robert/secp256r1
robert-zaremba Feb 18, 2021
9e66ef6
re-enable test
robert-zaremba Feb 18, 2021
ab780db
fix equals test
robert-zaremba Feb 18, 2021
0ac339f
fix ecdsa object receiver
robert-zaremba Feb 22, 2021
8d08d8b
add ProtoMarshaler implementation and tests
robert-zaremba Feb 23, 2021
0434d8d
move code to init and add interface registry
robert-zaremba Feb 23, 2021
6a8ada7
add bytes tests
robert-zaremba Feb 23, 2021
c414ca0
merge Unmarshal with UnmarshalAmino
robert-zaremba Feb 23, 2021
7d1f694
implement ProtoMarshaler to ecdsaSK
robert-zaremba Feb 23, 2021
e122dce
remove bytes.go
robert-zaremba Feb 23, 2021
1e8e455
Merge branch 'master' into robert/secp256r1
robert-zaremba Feb 23, 2021
c91e429
add private key marshaling tests
robert-zaremba Feb 24, 2021
fcdbb1a
break tests into 2 suites
robert-zaremba Feb 24, 2021
0593e41
add signature tests
robert-zaremba Feb 24, 2021
567f41a
remove TODO
robert-zaremba Feb 24, 2021
8f2108b
remove bytes.proto
robert-zaremba Feb 25, 2021
6dc666f
adding changelog
robert-zaremba Feb 25, 2021
97677a4
Update CHANGELOG.md
Feb 25, 2021
c49e66f
Update crypto/keys/ecdsa/ecdsa_privkey.go
Feb 25, 2021
45784bf
Update crypto/keys/ecdsa/ecdsa_pubkey.go
Feb 25, 2021
bd99f51
comments: add dot (.) at the end
robert-zaremba Feb 25, 2021
641b170
update comments
robert-zaremba Feb 25, 2021
ba95b2f
update commented code
robert-zaremba Feb 25, 2021
2fee315
rename files
robert-zaremba Feb 25, 2021
4f2a5cc
remove Amino methods
robert-zaremba Feb 25, 2021
314dbf2
use 2 spaces in protocgen.sh
robert-zaremba Feb 25, 2021
53a44a6
rollback changes in protocgen.sh
robert-zaremba Feb 25, 2021
9d28034
add MessageName
robert-zaremba Feb 25, 2021
be6a226
rework ecdsa proto structure
robert-zaremba Mar 2, 2021
c1fd439
move ecdsa to internal package
robert-zaremba Mar 2, 2021
a119d7c
add secp256r1 proto
robert-zaremba Mar 2, 2021
68ebb56
refactore proto definition for secp256r1
robert-zaremba Mar 3, 2021
784eb5a
Merge branch 'master' into robert/secp256r1
robert-zaremba Mar 3, 2021
057c131
fix err check
robert-zaremba Mar 3, 2021
5f5e9a8
Merge branch 'master' into robert/secp256r1
robert-zaremba Mar 3, 2021
1f3c36c
update comments
robert-zaremba Mar 3, 2021
6d0aaf4
create const for fieldSize+1
robert-zaremba Mar 3, 2021
8522668
simplify the PubKey.String test
robert-zaremba Mar 3, 2021
e970f52
Apply suggestions from code review
robert-zaremba Mar 3, 2021
01f2b4f
Update doc comments: SDK Interface -> sdk.Interface
robert-zaremba Mar 3, 2021
db18ea9
rename init.go to doc.go
robert-zaremba Mar 3, 2021
38f7cf3
Add PubKey.Type() test
robert-zaremba Mar 3, 2021
4a93f10
Revert "Update doc comments: SDK Interface -> sdk.Interface"
robert-zaremba Mar 3, 2021
15b866a
Use cryptotypes.Address instead of tmcrypto
robert-zaremba Mar 3, 2021
c9cbbf0
Revert "Use cryptotypes.Address instead of tmcrypto"
robert-zaremba Mar 3, 2021
52c0f90
add link to ANSI X9.62
robert-zaremba Mar 3, 2021
1ceba68
Merge branch 'master' into robert/secp256r1
Mar 3, 2021
3f3f8f9
move init.go -> doc.go
robert-zaremba Mar 4, 2021
594c186
use proto.MessageName()
robert-zaremba Mar 4, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ Ref: https://keepachangelog.com/en/1.0.0/

## [Unreleased]

## Features

* [\#8559](https://github.com/cosmos/cosmos-sdk/pull/8559) Adding Protobuf compatible secp256r1 ECDSA signatures.

### Client Breaking Changes

* [\#8363](https://github.com/cosmos/cosmos-sdk/pull/8363) Addresses no longer have a fixed 20-byte length. From the SDK modules' point of view, any 1-255 bytes-long byte array is a valid address.
Expand Down Expand Up @@ -137,7 +141,7 @@ he Cosmos Hub) should not use this release or any release in the v0.41.x series.
### State Machine Breaking

* (x/ibc) [\#8266](https://github.com/cosmos/cosmos-sdk/issues/8266) Add amino JSON support for IBC MsgTransfer in order to support Ledger text signing transfer transactions.
* (x/ibc) [\#8404](https://github.com/cosmos/cosmos-sdk/pull/8404) Reorder IBC `ChanOpenAck` and `ChanOpenConfirm` handler execution to perform core handler first, followed by application callbacks.
* (x/ibc) [\#8404](https://github.com/cosmos/cosmos-sdk/pull/8404) Reorder IBC `ChanOpenAck` and `ChanOpenConfirm` handler execution to perform core handler first, followed by application callbacks.

### Bug Fixes

Expand Down
5 changes: 1 addition & 4 deletions codec/types/any.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 7 additions & 4 deletions crypto/codec/proto.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,16 @@ import (
"github.com/cosmos/cosmos-sdk/crypto/keys/ed25519"
"github.com/cosmos/cosmos-sdk/crypto/keys/multisig"
"github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1"
"github.com/cosmos/cosmos-sdk/crypto/keys/secp256r1"
cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
)

// RegisterInterfaces registers the sdk.Tx interface.
func RegisterInterfaces(registry codectypes.InterfaceRegistry) {
registry.RegisterInterface("cosmos.crypto.PubKey", (*cryptotypes.PubKey)(nil))
registry.RegisterImplementations((*cryptotypes.PubKey)(nil), &ed25519.PubKey{})
registry.RegisterImplementations((*cryptotypes.PubKey)(nil), &secp256k1.PubKey{})
registry.RegisterImplementations((*cryptotypes.PubKey)(nil), &multisig.LegacyAminoPubKey{})
var pk *cryptotypes.PubKey
registry.RegisterInterface("cosmos.crypto.PubKey", pk)
registry.RegisterImplementations(pk, &ed25519.PubKey{})
registry.RegisterImplementations(pk, &secp256k1.PubKey{})
registry.RegisterImplementations(pk, &multisig.LegacyAminoPubKey{})
secp256r1.RegisterInterfaces(registry)
}
3 changes: 3 additions & 0 deletions crypto/keys/internal/ecdsa/doc.go
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
69 changes: 69 additions & 0 deletions crypto/keys/internal/ecdsa/privkey.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package ecdsa
Copy link
Contributor

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.

Copy link
Collaborator Author

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


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
}
66 changes: 66 additions & 0 deletions crypto/keys/internal/ecdsa/privkey_internal_test.go
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))
}
83 changes: 83 additions & 0 deletions crypto/keys/internal/ecdsa/pubkey.go
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
}
69 changes: 69 additions & 0 deletions crypto/keys/internal/ecdsa/pubkey_internal_test.go
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()
Copy link
Contributor

Choose a reason for hiding this comment

The 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 crypto/keys/secp256r1/pubkey_test.go, is that correct?

Still ok to keep it though.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The 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 secp256r1.PubKeyString has some duplication though.


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))
}
35 changes: 35 additions & 0 deletions crypto/keys/secp256r1/init.go
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{})
}
Loading