# Nimbus # Copyright (c) 2018-2024 Status Research & Development GmbH # Licensed under either of # * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or # http://www.apache.org/licenses/LICENSE-2.0) # * MIT license ([LICENSE-MIT](LICENSE-MIT) or # http://opensource.org/licenses/MIT) # at your option. This file may not be copied, modified, or distributed except # according to those terms. import std/[macros], results, "."/[types, blake2b_f, blscurve], ./interpreter/[gas_meter, gas_costs, utils/utils_numeric], eth/common/keys, chronicles, nimcrypto/[ripemd, sha2, utils], bncurve/[fields, groups], stew/assign2, ../common/evmforks, ../core/eip4844, ./modexp, ./evm_errors, ./computation, eth/common/[base, addresses] type PrecompileAddresses* = enum # Frontier to Spurious Dragron paEcRecover = 0x01, paSha256 = 0x02, paRipeMd160 = 0x03, paIdentity = 0x04, # Byzantium and Constantinople paModExp = 0x05, paEcAdd = 0x06, paEcMul = 0x07, paPairing = 0x08, # Istanbul paBlake2bf = 0x09, # Cancun paPointEvaluation = 0x0A, # Prague (EIP-2537) paBlsG1Add = 0x0b, paBlsG1Mul = 0x0c, paBlsG1MultiExp = 0x0d, paBlsG2Add = 0x0e, paBlsG2Mul = 0x0f, paBlsG2MultiExp = 0x10, paBlsPairing = 0x11, paBlsMapG1 = 0x12, paBlsMapG2 = 0x13 SigRes = object msgHash: array[32, byte] sig: Signature # ------------------------------------------------------------------------------ # Private functions # ------------------------------------------------------------------------------ func getMaxPrecompileAddr(fork: EVMFork): PrecompileAddresses = if fork < FkByzantium: paIdentity elif fork < FkIstanbul: paPairing elif fork < FkCancun: paBlake2bf elif fork < FkPrague: paPointEvaluation else: PrecompileAddresses.high func validPrecompileAddr(addrByte, maxPrecompileAddr: byte): bool = (addrByte in PrecompileAddresses.low.byte .. maxPrecompileAddr) func getSignature(c: Computation): EvmResult[SigRes] = # input is Hash, V, R, S template data: untyped = c.msg.data var bytes: array[65, byte] # will hold R[32], S[32], V[1], in that order let maxPos = min(data.high, 127) # if we don't have at minimum 64 bytes, there can be no valid V if maxPos < 63: return err(prcErr(PrcInvalidSig)) let v = data[63] # check if V[32] is 27 or 28 if not (v.int in 27..28): return err(prcErr(PrcInvalidSig)) for x in 32..<63: if data[x] != 0: return err(prcErr(PrcInvalidSig)) bytes[64] = v - 27 # if there is more data for R and S, copy it. Else, defaulted zeroes are # used for R and S if maxPos >= 64: # Copy message data to buffer assign(bytes.toOpenArray(0, (maxPos-64)), data.toOpenArray(64, maxPos)) let sig = Signature.fromRaw(bytes).valueOr: return err(prcErr(PrcInvalidSig)) var res = SigRes(sig: sig) # extract message hash, only need to copy when there is a valid signature assign(res.msgHash, data.toOpenArray(0, 31)) ok(res) func simpleDecode(dst: var FQ2, src: openArray[byte]): bool {.noinit.} = # bypassing FQ2.fromBytes # because we want to check `value > modulus` result = false if dst.c1.fromBytes(src.toOpenArray(0, 31)) and dst.c0.fromBytes(src.toOpenArray(32, 63)): result = true template simpleDecode(dst: var FQ, src: openArray[byte]): bool = fromBytes(dst, src) func getPoint[T: G1|G2](_: typedesc[T], data: openArray[byte]): EvmResult[Point[T]] = when T is G1: const nextOffset = 32 var px, py: FQ else: const nextOffset = 64 var px, py: FQ2 if not px.simpleDecode(data.toOpenArray(0, nextOffset - 1)): return err(prcErr(PrcInvalidPoint)) if not py.simpleDecode(data.toOpenArray(nextOffset, nextOffset * 2 - 1)): return err(prcErr(PrcInvalidPoint)) if px.isZero() and py.isZero(): ok(T.zero()) else: var ap: AffinePoint[T] if not ap.init(px, py): return err(prcErr(PrcInvalidPoint)) ok(ap.toJacobian()) func getFR(data: openArray[byte]): EvmResult[FR] = var res: FR if not res.fromBytes2(data): return err(prcErr(PrcInvalidPoint)) ok(res) # ------------------------------------------------------------------------------ # Precompiles functions # ------------------------------------------------------------------------------ func ecRecover(c: Computation): EvmResultVoid = ? c.gasMeter.consumeGas( GasECRecover, reason="ECRecover Precompile") let sig = ? c.getSignature() pubkey = recover(sig.sig, SkMessage(sig.msgHash)).valueOr: return err(prcErr(PrcInvalidSig)) c.output.setLen(32) assign(c.output.toOpenArray(12, 31), pubkey.toCanonicalAddress().data) ok() func sha256(c: Computation): EvmResultVoid = let wordCount = wordCount(c.msg.data.len) gasFee = GasSHA256 + wordCount.GasInt * GasSHA256Word ? c.gasMeter.consumeGas(gasFee, reason="SHA256 Precompile") assign(c.output, sha2.sha256.digest(c.msg.data).data) ok() func ripemd160(c: Computation): EvmResultVoid = let wordCount = wordCount(c.msg.data.len) gasFee = GasRIPEMD160 + wordCount.GasInt * GasRIPEMD160Word ? c.gasMeter.consumeGas(gasFee, reason="RIPEMD160 Precompile") c.output.setLen(32) assign(c.output.toOpenArray(12, 31), ripemd.ripemd160.digest(c.msg.data).data) ok() func identity(c: Computation): EvmResultVoid = let wordCount = wordCount(c.msg.data.len) gasFee = GasIdentity + wordCount.GasInt * GasIdentityWord ? c.gasMeter.consumeGas(gasFee, reason="Identity Precompile") assign(c.output, c.msg.data) ok() func modExpFee(c: Computation, baseLen, expLen, modLen: UInt256, fork: EVMFork): EvmResult[GasInt] = template data: untyped {.dirty.} = c.msg.data func mulComplexity(x: UInt256): UInt256 = ## Estimates the difficulty of Karatsuba multiplication if x <= 64.u256: x * x elif x <= 1024.u256: x * x div 4.u256 + 96.u256 * x - 3072.u256 else: x * x div 16.u256 + 480.u256 * x - 199680.u256 func mulComplexityEIP2565(x: UInt256): UInt256 = # gas = ceil(x div 8) ^ 2 result = x + 7 result = result div 8 result = result * result let adjExpLen = block: let baseL = baseLen.safeInt expL = expLen.safeInt first32 = if baseL.uint64 + expL.uint64 < high(int32).uint64 and baseL < data.len: data.rangeToPadded[:UInt256](96 + baseL, 95 + baseL + expL, min(expL, 32)) else: 0.u256 if expLen <= 32: if first32.isZero(): 0.u256 else: first32.log2.u256 # highest-bit in exponent else: if not first32.isZero: 8.u256 * (expLen - 32.u256) + first32.log2.u256 else: 8.u256 * (expLen - 32.u256) template gasCalc(comp, divisor: untyped): untyped = ( max(modLen, baseLen).comp * max(adjExpLen, 1.u256) ) div divisor # EIP2565: modExp gas cost let gasFee = if fork >= FkBerlin: gasCalc(mulComplexityEIP2565, GasQuadDivisorEIP2565) else: gasCalc(mulComplexity, GasQuadDivisor) if gasFee > high(GasInt).u256: return err(gasErr(OutOfGas)) var res = gasFee.truncate(GasInt) # EIP2565: modExp gas cost if fork >= FkBerlin and res < 200.GasInt: res = 200.GasInt ok(res) func modExp(c: Computation, fork: EVMFork = FkByzantium): EvmResultVoid = ## Modular exponentiation precompiled contract ## Yellow Paper Appendix E ## EIP-198 - https://github.com/ethereum/EIPs/blob/master/EIPS/eip-198.md # Parsing the data template data: untyped {.dirty.} = c.msg.data let # lengths Base, Exponent, Modulus baseL = data.rangeToPadded[:UInt256](0, 31, 32) expL = data.rangeToPadded[:UInt256](32, 63, 32) modL = data.rangeToPadded[:UInt256](64, 95, 32) baseLen = baseL.safeInt expLen = expL.safeInt modLen = modL.safeInt let gasFee = ? modExpFee(c, baseL, expL, modL, fork) ? c.gasMeter.consumeGas(gasFee, reason="ModExp Precompile") if baseLen == 0 and modLen == 0: # This is a special case where expLength can be very big. c.output = @[] return ok() const maxSize = int32.high.u256 if baseL > maxSize or expL > maxSize or modL > maxSize: return err(prcErr(PrcInvalidParam)) # TODO: # add EVM special case: # - modulo <= 1: return zero # - exp == zero: return one let output = modExp( data.rangeToPadded(96, baseLen), data.rangeToPadded(96 + baseLen, expLen), data.rangeToPadded(96 + baseLen + expLen, modLen) ) # maximum output len is the same as modLen # if it less than modLen, it will be zero padded at left if output.len >= modLen: assign(c.output, output.toOpenArray(output.len-modLen, output.len-1)) else: c.output = newSeq[byte](modLen) assign(c.output.toOpenArray(c.output.len-output.len, c.output.len-1), output) ok() func bn256ecAdd(c: Computation, fork: EVMFork = FkByzantium): EvmResultVoid = let gasFee = if fork < FkIstanbul: GasECAdd else: GasECAddIstanbul ? c.gasMeter.consumeGas(gasFee, reason = "ecAdd Precompile") var input: array[128, byte] # Padding data let len = min(c.msg.data.len, 128) - 1 input[0..len] = c.msg.data[0..len] var p1 = ? G1.getPoint(input.toOpenArray(0, 63)) var p2 = ? G1.getPoint(input.toOpenArray(64, 127)) var apo = (p1 + p2).toAffine() c.output.setLen(64) if isSome(apo): # we can discard here because we supply proper buffer discard apo.get().toBytes(c.output) ok() func bn256ecMul(c: Computation, fork: EVMFork = FkByzantium): EvmResultVoid = let gasFee = if fork < FkIstanbul: GasECMul else: GasECMulIstanbul ? c.gasMeter.consumeGas(gasFee, reason="ecMul Precompile") var input: array[96, byte] # Padding data let len = min(c.msg.data.len, 96) - 1 assign(input.toOpenArray(0, len), c.msg.data.toOpenArray(0, len)) var p1 = ? G1.getPoint(input.toOpenArray(0, 63)) var fr = ? getFR(input.toOpenArray(64, 95)) var apo = (p1 * fr).toAffine() c.output.setLen(64) if isSome(apo): # we can discard here because we supply buffer of proper size discard apo.get().toBytes(c.output) ok() func bn256ecPairing(c: Computation, fork: EVMFork = FkByzantium): EvmResultVoid = let msglen = c.msg.data.len if msglen mod 192 != 0: return err(prcErr(PrcInvalidParam)) let numPoints = GasInt msglen div 192 let gasFee = if fork < FkIstanbul: GasECPairingBase + numPoints * GasECPairingPerPoint else: GasECPairingBaseIstanbul + numPoints * GasECPairingPerPointIstanbul ? c.gasMeter.consumeGas(gasFee, reason="ecPairing Precompile") c.output.setLen(32) if msglen == 0: # we can discard here because we supply buffer of proper size discard BNU256.one().toBytes(c.output) else: # Calculate number of pairing pairs let count = msglen div 192 # Pairing accumulator var acc = FQ12.one() for i in 0..= FkByzantium and precompile > paIdentity: c.setError(EVMC_PRECOMPILE_FAILURE, $res.error.code, true) else: # swallow any other precompiles errors debug "execPrecompiles validation error", errCode = $res.error.code