aboutsummaryrefslogtreecommitdiffstats
path: root/signer/core/validation.go
blob: 4d64567f644e6c62bc15155d5e23b8a6ff82bde6 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
// Copyright 2018 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 core

import (
    "bytes"
    "errors"
    "fmt"
    "math/big"
    "regexp"

    "github.com/ethereum/go-ethereum/common"
)

// The validation package contains validation checks for transactions
// - ABI-data validation
// - Transaction semantics validation
// The package provides warnings for typical pitfalls

type Validator struct {
    db *AbiDb
}

func NewValidator(db *AbiDb) *Validator {
    return &Validator{db}
}

func testSelector(selector string, data []byte) (*decodedCallData, error) {
    if selector == "" {
        return nil, fmt.Errorf("selector not found")
    }
    abiData, err := MethodSelectorToAbi(selector)
    if err != nil {
        return nil, err
    }
    info, err := parseCallData(data, string(abiData))
    if err != nil {
        return nil, err
    }
    return info, nil

}

// validateCallData checks if the ABI-data + methodselector (if given) can be parsed and seems to match
func (v *Validator) validateCallData(msgs *ValidationMessages, data []byte, methodSelector *string) {
    if len(data) == 0 {
        return
    }
    if len(data) < 4 {
        msgs.warn("Tx contains data which is not valid ABI")
        return
    }
    if arglen := len(data) - 4; arglen%32 != 0 {
        msgs.warn(fmt.Sprintf("Not ABI-encoded data; length should be a multiple of 32 (was %d)", arglen))
    }
    var (
        info *decodedCallData
        err  error
    )
    // Check the provided one
    if methodSelector != nil {
        info, err = testSelector(*methodSelector, data)
        if err != nil {
            msgs.warn(fmt.Sprintf("Tx contains data, but provided ABI signature could not be matched: %v", err))
        } else {
            msgs.info(info.String())
            //Successfull match. add to db if not there already (ignore errors there)
            v.db.AddSignature(*methodSelector, data[:4])
        }
        return
    }
    // Check the db
    selector, err := v.db.LookupMethodSelector(data[:4])
    if err != nil {
        msgs.warn(fmt.Sprintf("Tx contains data, but the ABI signature could not be found: %v", err))
        return
    }
    info, err = testSelector(selector, data)
    if err != nil {
        msgs.warn(fmt.Sprintf("Tx contains data, but provided ABI signature could not be matched: %v", err))
    } else {
        msgs.info(info.String())
    }
}

// validateSemantics checks if the transactions 'makes sense', and generate warnings for a couple of typical scenarios
func (v *Validator) validate(msgs *ValidationMessages, txargs *SendTxArgs, methodSelector *string) error {
    // Prevent accidental erroneous usage of both 'input' and 'data'
    if txargs.Data != nil && txargs.Input != nil && !bytes.Equal(*txargs.Data, *txargs.Input) {
        // This is a showstopper
        return errors.New(`Ambiguous request: both "data" and "input" are set and are not identical`)
    }
    var (
        data []byte
    )
    // Place data on 'data', and nil 'input'
    if txargs.Input != nil {
        txargs.Data = txargs.Input
        txargs.Input = nil
    }
    if txargs.Data != nil {
        data = *txargs.Data
    }

    if txargs.To == nil {
        //Contract creation should contain sufficient data to deploy a contract
        // A typical error is omitting sender due to some quirk in the javascript call
        // e.g. https://github.com/ethereum/go-ethereum/issues/16106
        if len(data) == 0 {
            if txargs.Value.ToInt().Cmp(big.NewInt(0)) > 0 {
                // Sending ether into black hole
                return errors.New("Tx will create contract with value but empty code!")
            }
            // No value submitted at least
            msgs.crit("Tx will create contract with empty code!")
        } else if len(data) < 40 { //Arbitrary limit
            msgs.warn(fmt.Sprintf("Tx will will create contract, but payload is suspiciously small (%d b)", len(data)))
        }
        // methodSelector should be nil for contract creation
        if methodSelector != nil {
            msgs.warn("Tx will create contract, but method selector supplied; indicating intent to call a method.")
        }

    } else {
        if !txargs.To.ValidChecksum() {
            msgs.warn("Invalid checksum on to-address")
        }
        // Normal transaction
        if bytes.Equal(txargs.To.Address().Bytes(), common.Address{}.Bytes()) {
            // Sending to 0
            msgs.crit("Tx destination is the zero address!")
        }
        // Validate calldata
        v.validateCallData(msgs, data, methodSelector)
    }
    return nil
}

// ValidateTransaction does a number of checks on the supplied transaction, and returns either a list of warnings,
// or an error, indicating that the transaction should be immediately rejected
func (v *Validator) ValidateTransaction(txArgs *SendTxArgs, methodSelector *string) (*ValidationMessages, error) {
    msgs := &ValidationMessages{}
    return msgs, v.validate(msgs, txArgs, methodSelector)
}

var Printable7BitAscii = regexp.MustCompile("^[A-Za-z0-9!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~ ]+$")

// ValidatePasswordFormat returns an error if the password is too short, or consists of characters
// outside the range of the printable 7bit ascii set
func ValidatePasswordFormat(password string) error {
    if len(password) < 10 {
        return errors.New("password too short (<10 characters)")
    }
    if !Printable7BitAscii.MatchString(password) {
        return errors.New("password contains invalid characters - only 7bit printable ascii allowed")
    }
    return nil
}