This repository has been archived by the owner on May 13, 2022. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 344
/
Copy pathprecompiles.go
223 lines (194 loc) · 6.68 KB
/
precompiles.go
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
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
package native
import (
cryptoSha256 "crypto/sha256"
"fmt"
"math/big"
"github.com/hyperledger/burrow/execution/engine"
"github.com/btcsuite/btcd/btcec"
"github.com/hyperledger/burrow/binary"
"github.com/hyperledger/burrow/crypto"
"github.com/hyperledger/burrow/permission"
"golang.org/x/crypto/ripemd160"
"golang.org/x/crypto/sha3"
)
var Precompiles = New().
MustFunction(`Recover public key/address of account that signed the data`,
leftPadAddress(1),
permission.None,
ecrecover).
MustFunction(`Compute the sha256 hash of input`,
leftPadAddress(2),
permission.None,
sha256).
MustFunction(`Compute the ripemd160 hash of input`,
leftPadAddress(3),
permission.None,
ripemd160Func).
MustFunction(`Return an output identical to the input`,
leftPadAddress(4),
permission.None,
identity).
MustFunction(`Compute the operation base**exp % mod where the values are big ints`,
leftPadAddress(5),
permission.None,
expMod).
MustFunction(`Compute the keccak256 hash of input`,
leftPadAddress(20),
permission.None,
keccak256Func)
func leftPadAddress(bs ...byte) crypto.Address {
return crypto.AddressFromWord256(binary.LeftPadWord256(bs))
}
// SECP256K1 Recovery
func ecrecover(ctx Context) ([]byte, error) {
// Deduct gas
gasRequired := engine.GasEcRecover
var err error = engine.UseGasNegative(ctx.Gas, gasRequired)
if err != nil {
return nil, err
}
// layout is:
// input: [ hash | v | r | s ]
// bytes: [ 32 | 32 | 32 | 32 ]
// Where:
// hash = message digest
// v = 27 + recovery id (which of 4 possible x coords do we take as public key) (single byte but padded)
// r = encrypted random point
// s = signature proof
// Signature layout required by ethereum:
// sig: [ r | s | v ]
// bytes: [ 32 | 32 | 1 ]
hash := ctx.Input[:32]
const compactSigLength = 2*binary.Word256Bytes + 1
sig := make([]byte, compactSigLength)
// Copy in r, s
copy(sig, ctx.Input[2*binary.Word256Bytes:4*binary.Word256Bytes])
// Check v is single byte
v := ctx.Input[binary.Word256Bytes : 2*binary.Word256Bytes]
if !binary.IsZeros(v[:len(v)-1]) {
return nil, fmt.Errorf("ecrecover: recovery ID is larger than one byte")
}
// Copy in v to last element of sig
sig[2*binary.Word256Bytes] = v[len(v)-1]
publicKey, isCompressed, err := btcec.RecoverCompact(btcec.S256(), sig, hash)
if err != nil {
return nil, err
}
var serializedPublicKey []byte
if isCompressed {
serializedPublicKey = publicKey.SerializeCompressed()
} else {
serializedPublicKey = publicKey.SerializeUncompressed()
}
// First byte is a length-prefix
hashed := crypto.Keccak256(serializedPublicKey[1:])
hashed = hashed[len(hashed)-crypto.AddressLength:]
return binary.LeftPadBytes(hashed, binary.Word256Bytes), nil
}
func sha256(ctx Context) (output []byte, err error) {
// Deduct gas
gasRequired := wordsIn(uint64(len(ctx.Input)))*engine.GasSha256Word + engine.GasSha256Base
err = engine.UseGasNegative(ctx.Gas, gasRequired)
if err != nil {
return nil, err
}
// Hash
hasher := cryptoSha256.New()
// CONTRACT: this does not err
hasher.Write(ctx.Input)
return hasher.Sum(nil), nil
}
func ripemd160Func(ctx Context) (output []byte, err error) {
// Deduct gas
gasRequired := wordsIn(uint64(len(ctx.Input)))*engine.GasRipemd160Word + engine.GasRipemd160Base
err = engine.UseGasNegative(ctx.Gas, gasRequired)
if err != nil {
return nil, err
}
// Hash
hasher := ripemd160.New()
// CONTRACT: this does not err
hasher.Write(ctx.Input)
return binary.LeftPadBytes(hasher.Sum(nil), 32), nil
}
func keccak256Func(ctx Context) (output []byte, err error) {
// Deduct gas
gasRequired := wordsIn(uint64(len(ctx.Input)))*engine.GasRipemd160Word + engine.GasRipemd160Base
err = engine.UseGasNegative(ctx.Gas, gasRequired)
if err != nil {
return nil, err
}
// Hash
hasher := sha3.NewLegacyKeccak256()
// CONTRACT: this does not err
hasher.Write(ctx.Input)
return binary.LeftPadBytes(hasher.Sum(nil), 32), nil
}
func identity(ctx Context) (output []byte, err error) {
// Deduct gas
gasRequired := wordsIn(uint64(len(ctx.Input)))*engine.GasIdentityWord + engine.GasIdentityBase
err = engine.UseGasNegative(ctx.Gas, gasRequired)
if err != nil {
return nil, err
}
// Return identity
return ctx.Input, nil
}
// expMod: function that implements the EIP 198 (https://github.com/ethereum/EIPs/blob/master/EIPS/eip-198.md with
// a fixed gas requirement)
func expMod(ctx Context) (output []byte, err error) {
const errHeader = "expMod"
input, segments, err := cut(ctx.Input, binary.Word256Bytes, binary.Word256Bytes, binary.Word256Bytes)
if err != nil {
return nil, fmt.Errorf("%s: %v", errHeader, err)
}
// get the lengths of base, exp and mod
baseLength := getUint64(segments[0])
expLength := getUint64(segments[1])
modLength := getUint64(segments[2])
// TODO: implement non-trivial gas schedule for this operation. Probably a parameterised version of the one
// described in EIP though that one seems like a bit of a complicated fudge
gasRequired := engine.GasExpModBase + engine.GasExpModWord*(wordsIn(baseLength)*wordsIn(expLength)*wordsIn(modLength))
err = engine.UseGasNegative(ctx.Gas, gasRequired)
if err != nil {
return nil, err
}
input, segments, err = cut(input, baseLength, expLength, modLength)
if err != nil {
return nil, fmt.Errorf("%s: %v", errHeader, err)
}
// get the values of base, exp and mod
base := getBigInt(segments[0], baseLength)
exp := getBigInt(segments[1], expLength)
mod := getBigInt(segments[2], modLength)
// handle mod 0
if mod.Sign() == 0 {
return binary.LeftPadBytes([]byte{}, int(modLength)), nil
}
// return base**exp % mod left padded
return binary.LeftPadBytes(new(big.Int).Exp(base, exp, mod).Bytes(), int(modLength)), nil
}
// Partition the head of input into segments for each length in lengths. The first return value is the unconsumed tail
// of input and the seconds is the segments. Returns an error if input is of insufficient length to establish each segment.
func cut(input []byte, lengths ...uint64) ([]byte, [][]byte, error) {
segments := make([][]byte, len(lengths))
for i, length := range lengths {
if uint64(len(input)) < length {
return nil, nil, fmt.Errorf("input is not long enough")
}
segments[i] = input[:length]
input = input[length:]
}
return input, segments, nil
}
func getBigInt(bs []byte, numBytes uint64) *big.Int {
bits := uint(numBytes) * 8
// Push bytes into big.Int and interpret as twos complement encoding with of bits width
return binary.FromTwosComplement(new(big.Int).SetBytes(bs), bits)
}
func getUint64(bs []byte) uint64 {
return binary.Uint64FromWord256(binary.LeftPadWord256(bs))
}
func wordsIn(numBytes uint64) uint64 {
return numBytes + binary.Word256Bytes - 1/binary.Word256Bytes
}