From e235b25ee7e44f1d203e6f2d3a36e1dcb07de53a Mon Sep 17 00:00:00 2001 From: dylanhuang Date: Fri, 23 Dec 2022 14:23:00 +0800 Subject: [PATCH] feat: add bls key types into keyring management tool (#73) * keyring: add eth bls key types * fix: keys/codec_test --- client/keys/codec_test.go | 16 +- crypto/codec/amino.go | 6 +- crypto/codec/proto.go | 3 + crypto/hd/algo.go | 48 ++ crypto/keyring/output.go | 22 +- crypto/keyring/output_test.go | 2 +- crypto/keys/eth/bls/bls.go | 33 ++ crypto/keys/eth/bls/keys.pb.go | 495 +++++++++++++++++++ crypto/keys/eth/bls/privkey.go | 100 ++++ crypto/keys/eth/bls/privkey_internal_test.go | 107 ++++ crypto/keys/eth/bls/pubkey.go | 84 ++++ crypto/keys/eth/bls/pubkey_internal_test.go | 105 ++++ crypto/keys/multisig/codec.go | 3 + go.mod | 3 + go.sum | 3 + proto/cosmos/crypto/eth/bls/keys.proto | 21 + 16 files changed, 1032 insertions(+), 19 deletions(-) create mode 100644 crypto/keys/eth/bls/bls.go create mode 100644 crypto/keys/eth/bls/keys.pb.go create mode 100644 crypto/keys/eth/bls/privkey.go create mode 100644 crypto/keys/eth/bls/privkey_internal_test.go create mode 100644 crypto/keys/eth/bls/pubkey.go create mode 100644 crypto/keys/eth/bls/pubkey_internal_test.go create mode 100644 proto/cosmos/crypto/eth/bls/keys.proto diff --git a/client/keys/codec_test.go b/client/keys/codec_test.go index f61792e600..e71a301a75 100644 --- a/client/keys/codec_test.go +++ b/client/keys/codec_test.go @@ -20,17 +20,17 @@ func getTestCases() testCases { return testCases{ // nolint:govet []keyring.KeyOutput{ - {"A", "B", "C", "D", "E"}, - {"A", "B", "C", "D", ""}, - {"", "B", "C", "D", ""}, - {"", "", "", "", ""}, + {"A", "B", "C", "D", "D", "E"}, + {"A", "B", "C", "D", "D", ""}, + {"", "B", "C", "D", "D", ""}, + {"", "", "", "", "", ""}, }, make([]keyring.KeyOutput, 4), [][]byte{ - []byte(`{"name":"A","type":"B","address":"C","pubkey":"D","mnemonic":"E"}`), - []byte(`{"name":"A","type":"B","address":"C","pubkey":"D"}`), - []byte(`{"name":"","type":"B","address":"C","pubkey":"D"}`), - []byte(`{"name":"","type":"","address":"","pubkey":""}`), + []byte(`{"name":"A","type":"B","address":"C","pubkey":"D","pubkey_hex":"D","mnemonic":"E"}`), + []byte(`{"name":"A","type":"B","address":"C","pubkey":"D","pubkey_hex":"D"}`), + []byte(`{"name":"","type":"B","address":"C","pubkey":"D","pubkey_hex":"D"}`), + []byte(`{"name":"","type":"","address":"","pubkey":"","pubkey_hex":""}`), }, } } diff --git a/crypto/codec/amino.go b/crypto/codec/amino.go index 2914790bfa..b8b8e846d2 100644 --- a/crypto/codec/amino.go +++ b/crypto/codec/amino.go @@ -7,6 +7,7 @@ import ( "github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/crypto/keys/ed25519" + "github.com/cosmos/cosmos-sdk/crypto/keys/eth/bls" kmultisig "github.com/cosmos/cosmos-sdk/crypto/keys/multisig" "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" @@ -26,7 +27,8 @@ func RegisterCrypto(cdc *codec.LegacyAmino) { kmultisig.PubKeyAminoRoute, nil) cdc.RegisterConcrete(ðsecp256k1.PubKey{}, ethsecp256k1.PubKeyName, nil) - + cdc.RegisterConcrete(&bls.PubKey{}, + bls.PubKeyName, nil) cdc.RegisterInterface((*cryptotypes.PrivKey)(nil), &amino.InterfaceOptions{Priority: []string{"ethermint/PrivKeyEthSecp256k1"}}) cdc.RegisterConcrete(sr25519.PrivKey{}, sr25519.PrivKeyName, nil) @@ -36,4 +38,6 @@ func RegisterCrypto(cdc *codec.LegacyAmino) { secp256k1.PrivKeyName, nil) cdc.RegisterConcrete(ðsecp256k1.PrivKey{}, ethsecp256k1.PrivKeyName, nil) + cdc.RegisterConcrete(&bls.PrivKey{}, + bls.PrivKeyName, nil) } diff --git a/crypto/codec/proto.go b/crypto/codec/proto.go index ed22ff0861..212e930abd 100644 --- a/crypto/codec/proto.go +++ b/crypto/codec/proto.go @@ -5,6 +5,7 @@ import ( codectypes "github.com/cosmos/cosmos-sdk/codec/types" "github.com/cosmos/cosmos-sdk/crypto/keys/ed25519" + "github.com/cosmos/cosmos-sdk/crypto/keys/eth/bls" "github.com/cosmos/cosmos-sdk/crypto/keys/multisig" "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" "github.com/cosmos/cosmos-sdk/crypto/keys/secp256r1" @@ -19,11 +20,13 @@ func RegisterInterfaces(registry codectypes.InterfaceRegistry) { registry.RegisterImplementations(pk, &secp256k1.PubKey{}) registry.RegisterImplementations(pk, &multisig.LegacyAminoPubKey{}) registry.RegisterImplementations(pk, ðsecp256k1.PubKey{}) + registry.RegisterImplementations(pk, &bls.PubKey{}) var priv *cryptotypes.PrivKey registry.RegisterInterface("cosmos.crypto.PrivKey", priv) registry.RegisterImplementations(priv, &secp256k1.PrivKey{}) registry.RegisterImplementations(priv, &ed25519.PrivKey{}) //nolint registry.RegisterImplementations(priv, ðsecp256k1.PrivKey{}) + registry.RegisterImplementations(priv, &bls.PrivKey{}) secp256r1.RegisterInterfaces(registry) } diff --git a/crypto/hd/algo.go b/crypto/hd/algo.go index 0feb4ff49b..1e75074050 100644 --- a/crypto/hd/algo.go +++ b/crypto/hd/algo.go @@ -1,8 +1,12 @@ package hd import ( + "strings" + "github.com/cosmos/go-bip39" + util "github.com/wealdtech/go-eth2-util" + "github.com/cosmos/cosmos-sdk/crypto/keys/eth/bls" "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" "github.com/cosmos/cosmos-sdk/crypto/types" ) @@ -68,3 +72,47 @@ func (s secp256k1Algo) Generate() GenerateFn { return &secp256k1.PrivKey{Key: bzArr} } } + +const ( + // BLSType uses the ethereum BLS parameters. + BLSType = PubKeyType(bls.KeyType) +) + +// EthBLS uses the Bitcoin eth_bls parameters. +var EthBLS = ethBLSAlgo{} + +type ethBLSAlgo struct{} + +// Name returns eth_bls +func (s ethBLSAlgo) Name() PubKeyType { + return BLSType +} + +// Derive derives and returns the eth_bls private key for the given seed and HD path. +func (s ethBLSAlgo) Derive() DeriveFn { + // Derive derives and returns the eth_bls private key for the given mnemonic and HD path. + return func(mnemonic, bip39Passphrase, path string) ([]byte, error) { + seed, err := bip39.NewSeedWithErrorChecking(mnemonic, bip39Passphrase) + if err != nil { + return nil, err + } + + privKey, err := util.PrivateKeyFromSeedAndPath( + seed, strings.ReplaceAll(path, "'", ""), + ) + if err != nil { + return nil, err + } + + return privKey.Marshal(), nil + } +} + +// Generate generates a eth_bls private key from the given bytes. +func (s ethBLSAlgo) Generate() GenerateFn { + return func(bz []byte) types.PrivKey { + return &bls.PrivKey{ + Key: bz, + } + } +} diff --git a/crypto/keyring/output.go b/crypto/keyring/output.go index 87bec26ad8..e07c6fde03 100644 --- a/crypto/keyring/output.go +++ b/crypto/keyring/output.go @@ -1,6 +1,8 @@ package keyring import ( + "encoding/hex" + "github.com/cosmos/cosmos-sdk/codec" codectypes "github.com/cosmos/cosmos-sdk/codec/types" cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" @@ -13,11 +15,12 @@ import ( // KeyOutput defines a structure wrapping around an Info object used for output // functionality. type KeyOutput struct { - Name string `json:"name" yaml:"name"` - Type string `json:"type" yaml:"type"` - Address string `json:"address" yaml:"address"` - PubKey string `json:"pubkey" yaml:"pubkey"` - Mnemonic string `json:"mnemonic,omitempty" yaml:"mnemonic"` + Name string `json:"name" yaml:"name"` + Type string `json:"type" yaml:"type"` + Address string `json:"address" yaml:"address"` + PubKey string `json:"pubkey" yaml:"pubkey"` + PubKeyHex string `json:"pubkey_hex" yaml:"pubkey_hex"` + Mnemonic string `json:"mnemonic,omitempty" yaml:"mnemonic"` } // NewKeyOutput creates a default KeyOutput instance without Mnemonic, Threshold and PubKeys @@ -31,10 +34,11 @@ func NewKeyOutput(name string, keyType KeyType, a sdk.Address, pk cryptotypes.Pu return KeyOutput{}, err } return KeyOutput{ - Name: name, - Type: keyType.String(), - Address: a.String(), - PubKey: string(bz), + Name: name, + Type: keyType.String(), + Address: a.String(), + PubKey: string(bz), + PubKeyHex: hex.EncodeToString(pk.Bytes()), }, nil } diff --git a/crypto/keyring/output_test.go b/crypto/keyring/output_test.go index f63ab1b32c..66353ccead 100644 --- a/crypto/keyring/output_test.go +++ b/crypto/keyring/output_test.go @@ -29,5 +29,5 @@ func TestBech32KeysOutput(t *testing.T) { out, err := MkAccKeyOutput(k) require.NoError(t, err) require.Equal(t, expectedOutput, out) - require.Equal(t, "{Name:multisig Type:multi Address:0x9a4fF4eA75776B118b6d13B3aBD8737511CcdAE0 PubKey:{\"@type\":\"/cosmos.crypto.multisig.LegacyAminoPubKey\",\"threshold\":1,\"public_keys\":[{\"@type\":\"/cosmos.crypto.secp256k1.PubKey\",\"key\":\"AurroA7jvfPd1AadmmOvWM2rJSwipXfRf8yD6pLbA2DJ\"}]} Mnemonic:}", fmt.Sprintf("%+v", out)) + require.Equal(t, "{Name:multisig Type:multi Address:0x9a4fF4eA75776B118b6d13B3aBD8737511CcdAE0 PubKey:{\"@type\":\"/cosmos.crypto.multisig.LegacyAminoPubKey\",\"threshold\":1,\"public_keys\":[{\"@type\":\"/cosmos.crypto.secp256k1.PubKey\",\"key\":\"AurroA7jvfPd1AadmmOvWM2rJSwipXfRf8yD6pLbA2DJ\"}]} PubKeyHex:22c1f7e208011226eb5ae9872102eaeba00ee3bdf3ddd4069d9a63af58cdab252c22a577d17fcc83ea92db0360c9 Mnemonic:}", fmt.Sprintf("%+v", out)) } diff --git a/crypto/keys/eth/bls/bls.go b/crypto/keys/eth/bls/bls.go new file mode 100644 index 0000000000..cd8a954ef3 --- /dev/null +++ b/crypto/keys/eth/bls/bls.go @@ -0,0 +1,33 @@ +package bls + +import ( + "github.com/cosmos/cosmos-sdk/codec" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" +) + +const ( + // KeyType is the string constant for the BLS algorithm + KeyType = "eth_bls" +) + +// Amino encoding names +const ( + // PrivKeyName defines the amino encoding name for the EthBLS private key + PrivKeyName = "ethereum/PrivKeyEthBLS" + // PubKeyName defines the amino encoding name for the EthBLS public key + PubKeyName = "ethereum/PubKeyEthBLS" +) + +// ---------------------------------------------------------------------------- +// secp256k1 Public Key + +var ( + _ cryptotypes.PubKey = &PubKey{} + _ codec.AminoMarshaler = &PubKey{} +) + +// RegisterInterfaces adds BLS PubKey to pubkey registry +func RegisterInterfaces(registry codectypes.InterfaceRegistry) { + registry.RegisterImplementations((*cryptotypes.PubKey)(nil), &PubKey{}) +} diff --git a/crypto/keys/eth/bls/keys.pb.go b/crypto/keys/eth/bls/keys.pb.go new file mode 100644 index 0000000000..9915d3738e --- /dev/null +++ b/crypto/keys/eth/bls/keys.pb.go @@ -0,0 +1,495 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: cosmos/crypto/eth/bls/keys.proto + +package bls + +import ( + fmt "fmt" + _ "github.com/gogo/protobuf/gogoproto" + proto "github.com/gogo/protobuf/proto" + io "io" + math "math" + math_bits "math/bits" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +// PubKey defines a bls public key +// Key is the compressed form of the pubkey. +type PubKey struct { + // key is the public key in byte form + Key []byte `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` +} + +func (m *PubKey) Reset() { *m = PubKey{} } +func (*PubKey) ProtoMessage() {} +func (*PubKey) Descriptor() ([]byte, []int) { + return fileDescriptor_c8fb724eccfaffbf, []int{0} +} +func (m *PubKey) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *PubKey) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_PubKey.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *PubKey) XXX_Merge(src proto.Message) { + xxx_messageInfo_PubKey.Merge(m, src) +} +func (m *PubKey) XXX_Size() int { + return m.Size() +} +func (m *PubKey) XXX_DiscardUnknown() { + xxx_messageInfo_PubKey.DiscardUnknown(m) +} + +var xxx_messageInfo_PubKey proto.InternalMessageInfo + +func (m *PubKey) GetKey() []byte { + if m != nil { + return m.Key + } + return nil +} + +// PrivKey defines a bls private key. +type PrivKey struct { + // key is the private key in byte form + Key []byte `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` +} + +func (m *PrivKey) Reset() { *m = PrivKey{} } +func (m *PrivKey) String() string { return proto.CompactTextString(m) } +func (*PrivKey) ProtoMessage() {} +func (*PrivKey) Descriptor() ([]byte, []int) { + return fileDescriptor_c8fb724eccfaffbf, []int{1} +} +func (m *PrivKey) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *PrivKey) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_PrivKey.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *PrivKey) XXX_Merge(src proto.Message) { + xxx_messageInfo_PrivKey.Merge(m, src) +} +func (m *PrivKey) XXX_Size() int { + return m.Size() +} +func (m *PrivKey) XXX_DiscardUnknown() { + xxx_messageInfo_PrivKey.DiscardUnknown(m) +} + +var xxx_messageInfo_PrivKey proto.InternalMessageInfo + +func (m *PrivKey) GetKey() []byte { + if m != nil { + return m.Key + } + return nil +} + +func init() { + proto.RegisterType((*PubKey)(nil), "cosmos.crypto.eth.bls.PubKey") + proto.RegisterType((*PrivKey)(nil), "cosmos.crypto.eth.bls.PrivKey") +} + +func init() { proto.RegisterFile("cosmos/crypto/eth/bls/keys.proto", fileDescriptor_c8fb724eccfaffbf) } + +var fileDescriptor_c8fb724eccfaffbf = []byte{ + // 188 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x52, 0x48, 0xce, 0x2f, 0xce, + 0xcd, 0x2f, 0xd6, 0x4f, 0x2e, 0xaa, 0x2c, 0x28, 0xc9, 0xd7, 0x4f, 0x2d, 0xc9, 0xd0, 0x4f, 0xca, + 0x29, 0xd6, 0xcf, 0x4e, 0xad, 0x2c, 0xd6, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x12, 0x85, 0xa8, + 0xd0, 0x83, 0xa8, 0xd0, 0x4b, 0x2d, 0xc9, 0xd0, 0x4b, 0xca, 0x29, 0x96, 0x12, 0x49, 0xcf, 0x4f, + 0xcf, 0x07, 0xab, 0xd0, 0x07, 0xb1, 0x20, 0x8a, 0x95, 0x14, 0xb8, 0xd8, 0x02, 0x4a, 0x93, 0xbc, + 0x53, 0x2b, 0x85, 0x04, 0xb8, 0x98, 0xb3, 0x53, 0x2b, 0x25, 0x18, 0x15, 0x18, 0x35, 0x78, 0x82, + 0x40, 0x4c, 0x2b, 0x96, 0x19, 0x0b, 0xe4, 0x19, 0x94, 0xa4, 0xb9, 0xd8, 0x03, 0x8a, 0x32, 0xcb, + 0xb0, 0x2a, 0x71, 0xf2, 0x3a, 0xf1, 0x48, 0x8e, 0xf1, 0xc2, 0x23, 0x39, 0xc6, 0x07, 0x8f, 0xe4, + 0x18, 0x27, 0x3c, 0x96, 0x63, 0xb8, 0xf0, 0x58, 0x8e, 0xe1, 0xc6, 0x63, 0x39, 0x86, 0x28, 0x83, + 0xf4, 0xcc, 0x92, 0x8c, 0xd2, 0x24, 0xbd, 0xe4, 0xfc, 0x5c, 0x7d, 0x98, 0x93, 0xc1, 0x94, 0x6e, + 0x71, 0x4a, 0x36, 0xcc, 0xf5, 0x20, 0x57, 0xc3, 0xbc, 0x90, 0xc4, 0x06, 0x76, 0x91, 0x31, 0x20, + 0x00, 0x00, 0xff, 0xff, 0xc8, 0x27, 0x3e, 0x2d, 0xe2, 0x00, 0x00, 0x00, +} + +func (m *PubKey) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *PubKey) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *PubKey) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Key) > 0 { + i -= len(m.Key) + copy(dAtA[i:], m.Key) + i = encodeVarintKeys(dAtA, i, uint64(len(m.Key))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *PrivKey) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *PrivKey) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *PrivKey) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Key) > 0 { + i -= len(m.Key) + copy(dAtA[i:], m.Key) + i = encodeVarintKeys(dAtA, i, uint64(len(m.Key))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func encodeVarintKeys(dAtA []byte, offset int, v uint64) int { + offset -= sovKeys(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *PubKey) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Key) + if l > 0 { + n += 1 + l + sovKeys(uint64(l)) + } + return n +} + +func (m *PrivKey) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Key) + if l > 0 { + n += 1 + l + sovKeys(uint64(l)) + } + return n +} + +func sovKeys(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozKeys(x uint64) (n int) { + return sovKeys(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *PubKey) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowKeys + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: PubKey: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: PubKey: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Key", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowKeys + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthKeys + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthKeys + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Key = append(m.Key[:0], dAtA[iNdEx:postIndex]...) + if m.Key == nil { + m.Key = []byte{} + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipKeys(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthKeys + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *PrivKey) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowKeys + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: PrivKey: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: PrivKey: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Key", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowKeys + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthKeys + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthKeys + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Key = append(m.Key[:0], dAtA[iNdEx:postIndex]...) + if m.Key == nil { + m.Key = []byte{} + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipKeys(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthKeys + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipKeys(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowKeys + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowKeys + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowKeys + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthKeys + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupKeys + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthKeys + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthKeys = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowKeys = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupKeys = fmt.Errorf("proto: unexpected end of group") +) diff --git a/crypto/keys/eth/bls/privkey.go b/crypto/keys/eth/bls/privkey.go new file mode 100644 index 0000000000..f2168639ff --- /dev/null +++ b/crypto/keys/eth/bls/privkey.go @@ -0,0 +1,100 @@ +package bls + +import ( + "crypto/subtle" + + "github.com/cosmos/cosmos-sdk/codec" + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" + "github.com/prysmaticlabs/prysm/crypto/bls" +) + +var ( + _ cryptotypes.PrivKey = &PrivKey{} + _ codec.AminoMarshaler = &PrivKey{} +) + +// GenerateKey generates a new random private key. It returns an error upon +// failure. +func GenerateKey() (*PrivKey, error) { + secretKey, err := bls.RandKey() + if err != nil { + return nil, err + } + + return &PrivKey{ + Key: secretKey.Marshal(), + }, nil +} + +// Bytes returns the byte representation of the BLS Private Key. +func (privKey *PrivKey) Bytes() []byte { + if privKey == nil { + return nil + } + bz := make([]byte, len(privKey.Key)) + copy(bz, privKey.Key) + + return bz +} + +// PubKey returns the BLS private key's public key. If the privkey is not valid +// it returns a nil value. +func (privKey *PrivKey) PubKey() cryptotypes.PubKey { + secretKey, err := bls.SecretKeyFromBytes(privKey.Bytes()) + if err != nil { + return nil + } + + return &PubKey{ + Key: secretKey.PublicKey().Marshal(), + } +} + +// Equals returns true if two BLS private keys are equal and false otherwise. +func (privKey *PrivKey) Equals(other cryptotypes.LedgerPrivKey) bool { + return privKey.Type() == other.Type() && subtle.ConstantTimeCompare(privKey.Bytes(), other.Bytes()) == 1 +} + +// Type returns eth_bls +func (privKey *PrivKey) Type() string { + return KeyType +} + +// MarshalAmino overrides Amino binary marshaling. +func (privKey *PrivKey) MarshalAmino() ([]byte, error) { + return privKey.Key, nil +} + +// UnmarshalAmino overrides Amino binary marshaling. +func (privKey *PrivKey) UnmarshalAmino(bz []byte) error { + privKey.Key = bz + + return nil +} + +// MarshalAminoJSON overrides Amino JSON marshaling. +func (privKey *PrivKey) MarshalAminoJSON() ([]byte, error) { + // When we marshal to Amino JSON, we don't marshal the "key" field itself, + // just its contents (i.e. the key bytes). + return privKey.MarshalAmino() +} + +// UnmarshalAminoJSON overrides Amino JSON marshaling. +func (privKey *PrivKey) UnmarshalAminoJSON(bz []byte) error { + return privKey.UnmarshalAmino(bz) +} + +// Sign a message using a secret key - in a beacon/validator client. +// +// In IETF draft BLS specification: +// Sign(SK, message) -> signature: a signing algorithm that generates +// +// a deterministic signature given a secret key SK and a message. +func (privKey *PrivKey) Sign(digestBz []byte) ([]byte, error) { + secretKey, err := bls.SecretKeyFromBytes(privKey.Bytes()) + if err != nil { + return nil, err + } + + return secretKey.Sign(digestBz).Marshal(), nil +} diff --git a/crypto/keys/eth/bls/privkey_internal_test.go b/crypto/keys/eth/bls/privkey_internal_test.go new file mode 100644 index 0000000000..a838209007 --- /dev/null +++ b/crypto/keys/eth/bls/privkey_internal_test.go @@ -0,0 +1,107 @@ +package bls + +import ( + "bytes" + "testing" + + "github.com/gogo/protobuf/proto" + "github.com/prysmaticlabs/prysm/crypto/bls" + "github.com/stretchr/testify/suite" + "github.com/tendermint/tendermint/crypto" + + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/codec/types" + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" +) + +var _ cryptotypes.PrivKey = &PrivKey{} + +func TestSKSuite(t *testing.T) { + suite.Run(t, new(SKSuite)) +} + +type SKSuite struct{ CommonSuite } + +func (suite *SKSuite) TestEquals() { + require := suite.Require() + + skOther, err := GenerateKey() + require.NoError(err) + require.False(suite.sk.Equals(skOther)) + + skOther2 := &PrivKey{skOther.Key} + require.True(skOther.Equals(skOther2)) + require.True(skOther2.Equals(skOther), "Equals must be reflexive") +} + +func (suite *SKSuite) TestPubKey() { + pk := suite.sk.PubKey() + sk, err := bls.SecretKeyFromBytes(suite.sk.Bytes()) + if !suite.Assert().NoError(err) { + return + } + + suite.True(bytes.Equal(pk.Bytes(), sk.PublicKey().Marshal())) +} + +func (suite *SKSuite) TestBytes() { + var sk *PrivKey + suite.Nil(sk.Bytes()) +} + +func (suite *SKSuite) TestMarshalProto() { + require := suite.Require() + + /**** test structure marshalling ****/ + + var sk PrivKey + bz, err := proto.Marshal(suite.sk) + require.NoError(err) + require.NoError(proto.Unmarshal(bz, &sk)) + require.True(sk.Equals(suite.sk)) + + /**** test structure marshalling with codec ****/ + + sk = PrivKey{} + registry := types.NewInterfaceRegistry() + cdc := codec.NewProtoCodec(registry) + bz, err = cdc.Marshal(suite.sk.(*PrivKey)) + require.NoError(err) + require.NoError(cdc.Unmarshal(bz, &sk)) + require.True(sk.Equals(suite.sk)) + + const bufSize = 100 + bz2 := make([]byte, bufSize) + skCpy := suite.sk.(*PrivKey) + _, err = skCpy.MarshalTo(bz2) + require.NoError(err) + require.Len(bz2, bufSize) + require.Equal(bz, bz2[:sk.Size()]) + + bz2 = make([]byte, bufSize) + _, err = skCpy.MarshalToSizedBuffer(bz2) + require.NoError(err) + require.Len(bz2, bufSize) + require.Equal(bz, bz2[(bufSize-sk.Size()):]) +} + +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)) +} diff --git a/crypto/keys/eth/bls/pubkey.go b/crypto/keys/eth/bls/pubkey.go new file mode 100644 index 0000000000..aaabda31ef --- /dev/null +++ b/crypto/keys/eth/bls/pubkey.go @@ -0,0 +1,84 @@ +package bls + +import ( + "bytes" + + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" + "github.com/prysmaticlabs/prysm/crypto/bls" + tmcrypto "github.com/tendermint/tendermint/crypto" +) + +// Address returns the address of the BLS public key. +// The function will return an empty address if the public key is invalid. +func (pubKey *PubKey) Address() tmcrypto.Address { + if pubKey == nil { + return tmcrypto.Address(nil) + } + + return tmcrypto.Address(pubKey.Bytes()) +} + +// Bytes returns the raw bytes of the BLS public key. +func (pubKey *PubKey) Bytes() []byte { + if pubKey == nil { + return nil + } + bz := make([]byte, len(pubKey.Key)) + copy(bz, pubKey.Key) + + return bz +} + +// String implements the fmt.Stringer interface. +func (pubKey *PubKey) String() string { + return pubKey.Address().String() +} + +// Type returns eth_bls +func (pubKey *PubKey) Type() string { + return KeyType +} + +// Equals returns true if the pubkey type is the same and their bytes are deeply equal. +func (pubKey *PubKey) Equals(other cryptotypes.PubKey) bool { + return pubKey.Type() == other.Type() && bytes.Equal(pubKey.Bytes(), other.Bytes()) +} + +// MarshalAmino overrides Amino binary marshaling. +func (pubKey *PubKey) MarshalAmino() ([]byte, error) { + return pubKey.Key, nil +} + +// UnmarshalAmino overrides Amino binary marshaling. +func (pubKey *PubKey) UnmarshalAmino(bz []byte) error { + pubKey.Key = bz + + return nil +} + +// MarshalAminoJSON overrides Amino JSON marshaling. +func (pubKey *PubKey) MarshalAminoJSON() ([]byte, error) { + // When we marshal to Amino JSON, we don't marshal the "key" field itself, + // just its contents (i.e. the key bytes). + return pubKey.MarshalAmino() +} + +// UnmarshalAminoJSON overrides Amino JSON marshaling. +func (pubKey *PubKey) UnmarshalAminoJSON(bz []byte) error { + return pubKey.UnmarshalAmino(bz) +} + +// VerifySignature verifies that the BLS public key created a given signature over +// the provided message. +func (pubKey *PubKey) VerifySignature(msg, sig []byte) bool { + key, err := bls.PublicKeyFromBytes(pubKey.Bytes()) + if err != nil { + return false + } + signature, err := bls.SignatureFromBytes(sig) + if err != nil { + return false + } + + return signature.Verify(key, msg) +} diff --git a/crypto/keys/eth/bls/pubkey_internal_test.go b/crypto/keys/eth/bls/pubkey_internal_test.go new file mode 100644 index 0000000000..bf0fc692e4 --- /dev/null +++ b/crypto/keys/eth/bls/pubkey_internal_test.go @@ -0,0 +1,105 @@ +package bls + +import ( + "testing" + + proto "github.com/gogo/protobuf/proto" + "github.com/stretchr/testify/suite" + + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/codec/types" + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" +) + +var _ cryptotypes.PubKey = (*PubKey)(nil) + +func TestPKSuite(t *testing.T) { + suite.Run(t, new(PKSuite)) +} + +type CommonSuite struct { + suite.Suite + pk *PubKey // cryptotypes.PubKey + sk cryptotypes.PrivKey +} + +func (suite *CommonSuite) SetupSuite() { + sk, err := GenerateKey() + suite.Require().NoError(err) + suite.sk = sk + suite.pk = sk.PubKey().(*PubKey) +} + +type PKSuite struct{ CommonSuite } + +func (suite *PKSuite) TestType() { + suite.Require().Equal(KeyType, suite.pk.Type()) +} + +func (suite *PKSuite) TestBytes() { + var pk *PubKey + suite.Nil(pk.Bytes()) +} + +func (suite *PKSuite) TestEquals() { + require := suite.Require() + + skOther, err := GenerateKey() + require.NoError(err) + pkOther := skOther.PubKey() + pkOther2 := &PubKey{Key: skOther.PubKey().Bytes()} + + require.False(suite.pk.Equals(pkOther)) + require.True(pkOther.Equals(pkOther2)) + require.True(pkOther2.Equals(pkOther)) + require.True(pkOther.Equals(pkOther), "Equals must be reflexive") +} + +func (suite *PKSuite) TestMarshalProto() { + require := suite.Require() + + /**** test structure marshalling ****/ + + var pk PubKey + bz, err := proto.Marshal(suite.pk) + require.NoError(err) + require.NoError(proto.Unmarshal(bz, &pk)) + require.True(pk.Equals(suite.pk)) + + /**** test structure marshalling with codec ****/ + + pk = PubKey{} + registry := types.NewInterfaceRegistry() + cdc := codec.NewProtoCodec(registry) + bz, err = cdc.Marshal(suite.pk) + require.NoError(err) + require.NoError(cdc.Unmarshal(bz, &pk)) + require.True(pk.Equals(suite.pk)) + + const bufSize = 100 + bz2 := make([]byte, bufSize) + pkCpy := suite.pk + _, err = pkCpy.MarshalTo(bz2) + require.NoError(err) + require.Len(bz2, bufSize) + require.Equal(bz, bz2[:pk.Size()]) + + bz2 = make([]byte, bufSize) + _, err = pkCpy.MarshalToSizedBuffer(bz2) + require.NoError(err) + require.Len(bz2, bufSize) + require.Equal(bz, bz2[(bufSize-pk.Size()):]) + + /**** test interface marshalling ****/ + bz, err = cdc.MarshalInterface(suite.pk) + require.NoError(err) + var pkI cryptotypes.PubKey + err = cdc.UnmarshalInterface(bz, &pkI) + require.EqualError(err, "no registered implementations of type types.PubKey") + + RegisterInterfaces(registry) + require.NoError(cdc.UnmarshalInterface(bz, &pkI)) + require.True(pkI.Equals(suite.pk)) + + require.Error(cdc.UnmarshalInterface(bz, nil), "nil should fail") +} diff --git a/crypto/keys/multisig/codec.go b/crypto/keys/multisig/codec.go index e7e627755e..3ee51f350f 100644 --- a/crypto/keys/multisig/codec.go +++ b/crypto/keys/multisig/codec.go @@ -6,6 +6,7 @@ import ( "github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/crypto/keys/ed25519" + "github.com/cosmos/cosmos-sdk/crypto/keys/eth/bls" "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" ) @@ -31,6 +32,8 @@ func init() { secp256k1.PubKeyName, nil) AminoCdc.RegisterConcrete(ðsecp256k1.PubKey{}, ethsecp256k1.PubKeyName, nil) + AminoCdc.RegisterConcrete(&bls.PubKey{}, + bls.PubKeyName, nil) AminoCdc.RegisterConcrete(&LegacyAminoPubKey{}, PubKeyAminoRoute, nil) } diff --git a/go.mod b/go.mod index 7511739a15..0856222733 100644 --- a/go.mod +++ b/go.mod @@ -162,6 +162,9 @@ require ( github.com/tyler-smith/go-bip39 v1.1.0 // indirect github.com/ulikunitz/xz v0.5.8 // indirect github.com/urfave/cli/v2 v2.3.0 // indirect + github.com/wealdtech/go-bytesutil v1.1.1 // indirect + github.com/wealdtech/go-eth2-types/v2 v2.5.2 // indirect + github.com/wealdtech/go-eth2-util v1.6.3 // indirect github.com/zondax/hid v0.9.1-0.20220302062450-5552068d2266 // indirect go.etcd.io/bbolt v1.3.6 // indirect go.opencensus.io v0.23.0 // indirect diff --git a/go.sum b/go.sum index b7a7aa5120..c32cadd841 100644 --- a/go.sum +++ b/go.sum @@ -1505,8 +1505,11 @@ github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYp github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU= github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc= github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= +github.com/wealdtech/go-bytesutil v1.1.1 h1:ocEg3Ke2GkZ4vQw5lp46rmO+pfqCCTgq35gqOy8JKVc= github.com/wealdtech/go-bytesutil v1.1.1/go.mod h1:jENeMqeTEU8FNZyDFRVc7KqBdRKSnJ9CCh26TcuNb9s= +github.com/wealdtech/go-eth2-types/v2 v2.5.2 h1:tiA6T88M6XQIbrV5Zz53l1G5HtRERcxQfmET225V4Ls= github.com/wealdtech/go-eth2-types/v2 v2.5.2/go.mod h1:8lkNUbgklSQ4LZ2oMSuxSdR7WwJW3L9ge1dcoCVyzws= +github.com/wealdtech/go-eth2-util v1.6.3 h1:2INPeOR35x5LdFFpSzyw954WzTD+DFyHe3yKlJnG5As= github.com/wealdtech/go-eth2-util v1.6.3/go.mod h1:0hFMj/qtio288oZFHmAbCnPQ9OB3c4WFzs5NVPKTY4k= github.com/wealdtech/go-eth2-wallet-encryptor-keystorev4 v1.1.3/go.mod h1:qiIimacW5NhVRy8o+YxWo9YrecXqDAKKbL0+sOa0SJ4= github.com/wealdtech/go-eth2-wallet-types/v2 v2.8.2/go.mod h1:k6kmiKWSWBTd4OxFifTEkPaBLhZspnO2KFD5XJY9nqg= diff --git a/proto/cosmos/crypto/eth/bls/keys.proto b/proto/cosmos/crypto/eth/bls/keys.proto new file mode 100644 index 0000000000..f5289d8347 --- /dev/null +++ b/proto/cosmos/crypto/eth/bls/keys.proto @@ -0,0 +1,21 @@ +syntax = "proto3"; +package cosmos.crypto.eth.bls; + +import "gogoproto/gogo.proto"; + +option go_package = "github.com/cosmos/cosmos-sdk/crypto/keys/eth/bls"; + +// PubKey defines a bls public key +// Key is the compressed form of the pubkey. +message PubKey { + option (gogoproto.goproto_stringer) = false; + + // key is the public key in byte form + bytes key = 1; +} + +// PrivKey defines a bls private key. +message PrivKey { + // key is the private key in byte form + bytes key = 1; +} \ No newline at end of file