Skip to content

Commit

Permalink
epic(eth): protos, go-ethereum, eth types, and evm module types (#1837)
Browse files Browse the repository at this point in the history
* feat(eth): protos, eth types, and evm module types

* chore: changelog

* feat(eth): unit tests for types, crypto, encoding (#1838)

* feat(eth): unit tests for types, crypto, encoding

## Impl `go-ethereum`

Adds `go-ethereum` fork with an `Interpreter` interface for running smart
contracts in integration tests.

```go
type Interpreter interface {
	// Run loops and evaluates the contract's code with the given input data and returns
	// the return byte-slice and an error if one occurred.
	Run(contract *Contract, input []byte, static bool) ([]byte, error)
}
```

An `Interpreter` is used to run Ethereum based contracts and will utilize the
passed environment to query external sources for state information.
The Interpreter will run the byte code VM based on the passed
configuration.

Changes from go-ethereum v1.11:

* Set `callcode` to use `readOnly` mode for precompiled calls.
* Remove `IsStateful` function from the `PrecompiledContract` interface, as this remains unused.
* Support stateful precompiled contracts.
* Add `Address` function to `PrecompiledContract` interface.
* Implement custom active precompiles for the EVM.
* Define `Interpreter` interface for the EVM.
* Move the `JumpTable` defaults to a separate function.
* Refactor `Stack` implementation

* chore: linter

* docs(sample.go): PrivKeyEth

* refactor: fix copyright lines and LICENSE entity

* feat(eth): Collections encoders for bytes, Ethereum addresses, and Ethereum hashes (#1841)
  • Loading branch information
Unique-Divine authored Apr 16, 2024
1 parent 92662eb commit 1a46a9b
Show file tree
Hide file tree
Showing 1,894 changed files with 695,438 additions and 27 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- [#1823](https://github.com/NibiruChain/nibiru/pull/1823) - feat(inflation): add burn method
- [#1832](https://github.com/NibiruChain/nibiru/pull/1832) - feat(tokenfactory): add burn method for native tokens

#### Nibiru EVM

- [#1837](https://github.com/NibiruChain/nibiru/pull/1837) - feat(eth): protos, eth types, and evm module types
- [#1838](https://github.com/NibiruChain/nibiru/pull/1838) - feat(eth): Go-ethereum, crypto, encoding, and unit tests for evm/types
- [#1841](https://github.com/NibiruChain/nibiru/pull/1841) - feat(eth): Collections encoders for bytes, Ethereum addresses, and Ethereum hashes

#### Dapp modules: perp, spot, etc

- [#1573](https://github.com/NibiruChain/nibiru/pull/1573) - feat(perp): Close markets and compute settlement price
Expand Down
8 changes: 1 addition & 7 deletions LICENSE.md
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@
same "printed page" as the copyright notice for easier
identification within third-party archives.

Copyright 2020 Nibiru Labs, Inc.
Copyright 2024 MTRX Services Ltd.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
Expand All @@ -199,9 +199,3 @@
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
$10-worth New Year Gift for You
Make your 2022 productive
with LINER Premium

Dismiss
Open the gift
3 changes: 3 additions & 0 deletions cmd/ethclient/const.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package ethclient

const Bech32Prefix = "nibi"
27 changes: 27 additions & 0 deletions eth/crypto/codec/amino.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Copyright (c) 2023-2024 Nibi, Inc.
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/NibiruChain/nibiru/eth/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
}
15 changes: 15 additions & 0 deletions eth/crypto/codec/codec.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Copyright (c) 2023-2024 Nibi, Inc.
package codec

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

"github.com/NibiruChain/nibiru/eth/crypto/ethsecp256k1"
)

// RegisterInterfaces register the cryptographic 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 eth/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 eth/crypto/ethsecp256k1/ethsecp256k1.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,249 @@
// Copyright (c) 2023-2024 Nibi, Inc.

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/NibiruChain/nibiru/eth/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 = "ethermint/PrivKeyEthSecp256k1"
// PubKeyName defines the amino encoding name for the EthSecp256k1 public key
PubKeyName = "ethermint/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 1a46a9b

Please sign in to comment.