github.com/hyperledger/burrow@v0.34.5-0.20220512172541-77f09336001d/execution/native/precompiles.go (about) 1 package native 2 3 import ( 4 cryptoSha256 "crypto/sha256" 5 "fmt" 6 "math/big" 7 8 "github.com/hyperledger/burrow/execution/engine" 9 10 "github.com/btcsuite/btcd/btcec" 11 12 "github.com/hyperledger/burrow/binary" 13 "github.com/hyperledger/burrow/crypto" 14 "github.com/hyperledger/burrow/permission" 15 "golang.org/x/crypto/ripemd160" 16 "golang.org/x/crypto/sha3" 17 ) 18 19 var Precompiles = New(). 20 MustFunction(`Recover public key/address of account that signed the data`, 21 leftPadAddress(1), 22 permission.None, 23 ecrecover). 24 MustFunction(`Compute the sha256 hash of input`, 25 leftPadAddress(2), 26 permission.None, 27 sha256). 28 MustFunction(`Compute the ripemd160 hash of input`, 29 leftPadAddress(3), 30 permission.None, 31 ripemd160Func). 32 MustFunction(`Return an output identical to the input`, 33 leftPadAddress(4), 34 permission.None, 35 identity). 36 MustFunction(`Compute the operation base**exp % mod where the values are big ints`, 37 leftPadAddress(5), 38 permission.None, 39 expMod). 40 MustFunction(`Compute the keccak256 hash of input`, 41 leftPadAddress(20), 42 permission.None, 43 keccak256Func) 44 45 func leftPadAddress(bs ...byte) crypto.Address { 46 return crypto.AddressFromWord256(binary.LeftPadWord256(bs)) 47 } 48 49 // SECP256K1 Recovery 50 func ecrecover(ctx Context) ([]byte, error) { 51 // Deduct gas 52 gasRequired := engine.GasEcRecover 53 var err error = engine.UseGasNegative(ctx.Gas, gasRequired) 54 if err != nil { 55 return nil, err 56 } 57 58 // layout is: 59 // input: [ hash | v | r | s ] 60 // bytes: [ 32 | 32 | 32 | 32 ] 61 // Where: 62 // hash = message digest 63 // v = 27 + recovery id (which of 4 possible x coords do we take as public key) (single byte but padded) 64 // r = encrypted random point 65 // s = signature proof 66 67 // Signature layout required by ethereum: 68 // sig: [ r | s | v ] 69 // bytes: [ 32 | 32 | 1 ] 70 hash := ctx.Input[:32] 71 72 const compactSigLength = 2*binary.Word256Bytes + 1 73 sig := make([]byte, compactSigLength) 74 // Copy in r, s 75 copy(sig, ctx.Input[2*binary.Word256Bytes:4*binary.Word256Bytes]) 76 // Check v is single byte 77 v := ctx.Input[binary.Word256Bytes : 2*binary.Word256Bytes] 78 if !binary.IsZeros(v[:len(v)-1]) { 79 return nil, fmt.Errorf("ecrecover: recovery ID is larger than one byte") 80 } 81 // Copy in v to last element of sig 82 sig[2*binary.Word256Bytes] = v[len(v)-1] 83 84 publicKey, isCompressed, err := btcec.RecoverCompact(btcec.S256(), sig, hash) 85 if err != nil { 86 return nil, err 87 } 88 89 var serializedPublicKey []byte 90 if isCompressed { 91 serializedPublicKey = publicKey.SerializeCompressed() 92 } else { 93 serializedPublicKey = publicKey.SerializeUncompressed() 94 } 95 // First byte is a length-prefix 96 hashed := crypto.Keccak256(serializedPublicKey[1:]) 97 hashed = hashed[len(hashed)-crypto.AddressLength:] 98 return binary.LeftPadBytes(hashed, binary.Word256Bytes), nil 99 } 100 101 func sha256(ctx Context) (output []byte, err error) { 102 // Deduct gas 103 gasRequired := wordsIn(uint64(len(ctx.Input)))*engine.GasSha256Word + engine.GasSha256Base 104 err = engine.UseGasNegative(ctx.Gas, gasRequired) 105 if err != nil { 106 return nil, err 107 } 108 // Hash 109 hasher := cryptoSha256.New() 110 // CONTRACT: this does not err 111 hasher.Write(ctx.Input) 112 return hasher.Sum(nil), nil 113 } 114 115 func ripemd160Func(ctx Context) (output []byte, err error) { 116 // Deduct gas 117 gasRequired := wordsIn(uint64(len(ctx.Input)))*engine.GasRipemd160Word + engine.GasRipemd160Base 118 err = engine.UseGasNegative(ctx.Gas, gasRequired) 119 if err != nil { 120 return nil, err 121 } 122 // Hash 123 hasher := ripemd160.New() 124 // CONTRACT: this does not err 125 hasher.Write(ctx.Input) 126 return binary.LeftPadBytes(hasher.Sum(nil), 32), nil 127 } 128 129 func keccak256Func(ctx Context) (output []byte, err error) { 130 // Deduct gas 131 gasRequired := wordsIn(uint64(len(ctx.Input)))*engine.GasRipemd160Word + engine.GasRipemd160Base 132 err = engine.UseGasNegative(ctx.Gas, gasRequired) 133 if err != nil { 134 return nil, err 135 } 136 // Hash 137 hasher := sha3.NewLegacyKeccak256() 138 // CONTRACT: this does not err 139 hasher.Write(ctx.Input) 140 return binary.LeftPadBytes(hasher.Sum(nil), 32), nil 141 } 142 143 func identity(ctx Context) (output []byte, err error) { 144 // Deduct gas 145 gasRequired := wordsIn(uint64(len(ctx.Input)))*engine.GasIdentityWord + engine.GasIdentityBase 146 err = engine.UseGasNegative(ctx.Gas, gasRequired) 147 if err != nil { 148 return nil, err 149 } 150 // Return identity 151 return ctx.Input, nil 152 } 153 154 // expMod: function that implements the EIP 198 (https://github.com/ethereum/EIPs/blob/master/EIPS/eip-198.md with 155 // a fixed gas requirement) 156 func expMod(ctx Context) (output []byte, err error) { 157 const errHeader = "expMod" 158 159 input, segments, err := cut(ctx.Input, binary.Word256Bytes, binary.Word256Bytes, binary.Word256Bytes) 160 if err != nil { 161 return nil, fmt.Errorf("%s: %v", errHeader, err) 162 } 163 164 // get the lengths of base, exp and mod 165 baseLength := getUint64(segments[0]) 166 expLength := getUint64(segments[1]) 167 modLength := getUint64(segments[2]) 168 169 // TODO: implement non-trivial gas schedule for this operation. Probably a parameterised version of the one 170 // described in EIP though that one seems like a bit of a complicated fudge 171 gasRequired := engine.GasExpModBase + engine.GasExpModWord*(wordsIn(baseLength)*wordsIn(expLength)*wordsIn(modLength)) 172 173 err = engine.UseGasNegative(ctx.Gas, gasRequired) 174 if err != nil { 175 return nil, err 176 } 177 178 input, segments, err = cut(input, baseLength, expLength, modLength) 179 if err != nil { 180 return nil, fmt.Errorf("%s: %v", errHeader, err) 181 } 182 183 // get the values of base, exp and mod 184 base := getBigInt(segments[0], baseLength) 185 exp := getBigInt(segments[1], expLength) 186 mod := getBigInt(segments[2], modLength) 187 188 // handle mod 0 189 if mod.Sign() == 0 { 190 return binary.LeftPadBytes([]byte{}, int(modLength)), nil 191 } 192 193 // return base**exp % mod left padded 194 return binary.LeftPadBytes(new(big.Int).Exp(base, exp, mod).Bytes(), int(modLength)), nil 195 } 196 197 // Partition the head of input into segments for each length in lengths. The first return value is the unconsumed tail 198 // of input and the seconds is the segments. Returns an error if input is of insufficient length to establish each segment. 199 func cut(input []byte, lengths ...uint64) ([]byte, [][]byte, error) { 200 segments := make([][]byte, len(lengths)) 201 for i, length := range lengths { 202 if uint64(len(input)) < length { 203 return nil, nil, fmt.Errorf("input is not long enough") 204 } 205 segments[i] = input[:length] 206 input = input[length:] 207 } 208 return input, segments, nil 209 } 210 211 func getBigInt(bs []byte, numBytes uint64) *big.Int { 212 bits := uint(numBytes) * 8 213 // Push bytes into big.Int and interpret as twos complement encoding with of bits width 214 return binary.FromTwosComplement(new(big.Int).SetBytes(bs), bits) 215 } 216 217 func getUint64(bs []byte) uint64 { 218 return binary.Uint64FromWord256(binary.LeftPadWord256(bs)) 219 } 220 221 func wordsIn(numBytes uint64) uint64 { 222 return numBytes + binary.Word256Bytes - 1/binary.Word256Bytes 223 }