cmd/signer: methods to validate calldata against abi

This commit is contained in:
Martin Holst Swende 2017-11-26 00:41:38 +01:00
parent b8b84fec75
commit 08301bbcc0
No known key found for this signature in database
GPG key ID: 683B438C05A5DDF0
2 changed files with 186 additions and 0 deletions

107
cmd/signer/abihelper.go Normal file
View file

@ -0,0 +1,107 @@
// Copyright 2017 The go-ethereum Authors
// This file is part of go-ethereum.
//
// go-ethereum is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// go-ethereum is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
package main
import (
"fmt"
"github.com/ethereum/go-ethereum/accounts/abi"
"strings"
"bytes"
"github.com/ethereum/go-ethereum/common"
)
type decodedArgument struct {
soltype string
value interface{}
}
type decodedCallData struct {
signature string
name string
inputs []decodedArgument
}
func (arg decodedArgument) String() string {
return fmt.Sprintf("%v: %v", arg.soltype, arg.value)
}
func (cd decodedCallData) String() string {
args := make([]string, len(cd.inputs))
for i, arg := range cd.inputs {
args[i] = arg.String()
}
return fmt.Sprintf("%s(%s)", cd.name, strings.Join(args, ","))
}
// parseCallData matches the provided call data against the abi definition,
// and returns a struct containing the actual go-typed values
func parseCallData(calldata []byte, abidata string) (*decodedCallData, error) {
if len(calldata) < 4 {
return nil, fmt.Errorf("Invalid ABI-data, incomplete method signature of (%d bytes)", len(calldata))
}
abispec, err := abi.JSON(strings.NewReader(abidata))
if err != nil {
return nil, fmt.Errorf("Failed parsing JSON ABI: %v", err)
}
sigdata, argdata := calldata[:4], calldata[4:]
if len(argdata)%32 != 0 {
return nil, fmt.Errorf("Not ABI-encoded data; length should be a multiple of 32 (was %d)", len(argdata))
}
method := abispec.MethodById(sigdata)
if method == nil {
return nil, fmt.Errorf("Supplied ABI spec does not contain method signature in data: 0x%x", sigdata)
}
decoded := decodedCallData{signature: method.Sig(), name: method.Name}
for n, argument := range method.Inputs {
value, err := abi.ToGoType(n*32, argument.Type, argdata)
if err != nil {
return nil, fmt.Errorf("Failed to decode argument %d (signature %v): %v", n, method.Sig(), err)
} else {
decodedArg := decodedArgument{
soltype: argument.Type.String(),
value: value,
}
decoded.inputs = append(decoded.inputs, decodedArg)
}
}
// We're finished decoding the data. At this point, we encode the decoded data to see if it matches with the
// original data. If we didn't do that, it would e.g. be possible to stuff extra data into the arguments, which
// is not detected by merely decoding the data.
var (
gotypedArguments = make([]interface{}, len(decoded.inputs))
encoded []byte
)
for i, arg := range decoded.inputs {
gotypedArguments[i] = arg.value
}
encoded, err = abispec.Pack(method.Name, gotypedArguments...)
if !bytes.Equal(encoded, calldata){
exp := common.Bytes2Hex(encoded)
was := common.Bytes2Hex(calldata)
return nil, fmt.Errorf("WARNING: Supplied data is stuffed with extra data. %v \nWant %s\nHave %s", decoded,was, exp)
}
return &decoded, nil
}

View file

@ -0,0 +1,79 @@
// Copyright 2017 The go-ethereum Authors
// This file is part of go-ethereum.
//
// go-ethereum is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// go-ethereum is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
package main
import (
"github.com/ethereum/go-ethereum/common"
"testing"
)
func TestMartin(t *testing.T) {
// send(uint256) : a52c101e
// compareAndApprove(address,uint256,uint256) : 751e1079
// issue(address[],uint256) : 42958b54
jsondata := `
[
{"type":"function","name":"send","inputs":[{"name":"a","type":"uint256"}]},
{"type":"function","name":"compareAndApprove","inputs":[{"name":"a","type":"address"},{"name":"a","type":"uint256"},{"name":"a","type":"uint256"}]},
{"type":"function","name":"issue","inputs":[{"name":"a","type":"address[]"},{"name":"a","type":"uint256"}]},
{"type":"function","name":"sam","inputs":[{"name":"a","type":"bytes"},{"name":"a","type":"bool"},{"name":"a","type":"uint256[]"}]}
]`
//Expected failures
for _, hexdata := range []string{
"a52c101e00000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000042",
"a52c101e000000000000000000000000000000000000000000000000000000000000001200",
"a52c101e00000000000000000000000000000000000000000000000000000000000000",
"a52c101e",
"a52c10",
"",
"751e10790000000000000000000000000000000000000000000000000000000000000012",
"751e1079FFffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
"deadbeef00000000000000000000000000000000000000000000000000000000000000",
"42958b5400000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000042",
"a52c101e00ff0000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000042",
} {
_, err := parseCallData(common.Hex2Bytes(hexdata), jsondata)
if err == nil {
t.Errorf("Expected decoding to fail: %s", hexdata)
}
}
//Expected success
for _, hexdata := range []string{
// From https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI
"a5643bf20000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000464617665000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003",
"a52c101e0000000000000000000000000000000000000000000000000000000000000012",
"a52c101eFFffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
"751e1079000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
"42958b54" +
// start of dynamic type
"0000000000000000000000000000000000000000000000000000000000000040" +
//uint256
"0000000000000000000000000000000000000000000000000000000000000001" +
// length of array
"0000000000000000000000000000000000000000000000000000000000000002" +
// array values
"000000000000000000000000000000000000000000000000000000000000dead" +
"000000000000000000000000000000000000000000000000000000000000beef",
} {
_, err := parseCallData(common.Hex2Bytes(hexdata), jsondata)
if err != nil {
t.Errorf("Unexpected failure on input %s:\n %v (%d bytes) ", hexdata, err, len(common.Hex2Bytes(hexdata)))
}
}
}