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

Commit 8d6630d

Browse files
author
Silas Davis
committed
Support 0x-encoded integer ChainID for web3 integration
- Moved the web3 hex and ChainID handling code to encoding to prevent import cycles Signed-off-by: Silas Davis <[email protected]>
1 parent 0e91dad commit 8d6630d

22 files changed

+332
-289
lines changed

Diff for: crypto/crypto_test.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"bytes"
55
"crypto/rand"
66
"fmt"
7+
"math/big"
78
"testing"
89

910
"github.com/stretchr/testify/assert"
@@ -48,7 +49,7 @@ func TestSigning(t *testing.T) {
4849
msg := []byte(("Flipity flobity floo"))
4950
sig, err := pk.Sign(msg)
5051
require.NoError(t, err)
51-
ethSig, err := sig.GetEthSignature("floob")
52+
ethSig, err := sig.GetEthSignature(big.NewInt(12342))
5253
require.NoError(t, err)
5354
parity := ethSig.RecoveryIndex()
5455
require.True(t, parity == 0 || parity == 1)

Diff for: crypto/signature.go

+3-12
Original file line numberDiff line numberDiff line change
@@ -117,25 +117,16 @@ func (sig *Signature) String() string {
117117
return hex.EncodeUpperToString(sig.Signature)
118118
}
119119

120-
func GetEthChainID(chainID string) *big.Int {
121-
b := new(big.Int)
122-
id, ok := b.SetString(chainID, 10)
123-
if ok {
124-
return id
125-
}
126-
return b.SetBytes([]byte(chainID))
127-
}
128-
129-
func GetEthSignatureRecoveryID(chainID string, parity *big.Int) *big.Int {
120+
func GetEthSignatureRecoveryID(chainID *big.Int, parity *big.Int) *big.Int {
130121
// https://github.com/ethereum/EIPs/blob/b3bbee93dc8a775af6a6b2525c9ac5f70a7e5710/EIPS/eip-155.md
131122
v := new(big.Int)
132-
v.Mul(GetEthChainID(chainID), big2)
123+
v.Mul(chainID, big2)
133124
v.Add(v, parity)
134125
v.Add(v, ethereumRecoveryIDOffset)
135126
return v
136127
}
137128

138-
func (sig *Signature) GetEthSignature(chainID string) (*EIP155Signature, error) {
129+
func (sig *Signature) GetEthSignature(chainID *big.Int) (*EIP155Signature, error) {
139130
if sig.CurveType != CurveTypeSecp256k1 {
140131
return nil, fmt.Errorf("can only GetEthSignature for %v keys, but got %v",
141132
CurveTypeSecp256k1, sig.CurveType)

Diff for: crypto/signature_test.go

-16
This file was deleted.

Diff for: encoding/ethereum.go

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package encoding
2+
3+
import (
4+
"math/big"
5+
"strings"
6+
7+
"github.com/hyperledger/burrow/encoding/web3hex"
8+
)
9+
10+
// Convert Burrow's ChainID to a *big.Int so it can be used as a nonce for Ethereum signing.
11+
// For compatibility with Ethereum tooling this function first tries to interpret the ChainID as an integer encoded
12+
// either as an eth-style 0x-prefixed hex string or a base 10 integer, falling back to interpreting the string's
13+
// raw bytes as a big-endian integer
14+
func GetEthChainID(chainID string) *big.Int {
15+
if strings.HasPrefix(chainID, "0x") {
16+
d := new(web3hex.Decoder)
17+
b := d.BigInt(chainID)
18+
if d.Err() == nil {
19+
return b
20+
}
21+
}
22+
b := new(big.Int)
23+
id, ok := b.SetString(chainID, 10)
24+
if ok {
25+
return id
26+
}
27+
return b.SetBytes([]byte(chainID))
28+
}

Diff for: encoding/ethereum_test.go

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package encoding
2+
3+
import (
4+
"math/big"
5+
"testing"
6+
7+
"github.com/stretchr/testify/assert"
8+
"github.com/stretchr/testify/require"
9+
)
10+
11+
func TestGetEthChainID(t *testing.T) {
12+
assert.Equal(t, big.NewInt(1234), GetEthChainID("1234"))
13+
assert.Equal(t, big.NewInt(1234), GetEthChainID("0x4d2"))
14+
chainID, ok := new(big.Int).SetString("28980219985052679991929851741845949978287371722649499714751652210", 10)
15+
require.True(t, ok)
16+
assert.Equal(t, chainID, GetEthChainID("FrogsEatApplesOnlyWhenClear"))
17+
}

Diff for: encoding/rlp/rlp_test.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"testing"
66

77
"github.com/hyperledger/burrow/crypto"
8+
"github.com/hyperledger/burrow/encoding"
89

910
"github.com/test-go/testify/require"
1011
)
@@ -285,7 +286,7 @@ func TestEthRawTx(t *testing.T) {
285286
To: to[:],
286287
Amount: big.NewInt(232),
287288
Data: []byte{1, 3, 4},
288-
ChainID: crypto.GetEthChainID("flgoo"),
289+
ChainID: encoding.GetEthChainID("flgoo"),
289290
V: big.NewInt(272),
290291
R: bigly,
291292
S: bigly,

Diff for: encoding/web3hex/decoder.go

+76
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
package web3hex
2+
3+
import (
4+
"fmt"
5+
"math/big"
6+
"strings"
7+
8+
"github.com/hyperledger/burrow/crypto"
9+
"github.com/tmthrgd/go-hex"
10+
)
11+
12+
type Decoder struct {
13+
error
14+
must bool
15+
}
16+
17+
func (d *Decoder) Must() *Decoder {
18+
return &Decoder{must: true}
19+
}
20+
21+
func (d *Decoder) Err() error {
22+
return d.error
23+
}
24+
25+
func (d *Decoder) pushErr(err error) {
26+
if d.must {
27+
panic(err)
28+
}
29+
if d.error == nil {
30+
d.error = err
31+
}
32+
}
33+
34+
func (d *Decoder) Bytes(hs string) []byte {
35+
hexString := strings.TrimPrefix(hs, "0x")
36+
// Ethereum returns odd-length hexString strings when it removes leading zeros
37+
if len(hexString)%2 == 1 {
38+
hexString = "0" + hexString
39+
}
40+
bs, err := hex.DecodeString(hexString)
41+
if err != nil {
42+
d.pushErr(fmt.Errorf("could not decode bytes from '%s': %w", hs, err))
43+
}
44+
return bs
45+
}
46+
47+
func (d *Decoder) Address(hs string) crypto.Address {
48+
if hs == "" {
49+
return crypto.Address{}
50+
}
51+
address, err := crypto.AddressFromBytes(d.Bytes(hs))
52+
if err != nil {
53+
d.pushErr(fmt.Errorf("could not decode address from '%s': %w", hs, err))
54+
}
55+
return address
56+
}
57+
58+
func (d *Decoder) BigInt(hs string) *big.Int {
59+
return new(big.Int).SetBytes(d.Bytes(hs))
60+
}
61+
62+
func (d *Decoder) Uint64(hs string) uint64 {
63+
bi := d.BigInt(hs)
64+
if !bi.IsUint64() {
65+
d.pushErr(fmt.Errorf("%v is not uint64", bi))
66+
}
67+
return bi.Uint64()
68+
}
69+
70+
func (d *Decoder) Int64(hs string) int64 {
71+
bi := d.BigInt(hs)
72+
if !bi.IsInt64() {
73+
d.pushErr(fmt.Errorf("%v is not int64", bi))
74+
}
75+
return bi.Int64()
76+
}

Diff for: encoding/web3hex/decoder_test.go

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package web3hex
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/assert"
7+
"github.com/stretchr/testify/require"
8+
)
9+
10+
func TestDecoder_Bytes(t *testing.T) {
11+
d := new(Decoder)
12+
assert.Equal(t, []byte{}, d.Bytes(""))
13+
assert.Equal(t, []byte{1}, d.Bytes("0x1"))
14+
assert.Equal(t, []byte{1}, d.Bytes("0x01"))
15+
assert.Equal(t, []byte{1, 0xff}, d.Bytes("0x1ff"))
16+
require.NoError(t, d.Err())
17+
}

Diff for: encoding/web3hex/encoder.go

+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package web3hex
2+
3+
import (
4+
bin "encoding/binary"
5+
"math/big"
6+
"strings"
7+
8+
"github.com/hyperledger/burrow/crypto"
9+
"github.com/tmthrgd/go-hex"
10+
)
11+
12+
type encoder struct {
13+
}
14+
15+
var Encoder = new(encoder)
16+
17+
func (e *encoder) Bytes(bs []byte) string {
18+
return "0x" + hex.EncodeToString(bs)
19+
}
20+
21+
func (e *encoder) BytesTrim(bs []byte) string {
22+
if len(bs) == 0 {
23+
return ""
24+
}
25+
str := hex.EncodeToString(bs)
26+
// Ethereum expects leading zeros to be removed from RLP encodings (SMH)
27+
str = strings.TrimLeft(str, "0")
28+
if len(str) == 0 {
29+
// Special case for zero
30+
return "0x0"
31+
}
32+
return "0x" + str
33+
}
34+
35+
func (e *encoder) BigInt(x *big.Int) string {
36+
return e.BytesTrim(x.Bytes())
37+
}
38+
39+
func (e *encoder) Uint64OmitEmpty(x uint64) string {
40+
if x == 0 {
41+
return ""
42+
}
43+
return e.Uint64(x)
44+
}
45+
46+
func (e *encoder) Uint64(x uint64) string {
47+
bs := make([]byte, 8)
48+
bin.BigEndian.PutUint64(bs, x)
49+
return e.BytesTrim(bs)
50+
}
51+
52+
func (e *encoder) Address(address crypto.Address) string {
53+
return e.BytesTrim(address.Bytes())
54+
}

Diff for: encoding/web3hex/encoder_test.go

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package web3hex
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/assert"
7+
)
8+
9+
func TestEncoder_BytesTrim(t *testing.T) {
10+
assert.Equal(t, "", Encoder.BytesTrim(nil))
11+
assert.Equal(t, "", Encoder.BytesTrim([]byte{}))
12+
assert.Equal(t, "0x0", Encoder.BytesTrim([]byte{0}))
13+
assert.Equal(t, "0x1", Encoder.BytesTrim([]byte{1}))
14+
assert.Equal(t, "0x1ff", Encoder.BytesTrim([]byte{1, 255}))
15+
}

Diff for: execution/evm/contract.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"github.com/hyperledger/burrow/acm"
1212
. "github.com/hyperledger/burrow/binary"
1313
"github.com/hyperledger/burrow/crypto"
14+
"github.com/hyperledger/burrow/encoding"
1415
"github.com/hyperledger/burrow/execution/engine"
1516
"github.com/hyperledger/burrow/execution/errors"
1617
"github.com/hyperledger/burrow/execution/evm/abi"
@@ -488,7 +489,7 @@ func (c *Contract) execute(st engine.State, params engine.CallParams) ([]byte, e
488489
c.debugf(" => %v\n", *params.Gas)
489490

490491
case CHAINID: // 0x46
491-
id := crypto.GetEthChainID(st.Blockchain.ChainID())
492+
id := encoding.GetEthChainID(st.Blockchain.ChainID())
492493
stack.PushBigInt(id)
493494
c.debugf(" => %X\n", id)
494495

0 commit comments

Comments
 (0)