nimbus-eth1/nimbus/vm/precompiles.nim

718 lines
23 KiB
Nim
Raw Normal View History

import
./types, ../forks,
./interpreter/[gas_meter, gas_costs, utils/utils_numeric],
../errors, stint, eth/[keys, common], chronicles, tables, macros,
math, nimcrypto, bncurve/[fields, groups], ./blake2b_f, ./blscurve
type
PrecompileAddresses* = enum
2019-04-02 06:05:42 +00:00
# Frontier to Spurious Dragron
2020-11-27 14:42:17 +00:00
paEcRecover = 1
paSha256
paRipeMd160
paIdentity
2019-11-11 04:21:16 +00:00
# Byzantium and Constantinople
2020-11-27 14:42:17 +00:00
paModExp
paEcAdd
paEcMul
paPairing
2019-11-11 04:21:16 +00:00
# Istanbul
2020-11-27 14:42:17 +00:00
paBlake2bf
# Berlin
# EIP 2537: disabled
# reason: not included in berlin
# paBlsG1Add
# paBlsG1Mul
# paBlsG1MultiExp
# paBlsG2Add
# paBlsG2Mul
# paBlsG2MultiExp
# paBlsPairing
# paBlsMapG1
# paBlsMapG2
2020-12-11 10:51:17 +00:00
iterator activePrecompiles*(): EthAddress =
var res: EthAddress
for c in PrecompileAddresses.low..PrecompileAddresses.high:
res[^1] = c.byte
yield res
proc getSignature(computation: Computation): (array[32, byte], Signature) =
# input is Hash, V, R, S
template data: untyped = computation.msg.data
2019-07-19 09:38:08 +00:00
var bytes: array[65, byte] # will hold R[32], S[32], V[1], in that order
let maxPos = min(data.high, 127)
2019-07-19 09:38:08 +00:00
# if we don't have at minimum 64 bytes, there can be no valid V
2019-07-19 08:56:47 +00:00
if maxPos >= 63:
2019-07-19 09:38:08 +00:00
let v = data[63]
# check if V[32] is 27 or 28
if not (v.int in 27..28):
raise newException(ValidationError, "Invalid V in getSignature")
for x in 32..<63:
if data[x] != 0:
raise newException(ValidationError, "Invalid V in getSignature")
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
2019-04-24 13:42:16 +00:00
bytes[0..(maxPos-64)] = data[64..maxPos]
let sig = Signature.fromRaw(bytes)
if sig.isErr:
2019-07-19 09:38:08 +00:00
raise newException(ValidationError, "Could not recover signature computation")
result[1] = sig[]
2018-12-04 13:39:10 +00:00
2019-07-19 09:38:08 +00:00
# extract message hash, only need to copy when there is a valid signature
result[0][0..31] = data[0..31]
else:
raise newException(ValidationError, "Invalid V in getSignature")
2020-12-02 09:17:52 +00:00
proc 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)
proc getPoint[T: G1|G2](t: typedesc[T], data: openarray[byte]): Point[T] =
when T is G1:
const nextOffset = 32
var px, py: FQ
else:
const nextOffset = 64
var px, py: FQ2
2020-12-02 09:17:52 +00:00
if not px.simpleDecode(data.toOpenArray(0, nextOffset - 1)):
raise newException(ValidationError, "Could not get point value")
2020-12-02 09:17:52 +00:00
if not py.simpleDecode(data.toOpenArray(nextOffset, nextOffset * 2 - 1)):
raise newException(ValidationError, "Could not get point value")
2019-04-25 15:33:26 +00:00
if px.isZero() and py.isZero():
result = T.zero()
else:
var ap: AffinePoint[T]
if not ap.init(px, py):
raise newException(ValidationError, "Point is not on curve")
result = ap.toJacobian()
proc getFR(data: openarray[byte]): FR =
2018-10-16 08:49:13 +00:00
if not result.fromBytes2(data):
raise newException(ValidationError, "Could not get FR value")
proc ecRecover*(computation: Computation) =
computation.gasMeter.consumeGas(
GasECRecover,
reason="ECRecover Precompile")
var
(msgHash, sig) = computation.getSignature()
var pubkey = recover(sig, SkMessage(msgHash))
if pubkey.isErr:
raise newException(ValidationError, "Could not derive public key from computation")
2018-12-04 13:39:10 +00:00
2020-01-30 10:15:06 +00:00
computation.output.setLen(32)
computation.output[12..31] = pubkey[].toCanonicalAddress()
Tracing: Remove some trace messages that occur a lot during sync Disable some trace messages which appeared a lot in the output and probably aren't so useful any more, when block processing is functioning well at high speed. Turning on the trace level globally is useful to get a feel for what's happening, but only if each category is kept to a reasonable amount. As well as overwhelming the output so that it's hard to see general activity, some of these messages happen so much they severely slow down processing. Ones called every time an EVM opcode uses some gas are particularly extreme. These messages have all been chosen as things which are probably not useful any more (the relevant functionality has been debugged and is tested plenty). These have been commented out rather than removed. It may be that turning trace topics on/off, or other selection, is a better longer term solution, but that will require better command line options and good defaults for sure. (I think higher levels `tracev` and `tracevv` levels (extra verbose) would be more useful for this sort of deep tracing on request.) For now, enabling `--log-level:TRACE` on the command line is quite useful as long as we keep each category reasonable, and this patch tries to keep that balance. - Don't show "has transactions" on virtually every block imported. - Don't show "Sender" and "txHash" lines on every transaction processed. - Don't show "GAS CONSUMPTION" on every opcode executed", this is way too much. - Don't show "GAS RETURNED" and "GAS REFUND" on each contract call. - Don't show "op: Stop" on every Stop opcode, which means every transaction. - Don't show "Insufficient funds" whenever a contract can't call another. - Don't show "ECRecover", "SHA256 precompile", "RIPEMD160", "Identity" or even "Call precompile" every time a precompile is called. These are very well tested now. - Don't show "executeOpcodes error" whenever a contract returns an error. (This is changed to `trace` too, it's a normal event that is well tested.) Signed-off-by: Jamie Lokier <jamie@shareable.org>
2021-07-22 13:35:41 +00:00
#trace "ECRecover precompile", derivedKey = pubkey[].toCanonicalAddress()
proc sha256*(computation: Computation) =
let
2019-03-11 05:03:57 +00:00
wordCount = wordCount(computation.msg.data.len)
gasFee = GasSHA256 + wordCount * GasSHA256Word
computation.gasMeter.consumeGas(gasFee, reason="SHA256 Precompile")
2020-01-30 10:15:06 +00:00
computation.output = @(nimcrypto.sha_256.digest(computation.msg.data).data)
Tracing: Remove some trace messages that occur a lot during sync Disable some trace messages which appeared a lot in the output and probably aren't so useful any more, when block processing is functioning well at high speed. Turning on the trace level globally is useful to get a feel for what's happening, but only if each category is kept to a reasonable amount. As well as overwhelming the output so that it's hard to see general activity, some of these messages happen so much they severely slow down processing. Ones called every time an EVM opcode uses some gas are particularly extreme. These messages have all been chosen as things which are probably not useful any more (the relevant functionality has been debugged and is tested plenty). These have been commented out rather than removed. It may be that turning trace topics on/off, or other selection, is a better longer term solution, but that will require better command line options and good defaults for sure. (I think higher levels `tracev` and `tracevv` levels (extra verbose) would be more useful for this sort of deep tracing on request.) For now, enabling `--log-level:TRACE` on the command line is quite useful as long as we keep each category reasonable, and this patch tries to keep that balance. - Don't show "has transactions" on virtually every block imported. - Don't show "Sender" and "txHash" lines on every transaction processed. - Don't show "GAS CONSUMPTION" on every opcode executed", this is way too much. - Don't show "GAS RETURNED" and "GAS REFUND" on each contract call. - Don't show "op: Stop" on every Stop opcode, which means every transaction. - Don't show "Insufficient funds" whenever a contract can't call another. - Don't show "ECRecover", "SHA256 precompile", "RIPEMD160", "Identity" or even "Call precompile" every time a precompile is called. These are very well tested now. - Don't show "executeOpcodes error" whenever a contract returns an error. (This is changed to `trace` too, it's a normal event that is well tested.) Signed-off-by: Jamie Lokier <jamie@shareable.org>
2021-07-22 13:35:41 +00:00
#trace "SHA256 precompile", output = computation.output.toHex
proc ripemd160*(computation: Computation) =
let
2019-03-11 05:03:57 +00:00
wordCount = wordCount(computation.msg.data.len)
gasFee = GasRIPEMD160 + wordCount * GasRIPEMD160Word
computation.gasMeter.consumeGas(gasFee, reason="RIPEMD160 Precompile")
2020-01-30 10:15:06 +00:00
computation.output.setLen(32)
computation.output[12..31] = @(nimcrypto.ripemd160.digest(computation.msg.data).data)
Tracing: Remove some trace messages that occur a lot during sync Disable some trace messages which appeared a lot in the output and probably aren't so useful any more, when block processing is functioning well at high speed. Turning on the trace level globally is useful to get a feel for what's happening, but only if each category is kept to a reasonable amount. As well as overwhelming the output so that it's hard to see general activity, some of these messages happen so much they severely slow down processing. Ones called every time an EVM opcode uses some gas are particularly extreme. These messages have all been chosen as things which are probably not useful any more (the relevant functionality has been debugged and is tested plenty). These have been commented out rather than removed. It may be that turning trace topics on/off, or other selection, is a better longer term solution, but that will require better command line options and good defaults for sure. (I think higher levels `tracev` and `tracevv` levels (extra verbose) would be more useful for this sort of deep tracing on request.) For now, enabling `--log-level:TRACE` on the command line is quite useful as long as we keep each category reasonable, and this patch tries to keep that balance. - Don't show "has transactions" on virtually every block imported. - Don't show "Sender" and "txHash" lines on every transaction processed. - Don't show "GAS CONSUMPTION" on every opcode executed", this is way too much. - Don't show "GAS RETURNED" and "GAS REFUND" on each contract call. - Don't show "op: Stop" on every Stop opcode, which means every transaction. - Don't show "Insufficient funds" whenever a contract can't call another. - Don't show "ECRecover", "SHA256 precompile", "RIPEMD160", "Identity" or even "Call precompile" every time a precompile is called. These are very well tested now. - Don't show "executeOpcodes error" whenever a contract returns an error. (This is changed to `trace` too, it's a normal event that is well tested.) Signed-off-by: Jamie Lokier <jamie@shareable.org>
2021-07-22 13:35:41 +00:00
#trace "RIPEMD160 precompile", output = computation.output.toHex
proc identity*(computation: Computation) =
let
wordCount = wordCount(computation.msg.data.len)
gasFee = GasIdentity + wordCount * GasIdentityWord
computation.gasMeter.consumeGas(gasFee, reason="Identity Precompile")
2020-01-30 10:15:06 +00:00
computation.output = computation.msg.data
Tracing: Remove some trace messages that occur a lot during sync Disable some trace messages which appeared a lot in the output and probably aren't so useful any more, when block processing is functioning well at high speed. Turning on the trace level globally is useful to get a feel for what's happening, but only if each category is kept to a reasonable amount. As well as overwhelming the output so that it's hard to see general activity, some of these messages happen so much they severely slow down processing. Ones called every time an EVM opcode uses some gas are particularly extreme. These messages have all been chosen as things which are probably not useful any more (the relevant functionality has been debugged and is tested plenty). These have been commented out rather than removed. It may be that turning trace topics on/off, or other selection, is a better longer term solution, but that will require better command line options and good defaults for sure. (I think higher levels `tracev` and `tracevv` levels (extra verbose) would be more useful for this sort of deep tracing on request.) For now, enabling `--log-level:TRACE` on the command line is quite useful as long as we keep each category reasonable, and this patch tries to keep that balance. - Don't show "has transactions" on virtually every block imported. - Don't show "Sender" and "txHash" lines on every transaction processed. - Don't show "GAS CONSUMPTION" on every opcode executed", this is way too much. - Don't show "GAS RETURNED" and "GAS REFUND" on each contract call. - Don't show "op: Stop" on every Stop opcode, which means every transaction. - Don't show "Insufficient funds" whenever a contract can't call another. - Don't show "ECRecover", "SHA256 precompile", "RIPEMD160", "Identity" or even "Call precompile" every time a precompile is called. These are very well tested now. - Don't show "executeOpcodes error" whenever a contract returns an error. (This is changed to `trace` too, it's a normal event that is well tested.) Signed-off-by: Jamie Lokier <jamie@shareable.org>
2021-07-22 13:35:41 +00:00
#trace "Identity precompile", output = computation.output.toHex
2020-02-11 02:28:34 +00:00
proc modExpInternal(computation: Computation, baseLen, expLen, modLen: int, T: type StUint) =
template data: untyped {.dirty.} =
2018-10-05 15:26:20 +00:00
computation.msg.data
2020-02-11 02:28:34 +00:00
let
base = data.rangeToPadded[:T](96, 95 + baseLen, baseLen)
exp = data.rangeToPadded[:T](96 + baseLen, 95 + baseLen + expLen, expLen)
modulo = data.rangeToPadded[:T](96 + baseLen + expLen, 95 + baseLen + expLen + modLen, modLen)
# TODO: specs mentions that we should return in "M" format
# i.e. if Base and exp are uint512 and Modulo an uint256
# we should return a 256-bit big-endian byte array
# Force static evaluation
func zero(): array[T.bits div 8, byte] {.compileTime.} = discard
func one(): array[T.bits div 8, byte] {.compileTime.} =
when cpuEndian == bigEndian:
result[0] = 1
else:
result[^1] = 1
# Start with EVM special cases
let output = if modulo <= 1:
# If m == 0: EVM returns 0.
# If m == 1: we can shortcut that to 0 as well
zero()
elif exp.isZero():
# If 0^0: EVM returns 1
# For all x != 0, x^0 == 1 as well
one()
else:
powmod(base, exp, modulo).toByteArrayBE
# maximum output len is the same as modLen
# if it less than modLen, it will be zero padded at left
if output.len >= modLen:
computation.output = @(output[^modLen..^1])
else:
computation.output = newSeq[byte](modLen)
computation.output[^output.len..^1] = output[0..^1]
2018-10-05 15:26:20 +00:00
2020-11-24 09:19:02 +00:00
proc modExpFee(c: Computation, baseLen, expLen, modLen: Uint256, fork: Fork): GasInt =
2020-02-11 02:28:34 +00:00
template data: untyped {.dirty.} =
c.msg.data
2018-10-05 15:26:20 +00:00
2020-11-24 09:19:02 +00:00
func mulComplexity(x: Uint256): Uint256 =
2020-02-11 02:28:34 +00:00
## 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
2020-11-24 09:19:02 +00:00
func mulComplexityEIP2565(x: Uint256): Uint256 =
# gas = ceil(x div 8) ^ 2
result = x + 7
result = result div 8
result = result * result
2020-02-11 02:28:34 +00:00
let adjExpLen = block:
2019-05-09 01:56:10 +00:00
let
2020-02-11 02:28:34 +00:00
baseL = baseLen.safeInt
expL = expLen.safeInt
first32 = if baseL.uint64 + expL.uint64 < high(int32).uint64 and baseL < data.len:
data.rangeToPadded2[:Uint256](96 + baseL, 95 + baseL + expL, min(expL, 32))
2019-05-09 01:56:10 +00:00
else:
2020-02-11 02:28:34 +00:00
0.u256
2019-04-24 15:37:34 +00:00
2020-02-11 02:28:34 +00:00
if expLen <= 32:
if first32.isZero(): 0.u256
else: first32.log2.u256 # highest-bit in exponent
2018-10-05 15:26:20 +00:00
else:
2020-02-11 02:28:34 +00:00
if not first32.isZero:
8.u256 * (expLen - 32.u256) + first32.log2.u256
else:
8.u256 * (expLen - 32.u256)
2020-11-24 09:19:02 +00:00
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)
2021-01-11 07:53:51 +00:00
#let gasFee = gasCalc(mulComplexity, GasQuadDivisor)
2020-02-11 02:28:34 +00:00
if gasFee > high(GasInt).u256:
raise newException(OutOfGas, "modExp gas overflow")
result = gasFee.truncate(GasInt)
2021-01-11 08:33:30 +00:00
# EIP2565: modExp gas cost
if fork >= FkBerlin and result < 200.GasInt:
result = 200.GasInt
2018-10-05 15:26:20 +00:00
2020-11-24 09:19:02 +00:00
proc modExp*(c: Computation, fork: Fork = FkByzantium) =
2018-12-04 13:39:10 +00:00
## Modular exponentiation precompiled contract
## Yellow Paper Appendix E
## EIP-198 - https://github.com/ethereum/EIPs/blob/master/EIPS/eip-198.md
# Parsing the data
2020-02-11 02:28:34 +00:00
template data: untyped {.dirty.} =
2020-11-24 09:19:02 +00:00
c.msg.data
2019-05-09 01:56:10 +00:00
2018-12-04 13:39:10 +00:00
let # lengths Base, Exponent, Modulus
2020-02-11 02:28:34 +00:00
baseL = data.rangeToPadded[:Uint256](0, 31)
expL = data.rangeToPadded[:Uint256](32, 63)
modL = data.rangeToPadded[:Uint256](64, 95)
baseLen = baseL.safeInt
expLen = expL.safeInt
modLen = modL.safeInt
2020-11-24 09:19:02 +00:00
let gasFee = modExpFee(c, baseL, expL, modL, fork)
c.gasMeter.consumeGas(gasFee, reason="ModExp Precompile")
2020-02-11 02:28:34 +00:00
if baseLen == 0 and modLen == 0:
# This is a special case where expLength can be very big.
2020-11-24 09:19:02 +00:00
c.output = @[]
2020-02-11 02:28:34 +00:00
return
let maxBytes = max(baseLen, max(expLen, modLen))
2018-12-04 13:39:10 +00:00
if maxBytes <= 32:
2020-11-24 09:19:02 +00:00
c.modExpInternal(baseLen, expLen, modLen, UInt256)
2018-12-04 13:39:10 +00:00
elif maxBytes <= 64:
2020-11-24 09:19:02 +00:00
c.modExpInternal(baseLen, expLen, modLen, StUint[512])
2018-12-04 13:39:10 +00:00
elif maxBytes <= 128:
2020-11-24 09:19:02 +00:00
c.modExpInternal(baseLen, expLen, modLen, StUint[1024])
2018-12-04 13:39:10 +00:00
elif maxBytes <= 256:
2020-11-24 09:19:02 +00:00
c.modExpInternal(baseLen, expLen, modLen, StUint[2048])
2018-12-04 13:39:10 +00:00
elif maxBytes <= 512:
2020-11-24 09:19:02 +00:00
c.modExpInternal(baseLen, expLen, modLen, StUint[4096])
2018-12-04 14:46:33 +00:00
elif maxBytes <= 1024:
2020-11-24 09:19:02 +00:00
c.modExpInternal(baseLen, expLen, modLen, StUint[8192])
2018-12-04 13:39:10 +00:00
else:
raise newException(EVMError, "The Nimbus VM doesn't support modular exponentiation with numbers larger than uint8192")
2018-12-04 13:39:10 +00:00
proc bn256ecAdd*(computation: Computation, fork: Fork = FkByzantium) =
2019-11-11 04:33:56 +00:00
let gasFee = if fork < FkIstanbul: GasECAdd else: GasECAddIstanbul
computation.gasMeter.consumeGas(gasFee, reason = "ecAdd Precompile")
var
input: array[128, byte]
output: array[64, byte]
# Padding data
2020-02-06 04:37:44 +00:00
let len = min(computation.msg.data.len, 128) - 1
input[0..len] = computation.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()
if isSome(apo):
# we can discard here because we supply proper buffer
2018-10-16 08:49:13 +00:00
discard apo.get().toBytes(output)
2020-01-30 10:15:06 +00:00
computation.output = @output
proc bn256ecMul*(computation: Computation, fork: Fork = FkByzantium) =
2019-11-11 04:33:56 +00:00
let gasFee = if fork < FkIstanbul: GasECMul else: GasECMulIstanbul
computation.gasMeter.consumeGas(gasFee, reason="ecMul Precompile")
var
input: array[96, byte]
output: array[64, byte]
# Padding data
2020-02-06 04:37:44 +00:00
let len = min(computation.msg.data.len, 96) - 1
input[0..len] = computation.msg.data[0..len]
var p1 = G1.getPoint(input.toOpenArray(0, 63))
var fr = getFR(input.toOpenArray(64, 95))
var apo = (p1 * fr).toAffine()
if isSome(apo):
# we can discard here because we supply buffer of proper size
2018-10-16 08:49:13 +00:00
discard apo.get().toBytes(output)
2020-01-30 10:15:06 +00:00
computation.output = @output
proc bn256ecPairing*(computation: Computation, fork: Fork = FkByzantium) =
let msglen = len(computation.msg.data)
if msglen mod 192 != 0:
raise newException(ValidationError, "Invalid input length")
let numPoints = msglen div 192
2019-11-11 04:33:56 +00:00
let gasFee = if fork < FkIstanbul:
GasECPairingBase + numPoints * GasECPairingPerPoint
else:
GasECPairingBaseIstanbul + numPoints * GasECPairingPerPointIstanbul
computation.gasMeter.consumeGas(gasFee, reason="ecPairing Precompile")
var output: array[32, byte]
if msglen == 0:
# we can discard here because we supply buffer of proper size
discard BNU256.one().toBytes(output)
else:
# Calculate number of pairing pairs
let count = msglen div 192
# Pairing accumulator
var acc = FQ12.one()
for i in 0..<count:
let s = i * 192
# Loading AffinePoint[G1], bytes from [0..63]
var p1 = G1.getPoint(computation.msg.data.toOpenArray(s, s + 63))
# Loading AffinePoint[G2], bytes from [64..191]
var p2 = G2.getPoint(computation.msg.data.toOpenArray(s + 64, s + 191))
# Accumulate pairing result
acc = acc * pairing(p1, p2)
if acc == FQ12.one():
# we can discard here because we supply buffer of proper size
discard BNU256.one().toBytes(output)
2020-01-30 10:15:06 +00:00
computation.output = @output
2020-11-27 14:42:17 +00:00
proc blake2bf*(c: Computation) =
template input: untyped =
c.msg.data
2019-11-11 04:21:16 +00:00
if len(input) == blake2FInputLength:
let gasFee = GasInt(beLoad32(input, 0))
2020-11-27 14:42:17 +00:00
c.gasMeter.consumeGas(gasFee, reason="blake2bf Precompile")
2019-11-11 04:21:16 +00:00
var output: array[64, byte]
if not blake2b_F(input, output):
raise newException(ValidationError, "Blake2b F function invalid input")
else:
2020-11-27 14:42:17 +00:00
c.output = @output
proc blsG1Add*(c: Computation) =
template input: untyped =
c.msg.data
if input.len != 256:
raise newException(ValidationError, "blsG1Add invalid input len")
c.gasMeter.consumeGas(Bls12381G1AddGas, reason="blsG1Add Precompile")
var a, b: BLS_G1
if not a.decodePoint(input.toOpenArray(0, 127)):
raise newException(ValidationError, "blsG1Add invalid input A")
if not b.decodePoint(input.toOpenArray(128, 255)):
raise newException(ValidationError, "blsG1Add invalid input B")
a.add b
c.output = newSeq[byte](128)
if not encodePoint(a, c.output):
raise newException(ValidationError, "blsG1Add encodePoint error")
proc blsG1Mul*(c: Computation) =
template input: untyped =
c.msg.data
if input.len != 160:
raise newException(ValidationError, "blsG1Mul invalid input len")
c.gasMeter.consumeGas(Bls12381G1MulGas, reason="blsG1Mul Precompile")
var a: BLS_G1
if not a.decodePoint(input.toOpenArray(0, 127)):
raise newException(ValidationError, "blsG1Mul invalid input A")
var scalar: BLS_SCALAR
if not scalar.fromBytes(input.toOpenArray(128, 159)):
raise newException(ValidationError, "blsG1Mul invalid scalar")
a.mul(scalar)
c.output = newSeq[byte](128)
if not encodePoint(a, c.output):
raise newException(ValidationError, "blsG1Mul encodePoint error")
const
Bls12381MultiExpDiscountTable = [
1200, 888, 764, 641, 594, 547, 500, 453, 438, 423,
408, 394, 379, 364, 349, 334, 330, 326, 322, 318,
314, 310, 306, 302, 298, 294, 289, 285, 281, 277,
273, 269, 268, 266, 265, 263, 262, 260, 259, 257,
256, 254, 253, 251, 250, 248, 247, 245, 244, 242,
241, 239, 238, 236, 235, 233, 232, 231, 229, 228,
226, 225, 223, 222, 221, 220, 219, 219, 218, 217,
216, 216, 215, 214, 213, 213, 212, 211, 211, 210,
209, 208, 208, 207, 206, 205, 205, 204, 203, 202,
202, 201, 200, 199, 199, 198, 197, 196, 196, 195,
194, 193, 193, 192, 191, 191, 190, 189, 188, 188,
187, 186, 185, 185, 184, 183, 182, 182, 181, 180,
179, 179, 178, 177, 176, 176, 175, 174
]
func calcBlsMultiExpGas(K: int, gasCost: GasInt): GasInt =
# Calculate G1 point, scalar value pair length
if K == 0:
# Return 0 gas for small input length
return 0.GasInt
const dLen = Bls12381MultiExpDiscountTable.len
# Lookup discount value for G1 point, scalar value pair length
let discount = if K < dLen: Bls12381MultiExpDiscountTable[K-1]
else: Bls12381MultiExpDiscountTable[dLen-1]
# Calculate gas and return the result
result = (K * gasCost * discount) div 1000
proc blsG1MultiExp*(c: Computation) =
template input: untyped =
c.msg.data
const L = 160
if (input.len == 0) or ((input.len mod L) != 0):
raise newException(ValidationError, "blsG1MultiExp invalid input len")
let
K = input.len div L
gas = K.calcBlsMultiExpGas(Bls12381G1MulGas)
c.gasMeter.consumeGas(gas, reason="blsG1MultiExp Precompile")
var
p: BLS_G1
s: BLS_SCALAR
acc: BLS_G1
# Decode point scalar pairs
for i in 0..<K:
let off = L * i
# Decode G1 point
if not p.decodePoint(input.toOpenArray(off, off+127)):
raise newException(ValidationError, "blsG1MultiExp invalid input P")
# Decode scalar value
if not s.fromBytes(input.toOpenArray(off+128, off+159)):
raise newException(ValidationError, "blsG1MultiExp invalid scalar")
p.mul(s)
if i == 0:
acc = p
else:
acc.add(p)
c.output = newSeq[byte](128)
if not encodePoint(acc, c.output):
raise newException(ValidationError, "blsG1MuliExp encodePoint error")
proc blsG2Add*(c: Computation) =
template input: untyped =
c.msg.data
if input.len != 512:
raise newException(ValidationError, "blsG2Add invalid input len")
c.gasMeter.consumeGas(Bls12381G2AddGas, reason="blsG2Add Precompile")
var a, b: BLS_G2
if not a.decodePoint(input.toOpenArray(0, 255)):
raise newException(ValidationError, "blsG2Add invalid input A")
if not b.decodePoint(input.toOpenArray(256, 511)):
raise newException(ValidationError, "blsG2Add invalid input B")
a.add b
c.output = newSeq[byte](256)
if not encodePoint(a, c.output):
raise newException(ValidationError, "blsG2Add encodePoint error")
proc blsG2Mul*(c: Computation) =
template input: untyped =
c.msg.data
if input.len != 288:
raise newException(ValidationError, "blsG2Mul invalid input len")
c.gasMeter.consumeGas(Bls12381G2MulGas, reason="blsG2Mul Precompile")
var a: BLS_G2
if not a.decodePoint(input.toOpenArray(0, 255)):
raise newException(ValidationError, "blsG2Mul invalid input A")
var scalar: BLS_SCALAR
if not scalar.fromBytes(input.toOpenArray(256, 287)):
raise newException(ValidationError, "blsG2Mul invalid scalar")
a.mul(scalar)
c.output = newSeq[byte](256)
if not encodePoint(a, c.output):
raise newException(ValidationError, "blsG2Mul encodePoint error")
proc blsG2MultiExp*(c: Computation) =
template input: untyped =
c.msg.data
const L = 288
if (input.len == 0) or ((input.len mod L) != 0):
raise newException(ValidationError, "blsG2MultiExp invalid input len")
let
K = input.len div L
gas = K.calcBlsMultiExpGas(Bls12381G2MulGas)
c.gasMeter.consumeGas(gas, reason="blsG2MultiExp Precompile")
var
p: BLS_G2
s: BLS_SCALAR
acc: BLS_G2
# Decode point scalar pairs
for i in 0..<K:
let off = L * i
# Decode G1 point
if not p.decodePoint(input.toOpenArray(off, off+255)):
raise newException(ValidationError, "blsG2MultiExp invalid input P")
# Decode scalar value
if not s.fromBytes(input.toOpenArray(off+256, off+287)):
raise newException(ValidationError, "blsG2MultiExp invalid scalar")
p.mul(s)
if i == 0:
acc = p
else:
acc.add(p)
c.output = newSeq[byte](256)
if not encodePoint(acc, c.output):
raise newException(ValidationError, "blsG2MuliExp encodePoint error")
proc blsPairing*(c: Computation) =
2020-11-28 16:13:10 +00:00
template input: untyped =
c.msg.data
const L = 384
if (input.len == 0) or ((input.len mod L) != 0):
raise newException(ValidationError, "blsG2Pairing invalid input len")
let
K = input.len div L
gas = Bls12381PairingBaseGas + K.GasInt * Bls12381PairingPerPairGas
c.gasMeter.consumeGas(gas, reason="blsG2Pairing Precompile")
var
2020-12-02 07:03:40 +00:00
g1: BLS_G1P
g2: BLS_G2P
acc: BLS_ACC
2020-11-28 16:13:10 +00:00
# Decode pairs
for i in 0..<K:
let off = L * i
# Decode G1 point
if not g1.decodePoint(input.toOpenArray(off, off+127)):
raise newException(ValidationError, "blsG2Pairing invalid G1")
# Decode G2 point
if not g2.decodePoint(input.toOpenArray(off+128, off+383)):
raise newException(ValidationError, "blsG2Pairing invalid G2")
# 'point is on curve' check already done,
# Here we need to apply subgroup checks.
if not g1.subgroupCheck:
raise newException(ValidationError, "blsG2Pairing invalid G1 subgroup")
if not g2.subgroupCheck:
raise newException(ValidationError, "blsG2Pairing invalid G2 subgroup")
# Update pairing engine with G1 and G2 points
if i == 0:
2020-12-02 07:03:40 +00:00
acc = millerLoop(g1, g2)
2020-11-28 16:13:10 +00:00
else:
2020-12-02 07:03:40 +00:00
acc.mul(millerLoop(g1, g2))
2020-11-28 16:13:10 +00:00
c.output = newSeq[byte](32)
2020-12-02 07:03:40 +00:00
if acc.check():
2020-11-28 16:13:10 +00:00
c.output[^1] = 1.byte
2020-11-27 14:42:17 +00:00
proc blsMapG1*(c: Computation) =
2020-11-28 16:13:10 +00:00
template input: untyped =
c.msg.data
if input.len != 64:
raise newException(ValidationError, "blsMapG1 invalid input len")
c.gasMeter.consumeGas(Bls12381MapG1Gas, reason="blsMapG1 Precompile")
var fe: BLS_FE
2020-11-29 01:01:17 +00:00
if not fe.decodeFE(input):
2020-11-28 16:13:10 +00:00
raise newException(ValidationError, "blsMapG1 invalid field element")
let p = fe.mapFPToG1()
c.output = newSeq[byte](128)
if not encodePoint(p, c.output):
raise newException(ValidationError, "blsMapG1 encodePoint error")
2020-11-27 14:42:17 +00:00
proc blsMapG2*(c: Computation) =
2020-11-28 16:13:10 +00:00
template input: untyped =
c.msg.data
if input.len != 128:
raise newException(ValidationError, "blsMapG2 invalid input len")
c.gasMeter.consumeGas(Bls12381MapG2Gas, reason="blsMapG2 Precompile")
var fe: BLS_FE2
2020-11-29 01:01:17 +00:00
if not fe.decodeFE(input):
2020-11-28 16:13:10 +00:00
raise newException(ValidationError, "blsMapG2 invalid field element")
let p = fe.mapFPToG2()
c.output = newSeq[byte](256)
if not encodePoint(p, c.output):
raise newException(ValidationError, "blsMapG2 encodePoint error")
2019-11-11 04:21:16 +00:00
proc getMaxPrecompileAddr(fork: Fork): PrecompileAddresses =
if fork < FkByzantium: paIdentity
elif fork < FkIstanbul: paPairing
# EIP 2537: disabled
# reason: not included in berlin
# elif fork < FkBerlin: paBlake2bf
2019-11-11 04:21:16 +00:00
else: PrecompileAddresses.high
proc execPrecompiles*(computation: Computation, fork: Fork): bool {.inline.} =
for i in 0..18:
if computation.msg.codeAddress[i] != 0: return
let lb = computation.msg.codeAddress[19]
2019-11-11 04:21:16 +00:00
let maxPrecompileAddr = getMaxPrecompileAddr(fork)
2019-04-02 06:05:42 +00:00
if lb in PrecompileAddresses.low.byte .. maxPrecompileAddr.byte:
result = true
let precompile = PrecompileAddresses(lb)
Tracing: Remove some trace messages that occur a lot during sync Disable some trace messages which appeared a lot in the output and probably aren't so useful any more, when block processing is functioning well at high speed. Turning on the trace level globally is useful to get a feel for what's happening, but only if each category is kept to a reasonable amount. As well as overwhelming the output so that it's hard to see general activity, some of these messages happen so much they severely slow down processing. Ones called every time an EVM opcode uses some gas are particularly extreme. These messages have all been chosen as things which are probably not useful any more (the relevant functionality has been debugged and is tested plenty). These have been commented out rather than removed. It may be that turning trace topics on/off, or other selection, is a better longer term solution, but that will require better command line options and good defaults for sure. (I think higher levels `tracev` and `tracevv` levels (extra verbose) would be more useful for this sort of deep tracing on request.) For now, enabling `--log-level:TRACE` on the command line is quite useful as long as we keep each category reasonable, and this patch tries to keep that balance. - Don't show "has transactions" on virtually every block imported. - Don't show "Sender" and "txHash" lines on every transaction processed. - Don't show "GAS CONSUMPTION" on every opcode executed", this is way too much. - Don't show "GAS RETURNED" and "GAS REFUND" on each contract call. - Don't show "op: Stop" on every Stop opcode, which means every transaction. - Don't show "Insufficient funds" whenever a contract can't call another. - Don't show "ECRecover", "SHA256 precompile", "RIPEMD160", "Identity" or even "Call precompile" every time a precompile is called. These are very well tested now. - Don't show "executeOpcodes error" whenever a contract returns an error. (This is changed to `trace` too, it's a normal event that is well tested.) Signed-off-by: Jamie Lokier <jamie@shareable.org>
2021-07-22 13:35:41 +00:00
#trace "Call precompile", precompile = precompile, codeAddr = computation.msg.codeAddress
2019-03-11 11:50:13 +00:00
try:
case precompile
of paEcRecover: ecRecover(computation)
of paSha256: sha256(computation)
of paRipeMd160: ripeMd160(computation)
of paIdentity: identity(computation)
2020-11-24 09:19:02 +00:00
of paModExp: modExp(computation, fork)
2019-11-11 04:33:56 +00:00
of paEcAdd: bn256ecAdd(computation, fork)
of paEcMul: bn256ecMul(computation, fork)
of paPairing: bn256ecPairing(computation, fork)
2019-11-11 04:21:16 +00:00
of paBlake2bf: blake2bf(computation)
# EIP 2537: disabled
# reason: not included in berlin
# of paBlsG1Add: blsG1Add(computation)
# of paBlsG1Mul: blsG1Mul(computation)
# of paBlsG1MultiExp: blsG1MultiExp(computation)
# of paBlsG2Add: blsG2Add(computation)
# of paBlsG2Mul: blsG2Mul(computation)
# of paBlsG2MultiExp: blsG2MultiExp(computation)
# of paBlsPairing: blsPairing(computation)
# of paBlsMapG1: blsMapG1(computation)
# of paBlsMapG2: blsMapG2(computation)
2019-12-04 12:36:16 +00:00
except OutOfGas as e:
# cannot use setError here, cyclic dependency
2019-12-04 12:36:16 +00:00
computation.error = Error(info: e.msg, burnsGas: true)
except CatchableError as e:
if fork >= FKByzantium and precompile > paIdentity:
2019-12-04 12:36:16 +00:00
computation.error = Error(info: e.msg, burnsGas: true)
else:
# swallow any other precompiles errors
2019-12-04 12:36:16 +00:00
debug "execPrecompiles validation error", msg=e.msg