diff --git a/cmd/ethproof/ethproof.go b/cmd/ethproof/ethproof.go index d11bc9f..0327084 100644 --- a/cmd/ethproof/ethproof.go +++ b/cmd/ethproof/ethproof.go @@ -80,7 +80,7 @@ func main() { switch ttype { case token.TokenTypeMinime: balance, fullBalance, block, err := minime.ParseMinimeValue( - sproof.StorageProof[0].Value.String(), + sproof.StorageProof[0].Value, int(tokenData.Decimals), ) if err != nil { @@ -101,7 +101,7 @@ func main() { } case token.TokenTypeMapbased: balance, fullBalance, err := helpers.ValueToBalance( - sproof.StorageProof[0].Value.String(), + sproof.StorageProof[0].Value, int(tokenData.Decimals), ) if err != nil { diff --git a/ethstorageproof/ethstorageproof.go b/ethstorageproof/ethstorageproof.go index a48a626..cb212ba 100644 --- a/ethstorageproof/ethstorageproof.go +++ b/ethstorageproof/ethstorageproof.go @@ -5,7 +5,6 @@ package ethstorageproof import ( "bytes" "errors" - "math/big" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" @@ -72,9 +71,8 @@ func VerifyEthStorageProof(proof *StorageResult, storageHash common.Hash) (bool, var err error var value []byte - v := big.Int(*proof.Value) - if v.BitLen() != 0 { - value, err = rlp.EncodeToBytes(&v) + if len(proof.Value) != 0 { + value, err = rlp.EncodeToBytes(proof.Value) if err != nil { return false, err } @@ -85,6 +83,10 @@ func VerifyEthStorageProof(proof *StorageResult, storageHash common.Hash) (bool, // VerifyProof verifies that the path generated from key, following the nodes // in proof leads to a leaf with value, where the hashes are correct up to the // rootHash. +// WARNING: When the value is not found, `eth_getProof` will return "0x0" at +// the StorageProof `value` field. In order to verify the proof of non +// existence, you must set `value` to nil, *not* the RLP encoding of 0 or null +// (which would be 0x80). func VerifyProof(rootHash common.Hash, key []byte, value []byte, proof [][]byte) (bool, error) { proofDB := NewMemDB() for _, node := range proof { diff --git a/ethstorageproof/ethstorageproof_test.go b/ethstorageproof/ethstorageproof_test.go index c05ed64..1d4ba89 100644 --- a/ethstorageproof/ethstorageproof_test.go +++ b/ethstorageproof/ethstorageproof_test.go @@ -3,6 +3,7 @@ package ethstorageproof import ( "encoding/hex" "encoding/json" + "fmt" "strings" "testing" @@ -18,8 +19,8 @@ func TestVerify(t *testing.T) { verify bool }{ { - toBytes("0xa6eef7e35abe7026729641147f7915573c7e97b47efa546f5f6e3230263bcb49"), - toRLP("0x2710"), + toBytes(t, "0xa6eef7e35abe7026729641147f7915573c7e97b47efa546f5f6e3230263bcb49"), + asQuantity(t, "0x2710"), proofToBytes([]string{ "0xf871a0379a71a6fb36a75e085aff02beec9f5934b9648d24e2901da307492219608b3780a006a684f73e33f5c18739fd1339977f6fe328eb5cbe64239244b0cec88744355180808080a023866491ea0336f72e659c2a7daf61285de093b04fa353c48069a807c2ba845f808080808080808080", "0xe5a03eb5be412f275a18f6e4d622aee4ff40b21467c926224771b782d4c095d1444b83822710", @@ -27,16 +28,16 @@ func TestVerify(t *testing.T) { true, }, { - toBytes("0x0000000000000000000000000000000000000000000000000000000000000000"), - toRLP("0x6b746c656500000000000000000000000000000000000000000000000000000a"), + toBytes(t, "0x0000000000000000000000000000000000000000000000000000000000000000"), + asQuantity(t, "0x6b746c656500000000000000000000000000000000000000000000000000000a"), proofToBytes([]string{ "0xf844a120290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563a1a06b746c656500000000000000000000000000000000000000000000000000000a", }), true, }, { - toBytes("0x0000000000000000000000000000000000000000000000000000000000000001"), - toRLP("0x6b736c656500000000000000000000000000000000000000000000000000000a"), + toBytes(t, "0x0000000000000000000000000000000000000000000000000000000000000001"), + asQuantity(t, "0x6b736c656500000000000000000000000000000000000000000000000000000a"), proofToBytes([]string{ "0xf8518080a05d2423f2a53dd794285a39f2c7ba5c0c24cba719d05e3bdd1f5eefae445b34358080808080808080a01ec473dfa012cb440907fa4f2c34be3e733c92430d98a48831700bc8ab159f5d8080808080", "0xf843a0310e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6a1a06b736c656500000000000000000000000000000000000000000000000000000a", @@ -44,8 +45,8 @@ func TestVerify(t *testing.T) { true, }, { - toBytes("0x0000000000000000000000000000000000000000000000000000000000000000"), - mustRLPEncode(uint32(0x9)), + toBytes(t, "0x0000000000000000000000000000000000000000000000000000000000000000"), + asQuantity(t, "0x9"), proofToBytes([]string{ "0xf8518080a05d2423f2a53dd794285a39f2c7ba5c0c24cba719d05e3bdd1f5eefae445b34358080808080808080a01ec473dfa012cb440907fa4f2c34be3e733c92430d98a48831700bc8ab159f5d8080808080", @@ -54,8 +55,8 @@ func TestVerify(t *testing.T) { true, }, { - toBytes("0x0000000000000000000000000000000000000000000000000000000000000000"), - toRLP("0x6b736c656500000000000000000000000000000000000000000000000000000a"), + toBytes(t, "0x0000000000000000000000000000000000000000000000000000000000000000"), + asQuantity(t, "0x6b736c656500000000000000000000000000000000000000000000000000000a"), proofToBytes([]string{ "0xf871a0379a71a6fb36a75e085aff02beec9f5934b9648d24e2901da307492219608b3780a006a684f73e33f5c18739fd1339977f6fe328eb5cbe64239244b0cec88744355180808080a023866491ea0336f72e659c2a7daf61285de093b04fa353c48069a807c2ba845f808080808080808080", "0xf843a0390decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563a1a06b736c656500000000000000000000000000000000000000000000000000000a", @@ -63,8 +64,8 @@ func TestVerify(t *testing.T) { true, }, { - toBytes("0xac33ff75c19e70fe83507db0d683fd3465c996598dc972688b7ace676c89077b"), - toRLP("0x6b746c656500000000000000000000000000000000000000000000000000000a"), + toBytes(t, "0xac33ff75c19e70fe83507db0d683fd3465c996598dc972688b7ace676c89077b"), + asQuantity(t, "0x6b746c656500000000000000000000000000000000000000000000000000000a"), proofToBytes([]string{ "0xf871a0379a71a6fb36a75e085aff02beec9f5934b9648d24e2901da307492219608b3780a006a684f73e33f5c18739fd1339977f6fe328eb5cbe64239244b0cec88744355180808080a023866491ea0336f72e659c2a7daf61285de093b04fa353c48069a807c2ba845f808080808080808080", "0xf843a03d2944a272ac5bae96b5bd2f67b6c13276d541dc09eb1cf414d96b19a09e1c2fa1a06b746c656500000000000000000000000000000000000000000000000000000a", @@ -72,8 +73,8 @@ func TestVerify(t *testing.T) { true, }, { - toBytes("ac33ff75c19e70fe83507db0d683fd3465c996598dc972688b7ace676c89077b"), - toRLP("6b746c656500000000000000000000000000000000000000000000000000000a"), + toBytes(t, "ac33ff75c19e70fe83507db0d683fd3465c996598dc972688b7ace676c89077b"), + asQuantity(t, "6b746c656500000000000000000000000000000000000000000000000000000a"), proofToBytes([]string{ "0xf871a0379a71a6fb36a75e085aff02beec9f5934b9648d24e2901da307492219608b3780a006a684f73e33f5c18739fd1339977f6fe328eb5cbe64239244b0cec88744355180808080a023866491ea0336f72e659c2a7daf61285de093b04fa353c48069a807c2ba845f808080808080808080", "0xf843a03d2944a272ac5bae96b5bd2f67b6c13276d541dc09eb1cf414d96b19a09e1c2fa1a06b746c656500000000000000000000000000000000000000000000000000000a", @@ -81,8 +82,8 @@ func TestVerify(t *testing.T) { true, }, { - toBytes("0xac33ff75c19e70fe83507db0d683fd3465c996598dc972688b7ace676c89077b"), - toRLP(""), + toBytes(t, "0xac33ff75c19e70fe83507db0d683fd3465c996598dc972688b7ace676c89077b"), + asQuantity(t, "0x0"), proofToBytes([]string{ "0xf871a0379a71a6fb36a75e085aff02beec9f5934b9648d24e2901da307492219608b3780a006a684f73e33f5c18739fd1339977f6fe328eb5cbe64239244b0cec88744355180808080a023866491ea0336f72e659c2a7daf61285de093b04fa353c48069a807c2ba845f808080808080808080", "0xf843a03d2944a272ac5bae96b5bd2f67b6c13276d541dc09eb1cf414d96b19a09e1c2fa1a06b746c656500000000000000000000000000000000000000000000000000000a", @@ -90,7 +91,7 @@ func TestVerify(t *testing.T) { false, }, { - toBytes("25a7599f71cc507621bc7f672ca927b1800f73b2a3e7c8d22648bda4ccb055b5"), + toBytes(t, "25a7599f71cc507621bc7f672ca927b1800f73b2a3e7c8d22648bda4ccb055b5"), nil, proofToBytes([]string{"0xf90211a0dcc1e39d0a69b99e6ec5ec20c140cfe39a966d665c5eab729af9e5fa4c3778bca01469877a7a775ef8cfefd47ede98bb4fc04e2b63b5384f3528a34d918c335259a05a71a831adbc7ac9df83e4bcfff8b835f4e8ac584bb3b0d36c9f1421c368641ea0a28c6487a3018b15941fd7a89d6b4e909cf8ab50f669ee811c56141894539f07a0a1fffbe9ea9bde9f8b3a9c20d3275c54a0d9da8b6f053d47a12f1ed5f1aadbe9a0ec098bcc6e1a1e291e510db5c2f0df76e18e5a8e03cf476dfe251bda5605e810a08692406bc6f582c72572b92526747b2f6297af87dda1844a8932c2b4864842f5a03700357d763fd3935dd7c585d621da5b4a82e7d237169acf9d18618ca3aee2f6a02435caf55e43ddb879b3962e833ae560c906bf978ad6a0efd50adcf97f2e7111a092895b092500fa7f2068a04cda020cdd3e2d5e4793035ce5bca7e7c25fd1c067a084c852e8b21b0e1e1bc59067436152b717f355381d668025ba4e6bf6c6365270a0eca3831f7ad609a22ba6f4c52b3c1e9c571ff2649714449cfcf8e62d87e847b6a06c8ac45455a0bf559abf377c95e639e38c0d9b881fb5e1483c5736c130760d66a08218bdc141d4b3edca01320b16407f0da401de8d8186ea720baf9994c835796fa0ef5f7eccd4a593b267c717addbda2958817cf19be3a925284bd861470a1597aaa09b9e28a91bf0929f2bfc25ef5a59183d01f7122947c044a4fcd636427853fc0980", "0xf90211a09b71ba939391f11873619f1485b28ccff4f82c307ada11ebc55c89c399cb03ada08707d4ee35e0cddf0b1c5e0e741b9e5a06a845492e69335015d3e15ac071e93fa02b259d0a74389c95163ac48b7d7cc21fa6c4b88fd0e1a7cc11ec2705f298f143a0a8e47fb85490b054a6e5a41c3b6c4223c48f96b6e9a65a35c42d1f1c642af57aa0ed282db93aa4f7c1bd51c5c53319113d7af33d8638001159aa03f6f8e49faa7fa0442736a22acdabc8a748d4f9509ee60c31380a557b1b379f8bf218d34fc35c50a025dbee8981097d50c7d9b63bd517687c1437d87b9412afb1622b1f2ec2567b81a00b5890040ce417e090b1f4db3bf5b821a64b00fe5030076ba0057cb984656bc6a0b984e8c8307d9f125e2a512a6df13ca5c726e02f2cbdd08c1ff28791a42471b8a0033cfe45191e18d6c24754d719acc27004be45e7c00d77d453cf0300f622ec86a03febfa8e3a9af3ad3ed4b1bbcc000e8f375c198955347960a060c4df7e8d6262a01314a87f953887b83e268a036fea7a21cb23b6e9bc5a5a1342b638b359b93f34a060422f3c85e7493f3413afc6e73ef447372fcc22271e0f9fd25c742da26aa217a042f0be13d42d5001c8674b6df4ff5fda166b55276051ce00339d289c551f754aa00b1d9f4dd46cc489627374138ec313d38aeeb50baa7f6556f4bca64661ea80c8a039451466ed1b5d02a52cb962de7c52933cd4eb4f08f2ddd0ceff96c512f853d880", @@ -102,8 +103,8 @@ func TestVerify(t *testing.T) { true, }, { - toBytes("c2de2619bab76beef95434a377cbb768c64eefaf75bf29ef7065b6637bd6d201"), - toRLP("0x06f05b59d3b20000"), + toBytes(t, "c2de2619bab76beef95434a377cbb768c64eefaf75bf29ef7065b6637bd6d201"), + asQuantity(t, "0x6f05b59d3b20000"), proofToBytes([]string{ "0xf9013180a00740dd6c24a8afbe4a6a82f07661ec6632dbc24a000d46d67b54d31f7a50a0cba007b0f9b46e3b240859292ba8d37485f7ae9aea12df6678554ddbcce68159391380a0e4e5615581505df363f71539f1ad885a287d77c7e23047b1eefb54b03352d8b980a0afbcc7bc6e755d9d18d64d739ec4fddb77502e8ea15f6d539da3c9d2462fffc4a0f3bc3263c4351b24d5123773632b6c00ae4c6e98e842a43539f6b7f8a369ef64a060a62c7496da939d10ba2949cb4ac1e104e9ef86dd79c56b674f87245507db3f8080a031bf08ec19f881b068fe1d40cfbf411824be654f5d62f5d0b0c3ad43f72995cca0714766957876d306a626b1bfeb594f7902e178d400221957a62435c91185fc2ba05e872405ac6aca97f5bb9f8a937763e0b1683d2d2bba3e17e2bb5b8ebe9ba16b808080", "0xeba03de5c840e393521c7ff396023c973083a5d40a34e3caa72df575448f9c075661898806f05b59d3b20000", @@ -112,8 +113,8 @@ func TestVerify(t *testing.T) { }, { - toBytes("0a19943e412e0d591f8c1e2adf8224c6b15cee1294b7799f5b246b233709eab0"), - toRLP("0x0f43fc2c04ee0000"), + toBytes(t, "0a19943e412e0d591f8c1e2adf8224c6b15cee1294b7799f5b246b233709eab0"), + asQuantity(t, "0xf43fc2c04ee0000"), proofToBytes([]string{ "0xf9013180a00740dd6c24a8afbe4a6a82f07661ec6632dbc24a000d46d67b54d31f7a50a0cba007b0f9b46e3b240859292ba8d37485f7ae9aea12df6678554ddbcce68159391380a0e4e5615581505df363f71539f1ad885a287d77c7e23047b1eefb54b03352d8b980a0afbcc7bc6e755d9d18d64d739ec4fddb77502e8ea15f6d539da3c9d2462fffc4a0f3bc3263c4351b24d5123773632b6c00ae4c6e98e842a43539f6b7f8a369ef64a060a62c7496da939d10ba2949cb4ac1e104e9ef86dd79c56b674f87245507db3f8080a031bf08ec19f881b068fe1d40cfbf411824be654f5d62f5d0b0c3ad43f72995cca0714766957876d306a626b1bfeb594f7902e178d400221957a62435c91185fc2ba05e872405ac6aca97f5bb9f8a937763e0b1683d2d2bba3e17e2bb5b8ebe9ba16b808080", "0xeba03722ad3bc234001231f62bd4846e8a1f8b9eb7329931dd914156b6effca198cf89880f43fc2c04ee0000", @@ -122,8 +123,8 @@ func TestVerify(t *testing.T) { }, { - toBytes("74e56920b06fb78abf807c6931544c2aa09f5b8866249cd912f9932b65527a6a"), - toRLP("0x02c68af0bb140000"), + toBytes(t, "74e56920b06fb78abf807c6931544c2aa09f5b8866249cd912f9932b65527a6a"), + asQuantity(t, "0x2c68af0bb140000"), proofToBytes([]string{ "0xf9013180a00740dd6c24a8afbe4a6a82f07661ec6632dbc24a000d46d67b54d31f7a50a0cba007b0f9b46e3b240859292ba8d37485f7ae9aea12df6678554ddbcce68159391380a0e4e5615581505df363f71539f1ad885a287d77c7e23047b1eefb54b03352d8b980a0afbcc7bc6e755d9d18d64d739ec4fddb77502e8ea15f6d539da3c9d2462fffc4a0f3bc3263c4351b24d5123773632b6c00ae4c6e98e842a43539f6b7f8a369ef64a060a62c7496da939d10ba2949cb4ac1e104e9ef86dd79c56b674f87245507db3f8080a031bf08ec19f881b068fe1d40cfbf411824be654f5d62f5d0b0c3ad43f72995cca0714766957876d306a626b1bfeb594f7902e178d400221957a62435c91185fc2ba05e872405ac6aca97f5bb9f8a937763e0b1683d2d2bba3e17e2bb5b8ebe9ba16b808080", "0xf8518080a0f97cd0e4c4948fb939e5b65edd4a1ef1a3116e9067c65417c281cd82bfb4685ba07f60d6a908b996ecc5e67987f4961b881bc915cc8fb788d91d5146c3ba08b7b480808080808080808080808080", @@ -132,8 +133,8 @@ func TestVerify(t *testing.T) { true, }, { - toBytes("e377ba8914b6e3793dcb44495b80e0abb6697bb2d526bef4b103a2ca45571a18"), - toRLP("0x09b6e64a8ec60000"), + toBytes(t, "e377ba8914b6e3793dcb44495b80e0abb6697bb2d526bef4b103a2ca45571a18"), + asQuantity(t, "0x9b6e64a8ec60000"), proofToBytes([]string{ "0xf9013180a00740dd6c24a8afbe4a6a82f07661ec6632dbc24a000d46d67b54d31f7a50a0cba007b0f9b46e3b240859292ba8d37485f7ae9aea12df6678554ddbcce68159391380a0e4e5615581505df363f71539f1ad885a287d77c7e23047b1eefb54b03352d8b980a0afbcc7bc6e755d9d18d64d739ec4fddb77502e8ea15f6d539da3c9d2462fffc4a0f3bc3263c4351b24d5123773632b6c00ae4c6e98e842a43539f6b7f8a369ef64a060a62c7496da939d10ba2949cb4ac1e104e9ef86dd79c56b674f87245507db3f8080a031bf08ec19f881b068fe1d40cfbf411824be654f5d62f5d0b0c3ad43f72995cca0714766957876d306a626b1bfeb594f7902e178d400221957a62435c91185fc2ba05e872405ac6aca97f5bb9f8a937763e0b1683d2d2bba3e17e2bb5b8ebe9ba16b808080", "0xeba035fa9266a36d8b2833bc56464af71ecb47d5ba3fcb5a2adb5a0b8f12254580ba898809b6e64a8ec60000", @@ -141,8 +142,8 @@ func TestVerify(t *testing.T) { true, }, { - toBytes("96bca652e37bd5c8c08f80295480625858aa664924b5546a893fefaed4e281c7"), - toRLP("0x0429d069189e0000"), + toBytes(t, "96bca652e37bd5c8c08f80295480625858aa664924b5546a893fefaed4e281c7"), + asQuantity(t, "0x429d069189e0000"), proofToBytes([]string{ "0xf9013180a00740dd6c24a8afbe4a6a82f07661ec6632dbc24a000d46d67b54d31f7a50a0cba007b0f9b46e3b240859292ba8d37485f7ae9aea12df6678554ddbcce68159391380a0e4e5615581505df363f71539f1ad885a287d77c7e23047b1eefb54b03352d8b980a0afbcc7bc6e755d9d18d64d739ec4fddb77502e8ea15f6d539da3c9d2462fffc4a0f3bc3263c4351b24d5123773632b6c00ae4c6e98e842a43539f6b7f8a369ef64a060a62c7496da939d10ba2949cb4ac1e104e9ef86dd79c56b674f87245507db3f8080a031bf08ec19f881b068fe1d40cfbf411824be654f5d62f5d0b0c3ad43f72995cca0714766957876d306a626b1bfeb594f7902e178d400221957a62435c91185fc2ba05e872405ac6aca97f5bb9f8a937763e0b1683d2d2bba3e17e2bb5b8ebe9ba16b808080", "0xf8518080a0f97cd0e4c4948fb939e5b65edd4a1ef1a3116e9067c65417c281cd82bfb4685ba07f60d6a908b996ecc5e67987f4961b881bc915cc8fb788d91d5146c3ba08b7b480808080808080808080808080", @@ -153,8 +154,18 @@ func TestVerify(t *testing.T) { } for i, tt := range tests { + var err error + var value []byte + if len(tt.value) != 0 { + value, err = rlp.EncodeToBytes(tt.value) + if err != nil { + t.Fatalf("can't rlp.EncodeToBytes: %v", err) + } + } + + fmt.Printf("= %v -> %v\n", tt.value, value) if vp, err := VerifyProof(crypto.Keccak256Hash(tt.proof[0]), tt.key, - tt.value, tt.proof); vp != tt.verify { + value, tt.proof); vp != tt.verify { t.Errorf("testcase %d: want %v, got %v (err: %v)\n", i, tt.verify, !tt.verify, err) } } @@ -164,24 +175,20 @@ func removeHexPrefix(s string) string { return strings.TrimPrefix(s, "0x") } -func toBytes(s string) []byte { +func toBytes(t *testing.T, s string) []byte { n, err := hex.DecodeString(removeHexPrefix(s)) if err != nil { - panic(err) + t.Fatalf("can't hex.DecodeString: %v", err) } return n } -func mustRLPEncode(v interface{}) []byte { - value, err := rlp.EncodeToBytes(v) - if err != nil { - panic(err) +func asQuantity(t *testing.T, s string) []byte { + var q QuantityBytes + if err := q.UnmarshalText([]byte(s)); err != nil { + t.Fatalf("can't q.UnmarshalText: %v", err) } - return value -} - -func toRLP(s string) []byte { - return mustRLPEncode(toBytes(s)) + return q } func proofToBytes(proof []string) [][]byte { @@ -247,7 +254,7 @@ func Test_eipProof(t *testing.T) { if ok, err := VerifyEthStorageProof(&sp.StorageProof[0], sp.StorageHash); !ok { t.Errorf("proof must be valid but it is invalid: (%v)", err) } - sp.StorageProof[0].Key = toBytes("0x49c4c8b2db715e9f7e1d3306b9f6860a389635dfb3943db23f1005544a50fbb2") + sp.StorageProof[0].Key = toBytes(t, "0x49c4c8b2db715e9f7e1d3306b9f6860a389635dfb3943db23f1005544a50fbb2") if ok, _ := VerifyEIP1186(&sp); ok { t.Errorf("proof must be invalid but it is valid") } diff --git a/ethstorageproof/types.go b/ethstorageproof/types.go index 79df657..bd30a69 100644 --- a/ethstorageproof/types.go +++ b/ethstorageproof/types.go @@ -2,7 +2,6 @@ package ethstorageproof import ( "bytes" - "encoding/hex" "encoding/json" "fmt" "math/big" @@ -11,36 +10,34 @@ import ( "github.com/ethereum/go-ethereum/common/hexutil" ) -// BytesHex marshals/unmarshals as a JSON string in hex with 0x prefix. The empty -// slice marshals as "0x". -type BytesHex []byte +// QuantityBytes marshals/unmarshals as a JSON string in hex with 0x prefix encoded +// as a QUANTITY. The empty slice marshals as "0x0". +type QuantityBytes []byte // MarshalText implements encoding.TextMarshaler -func (b BytesHex) MarshalText() ([]byte, error) { - result := make([]byte, len(b)*2+2) - copy(result, `0x`) - hex.Encode(result[2:], b) - return result, nil +func (q QuantityBytes) MarshalText() ([]byte, error) { + return []byte(fmt.Sprintf("0x%v", + new(big.Int).SetBytes(q).Text(16))), nil } // UnmarshalText implements encoding.TextUnmarshaler. -func (b *BytesHex) UnmarshalText(input []byte) error { +func (q *QuantityBytes) UnmarshalText(input []byte) error { input = bytes.TrimPrefix(input, []byte("0x")) - dec := make([]byte, len(input)/2) - if _, err := hex.Decode(dec, input); err != nil { - return err + v, ok := new(big.Int).SetString(string(input), 16) + if !ok { + return fmt.Errorf("invalid hex input") } - *b = dec + *q = v.Bytes() return nil } -// SliceBytesHex marshals/unmarshals as a JSON vector of strings with in hex with +// SliceData marshals/unmarshals as a JSON vector of strings with in hex with // 0x prefix. -type SliceBytesHex [][]byte +type SliceData [][]byte // MarshalText implements encoding.TextMarshaler -func (s SliceBytesHex) MarshalJSON() ([]byte, error) { - bs := make([]BytesHex, len(s)) +func (s SliceData) MarshalJSON() ([]byte, error) { + bs := make([]hexutil.Bytes, len(s)) for i, b := range s { bs[i] = b } @@ -48,8 +45,8 @@ func (s SliceBytesHex) MarshalJSON() ([]byte, error) { } // UnmarshalText implements encoding.TextUnmarshaler. -func (s *SliceBytesHex) UnmarshalJSON(data []byte) error { - var bs []BytesHex +func (s *SliceData) UnmarshalJSON(data []byte) error { + var bs []hexutil.Bytes if err := json.Unmarshal(data, &bs); err != nil { return err } @@ -60,26 +57,60 @@ func (s *SliceBytesHex) UnmarshalJSON(data []byte) error { return nil } -// StorageProof allows unmarshaling the object returned by `eth_getProof`: -// https://eips.ethereum.org/EIPS/eip-1186 +// StorageProof allows unmarshaling the object returned by `eth_getProof`. +// From https://eips.ethereum.org/EIPS/eip-1186: +// +// Parameters +// +// DATA, 20 Bytes - address of the account. +// ARRAY, 32 Bytes - array of storage-keys which should be proofed and +// included. See eth_getStorageAt +// QUANTITY|TAG - integer block number, or the string "latest" or +// "earliest", see the default block parameter +// +// Returns +// +// Object - A account object: +// +// balance: QUANTITY - the balance of the account. See eth_getBalance +// codeHash: DATA, 32 Bytes - hash of the code of the account. For a simple +// Account without code it will return +// "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470" +// nonce: QUANTITY, - nonce of the account. See eth_getTransactionCount +// storageHash: DATA, 32 Bytes - SHA3 of the StorageRoot. All storage will +// deliver a MerkleProof starting with this rootHash. +// accountProof: ARRAY - Array of rlp-serialized MerkleTree-Nodes, starting +// with the stateRoot-Node, following the path of the SHA3 (address) as +// key. +// +// storageProof: ARRAY - Array of storage-entries as requested. Each entry is a object with these properties: +// key: QUANTITY - the requested storage key +// value: QUANTITY - the storage value +// proof: ARRAY - Array of rlp-serialized MerkleTree-Nodes, starting +// with the storageHash-Node, following the path of the SHA3 (key) as +// path. +// +// NOTE: QUANTITY is supposed to follow this spec: +// https://infura.io/docs/ethereum#section/Value-encoding/Quantity but +// go-ethereum sometimes gives the string without the `0x` prefix type StorageProof struct { - StateRoot common.Hash `json:"stateRoot"` Height *big.Int `json:"height"` Address common.Address `json:"address"` - AccountProof SliceBytesHex `json:"accountProof"` Balance *hexutil.Big `json:"balance"` CodeHash common.Hash `json:"codeHash"` Nonce hexutil.Uint64 `json:"nonce"` + StateRoot common.Hash `json:"stateRoot"` StorageHash common.Hash `json:"storageHash"` + AccountProof SliceData `json:"accountProof"` StorageProof []StorageResult `json:"storageProof"` } // StorageResult is an object from StorageProof that contains a proof of // storage. type StorageResult struct { - Key BytesHex `json:"key"` - Value *hexutil.Big `json:"value"` - Proof SliceBytesHex `json:"proof"` + Key QuantityBytes `json:"key"` + Value QuantityBytes `json:"value"` + Proof SliceData `json:"proof"` } // MemDB is an ethdb.KeyValueReader implementation which is not thread safe and diff --git a/helpers/helpers.go b/helpers/helpers.go index cf8cdb4..db288c9 100644 --- a/helpers/helpers.go +++ b/helpers/helpers.go @@ -37,11 +37,7 @@ func GetMapSlot(holder string, position int) ([32]byte, error) { // ValueToBalance takes a RLP encoded hexadecimal string and the number of decimals and returns // the balance as a big.Float number. -func ValueToBalance(hexValue string, decimals int) (*big.Float, *big.Int, error) { - value, err := hex.DecodeString(TrimHex(hexValue)) - if err != nil { - return nil, nil, err - } +func ValueToBalance(value []byte, decimals int) (*big.Float, *big.Int, error) { // Parse balance value amount := new(big.Float) value = common.TrimLeftZeroes(value) diff --git a/token/mapbased/mapbased.go b/token/mapbased/mapbased.go index 6c7c310..b7b12aa 100644 --- a/token/mapbased/mapbased.go +++ b/token/mapbased/mapbased.go @@ -3,7 +3,6 @@ package mapbased import ( "bytes" "context" - "encoding/hex" "errors" "fmt" "math/big" @@ -110,7 +109,7 @@ func (m *Mapbased) DiscoverSlot(holder common.Address) (int, *big.Float, error) } // Parse balance value - amount, _, err := helpers.ValueToBalance(fmt.Sprintf("%x", value), int(tokenData.Decimals)) + amount, _, err := helpers.ValueToBalance(value, int(tokenData.Decimals)) if err != nil { continue } @@ -163,11 +162,7 @@ func VerifyProof(holder common.Address, storageRoot common.Hash, } // Check value balances matches - proofValue, err := hex.DecodeString(helpers.TrimHex(proof.Value.String())) - if err != nil { - return err - } - proofBalance, _ := new(big.Int).SetString(fmt.Sprintf("%x", proofValue), 16) + proofBalance, _ := new(big.Int).SetString(fmt.Sprintf("%x", proof.Value), 16) if targetBalance.Cmp(proofBalance) != 0 { return fmt.Errorf("proof balance and provided balance mismatch (%s != %s)", proofBalance.String(), targetBalance.String()) diff --git a/token/minime/helpers.go b/token/minime/helpers.go index 01292c3..1bf07fa 100644 --- a/token/minime/helpers.go +++ b/token/minime/helpers.go @@ -1,7 +1,6 @@ package minime import ( - "encoding/hex" "fmt" "math" "math/big" @@ -43,7 +42,7 @@ func VerifyProof(holder common.Address, storageRoot common.Hash, } // Extract balance and block from the minime proof - _, proof0Balance, proof0Block, err := ParseMinimeValue(proofs[0].Value.String(), 1) + _, proof0Balance, proof0Block, err := ParseMinimeValue(proofs[0].Value, 1) if err != nil { return err } @@ -62,8 +61,8 @@ func VerifyProof(holder common.Address, storageRoot common.Hash, // Check if the proof1 is a proof of non existence (so proof0 is the last checkpoint). // If not the last, then check the target block is - if proofs[1].Value.String() != "0x0" { - _, _, proof1Block, err := ParseMinimeValue(proofs[1].Value.String(), 1) + if len(proofs[1].Value) != 0 { + _, _, proof1Block, err := ParseMinimeValue(proofs[1].Value, 1) if err != nil { return err } @@ -99,12 +98,7 @@ func VerifyProof(holder common.Address, storageRoot common.Hash, // // Returns the float balance (taking into account the decimals), the full integer without taking into // account the decimals and the Ethereum block number for the checkpoint. -func ParseMinimeValue(hexValue string, decimals int) (*big.Float, *big.Int, *big.Int, error) { - value, err := hex.DecodeString(helpers.TrimHex(hexValue)) - if err != nil { - return nil, nil, nil, err - } - +func ParseMinimeValue(value []byte, decimals int) (*big.Float, *big.Int, *big.Int, error) { // hexValue could be left zeroes trimed, so we need to expand it to 32 bytes value = common.LeftPadBytes(value, 32) mblock := new(big.Int).SetBytes(common.TrimLeftZeroes(value[16:])) diff --git a/token/minime/minime.go b/token/minime/minime.go index e4a02d8..794718f 100644 --- a/token/minime/minime.go +++ b/token/minime/minime.go @@ -193,7 +193,7 @@ func (m *Minime) getMinimeAtPosition(holder common.Address, mapIndexSlot, return nil, nil, nil, err } - balance, _, mblock, err := ParseMinimeValue(fmt.Sprintf("%x", value), int(token.Decimals)) + balance, _, mblock, err := ParseMinimeValue(value, int(token.Decimals)) if err != nil { return nil, nil, nil, err }