Skip to content

Commit

Permalink
imp(types): Add crypto and encoding packages (#20)
Browse files Browse the repository at this point in the history
  • Loading branch information
MalteHerrmann authored Aug 5, 2024
1 parent 579bac9 commit cff4d2a
Show file tree
Hide file tree
Showing 30 changed files with 1,740 additions and 89 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/bsr-push.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ jobs:
steps:
- uses: actions/checkout@v4
- uses: bufbuild/[email protected]
# Push Evmos protos to the Buf Schema Registry
# Push evmOS protos to the Buf Schema Registry
- uses: bufbuild/[email protected]
with:
input: ./proto
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/lint.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: Lint
# Lint runs golangci-lint over the entire Evmos repository This workflow is
# Lint runs golangci-lint over the entire evmOS repository This workflow is
# run on every pull request and push to main The `golangci` will pass without
# running if no *.{go, mod, sum} files have been changed.
on:
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ This changelog was created using the `clu` binary

### Improvements

- (types) [#20](https://github.com/evmos/os/pull/20) Add crypto and encoding packages.
- (types) [#19](https://github.com/evmos/os/pull/19) Add required wallet types for integration.
- (all) [#15](https://github.com/evmos/os/pull/15) Add general types and utils.
- (proto) [#14](https://github.com/evmos/os/pull/14) Add Protobufs and adjust scripts.
Expand Down
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -186,15 +186,15 @@ release:
###############################################################################

# Install the necessary dependencies, compile the solidity contracts found in the
# Evmos repository and then clean up the contracts data.
# evmOS repository and then clean up the contracts data.
contracts-all: contracts-compile contracts-clean

# Clean smart contract compilation artifacts, dependencies and cache files
contracts-clean:
@echo "Cleaning up the contracts directory..."
@python3 ./scripts/compile_smart_contracts/compile_smart_contracts.py --clean

# Compile the solidity contracts found in the Evmos repository.
# Compile the solidity contracts found in the evmOS repository.
contracts-compile:
@echo "Compiling smart contracts..."
@python3 ./scripts/compile_smart_contracts/compile_smart_contracts.py --compile
Expand Down
28 changes: 28 additions & 0 deletions crypto/codec/amino.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// Copyright Tharsis Labs Ltd.(Evmos)
// SPDX-License-Identifier:ENCL-1.0(https://github.com/evmos/evmos/blob/main/LICENSE)
package codec

import (
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/codec/legacy"
cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec"
"github.com/cosmos/cosmos-sdk/crypto/keyring"

"github.com/evmos/os/crypto/ethsecp256k1"
)

// RegisterCrypto registers all crypto dependency types with the provided Amino
// codec.
func RegisterCrypto(cdc *codec.LegacyAmino) {
cdc.RegisterConcrete(&ethsecp256k1.PubKey{},
ethsecp256k1.PubKeyName, nil)
cdc.RegisterConcrete(&ethsecp256k1.PrivKey{},
ethsecp256k1.PrivKeyName, nil)

keyring.RegisterLegacyAminoCodec(cdc)
cryptocodec.RegisterCrypto(cdc)

// NOTE: update SDK's amino codec to include the ethsecp256k1 keys.
// DO NOT REMOVE unless deprecated on the SDK.
legacy.Cdc = cdc
}
16 changes: 16 additions & 0 deletions crypto/codec/codec.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// Copyright Tharsis Labs Ltd.(Evmos)
// SPDX-License-Identifier:ENCL-1.0(https://github.com/evmos/evmos/blob/main/LICENSE)
package codec

import (
codectypes "github.com/cosmos/cosmos-sdk/codec/types"
cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"

"github.com/evmos/os/crypto/ethsecp256k1"
)

// RegisterInterfaces register the evmOS key concrete types.
func RegisterInterfaces(registry codectypes.InterfaceRegistry) {
registry.RegisterImplementations((*cryptotypes.PubKey)(nil), &ethsecp256k1.PubKey{})
registry.RegisterImplementations((*cryptotypes.PrivKey)(nil), &ethsecp256k1.PrivKey{})
}
34 changes: 34 additions & 0 deletions crypto/ethsecp256k1/benchmark_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package ethsecp256k1

import (
"fmt"
"testing"
)

func BenchmarkGenerateKey(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
if _, err := GenerateKey(); err != nil {
b.Fatal(err)
}
}
}

func BenchmarkPubKey_VerifySignature(b *testing.B) {
privKey, err := GenerateKey()
if err != nil {
b.Fatal(err)
}
pubKey := privKey.PubKey()

b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
msg := []byte(fmt.Sprintf("%10d", i))
sig, err := privKey.Sign(msg)
if err != nil {
b.Fatal(err)
}
pubKey.VerifySignature(msg, sig)
}
}
249 changes: 249 additions & 0 deletions crypto/ethsecp256k1/ethsecp256k1.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,249 @@
// Copyright Tharsis Labs Ltd.(Evmos)
// SPDX-License-Identifier:ENCL-1.0(https://github.com/evmos/evmos/blob/main/LICENSE)

package ethsecp256k1

import (
"bytes"
"crypto/ecdsa"
"crypto/subtle"
"fmt"

errorsmod "cosmossdk.io/errors"
tmcrypto "github.com/cometbft/cometbft/crypto"
"github.com/cosmos/cosmos-sdk/codec"
cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
errortypes "github.com/cosmos/cosmos-sdk/types/errors"
"github.com/ethereum/go-ethereum/crypto"
"github.com/evmos/os/ethereum/eip712"
)

const (
// PrivKeySize defines the size of the PrivKey bytes
PrivKeySize = 32
// PubKeySize defines the size of the PubKey bytes
PubKeySize = 33
// KeyType is the string constant for the Secp256k1 algorithm
KeyType = "eth_secp256k1"
)

// Amino encoding names
const (
// PrivKeyName defines the amino encoding name for the EthSecp256k1 private key
PrivKeyName = "os/PrivKeyEthSecp256k1"
// PubKeyName defines the amino encoding name for the EthSecp256k1 public key
PubKeyName = "os/PubKeyEthSecp256k1"
)

// ----------------------------------------------------------------------------
// secp256k1 Private Key

var (
_ cryptotypes.PrivKey = &PrivKey{}
_ codec.AminoMarshaler = &PrivKey{}
)

// GenerateKey generates a new random private key. It returns an error upon
// failure.
func GenerateKey() (*PrivKey, error) {
priv, err := crypto.GenerateKey()
if err != nil {
return nil, err
}

return &PrivKey{
Key: crypto.FromECDSA(priv),
}, nil
}

// Bytes returns the byte representation of the ECDSA Private Key.
func (privKey PrivKey) Bytes() []byte {
bz := make([]byte, len(privKey.Key))
copy(bz, privKey.Key)

return bz
}

// PubKey returns the ECDSA private key's public key. If the privkey is not valid
// it returns a nil value.
func (privKey PrivKey) PubKey() cryptotypes.PubKey {
ecdsaPrivKey, err := privKey.ToECDSA()
if err != nil {
return nil
}

return &PubKey{
Key: crypto.CompressPubkey(&ecdsaPrivKey.PublicKey),
}
}

// Equals returns true if two ECDSA 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_secp256k1
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 {
if len(bz) != PrivKeySize {
return fmt.Errorf("invalid privkey size, expected %d got %d", PrivKeySize, len(bz))
}
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 creates a recoverable ECDSA signature on the secp256k1 curve over the
// provided hash of the message. The produced signature is 65 bytes
// where the last byte contains the recovery ID.
func (privKey PrivKey) Sign(digestBz []byte) ([]byte, error) {
// TODO: remove
if len(digestBz) != crypto.DigestLength {
digestBz = crypto.Keccak256Hash(digestBz).Bytes()
}

key, err := privKey.ToECDSA()
if err != nil {
return nil, err
}

return crypto.Sign(digestBz, key)
}

// ToECDSA returns the ECDSA private key as a reference to ecdsa.PrivateKey type.
func (privKey PrivKey) ToECDSA() (*ecdsa.PrivateKey, error) {
return crypto.ToECDSA(privKey.Bytes())
}

// ----------------------------------------------------------------------------
// secp256k1 Public Key

var (
_ cryptotypes.PubKey = &PubKey{}
_ codec.AminoMarshaler = &PubKey{}
)

// Address returns the address of the ECDSA public key.
// The function will return an empty address if the public key is invalid.
func (pubKey PubKey) Address() tmcrypto.Address {
pubk, err := crypto.DecompressPubkey(pubKey.Key)
if err != nil {
return nil
}

return tmcrypto.Address(crypto.PubkeyToAddress(*pubk).Bytes())
}

// Bytes returns the raw bytes of the ECDSA public key.
func (pubKey PubKey) Bytes() []byte {
bz := make([]byte, len(pubKey.Key))
copy(bz, pubKey.Key)

return bz
}

// String implements the fmt.Stringer interface.
func (pubKey PubKey) String() string {
return fmt.Sprintf("EthPubKeySecp256k1{%X}", pubKey.Key)
}

// Type returns eth_secp256k1
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 {
if len(bz) != PubKeySize {
return errorsmod.Wrapf(errortypes.ErrInvalidPubKey, "invalid pubkey size, expected %d, got %d", PubKeySize, len(bz))
}
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 ECDSA public key created a given signature over
// the provided message. It will calculate the Keccak256 hash of the message
// prior to verification and approve verification if the signature can be verified
// from either the original message or its EIP-712 representation.
//
// CONTRACT: The signature should be in [R || S] format.
func (pubKey PubKey) VerifySignature(msg, sig []byte) bool {
return pubKey.verifySignatureECDSA(msg, sig) || pubKey.verifySignatureAsEIP712(msg, sig)
}

// Verifies the signature as an EIP-712 signature by first converting the message payload
// to EIP-712 object bytes, then performing ECDSA verification on the hash. This is to support
// signing a Cosmos payload using EIP-712.
func (pubKey PubKey) verifySignatureAsEIP712(msg, sig []byte) bool {
eip712Bytes, err := eip712.GetEIP712BytesForMsg(msg)
if err != nil {
return false
}

if pubKey.verifySignatureECDSA(eip712Bytes, sig) {
return true
}

// Try verifying the signature using the legacy EIP-712 encoding
legacyEIP712Bytes, err := eip712.LegacyGetEIP712BytesForMsg(msg)
if err != nil {
return false
}

return pubKey.verifySignatureECDSA(legacyEIP712Bytes, sig)
}

// Perform standard ECDSA signature verification for the given raw bytes and signature.
func (pubKey PubKey) verifySignatureECDSA(msg, sig []byte) bool {
if len(sig) == crypto.SignatureLength {
// remove recovery ID (V) if contained in the signature
sig = sig[:len(sig)-1]
}

// the signature needs to be in [R || S] format when provided to VerifySignature
return crypto.VerifySignature(pubKey.Key, crypto.Keccak256Hash(msg).Bytes(), sig)
}
Loading

0 comments on commit cff4d2a

Please sign in to comment.