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  }