Skip to content
This repository has been archived by the owner on May 13, 2022. It is now read-only.

Commit

Permalink
Support 0x-encoded integer ChainID for web3 integration
Browse files Browse the repository at this point in the history
- Moved the web3 hex and ChainID handling code to encoding to prevent
import cycles

Signed-off-by: Silas Davis <[email protected]>
  • Loading branch information
Silas Davis committed Mar 18, 2021
1 parent 0e91dad commit 8d6630d
Show file tree
Hide file tree
Showing 22 changed files with 332 additions and 289 deletions.
3 changes: 2 additions & 1 deletion crypto/crypto_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"bytes"
"crypto/rand"
"fmt"
"math/big"
"testing"

"github.com/stretchr/testify/assert"
Expand Down Expand Up @@ -48,7 +49,7 @@ func TestSigning(t *testing.T) {
msg := []byte(("Flipity flobity floo"))
sig, err := pk.Sign(msg)
require.NoError(t, err)
ethSig, err := sig.GetEthSignature("floob")
ethSig, err := sig.GetEthSignature(big.NewInt(12342))
require.NoError(t, err)
parity := ethSig.RecoveryIndex()
require.True(t, parity == 0 || parity == 1)
Expand Down
15 changes: 3 additions & 12 deletions crypto/signature.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,25 +117,16 @@ func (sig *Signature) String() string {
return hex.EncodeUpperToString(sig.Signature)
}

func GetEthChainID(chainID string) *big.Int {
b := new(big.Int)
id, ok := b.SetString(chainID, 10)
if ok {
return id
}
return b.SetBytes([]byte(chainID))
}

func GetEthSignatureRecoveryID(chainID string, parity *big.Int) *big.Int {
func GetEthSignatureRecoveryID(chainID *big.Int, parity *big.Int) *big.Int {
// https://github.com/ethereum/EIPs/blob/b3bbee93dc8a775af6a6b2525c9ac5f70a7e5710/EIPS/eip-155.md
v := new(big.Int)
v.Mul(GetEthChainID(chainID), big2)
v.Mul(chainID, big2)
v.Add(v, parity)
v.Add(v, ethereumRecoveryIDOffset)
return v
}

func (sig *Signature) GetEthSignature(chainID string) (*EIP155Signature, error) {
func (sig *Signature) GetEthSignature(chainID *big.Int) (*EIP155Signature, error) {
if sig.CurveType != CurveTypeSecp256k1 {
return nil, fmt.Errorf("can only GetEthSignature for %v keys, but got %v",
CurveTypeSecp256k1, sig.CurveType)
Expand Down
16 changes: 0 additions & 16 deletions crypto/signature_test.go

This file was deleted.

28 changes: 28 additions & 0 deletions encoding/ethereum.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package encoding

import (
"math/big"
"strings"

"github.com/hyperledger/burrow/encoding/web3hex"
)

// Convert Burrow's ChainID to a *big.Int so it can be used as a nonce for Ethereum signing.
// For compatibility with Ethereum tooling this function first tries to interpret the ChainID as an integer encoded
// either as an eth-style 0x-prefixed hex string or a base 10 integer, falling back to interpreting the string's
// raw bytes as a big-endian integer
func GetEthChainID(chainID string) *big.Int {
if strings.HasPrefix(chainID, "0x") {
d := new(web3hex.Decoder)
b := d.BigInt(chainID)
if d.Err() == nil {
return b
}
}
b := new(big.Int)
id, ok := b.SetString(chainID, 10)
if ok {
return id
}
return b.SetBytes([]byte(chainID))
}
17 changes: 17 additions & 0 deletions encoding/ethereum_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package encoding

import (
"math/big"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestGetEthChainID(t *testing.T) {
assert.Equal(t, big.NewInt(1234), GetEthChainID("1234"))
assert.Equal(t, big.NewInt(1234), GetEthChainID("0x4d2"))
chainID, ok := new(big.Int).SetString("28980219985052679991929851741845949978287371722649499714751652210", 10)
require.True(t, ok)
assert.Equal(t, chainID, GetEthChainID("FrogsEatApplesOnlyWhenClear"))
}
3 changes: 2 additions & 1 deletion encoding/rlp/rlp_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"testing"

"github.com/hyperledger/burrow/crypto"
"github.com/hyperledger/burrow/encoding"

"github.com/test-go/testify/require"
)
Expand Down Expand Up @@ -285,7 +286,7 @@ func TestEthRawTx(t *testing.T) {
To: to[:],
Amount: big.NewInt(232),
Data: []byte{1, 3, 4},
ChainID: crypto.GetEthChainID("flgoo"),
ChainID: encoding.GetEthChainID("flgoo"),
V: big.NewInt(272),
R: bigly,
S: bigly,
Expand Down
76 changes: 76 additions & 0 deletions encoding/web3hex/decoder.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package web3hex

import (
"fmt"
"math/big"
"strings"

"github.com/hyperledger/burrow/crypto"
"github.com/tmthrgd/go-hex"
)

type Decoder struct {
error
must bool
}

func (d *Decoder) Must() *Decoder {
return &Decoder{must: true}
}

func (d *Decoder) Err() error {
return d.error
}

func (d *Decoder) pushErr(err error) {
if d.must {
panic(err)
}
if d.error == nil {
d.error = err
}
}

func (d *Decoder) Bytes(hs string) []byte {
hexString := strings.TrimPrefix(hs, "0x")
// Ethereum returns odd-length hexString strings when it removes leading zeros
if len(hexString)%2 == 1 {
hexString = "0" + hexString
}
bs, err := hex.DecodeString(hexString)
if err != nil {
d.pushErr(fmt.Errorf("could not decode bytes from '%s': %w", hs, err))
}
return bs
}

func (d *Decoder) Address(hs string) crypto.Address {
if hs == "" {
return crypto.Address{}
}
address, err := crypto.AddressFromBytes(d.Bytes(hs))
if err != nil {
d.pushErr(fmt.Errorf("could not decode address from '%s': %w", hs, err))
}
return address
}

func (d *Decoder) BigInt(hs string) *big.Int {
return new(big.Int).SetBytes(d.Bytes(hs))
}

func (d *Decoder) Uint64(hs string) uint64 {
bi := d.BigInt(hs)
if !bi.IsUint64() {
d.pushErr(fmt.Errorf("%v is not uint64", bi))
}
return bi.Uint64()
}

func (d *Decoder) Int64(hs string) int64 {
bi := d.BigInt(hs)
if !bi.IsInt64() {
d.pushErr(fmt.Errorf("%v is not int64", bi))
}
return bi.Int64()
}
17 changes: 17 additions & 0 deletions encoding/web3hex/decoder_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package web3hex

import (
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestDecoder_Bytes(t *testing.T) {
d := new(Decoder)
assert.Equal(t, []byte{}, d.Bytes(""))
assert.Equal(t, []byte{1}, d.Bytes("0x1"))
assert.Equal(t, []byte{1}, d.Bytes("0x01"))
assert.Equal(t, []byte{1, 0xff}, d.Bytes("0x1ff"))
require.NoError(t, d.Err())
}
54 changes: 54 additions & 0 deletions encoding/web3hex/encoder.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package web3hex

import (
bin "encoding/binary"
"math/big"
"strings"

"github.com/hyperledger/burrow/crypto"
"github.com/tmthrgd/go-hex"
)

type encoder struct {
}

var Encoder = new(encoder)

func (e *encoder) Bytes(bs []byte) string {
return "0x" + hex.EncodeToString(bs)
}

func (e *encoder) BytesTrim(bs []byte) string {
if len(bs) == 0 {
return ""
}
str := hex.EncodeToString(bs)
// Ethereum expects leading zeros to be removed from RLP encodings (SMH)
str = strings.TrimLeft(str, "0")
if len(str) == 0 {
// Special case for zero
return "0x0"
}
return "0x" + str
}

func (e *encoder) BigInt(x *big.Int) string {
return e.BytesTrim(x.Bytes())
}

func (e *encoder) Uint64OmitEmpty(x uint64) string {
if x == 0 {
return ""
}
return e.Uint64(x)
}

func (e *encoder) Uint64(x uint64) string {
bs := make([]byte, 8)
bin.BigEndian.PutUint64(bs, x)
return e.BytesTrim(bs)
}

func (e *encoder) Address(address crypto.Address) string {
return e.BytesTrim(address.Bytes())
}
15 changes: 15 additions & 0 deletions encoding/web3hex/encoder_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package web3hex

import (
"testing"

"github.com/stretchr/testify/assert"
)

func TestEncoder_BytesTrim(t *testing.T) {
assert.Equal(t, "", Encoder.BytesTrim(nil))
assert.Equal(t, "", Encoder.BytesTrim([]byte{}))
assert.Equal(t, "0x0", Encoder.BytesTrim([]byte{0}))
assert.Equal(t, "0x1", Encoder.BytesTrim([]byte{1}))
assert.Equal(t, "0x1ff", Encoder.BytesTrim([]byte{1, 255}))
}
3 changes: 2 additions & 1 deletion execution/evm/contract.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"github.com/hyperledger/burrow/acm"
. "github.com/hyperledger/burrow/binary"
"github.com/hyperledger/burrow/crypto"
"github.com/hyperledger/burrow/encoding"
"github.com/hyperledger/burrow/execution/engine"
"github.com/hyperledger/burrow/execution/errors"
"github.com/hyperledger/burrow/execution/evm/abi"
Expand Down Expand Up @@ -488,7 +489,7 @@ func (c *Contract) execute(st engine.State, params engine.CallParams) ([]byte, e
c.debugf(" => %v\n", *params.Gas)

case CHAINID: // 0x46
id := crypto.GetEthChainID(st.Blockchain.ChainID())
id := encoding.GetEthChainID(st.Blockchain.ChainID())
stack.PushBigInt(id)
c.debugf(" => %X\n", id)

Expand Down
Loading

0 comments on commit 8d6630d

Please sign in to comment.