diff --git a/packages/evm/src/evm.ts b/packages/evm/src/evm.ts index 894466b3e3..57661464ac 100644 --- a/packages/evm/src/evm.ts +++ b/packages/evm/src/evm.ts @@ -42,6 +42,7 @@ import type { Account } from '@ethereumjs/util' const debug = createDebugLogger('evm:evm') const debugGas = createDebugLogger('evm:gas') +const debugPrecompiles = createDebugLogger('evm:precompiles') // very ugly way to detect if we are running in a browser const isBrowser = new Function('try {return this===window;}catch(e){ return false;}') @@ -386,9 +387,6 @@ export class EVM implements EVMInterface { let result: ExecResult if (message.isCompiled) { - if (this.DEBUG) { - debug(`Run precompile`) - } result = await this.runPrecompile( message.code as PrecompileFunc, message.data, @@ -864,6 +862,7 @@ export class EVM implements EVMInterface { gasLimit, _common: this._common, _EVM: this, + _debug: this.DEBUG ? debugPrecompiles : undefined, } return code(opts) diff --git a/packages/evm/src/precompiles/01-ecrecover.ts b/packages/evm/src/precompiles/01-ecrecover.ts index a431481b73..a886bc3148 100644 --- a/packages/evm/src/precompiles/01-ecrecover.ts +++ b/packages/evm/src/precompiles/01-ecrecover.ts @@ -4,6 +4,7 @@ import { publicToAddress, setLengthLeft, setLengthRight, + short, } from '@ethereumjs/util' import { OOGResult } from '../evm' @@ -13,8 +14,18 @@ import type { PrecompileInput } from './types' export function precompile01(opts: PrecompileInput): ExecResult { const gasUsed = opts._common.param('gasPrices', 'ecRecover') + if (opts._debug) { + opts._debug( + `Run ECRECOVER (0x01) precompile data=${short(opts.data)} length=${ + opts.data.length + } gasLimit=${opts.gasLimit} gasUsed=${gasUsed}` + ) + } if (opts.gasLimit < gasUsed) { + if (opts._debug) { + opts._debug(`ECRECOVER (0x01) failed: OOG`) + } return OOGResult(opts.gasLimit) } @@ -28,6 +39,9 @@ export function precompile01(opts: PrecompileInput): ExecResult { // a signature in most of the cases in the cases that `v=0` or `v=1` // However, this should throw, only 27 and 28 is allowed as input if (vBigInt !== BigInt(27) && vBigInt !== BigInt(28)) { + if (opts._debug) { + opts._debug(`ECRECOVER (0x01) failed: v neither 27 nor 28`) + } return { executionGasUsed: gasUsed, returnValue: Buffer.alloc(0), @@ -39,16 +53,29 @@ export function precompile01(opts: PrecompileInput): ExecResult { let publicKey try { + if (opts._debug) { + opts._debug( + `ECRECOVER (0x01): PK recovery with msgHash=${msgHash.toString('hex')} v=${v.toString( + 'hex' + )} r=${r.toString('hex')}s=${s.toString('hex')}}` + ) + } publicKey = ecrecover(msgHash, bufferToBigInt(v), r, s) } catch (e: any) { + if (opts._debug) { + opts._debug(`ECRECOVER (0x01) failed: PK recovery failed`) + } return { executionGasUsed: gasUsed, returnValue: Buffer.alloc(0), } } - + const address = setLengthLeft(publicToAddress(publicKey), 32) + if (opts._debug) { + opts._debug(`ECRECOVER (0x01) return address=${address.toString('hex')}`) + } return { executionGasUsed: gasUsed, - returnValue: setLengthLeft(publicToAddress(publicKey), 32), + returnValue: address, } } diff --git a/packages/evm/src/precompiles/02-sha256.ts b/packages/evm/src/precompiles/02-sha256.ts index e4c0b45b7c..7e74dfa03e 100644 --- a/packages/evm/src/precompiles/02-sha256.ts +++ b/packages/evm/src/precompiles/02-sha256.ts @@ -1,4 +1,4 @@ -import { toBuffer } from '@ethereumjs/util' +import { short, toBuffer } from '@ethereumjs/util' import { sha256 } from 'ethereum-cryptography/sha256' import { OOGResult } from '../evm' @@ -12,12 +12,28 @@ export function precompile02(opts: PrecompileInput): ExecResult { let gasUsed = opts._common.param('gasPrices', 'sha256') gasUsed += opts._common.param('gasPrices', 'sha256Word') * BigInt(Math.ceil(data.length / 32)) + if (opts._debug) { + opts._debug( + `Run KECCAK256 (0x02) precompile data=${short(opts.data)} length=${ + opts.data.length + } gasLimit=${opts.gasLimit} gasUsed=${gasUsed}` + ) + } + if (opts.gasLimit < gasUsed) { + if (opts._debug) { + opts._debug(`KECCAK256 (0x02) failed: OOG`) + } return OOGResult(opts.gasLimit) } + const hash = toBuffer(sha256(data)) + if (opts._debug) { + opts._debug(`KECCAK256 (0x02) return hash=${hash.toString('hex')}`) + } + return { executionGasUsed: gasUsed, - returnValue: toBuffer(sha256(data)), + returnValue: hash, } } diff --git a/packages/evm/src/precompiles/03-ripemd160.ts b/packages/evm/src/precompiles/03-ripemd160.ts index 729f43d12b..88d6baad51 100644 --- a/packages/evm/src/precompiles/03-ripemd160.ts +++ b/packages/evm/src/precompiles/03-ripemd160.ts @@ -1,4 +1,4 @@ -import { setLengthLeft, toBuffer } from '@ethereumjs/util' +import { setLengthLeft, short, toBuffer } from '@ethereumjs/util' import { ripemd160 } from 'ethereum-cryptography/ripemd160' import { OOGResult } from '../evm' @@ -12,12 +12,28 @@ export function precompile03(opts: PrecompileInput): ExecResult { let gasUsed = opts._common.param('gasPrices', 'ripemd160') gasUsed += opts._common.param('gasPrices', 'ripemd160Word') * BigInt(Math.ceil(data.length / 32)) + if (opts._debug) { + opts._debug( + `Run RIPEMD160 (0x03) precompile data=${short(opts.data)} length=${ + opts.data.length + } gasLimit=${opts.gasLimit} gasUsed=${gasUsed}` + ) + } + if (opts.gasLimit < gasUsed) { + if (opts._debug) { + opts._debug(`RIPEMD160 (0x03) failed: OOG`) + } return OOGResult(opts.gasLimit) } + const hash = setLengthLeft(toBuffer(ripemd160(data)), 32) + if (opts._debug) { + opts._debug(`RIPEMD160 (0x03) return hash=${hash.toString('hex')}`) + } + return { executionGasUsed: gasUsed, - returnValue: setLengthLeft(toBuffer(ripemd160(data)), 32), + returnValue: hash, } } diff --git a/packages/evm/src/precompiles/04-identity.ts b/packages/evm/src/precompiles/04-identity.ts index 48cc02be63..8d32e7f085 100644 --- a/packages/evm/src/precompiles/04-identity.ts +++ b/packages/evm/src/precompiles/04-identity.ts @@ -1,3 +1,5 @@ +import { short } from '@ethereumjs/util' + import { OOGResult } from '../evm' import type { ExecResult } from '../evm' @@ -8,11 +10,25 @@ export function precompile04(opts: PrecompileInput): ExecResult { let gasUsed = opts._common.param('gasPrices', 'identity') gasUsed += opts._common.param('gasPrices', 'identityWord') * BigInt(Math.ceil(data.length / 32)) + if (opts._debug) { + opts._debug( + `Run IDENTITY (0x04) precompile data=${short(opts.data)} length=${ + opts.data.length + } gasLimit=${opts.gasLimit} gasUsed=${gasUsed}` + ) + } if (opts.gasLimit < gasUsed) { + if (opts._debug) { + opts._debug(`IDENTITY (0x04) failed: OOG`) + } return OOGResult(opts.gasLimit) } + if (opts._debug) { + opts._debug(`IDENTITY (0x04) return data=${short(opts.data)}`) + } + return { executionGasUsed: gasUsed, returnValue: data, diff --git a/packages/evm/src/precompiles/05-modexp.ts b/packages/evm/src/precompiles/05-modexp.ts index 5167060c30..70d8cd0bbd 100644 --- a/packages/evm/src/precompiles/05-modexp.ts +++ b/packages/evm/src/precompiles/05-modexp.ts @@ -1,4 +1,10 @@ -import { bigIntToBuffer, bufferToBigInt, setLengthLeft, setLengthRight } from '@ethereumjs/util' +import { + bigIntToBuffer, + bufferToBigInt, + setLengthLeft, + setLengthRight, + short, +} from '@ethereumjs/util' import { OOGResult } from '../evm' @@ -110,8 +116,18 @@ export function precompile05(opts: PrecompileInput): ExecResult { gasUsed = BigInt(200) } } + if (opts._debug) { + opts._debug( + `Run MODEXP (0x05) precompile data=${short(opts.data)} length=${opts.data.length} gasLimit=${ + opts.gasLimit + } gasUsed=${gasUsed}` + ) + } if (opts.gasLimit < gasUsed) { + if (opts._debug) { + opts._debug(`MODEXP (0x05) failed: OOG`) + } return OOGResult(opts.gasLimit) } @@ -133,6 +149,9 @@ export function precompile05(opts: PrecompileInput): ExecResult { const maxSize = BigInt(2147483647) // @ethereumjs/util setLengthRight limitation if (bLen > maxSize || eLen > maxSize || mLen > maxSize) { + if (opts._debug) { + opts._debug(`MODEXP (0x05) failed: OOG`) + } return OOGResult(opts.gasLimit) } @@ -141,6 +160,9 @@ export function precompile05(opts: PrecompileInput): ExecResult { const M = bufferToBigInt(setLengthRight(data.slice(Number(mStart), Number(mEnd)), Number(mLen))) if (mEnd > maxInt) { + if (opts._debug) { + opts._debug(`MODEXP (0x05) failed: OOG`) + } return OOGResult(opts.gasLimit) } @@ -151,8 +173,13 @@ export function precompile05(opts: PrecompileInput): ExecResult { R = expmod(B, E, M) } + const res = setLengthLeft(bigIntToBuffer(R), Number(mLen)) + if (opts._debug) { + opts._debug(`MODEXP (0x05) return value=${res.toString('hex')}`) + } + return { executionGasUsed: gasUsed, - returnValue: setLengthLeft(bigIntToBuffer(R), Number(mLen)), + returnValue: res, } } diff --git a/packages/evm/src/precompiles/06-ecadd.ts b/packages/evm/src/precompiles/06-ecadd.ts index 60a2b27f2f..5bd67a3991 100644 --- a/packages/evm/src/precompiles/06-ecadd.ts +++ b/packages/evm/src/precompiles/06-ecadd.ts @@ -1,3 +1,5 @@ +import { short } from '@ethereumjs/util' + import { OOGResult } from '../evm' import type { ExecResult } from '../evm' @@ -9,17 +11,34 @@ export function precompile06(opts: PrecompileInput): ExecResult { const inputData = opts.data.slice(0, 128) const gasUsed = opts._common.param('gasPrices', 'ecAdd') + if (opts._debug) { + opts._debug( + `Run ECADD (0x06) precompile data=${short(opts.data)} length=${opts.data.length} gasLimit=${ + opts.gasLimit + } gasUsed=${gasUsed}` + ) + } if (opts.gasLimit < gasUsed) { + if (opts._debug) { + opts._debug(`ECADD (0x06) failed: OOG`) + } return OOGResult(opts.gasLimit) } - const returnData = bn128.add(inputData) + const returnData: Buffer = bn128.add(inputData) // check ecadd success or failure by comparing the output length if (returnData.length !== 64) { + if (opts._debug) { + opts._debug(`ECADD (0x06) failed: OOG`) + } return OOGResult(opts.gasLimit) } + if (opts._debug) { + opts._debug(`ECADD (0x06) return value=${returnData.toString('hex')}`) + } + return { executionGasUsed: gasUsed, returnValue: returnData, diff --git a/packages/evm/src/precompiles/07-ecmul.ts b/packages/evm/src/precompiles/07-ecmul.ts index f34815bb11..7670b3bc11 100644 --- a/packages/evm/src/precompiles/07-ecmul.ts +++ b/packages/evm/src/precompiles/07-ecmul.ts @@ -1,3 +1,5 @@ +import { short } from '@ethereumjs/util' + import { OOGResult } from '../evm' import type { ExecResult } from '../evm' @@ -8,17 +10,35 @@ const bn128 = require('rustbn.js') export function precompile07(opts: PrecompileInput): ExecResult { const inputData = opts.data.slice(0, 128) const gasUsed = opts._common.param('gasPrices', 'ecMul') + if (opts._debug) { + opts._debug( + `Run ECMUL (0x07) precompile data=${short(opts.data)} length=${opts.data.length} gasLimit=${ + opts.gasLimit + } gasUsed=${gasUsed}` + ) + } if (opts.gasLimit < gasUsed) { + if (opts._debug) { + opts._debug(`ECMUL (0x07) failed: OOG`) + } return OOGResult(opts.gasLimit) } const returnData = bn128.mul(inputData) // check ecmul success or failure by comparing the output length if (returnData.length !== 64) { + if (opts._debug) { + opts._debug(`ECMUL (0x07) failed: OOG`) + } + // TODO: should this really return OOG? return OOGResult(opts.gasLimit) } + if (opts._debug) { + opts._debug(`ECMUL (0x07) return value=${returnData.toString('hex')}`) + } + return { executionGasUsed: gasUsed, returnValue: returnData, diff --git a/packages/evm/src/precompiles/08-ecpairing.ts b/packages/evm/src/precompiles/08-ecpairing.ts index 97d7ae0b0d..ac45df8b0f 100644 --- a/packages/evm/src/precompiles/08-ecpairing.ts +++ b/packages/evm/src/precompiles/08-ecpairing.ts @@ -1,3 +1,5 @@ +import { short } from '@ethereumjs/util' + import { OOGResult } from '../evm' import type { ExecResult } from '../evm' @@ -12,8 +14,18 @@ export function precompile08(opts: PrecompileInput): ExecResult { const gasUsed = opts._common.param('gasPrices', 'ecPairing') + inputDataSize * opts._common.param('gasPrices', 'ecPairingWord') + if (opts._debug) { + opts._debug( + `Run ECPAIRING (0x08) precompile data=${short(opts.data)} length=${ + opts.data.length + } gasLimit=${opts.gasLimit} gasUsed=${gasUsed}` + ) + } if (opts.gasLimit < gasUsed) { + if (opts._debug) { + opts._debug(`ECPAIRING (0x08) failed: OOG`) + } return OOGResult(opts.gasLimit) } @@ -21,9 +33,17 @@ export function precompile08(opts: PrecompileInput): ExecResult { // check ecpairing success or failure by comparing the output length if (returnData.length !== 32) { + if (opts._debug) { + opts._debug(`ECPAIRING (0x08) failed: OOG`) + } + // TODO: should this really return OOG? return OOGResult(opts.gasLimit) } + if (opts._debug) { + opts._debug(`ECPAIRING (0x08) return value=${returnData.toString('hex')}`) + } + return { executionGasUsed: gasUsed, returnValue: returnData, diff --git a/packages/evm/src/precompiles/09-blake2f.ts b/packages/evm/src/precompiles/09-blake2f.ts index 4a3e9b08b4..1dcb4981cb 100644 --- a/packages/evm/src/precompiles/09-blake2f.ts +++ b/packages/evm/src/precompiles/09-blake2f.ts @@ -1,3 +1,5 @@ +import { short } from '@ethereumjs/util' + import { OOGResult } from '../evm' import { ERROR, EvmError } from '../exceptions' @@ -158,6 +160,9 @@ export function F(h: Uint32Array, m: Uint32Array, t: Uint32Array, f: boolean, ro export function precompile09(opts: PrecompileInput): ExecResult { const data = opts.data if (data.length !== 213) { + if (opts._debug) { + opts._debug(`BLAKE2F (0x09) failed: OUT_OF_RANGE dataLength=${data.length}`) + } return { returnValue: Buffer.alloc(0), executionGasUsed: opts.gasLimit, @@ -166,6 +171,9 @@ export function precompile09(opts: PrecompileInput): ExecResult { } const lastByte = data.slice(212, 213)[0] if (lastByte !== 1 && lastByte !== 0) { + if (opts._debug) { + opts._debug(`BLAKE2F (0x09) failed: OUT_OF_RANGE lastByte=${lastByte}`) + } return { returnValue: Buffer.alloc(0), executionGasUsed: opts.gasLimit, @@ -182,7 +190,18 @@ export function precompile09(opts: PrecompileInput): ExecResult { let gasUsed = opts._common.param('gasPrices', 'blake2Round') gasUsed *= BigInt(rounds) + if (opts._debug) { + opts._debug( + `Run BLAKE2F (0x09) precompile data=${short(opts.data)} length=${opts.data.length} gasLimit=${ + opts.gasLimit + } gasUsed=${gasUsed}` + ) + } + if (opts.gasLimit < gasUsed) { + if (opts._debug) { + opts._debug(`BLAKE2F (0x09) failed: OOG`) + } return OOGResult(opts.gasLimit) } @@ -208,6 +227,10 @@ export function precompile09(opts: PrecompileInput): ExecResult { output.writeUInt32LE(h[i], i * 4) } + if (opts._debug) { + opts._debug(`BLAKE2F (0x09) return hash=${output.toString('hex')}`) + } + return { executionGasUsed: gasUsed, returnValue: output, diff --git a/packages/evm/src/precompiles/0a-bls12-g1add.ts b/packages/evm/src/precompiles/0a-bls12-g1add.ts index bd52f40312..aa3853eaba 100644 --- a/packages/evm/src/precompiles/0a-bls12-g1add.ts +++ b/packages/evm/src/precompiles/0a-bls12-g1add.ts @@ -1,3 +1,5 @@ +import { short } from '@ethereumjs/util' + import { EvmErrorResult, OOGResult } from '../evm' import { ERROR, EvmError } from '../exceptions' @@ -13,12 +15,25 @@ export async function precompile0a(opts: PrecompileInput): Promise { // note: the gas used is constant; even if the input is incorrect. const gasUsed = opts._common.paramByEIP('gasPrices', 'Bls12381G1AddGas', 2537) ?? BigInt(0) + if (opts._debug) { + opts._debug( + `Run BLS12G1ADD (0x0a) precompile data=${short(opts.data)} length=${ + opts.data.length + } gasLimit=${opts.gasLimit} gasUsed=${gasUsed}` + ) + } if (opts.gasLimit < gasUsed) { + if (opts._debug) { + opts._debug(`BLS12G1ADD (0x0a) failed: OOG`) + } return OOGResult(opts.gasLimit) } if (inputData.length !== 256) { + if (opts._debug) { + opts._debug(`BLS12G1ADD (0x0a) failed: Invalid input length length=${inputData.length}`) + } return EvmErrorResult(new EvmError(ERROR.BLS_12_381_INVALID_INPUT_LENGTH), opts.gasLimit) } @@ -34,6 +49,9 @@ export async function precompile0a(opts: PrecompileInput): Promise { for (const index in zeroByteCheck) { const slicedBuffer = opts.data.slice(zeroByteCheck[index][0], zeroByteCheck[index][1]) if (!slicedBuffer.equals(zeroBytes16)) { + if (opts._debug) { + opts._debug(`BLS12G1ADD (0x0a) failed: Point not on curve`) + } return EvmErrorResult(new EvmError(ERROR.BLS_12_381_POINT_NOT_ON_CURVE), opts.gasLimit) } } @@ -45,6 +63,9 @@ export async function precompile0a(opts: PrecompileInput): Promise { mclPoint1 = BLS12_381_ToG1Point(opts.data.slice(0, 128), mcl) mclPoint2 = BLS12_381_ToG1Point(opts.data.slice(128, 256), mcl) } catch (e: any) { + if (opts._debug) { + opts._debug(`BLS12G1ADD (0x0a) failed: ${e.message}`) + } return EvmErrorResult(e, opts.gasLimit) } @@ -52,6 +73,10 @@ export async function precompile0a(opts: PrecompileInput): Promise { const returnValue = BLS12_381_FromG1Point(result) + if (opts._debug) { + opts._debug(`BLS12G1ADD (0x0a) return value=${returnValue.toString('hex')}`) + } + return { executionGasUsed: gasUsed, returnValue, diff --git a/packages/evm/src/precompiles/0b-bls12-g1mul.ts b/packages/evm/src/precompiles/0b-bls12-g1mul.ts index 9a3d850dd5..704255835a 100644 --- a/packages/evm/src/precompiles/0b-bls12-g1mul.ts +++ b/packages/evm/src/precompiles/0b-bls12-g1mul.ts @@ -1,3 +1,5 @@ +import { short } from '@ethereumjs/util' + import { EvmErrorResult, OOGResult } from '../evm' import { ERROR, EvmError } from '../exceptions' @@ -17,12 +19,25 @@ export async function precompile0b(opts: PrecompileInput): Promise { // note: the gas used is constant; even if the input is incorrect. const gasUsed = opts._common.paramByEIP('gasPrices', 'Bls12381G1MulGas', 2537) ?? BigInt(0) + if (opts._debug) { + opts._debug( + `Run BLS12G1MUL (0x0b) precompile data=${short(opts.data)} length=${ + opts.data.length + } gasLimit=${opts.gasLimit} gasUsed=${gasUsed}` + ) + } if (opts.gasLimit < gasUsed) { + if (opts._debug) { + opts._debug(`BLS12G1MUL (0x0b) failed: OOG`) + } return OOGResult(opts.gasLimit) } if (inputData.length !== 160) { + if (opts._debug) { + opts._debug(`BLS12G1MUL (0x0b) failed: Invalid input length length=${inputData.length}`) + } return EvmErrorResult(new EvmError(ERROR.BLS_12_381_INVALID_INPUT_LENGTH), opts.gasLimit) } @@ -36,6 +51,9 @@ export async function precompile0b(opts: PrecompileInput): Promise { for (const index in zeroByteCheck) { const slicedBuffer = opts.data.slice(zeroByteCheck[index][0], zeroByteCheck[index][1]) if (!slicedBuffer.equals(zeroBytes16)) { + if (opts._debug) { + opts._debug(`BLS12G1MUL (0x0b) failed: Point not on curve`) + } return EvmErrorResult(new EvmError(ERROR.BLS_12_381_POINT_NOT_ON_CURVE), opts.gasLimit) } } @@ -46,6 +64,9 @@ export async function precompile0b(opts: PrecompileInput): Promise { try { mclPoint = BLS12_381_ToG1Point(opts.data.slice(0, 128), mcl) } catch (e: any) { + if (opts._debug) { + opts._debug(`BLS12G1MUL (0x0b) failed: ${e.message}`) + } return EvmErrorResult(e, opts.gasLimit) } @@ -55,6 +76,10 @@ export async function precompile0b(opts: PrecompileInput): Promise { const returnValue = BLS12_381_FromG1Point(result) + if (opts._debug) { + opts._debug(`BLS12G1MUL (0x0b) return value=${returnValue.toString('hex')}`) + } + return { executionGasUsed: gasUsed, returnValue, diff --git a/packages/evm/src/precompiles/0c-bls12-g1multiexp.ts b/packages/evm/src/precompiles/0c-bls12-g1multiexp.ts index cbfb29f41b..cee17ba973 100644 --- a/packages/evm/src/precompiles/0c-bls12-g1multiexp.ts +++ b/packages/evm/src/precompiles/0c-bls12-g1multiexp.ts @@ -1,3 +1,5 @@ +import { short } from '@ethereumjs/util' + import { EvmErrorResult, OOGResult } from '../evm' import { ERROR, EvmError } from '../exceptions' @@ -16,6 +18,9 @@ export async function precompile0c(opts: PrecompileInput): Promise { const inputData = opts.data if (inputData.length === 0) { + if (opts._debug) { + opts._debug(`BLS12MULTIEXP (0x0c) failed: Empty input`) + } return EvmErrorResult(new EvmError(ERROR.BLS_12_381_INPUT_EMPTY), opts.gasLimit) // follow Geths implementation } @@ -41,12 +46,25 @@ export async function precompile0c(opts: PrecompileInput): Promise { } const gasUsed = (gasUsedPerPair * BigInt(numPairs) * BigInt(gasDiscountMultiplier)) / BigInt(1000) + if (opts._debug) { + opts._debug( + `Run BLS12MULTIEXP (0x0c) precompile data=${short(opts.data)} length=${ + opts.data.length + } gasLimit=${opts.gasLimit} gasUsed=${gasUsed}` + ) + } if (opts.gasLimit < gasUsed) { + if (opts._debug) { + opts._debug(`BLS12MULTIEXP (0x0c) failed: OOG`) + } return OOGResult(opts.gasLimit) } if (inputData.length % 160 !== 0) { + if (opts._debug) { + opts._debug(`BLS12MULTIEXP (0x0c) failed: Invalid input length length=${inputData.length}`) + } return EvmErrorResult(new EvmError(ERROR.BLS_12_381_INVALID_INPUT_LENGTH), opts.gasLimit) } @@ -70,6 +88,9 @@ export async function precompile0c(opts: PrecompileInput): Promise { zeroByteCheck[index][1] + pairStart ) if (!slicedBuffer.equals(zeroBytes16)) { + if (opts._debug) { + opts._debug(`BLS12MULTIEXP (0x0c) failed: Point not on curve`) + } return EvmErrorResult(new EvmError(ERROR.BLS_12_381_POINT_NOT_ON_CURVE), opts.gasLimit) } } @@ -77,6 +98,9 @@ export async function precompile0c(opts: PrecompileInput): Promise { try { G1 = BLS12_381_ToG1Point(opts.data.slice(pairStart, pairStart + 128), mcl) } catch (e: any) { + if (opts._debug) { + opts._debug(`BLS12MULTIEXP (0x0c) failed: ${e.message}`) + } return EvmErrorResult(e, opts.gasLimit) } const Fr = BLS12_381_ToFrPoint(opts.data.slice(pairStart + 128, pairStart + 160), mcl) @@ -89,6 +113,10 @@ export async function precompile0c(opts: PrecompileInput): Promise { const returnValue = BLS12_381_FromG1Point(result) + if (opts._debug) { + opts._debug(`BLS12MULTIEXP (0x0c) return value=${returnValue.toString('hex')}`) + } + return { executionGasUsed: gasUsed, returnValue, diff --git a/packages/evm/src/precompiles/0d-bls12-g2add.ts b/packages/evm/src/precompiles/0d-bls12-g2add.ts index f851b6c2a3..963c740c01 100644 --- a/packages/evm/src/precompiles/0d-bls12-g2add.ts +++ b/packages/evm/src/precompiles/0d-bls12-g2add.ts @@ -1,3 +1,5 @@ +import { short } from '@ethereumjs/util' + import { EvmErrorResult, OOGResult } from '../evm' import { ERROR, EvmError } from '../exceptions' @@ -13,12 +15,25 @@ export async function precompile0d(opts: PrecompileInput): Promise { // note: the gas used is constant; even if the input is incorrect. const gasUsed = opts._common.paramByEIP('gasPrices', 'Bls12381G2AddGas', 2537) ?? BigInt(0) + if (opts._debug) { + opts._debug( + `Run BLS12G2ADD (0x0d) precompile data=${short(opts.data)} length=${ + opts.data.length + } gasLimit=${opts.gasLimit} gasUsed=${gasUsed}` + ) + } if (opts.gasLimit < gasUsed) { + if (opts._debug) { + opts._debug(`BLS12G2ADD (0x0d) failed: OOG`) + } return OOGResult(opts.gasLimit) } if (inputData.length !== 512) { + if (opts._debug) { + opts._debug(`BLS12G2ADD (0x0d) failed: Invalid input length length=${inputData.length}`) + } return EvmErrorResult(new EvmError(ERROR.BLS_12_381_INVALID_INPUT_LENGTH), opts.gasLimit) } @@ -38,6 +53,9 @@ export async function precompile0d(opts: PrecompileInput): Promise { for (const index in zeroByteCheck) { const slicedBuffer = opts.data.slice(zeroByteCheck[index][0], zeroByteCheck[index][1]) if (!slicedBuffer.equals(zeroBytes16)) { + if (opts._debug) { + opts._debug(`BLS12G2ADD (0x0d) failed: Point not on curve`) + } return EvmErrorResult(new EvmError(ERROR.BLS_12_381_POINT_NOT_ON_CURVE), opts.gasLimit) } } @@ -59,6 +77,10 @@ export async function precompile0d(opts: PrecompileInput): Promise { const returnValue = BLS12_381_FromG2Point(result) + if (opts._debug) { + opts._debug(`BLS12G2ADD (0x0d) return value=${returnValue.toString('hex')}`) + } + return { executionGasUsed: gasUsed, returnValue, diff --git a/packages/evm/src/precompiles/0e-bls12-g2mul.ts b/packages/evm/src/precompiles/0e-bls12-g2mul.ts index 6de254dcab..ca72e35386 100644 --- a/packages/evm/src/precompiles/0e-bls12-g2mul.ts +++ b/packages/evm/src/precompiles/0e-bls12-g2mul.ts @@ -1,3 +1,5 @@ +import { short } from '@ethereumjs/util' + import { EvmErrorResult, OOGResult } from '../evm' import { ERROR, EvmError } from '../exceptions' @@ -17,12 +19,25 @@ export async function precompile0e(opts: PrecompileInput): Promise { // note: the gas used is constant; even if the input is incorrect. const gasUsed = opts._common.paramByEIP('gasPrices', 'Bls12381G2MulGas', 2537) ?? BigInt(0) + if (opts._debug) { + opts._debug( + `Run BLS12G2MUL (0x0e) precompile data=${short(opts.data)} length=${ + opts.data.length + } gasLimit=${opts.gasLimit} gasUsed=${gasUsed}` + ) + } if (opts.gasLimit < gasUsed) { + if (opts._debug) { + opts._debug(`BLS12G2MUL (0x0e) failed: OOG`) + } return OOGResult(opts.gasLimit) } if (inputData.length !== 288) { + if (opts._debug) { + opts._debug(`BLS12G2MUL (0x0e) failed: Invalid input length length=${inputData.length}`) + } return EvmErrorResult(new EvmError(ERROR.BLS_12_381_INVALID_INPUT_LENGTH), opts.gasLimit) } @@ -38,6 +53,9 @@ export async function precompile0e(opts: PrecompileInput): Promise { for (const index in zeroByteCheck) { const slicedBuffer = opts.data.slice(zeroByteCheck[index][0], zeroByteCheck[index][1]) if (!slicedBuffer.equals(zeroBytes16)) { + if (opts._debug) { + opts._debug(`BLS12G2MUL (0x0e) failed: Point not on curve`) + } return EvmErrorResult(new EvmError(ERROR.BLS_12_381_POINT_NOT_ON_CURVE), opts.gasLimit) } } @@ -49,6 +67,9 @@ export async function precompile0e(opts: PrecompileInput): Promise { try { mclPoint = BLS12_381_ToG2Point(opts.data.slice(0, 256), mcl) } catch (e: any) { + if (opts._debug) { + opts._debug(`BLS12G2MUL (0x0e) failed: ${e.message}`) + } return EvmErrorResult(e, opts.gasLimit) } @@ -58,6 +79,10 @@ export async function precompile0e(opts: PrecompileInput): Promise { const returnValue = BLS12_381_FromG2Point(result) + if (opts._debug) { + opts._debug(`BLS12G2MUL (0x0e) return value=${returnValue.toString('hex')}`) + } + return { executionGasUsed: gasUsed, returnValue, diff --git a/packages/evm/src/precompiles/0f-bls12-g2multiexp.ts b/packages/evm/src/precompiles/0f-bls12-g2multiexp.ts index 6d25856457..791c462d6a 100644 --- a/packages/evm/src/precompiles/0f-bls12-g2multiexp.ts +++ b/packages/evm/src/precompiles/0f-bls12-g2multiexp.ts @@ -1,3 +1,5 @@ +import { short } from '@ethereumjs/util' + import { EvmErrorResult, OOGResult } from '../evm' import { ERROR, EvmError } from '../exceptions' @@ -18,6 +20,9 @@ export async function precompile0f(opts: PrecompileInput): Promise { const inputData = opts.data if (inputData.length === 0) { + if (opts._debug) { + opts._debug(`BLS12G2MULTIEXP (0x0f) failed: Empty input`) + } return EvmErrorResult(new EvmError(ERROR.BLS_12_381_INPUT_EMPTY), opts.gasLimit) // follow Geths implementation } @@ -39,12 +44,25 @@ export async function precompile0f(opts: PrecompileInput): Promise { } const gasUsed = (gasUsedPerPair * BigInt(numPairs) * BigInt(gasDiscountMultiplier)) / BigInt(1000) + if (opts._debug) { + opts._debug( + `Run BLS12G2MULTIEXP (0x0f) precompile data=${short(opts.data)} length=${ + opts.data.length + } gasLimit=${opts.gasLimit} gasUsed=${gasUsed}` + ) + } if (opts.gasLimit < gasUsed) { + if (opts._debug) { + opts._debug(`BLS12G2MULTIEXP (0x0f) failed: OOG`) + } return OOGResult(opts.gasLimit) } if (inputData.length % 288 !== 0) { + if (opts._debug) { + opts._debug(`BLS12G2MULTIEXP (0x0f) failed: Invalid input length length=${inputData.length}`) + } return EvmErrorResult(new EvmError(ERROR.BLS_12_381_INVALID_INPUT_LENGTH), opts.gasLimit) } @@ -70,6 +88,9 @@ export async function precompile0f(opts: PrecompileInput): Promise { zeroByteCheck[index][1] + pairStart ) if (!slicedBuffer.equals(zeroBytes16)) { + if (opts._debug) { + opts._debug(`BLS12G2MULTIEXP (0x0f) failed: Point not on curve`) + } return EvmErrorResult(new EvmError(ERROR.BLS_12_381_POINT_NOT_ON_CURVE), opts.gasLimit) } } @@ -77,6 +98,9 @@ export async function precompile0f(opts: PrecompileInput): Promise { try { G2 = BLS12_381_ToG2Point(opts.data.slice(pairStart, pairStart + 256), mcl) } catch (e: any) { + if (opts._debug) { + opts._debug(`BLS12G2MULTIEXP (0x0f) failed: ${e.message}`) + } return EvmErrorResult(e, opts.gasLimit) } const Fr = BLS12_381_ToFrPoint(opts.data.slice(pairStart + 256, pairStart + 288), mcl) @@ -89,6 +113,10 @@ export async function precompile0f(opts: PrecompileInput): Promise { const returnValue = BLS12_381_FromG2Point(result) + if (opts._debug) { + opts._debug(`BLS12G2MULTIEXP (0x0f) return value=${returnValue.toString('hex')}`) + } + return { executionGasUsed: gasUsed, returnValue, diff --git a/packages/evm/src/precompiles/10-bls12-pairing.ts b/packages/evm/src/precompiles/10-bls12-pairing.ts index 4fe3fa5ef9..606e0f351f 100644 --- a/packages/evm/src/precompiles/10-bls12-pairing.ts +++ b/packages/evm/src/precompiles/10-bls12-pairing.ts @@ -1,3 +1,5 @@ +import { short } from '@ethereumjs/util' + import { EvmErrorResult, OOGResult } from '../evm' import { ERROR, EvmError } from '../exceptions' @@ -17,6 +19,9 @@ export async function precompile10(opts: PrecompileInput): Promise { const baseGas = opts._common.paramByEIP('gasPrices', 'Bls12381PairingBaseGas', 2537) ?? BigInt(0) if (inputData.length === 0) { + if (opts._debug) { + opts._debug(`BLS12PAIRING (0x10) failed: Empty input`) + } return EvmErrorResult(new EvmError(ERROR.BLS_12_381_INPUT_EMPTY), opts.gasLimit) } @@ -24,12 +29,25 @@ export async function precompile10(opts: PrecompileInput): Promise { opts._common.paramByEIP('gasPrices', 'Bls12381PairingPerPairGas', 2537) ?? BigInt(0) const gasUsed = baseGas + gasUsedPerPair * BigInt(Math.floor(inputData.length / 384)) + if (opts._debug) { + opts._debug( + `Run BLS12PAIRING (0x10) precompile data=${short(opts.data)} length=${ + opts.data.length + } gasLimit=${opts.gasLimit} gasUsed=${gasUsed}` + ) + } if (inputData.length % 384 !== 0) { + if (opts._debug) { + opts._debug(`BLS12PAIRING (0x10) failed: Invalid input length length=${inputData.length}`) + } return EvmErrorResult(new EvmError(ERROR.BLS_12_381_INVALID_INPUT_LENGTH), opts.gasLimit) } if (opts.gasLimit < gasUsed) { + if (opts._debug) { + opts._debug(`BLS12PAIRING (0x10) failed: OOG`) + } return OOGResult(opts.gasLimit) } @@ -56,6 +74,9 @@ export async function precompile10(opts: PrecompileInput): Promise { zeroByteCheck[index][1] + pairStart ) if (!slicedBuffer.equals(zeroBytes16)) { + if (opts._debug) { + opts._debug(`BLS12PAIRING (0x10) failed: Point not on curve`) + } return EvmErrorResult(new EvmError(ERROR.BLS_12_381_POINT_NOT_ON_CURVE), opts.gasLimit) } } @@ -63,6 +84,9 @@ export async function precompile10(opts: PrecompileInput): Promise { try { G1 = BLS12_381_ToG1Point(opts.data.slice(pairStart, pairStart + 128), mcl) } catch (e: any) { + if (opts._debug) { + opts._debug(`BLS12PAIRING (0x10) failed: ${e.message}`) + } return EvmErrorResult(e, opts.gasLimit) } @@ -71,6 +95,9 @@ export async function precompile10(opts: PrecompileInput): Promise { try { G2 = BLS12_381_ToG2Point(opts.data.slice(g2start, g2start + 256), mcl) } catch (e: any) { + if (opts._debug) { + opts._debug(`BLS12PAIRING (0x10) failed: ${e.message}`) + } return EvmErrorResult(e, opts.gasLimit) } @@ -104,6 +131,10 @@ export async function precompile10(opts: PrecompileInput): Promise { returnValue = zeroBuffer } + if (opts._debug) { + opts._debug(`BLS12PAIRING (0x10) return value=${returnValue.toString('hex')}`) + } + return { executionGasUsed: gasUsed, returnValue, diff --git a/packages/evm/src/precompiles/11-bls12-map-fp-to-g1.ts b/packages/evm/src/precompiles/11-bls12-map-fp-to-g1.ts index e34a6ca1b8..f7ca9ec4df 100644 --- a/packages/evm/src/precompiles/11-bls12-map-fp-to-g1.ts +++ b/packages/evm/src/precompiles/11-bls12-map-fp-to-g1.ts @@ -1,3 +1,5 @@ +import { short } from '@ethereumjs/util' + import { EvmErrorResult, OOGResult } from '../evm' import { ERROR, EvmError } from '../exceptions' @@ -13,18 +15,34 @@ export async function precompile11(opts: PrecompileInput): Promise { // note: the gas used is constant; even if the input is incorrect. const gasUsed = opts._common.paramByEIP('gasPrices', 'Bls12381MapG1Gas', 2537) ?? BigInt(0) + if (opts._debug) { + opts._debug( + `Run BLS12MAPFPTOG1 (0x11) precompile data=${short(opts.data)} length=${ + opts.data.length + } gasLimit=${opts.gasLimit} gasUsed=${gasUsed}` + ) + } if (opts.gasLimit < gasUsed) { + if (opts._debug) { + opts._debug(`BLS12MAPFPTOG1 (0x11) failed: OOG`) + } return OOGResult(opts.gasLimit) } if (inputData.length !== 64) { + if (opts._debug) { + opts._debug(`BLS12MAPFPTOG1 (0x11) failed: Invalid input length length=${inputData.length}`) + } return EvmErrorResult(new EvmError(ERROR.BLS_12_381_INVALID_INPUT_LENGTH), opts.gasLimit) } // check if some parts of input are zero bytes. const zeroBytes16 = Buffer.alloc(16, 0) if (!opts.data.slice(0, 16).equals(zeroBytes16)) { + if (opts._debug) { + opts._debug(`BLS12MAPFPTOG1 (0x11) failed: Point not on curve`) + } return EvmErrorResult(new EvmError(ERROR.BLS_12_381_POINT_NOT_ON_CURVE), opts.gasLimit) } @@ -34,6 +52,9 @@ export async function precompile11(opts: PrecompileInput): Promise { try { Fp1Point = BLS12_381_ToFpPoint(opts.data.slice(0, 64), mcl) } catch (e: any) { + if (opts._debug) { + opts._debug(`BLS12MAPFPTOG1 (0x11) failed: ${e.message}`) + } return EvmErrorResult(e, opts.gasLimit) } @@ -42,6 +63,10 @@ export async function precompile11(opts: PrecompileInput): Promise { const returnValue = BLS12_381_FromG1Point(result) + if (opts._debug) { + opts._debug(`BLS12MAPFPTOG1 (0x11) return value=${returnValue.toString('hex')}`) + } + return { executionGasUsed: gasUsed, returnValue, diff --git a/packages/evm/src/precompiles/12-bls12-map-fp2-to-g2.ts b/packages/evm/src/precompiles/12-bls12-map-fp2-to-g2.ts index b53a798eea..006b724910 100644 --- a/packages/evm/src/precompiles/12-bls12-map-fp2-to-g2.ts +++ b/packages/evm/src/precompiles/12-bls12-map-fp2-to-g2.ts @@ -1,3 +1,5 @@ +import { short } from '@ethereumjs/util' + import { EvmErrorResult, OOGResult } from '../evm' import { ERROR, EvmError } from '../exceptions' @@ -13,12 +15,25 @@ export async function precompile12(opts: PrecompileInput): Promise { // note: the gas used is constant; even if the input is incorrect. const gasUsed = opts._common.paramByEIP('gasPrices', 'Bls12381MapG2Gas', 2537) ?? BigInt(0) + if (opts._debug) { + opts._debug( + `Run BLS12MAPFP2TOG2 (0x12) precompile data=${short(opts.data)} length=${ + opts.data.length + } gasLimit=${opts.gasLimit} gasUsed=${gasUsed}` + ) + } if (opts.gasLimit < gasUsed) { + if (opts._debug) { + opts._debug(`BLS12MAPFP2TOG2 (0x12) failed: OOG`) + } return OOGResult(opts.gasLimit) } if (inputData.length !== 128) { + if (opts._debug) { + opts._debug(`BLS12MAPFP2TOG2 (0x12) failed: Invalid input length length=${inputData.length}`) + } return EvmErrorResult(new EvmError(ERROR.BLS_12_381_INVALID_INPUT_LENGTH), opts.gasLimit) } @@ -32,6 +47,9 @@ export async function precompile12(opts: PrecompileInput): Promise { for (const index in zeroByteCheck) { const slicedBuffer = opts.data.slice(zeroByteCheck[index][0], zeroByteCheck[index][1]) if (!slicedBuffer.equals(zeroBytes16)) { + if (opts._debug) { + opts._debug(`BLS12MAPFP2TOG2 (0x12) failed: Point not on curve`) + } return EvmErrorResult(new EvmError(ERROR.BLS_12_381_POINT_NOT_ON_CURVE), opts.gasLimit) } } @@ -42,6 +60,9 @@ export async function precompile12(opts: PrecompileInput): Promise { try { Fp2Point = BLS12_381_ToFp2Point(opts.data.slice(0, 64), opts.data.slice(64, 128), mcl) } catch (e: any) { + if (opts._debug) { + opts._debug(`BLS12MAPFP2TOG2 (0x12) failed: ${e.message}`) + } return EvmErrorResult(e, opts.gasLimit) } // map it to G2 @@ -49,6 +70,10 @@ export async function precompile12(opts: PrecompileInput): Promise { const returnValue = BLS12_381_FromG2Point(result) + if (opts._debug) { + opts._debug(`BLS12MAPFP2TOG2 (0x12) return value=${returnValue.toString('hex')}`) + } + return { executionGasUsed: gasUsed, returnValue, diff --git a/packages/evm/src/precompiles/14-kzg-point-evaluation.ts b/packages/evm/src/precompiles/14-kzg-point-evaluation.ts index a3f065154c..a41fdfc8d0 100644 --- a/packages/evm/src/precompiles/14-kzg-point-evaluation.ts +++ b/packages/evm/src/precompiles/14-kzg-point-evaluation.ts @@ -1,5 +1,5 @@ import { computeVersionedHash, kzg } from '@ethereumjs/tx' -import { bigIntToBuffer, bufferToBigInt, bufferToHex, setLengthLeft } from '@ethereumjs/util' +import { bigIntToBuffer, bufferToBigInt, bufferToHex, setLengthLeft, short } from '@ethereumjs/util' import { EvmErrorResult } from '../evm' import { ERROR, EvmError } from '../exceptions' @@ -13,6 +13,14 @@ export const BLS_MODULUS = BigInt( export async function precompile14(opts: PrecompileInput): Promise { const gasUsed = opts._common.param('gasPrices', 'kzgPointEvaluationGasPrecompilePrice') + if (opts._debug) { + opts._debug( + `Run KZG_POINT_EVALUATION (0x14) precompile data=${short(opts.data)} length=${ + opts.data.length + } gasLimit=${opts.gasLimit} gasUsed=${gasUsed}` + ) + } + const version = Number(opts._common.paramByEIP('sharding', 'blobCommitmentVersionKzg', 4844)) const fieldElementsPerBlob = opts._common.paramByEIP('sharding', 'fieldElementsPerBlob', 4844)! const versionedHash = opts.data.slice(0, 32) @@ -22,6 +30,9 @@ export async function precompile14(opts: PrecompileInput): Promise { const kzgProof = opts.data.slice(144, 192) if (bufferToBigInt(z) >= BLS_MODULUS || bufferToBigInt(y) >= BLS_MODULUS) { + if (opts._debug) { + opts._debug(`KZG_POINT_EVALUATION (0x14) failed: POINT_GREATER_THAN_BLS_MODULUS`) + } return EvmErrorResult(new EvmError(ERROR.POINT_GREATER_THAN_BLS_MODULUS), opts.gasLimit) } @@ -29,16 +40,35 @@ export async function precompile14(opts: PrecompileInput): Promise { bufferToHex(Buffer.from(computeVersionedHash(commitment, version))) !== bufferToHex(versionedHash) ) { + if (opts._debug) { + opts._debug(`KZG_POINT_EVALUATION (0x14) failed: INVALID_COMMITMENT`) + } return EvmErrorResult(new EvmError(ERROR.INVALID_COMMITMENT), opts.gasLimit) } + if (opts._debug) { + opts._debug( + `KZG_POINT_EVALUATION (0x14): proof verification with commitment=${commitment.toString( + 'hex' + )} z=${z.toString('hex')} y=${y.toString('hex')} kzgProof=${kzgProof.toString('hex')}` + ) + } kzg.verifyKzgProof(commitment, z, y, kzgProof) // Return value - FIELD_ELEMENTS_PER_BLOB and BLS_MODULUS as padded 32 byte big endian values - const fieldElementsBuffer = setLengthLeft(bigIntToBuffer(fieldElementsPerBlob), 32) - const modulusBuffer = setLengthLeft(bigIntToBuffer(BLS_MODULUS), 32) + const fieldElements = setLengthLeft(bigIntToBuffer(fieldElementsPerBlob), 32) + const modulus = setLengthLeft(bigIntToBuffer(BLS_MODULUS), 32) + + if (opts._debug) { + opts._debug( + `KZG_POINT_EVALUATION (0x14) return fieldElements=${fieldElements.toString( + 'hex' + )} modulus=${modulus.toString('hex')}` + ) + } + return { executionGasUsed: gasUsed, - returnValue: Buffer.concat([fieldElementsBuffer, modulusBuffer]), + returnValue: Buffer.concat([fieldElements, modulus]), } } diff --git a/packages/evm/src/precompiles/types.ts b/packages/evm/src/precompiles/types.ts index 4a375d73fe..8bd66ec91e 100644 --- a/packages/evm/src/precompiles/types.ts +++ b/packages/evm/src/precompiles/types.ts @@ -1,6 +1,7 @@ import type { ExecResult } from '../evm' import type { EVMInterface } from '../types' import type { Common } from '@ethereumjs/common' +import type { debug } from 'debug' export interface PrecompileFunc { (input: PrecompileInput): Promise | ExecResult @@ -11,4 +12,5 @@ export interface PrecompileInput { gasLimit: bigint _common: Common _EVM: EVMInterface + _debug?: debug.Debugger }