Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

EIP-7823: Set upper bounds for MODEXP #4070

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 23 commits into
base: master
Choose a base branch
Loading
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
e37d995
Do upper bounds check before any other checks
am1r021 May 6, 2025
dc806fc
evm: Implement eip 7823
am1r021 May 6, 2025
b1eb790
evm: Add tests for EIP-7823 implementation
am1r021 May 6, 2025
e64c1c1
Move length checks for inputs back to where it was
am1r021 May 6, 2025
0ab29ab
Merge branch 'master' of github.com:ethereumjs/ethereumjs-monorepo in…
am1r021 May 22, 2025
9a3c8b8
Add eip to osaka hardfork
am1r021 May 22, 2025
a297da9
Sanity check that there is enough gas
am1r021 May 26, 2025
6b48d98
Fix lint issues
am1r021 May 26, 2025
9bb33b8
Fix lint issue and use expMod instead of own helper method
am1r021 May 26, 2025
8e2cbb9
Merge branch 'master' into implement-eip-7823
am1r021 May 26, 2025
2602782
Fix lint issue
am1r021 May 26, 2025
982d369
Merge branch 'implement-eip-7823' of github.com:ethereumjs/ethereumjs…
am1r021 May 26, 2025
df5c650
Fix lint issue
am1r021 May 28, 2025
0c97ecf
Merge branch 'master' into implement-eip-7823
am1r021 May 28, 2025
bdebccd
Fix type error
am1r021 May 28, 2025
3c71394
Merge branch 'implement-eip-7823' of github.com:ethereumjs/ethereumjs…
am1r021 May 28, 2025
d942db5
Merge branch 'master' of github.com:ethereumjs/ethereumjs-monorepo in…
am1r021 May 28, 2025
f6ceb9b
Add opts to function call
am1r021 May 28, 2025
f76ed56
Remove duplicate import
am1r021 May 28, 2025
c30ab2f
Fix type issue
am1r021 May 28, 2025
c61ac14
Fix error caused by padding
am1r021 May 29, 2025
7d91fcf
Merge branch 'master' of github.com:ethereumjs/ethereumjs-monorepo in…
am1r021 May 29, 2025
aecc4b5
Merge remote-tracking branch 'origin/master' into implement-eip-7823
acolytec3 May 30, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions 8 packages/common/src/eips.ts
Original file line number Diff line number Diff line change
Expand Up @@ -479,6 +479,14 @@ export const eipsDict: EIPsDict = {
minimumHardfork: Hardfork.Chainstart,
requiredEIPs: [2935],
},
/**
* Description : Set upper bounds for MODEXP
* URL : https://eips.ethereum.org/EIPS/eip-7823
* Status : Review
*/
7823: {
minimumHardfork: Hardfork.Chainstart,
},
/**
* Description : Ethereum state using a unified binary tree (experimental)
* URL : hhttps://eips.ethereum.org/EIPS/eip-7864
Expand Down
2 changes: 1 addition & 1 deletion 2 packages/common/src/hardforks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ export const hardforksDict: HardforksDict = {
* Status : Final
*/
osaka: {
eips: [663, 3540, 3670, 4200, 4750, 5450, 6206, 7069, 7480, 7594, 7620, 7692, 7698, 7883],
eips: [663, 3540, 3670, 4200, 4750, 5450, 6206, 7069, 7480, 7594, 7620, 7692, 7698, 7823, 7883],
},
/**
* Description: Next feature hardfork after osaka, internally used for verkle testing/implementation (incomplete/experimental)
Expand Down
2 changes: 1 addition & 1 deletion 2 packages/evm/src/evm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,7 @@ export class EVM implements EVMInterface {
const supportedEIPs = [
663, 1153, 1559, 2537, 2565, 2718, 2929, 2930, 2935, 3198, 3529, 3540, 3541, 3607, 3651, 3670,
3855, 3860, 4200, 4399, 4750, 4788, 4844, 4895, 5133, 5450, 5656, 6110, 6206, 6780, 6800,
7002, 7069, 7251, 7480, 7516, 7594, 7620, 7685, 7691, 7692, 7698, 7702, 7709,
7002, 7069, 7251, 7480, 7516, 7594, 7620, 7685, 7691, 7692, 7698, 7702, 7709, 7823,
]

for (const eip of this.common.eips()) {
Expand Down
8 changes: 4 additions & 4 deletions 8 packages/evm/src/precompiles/05-modexp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,8 @@ const BIGINT_3072 = BigInt(3072)
const BIGINT_199680 = BigInt(199680)

const maxInt = BigInt(Number.MAX_SAFE_INTEGER)
const maxSize = BigInt(2147483647) // @ethereumjs/util setLengthRight limitation

function multiplicationComplexity(x: bigint): bigint {
export function multiplicationComplexity(x: bigint): bigint {
let fac1
let fac2
if (x <= BIGINT_64) {
Expand All @@ -52,12 +51,12 @@ function multiplicationComplexity(x: bigint): bigint {
}
}

function multiplicationComplexityEIP2565(x: bigint): bigint {
export function multiplicationComplexityEIP2565(x: bigint): bigint {
const words = (x + BIGINT_7) / BIGINT_8
return words * words
}

function getAdjustedExponentLength(data: Uint8Array, opts: PrecompileInput): bigint {
export function getAdjustedExponentLength(data: Uint8Array, opts: PrecompileInput): bigint {
let expBytesStart
try {
const baseLen = bytesToBigInt(data.subarray(0, 32))
Expand Down Expand Up @@ -157,6 +156,7 @@ export function precompile05(opts: PrecompileInput): ExecResult {
}
}

const maxSize = opts.common.isActivatedEIP(7823) ? BigInt(1024) : BigInt(2147483647) // @ethereumjs/util setLengthRight limitation
jochem-brouwer marked this conversation as resolved.
Show resolved Hide resolved
if (bLen > maxSize || eLen > maxSize || mLen > maxSize) {
if (opts._debug !== undefined) {
opts._debug(`${pName} failed: OOG`)
Expand Down
104 changes: 101 additions & 3 deletions 104 packages/evm/test/precompiles/05-modexp.spec.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,34 @@
import { Common, Mainnet } from '@ethereumjs/common'
import { bytesToHex, hexToBytes } from '@ethereumjs/util'
import {
BIGINT_1,
bytesToBigInt,
bytesToHex,
concatBytes,
hexToBytes,
randomBytes,
setLengthLeft,
setLengthRight,
toBytes,
} from '@ethereumjs/util'
import { assert, beforeAll, describe, it } from 'vitest'

import { createEVM, getActivePrecompiles } from '../../src/index.ts'
import { gasLimitCheck } from '../../src/precompiles/util.ts'

import { createEVM, getActivePrecompiles } from '../../src/index.ts'
import {
expMod,
getAdjustedExponentLength,
multiplicationComplexity,
multiplicationComplexityEIP2565,
} from '../../src/precompiles/05-modexp.ts'
import { getPrecompileName } from '../../src/precompiles/index.ts'
import { testData } from './modexp-testdata.ts'

import type { PrefixedHexString } from '@ethereumjs/util'
import type { EVM } from '../../src/index.ts'
import type { PrecompileFunc } from '../../src/precompiles/types.ts'
import type { PrecompileFunc, PrecompileInput } from '../../src/precompiles/types.ts'

const BIGINT_200 = BigInt(200)

const fuzzerTests = testData.data as PrefixedHexString[][]
describe('Precompiles: MODEXP', () => {
Expand Down Expand Up @@ -51,3 +71,81 @@ describe('Precompiles: MODEXP', () => {
assert.strictEqual(result.executionGasUsed, gas)
})
})

function enoughGas(opts: PrecompileInput): boolean {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now the "utility methods" are exported to calculate the gas fee here, but I think it would be more clean to instead write a gas calculator method inside the precompile and export that to calculate the required gas.

const pName = getPrecompileName('05')
const data = opts.data.length < 96 ? setLengthRight(opts.data, 96) : opts.data

let adjustedELen = getAdjustedExponentLength(data, opts)
if (adjustedELen < BIGINT_1) {
adjustedELen = BIGINT_1
}

const bLen = bytesToBigInt(data.subarray(0, 32))
const mLen = bytesToBigInt(data.subarray(64, 96))

let maxLen = bLen
if (maxLen < mLen) {
maxLen = mLen
}
const Gquaddivisor = opts.common.param('modexpGquaddivisorGas')
let gasUsed

if (!(opts.common.isActivatedEIP(2565) === true)) {
gasUsed = (adjustedELen * multiplicationComplexity(maxLen)) / Gquaddivisor
} else {
gasUsed = (adjustedELen * multiplicationComplexityEIP2565(maxLen)) / Gquaddivisor
if (gasUsed < BIGINT_200) {
gasUsed = BIGINT_200
}
}
if (!(gasLimitCheck(opts, gasUsed, pName) === true)) {
return false
}
return true
}

describe('Precompiles: MODEXP with EIP-7823', async () => {
const common = new Common({ chain: Mainnet, eips: [7823] })
const evm = await createEVM({
common,
})
const addressStr = '0000000000000000000000000000000000000005'
const MODEXP = getActivePrecompiles(common).get(addressStr)!

for (let i = 1020; i < 1028; i++) {
const bLen = i
const eLen = i
const mLen = i
const maxInputLen = Math.max(bLen, eLen, mLen)
const base = randomBytes(bLen)
const exponent = randomBytes(eLen)
const modulo = randomBytes(mLen)
const expected = expMod(bytesToBigInt(base), bytesToBigInt(exponent), bytesToBigInt(modulo))
const input = concatBytes(
setLengthLeft(toBytes(bLen), 32),
setLengthLeft(toBytes(eLen), 32),
setLengthLeft(toBytes(mLen), 32),
concatBytes(base, exponent, modulo),
)

it(`MODEXP should reject and consume all gas for inputs over 8192 bits in length - case ${maxInputLen} bytes`, async () => {
const opts = {
data: input,
gasLimit: BigInt(0xffffffff),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think there should be a sanity check to confirm that this gasLimit is enough to pay for the precompile, but that the precompile now rejects it due to the incode length limit

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have added

common,
_EVM: evm,
}

// sanity check to make sure there is enough gas in order to isolate error to cases causing OOG due to size limit of inputs
while (!enoughGas(opts)) opts.gasLimit *= BigInt(256)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we then have the "calculate gas" method, we can thus "just set" this as the gasLimit. Even if the gas limit is enough, then it will still run out-of-gas because of the inputs being too large.


const result = await MODEXP(opts)
if (maxInputLen > 1024) {
assert.strictEqual(result.exceptionError?.error, 'out of gas')
} else {
assert.deepEqual(bytesToBigInt(result.returnValue), expected)
}
})
}
})
Loading
Morty Proxy This is a proxified and sanitized view of the page, visit original site.