precompiles & solidity

Fancy vim solidity syntax highlighting courtesy of https://github.com/tomlion/vim-solidity. Also note the ability to do `if` in inline assembly!

inline assembly? The evm? Precompiles? Solidity???

I’m going to assume you know what solidity looks like, and the general idea of ethereum. What we care about here is that ethereum has a distributed (replicated on every computer running a full node) virtual machine (the EVM), with an instruction set that anyone can choose to have executed and recorded on the blockchain, with state updated accordingly. A quick update on the things that are important here, if you’re a little rusty: storage means information is permanently stored in the blockchain state. memory is simply evm working memory, where variables are stored during computation. uint is an alias for uint256, which holds up to 256 bits, perfect for elliptic curve coordinates. public means that the function is publicly callable. view used to be constant , and tells the compiler that the code doesn’t change anything in storage. There’s also a keyword pure, that means the code doesn’t even view anything in storage.

precompiles

The precompile directory in ethereum’s Go client currently looks like this:

var PrecompiledContractsByzantium = map[common.Address]PrecompiledContract{     common.BytesToAddress([]byte{1}): &ecrecover{}, common.BytesToAddress([]byte{2}): &sha256hash{}, common.BytesToAddress([]byte{3}): &ripemd160hash{}, common.BytesToAddress([]byte{4}): &dataCopy{}, common.BytesToAddress([]byte{5}): &bigModExp{}, common.BytesToAddress([]byte{6}): &bn256Add{}, common.BytesToAddress([]byte{7}): &bn256ScalarMul{}, common.BytesToAddress([]byte{8}): &bn256Pairing{},
}
  • bigModExp now resides at address 0x05, and performs b^e mod m, taking as input, in order:
    - length of the base;
    - length of the exponent;
    - length of the modulus;
    - the base itself (b above);
    - the exponent itself (e);
    - the modulus (m).
  • bn256Add lives at0x06, and performs (x1, y1) + (x2, y2), with x1, y1, x2, and y2 as 256 bit field elements, such that (x1, y1) and (x2, y2) are valid points on the curve bn256, which has equation y^2 = x^3 + 3 mod fieldOrder. The inputs here are simply x1, y1, x2, y2.
  • bn256ScalarMul lives at 0x07, and performs k * (x, y), for k in a group with the order of the curve, and (x, y) a valid curve point as above. The inputs here are x, y, k.
  • bn256Pairing lives at 0x08. This takes as input arbitrarily many pairs of elliptic curve points, and performs the pairing check e(g1, g2) = e(-h1, h2), with g1 and h1 from G1, and g2 and h2 from G2.
    - Points from G1 have the form (x, y), as we have seen above;
    - points from G2 have the form (ai + b, ci + d), and a, b, c, d (in that order — imaginary, real, imaginary, real) need to be supplied in the precompile call. The bn256Pairing code first checks that a multiple of 6 elements have been sent, and then performs the pairings check(s).
function ecmul(uint ax, uint ay, uint k) public view returns(uint[2] memory p) { uint[3] memory input;
input[0] = ax;
input[1] = ay;
input[2] = k;

assembly {
if iszero(staticcall(gas, 0x07, input, 0x60, p, 0x40)) {
revert(0,0)
}
}
return p;
}

the evm

persistent storage

The persistent memory associated with each address is called storage. This is a key-value store, mapping 256-bit words to 256-bit words. It cannot be enumerated from inside a contract, and contracts have no access or view of the storage associated with other addresses.

volatile memory

The EVM has a virtual stack with which to store 256 bit values. 256-bit words were chosen for compatibility with cryptographic operations. All EVM operations are performed using this virtual stack. The maximum number of elements the stack can contain is 1024. You can copy one of the top 16 elements, or swap the top element with one of the 16 below (meaning you can here access the 17th highest value on the stack). All other opcodes take the pre-determined number of elements from the top of the stack and then push the return value onto the stack.

uint256[2] memory inputToPrecompile;
input[0] = somePreviouslyStoredValue;
input[1] = someOtherPreviouslyStoredValue;
assembly {
if iszero(staticcall(gas, 0x07, input, 0x60, p, 0x40)) {
revert(0,0)
}
}
staticcall(gasLimit, to, inputOffset, inputSize, outputOffset, outputSize)
  • Sending the amount of gas currently available to us, after subtracting 2000;
  • Calling the contract at address 0x07, which the mapping at the top tells us corresponds to bn256ScalarMul;
  • Defining the input offset as input, as we have just declared in memory;
  • Declaring the input size as 0x60, corresponding to a value of three 256 bit words, exactly the size of an elliptic curve point and one 256 bit scalar;
  • the output will be stored at value p; and
  • the output size is 0x40, corresponding to the elliptic curve point that will be returned to us.

modexp

The code needed to call the bigModExp precompile is as follows:

function expmod(uint base, uint e, uint m) public view returns (uint o) {

assembly {
// define pointer
let p := mload(0x40)
// store data assembly-favouring ways
mstore(p, 0x20) // Length of Base
mstore(add(p, 0x20), 0x20) // Length of Exponent
mstore(add(p, 0x40), 0x20) // Length of Modulus
mstore(add(p, 0x60), base) // Base
mstore(add(p, 0x80), e) // Exponent
mstore(add(p, 0xa0), m) // Modulus
if iszero(staticcall(sub(gas, 2000), 0x05, p, 0xc0, p, 0x20)) {
revert(0, 0)
}
// data
o := mload(p)
}
}

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store