Skip to content

Commit

Permalink
EVM: Generic BN254 (alt_BN128) Interface for Precompiles / Use @noble…
Browse files Browse the repository at this point in the history
…/curves By Default (#3564)

* Rename BN254 (alt_BN128) interface to match the more generic naming scheme started with BLS

* Add bn254 EVM constructor option, use option for existing rustbn.js passing instead of separate constructor parameter

* Add a thin wrapper interface around pure rustbn, use interface within createEVM() method

* Naming adjustments

* Switch over to use Uint8Array as input and output values for the interface, encapsule string conversions for rustbn

* One-time WASM initialization fix

* Add dummy Noble interface

* Add custom (temporary) @noble/curves build to EVM package.json

* Rebuild package-lock.json

* Temporary fix for conflicting @noble/curves versions

* Integrate Noble usage for multiplication

* Add generic equalityLengthCheck, moduloLengthCheck methods from BLS utils to precompile utils

* Fix test

* Minor

* Fixes

* Add validity assertion for G1 point

* Replace equality length check with byte length correction (chop off or right-pad) as stated in EIP

* Fixes

* Add BN254 add implementation

* Add additional pairing modulo length check since not safe to rely on implementation

* Some basic alignment

* First try on pairing (not working yet)

* First pairing tests passing

* Fixes

* Merge branch 'master' into evm-bn254-precompile-native-js

* Update @noble/curves to final v1.5.0 release

* Rebuild package-lock.json

* Merge branch 'master' into evm-bn254-precompile-native-js

* Use plain Noble BN254 by default, clean-up, interface type exports, new --bn254 option for VM test runners (default: rustbn.js)

* Move rustbn-wasm dependency over to EVM dev dependencies, add to client dependencies

* Rebuild package-lock.json

* Integrate in client
  • Loading branch information
holgerd77 committed Aug 8, 2024
1 parent f9788a1 commit 6021203
Show file tree
Hide file tree
Showing 31 changed files with 425 additions and 123 deletions.
21 changes: 16 additions & 5 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions packages/client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@
"mcl-wasm": "^1.5.0",
"memory-level": "^1.0.0",
"prom-client": "^15.1.0",
"rustbn-wasm": "^0.4.0",
"verkle-cryptography-wasm": "^0.4.5",
"winston": "^3.3.3",
"winston-daily-rotate-file": "^4.5.5",
Expand Down
7 changes: 6 additions & 1 deletion packages/client/src/execution/vmexecution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
DBSetTD,
} from '@ethereumjs/blockchain'
import { CacheType, ConsensusType, Hardfork } from '@ethereumjs/common'
import { MCLBLS } from '@ethereumjs/evm'
import { MCLBLS, RustBN254 } from '@ethereumjs/evm'
import { getGenesis } from '@ethereumjs/genesis'
import { DefaultStateManager, StatelessVerkleStateManager } from '@ethereumjs/statemanager'
import { createTrie } from '@ethereumjs/trie'
Expand All @@ -21,6 +21,7 @@ import {
import { VM, runBlock, runTx } from '@ethereumjs/vm'
import { writeFileSync } from 'fs'
import * as mcl from 'mcl-wasm'
import { initRustBN } from 'rustbn-wasm'
import { loadVerkleCrypto } from 'verkle-cryptography-wasm'

import { Event } from '../types.js'
Expand Down Expand Up @@ -178,12 +179,14 @@ export class VMExecution extends Execution {
})

await mcl.init(mcl.BLS12_381)
const rustBN = await initRustBN()
this.merkleVM = await VM.create({
common: this.config.execCommon,
blockchain: this.chain.blockchain,
stateManager,
evmOpts: {
bls: new MCLBLS(mcl),
bn254: new RustBN254(rustBN),
},
profilerOpts: this.config.vmProfilerOpts,
})
Expand All @@ -201,12 +204,14 @@ export class VMExecution extends Execution {
verkleCrypto,
})
await mcl.init(mcl.BLS12_381)
const rustBN = await initRustBN()
this.verkleVM = await VM.create({
common: this.config.execCommon,
blockchain: this.chain.blockchain,
stateManager,
evmOpts: {
bls: new MCLBLS(mcl),
bn254: new RustBN254(rustBN),
},
profilerOpts: this.config.vmProfilerOpts,
})
Expand Down
6 changes: 3 additions & 3 deletions packages/evm/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,11 +60,10 @@
"@ethereumjs/statemanager": "^2.3.0",
"@ethereumjs/tx": "^5.3.0",
"@ethereumjs/util": "^9.0.3",
"@noble/curves": "^1.4.2",
"@noble/curves": "^1.5.0",
"@types/debug": "^4.1.9",
"debug": "^4.3.3",
"ethereum-cryptography": "^2.2.1",
"rustbn-wasm": "^0.4.0"
"ethereum-cryptography": "^2.2.1"
},
"devDependencies": {
"@ethersproject/abi": "^5.0.12",
Expand All @@ -81,6 +80,7 @@
"minimist": "^1.2.5",
"node-dir": "^0.1.17",
"rollup-plugin-visualizer": "^5.12.0",
"rustbn-wasm": "^0.4.0",
"solc": "^0.8.1",
"split": "^1.0.1"
},
Expand Down
12 changes: 5 additions & 7 deletions packages/evm/src/constructors.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
import { Common, Mainnet } from '@ethereumjs/common'
import { SimpleStateManager } from '@ethereumjs/statemanager'
import { initRustBN } from 'rustbn-wasm'

import { NobleBN254 } from './precompiles/index.js'
import { DefaultBlockchain } from './types.js'

import { EVM } from './index.js'

import type { EVMOpts, bn128 } from './index.js'

let initializedRustBN: bn128 | undefined = undefined
import type { EVMOpts } from './index.js'

/**
* Use this async static constructor for the initialization
Expand All @@ -19,8 +17,8 @@ let initializedRustBN: bn128 | undefined = undefined
*/
export async function createEVM(createOpts?: EVMOpts) {
const opts = createOpts ?? ({} as EVMOpts)
const bn128 = initializedRustBN ?? ((await initRustBN()) as bn128)
initializedRustBN = bn128

opts.bn254 = new NobleBN254()

if (opts.common === undefined) {
opts.common = new Common({ chain: Mainnet })
Expand All @@ -34,5 +32,5 @@ export async function createEVM(createOpts?: EVMOpts) {
opts.stateManager = new SimpleStateManager()
}

return new EVM(opts, bn128)
return new EVM(opts)
}
11 changes: 6 additions & 5 deletions packages/evm/src/evm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,14 +42,14 @@ import type {
Blockchain,
CustomOpcode,
EVMBLSInterface,
EVMBN254Interface,
EVMEvents,
EVMInterface,
EVMOpts,
EVMResult,
EVMRunCallOpts,
EVMRunCodeOpts,
ExecResult,
bn128,
} from './types.js'
import type { Common, StateManagerInterface } from '@ethereumjs/common'

Expand Down Expand Up @@ -143,7 +143,7 @@ export class EVM implements EVMInterface {

protected readonly _emit: (topic: string, data: any) => Promise<void>

private _bn128: bn128
private _bn254: EVMBN254Interface

/**
*
Expand All @@ -156,7 +156,7 @@ export class EVM implements EVMInterface {
* @param opts The EVM options
* @param bn128 Initialized bn128 WASM object for precompile usage (internal)
*/
constructor(opts: EVMOpts, bn128: bn128) {
constructor(opts: EVMOpts) {
this.common = opts.common!
this.blockchain = opts.blockchain!
this.stateManager = opts.stateManager!
Expand All @@ -172,7 +172,6 @@ export class EVM implements EVMInterface {
}
}

this._bn128 = bn128
this.events = new AsyncEventEmitter()
this._optsCached = opts

Expand Down Expand Up @@ -215,10 +214,12 @@ export class EVM implements EVMInterface {
this.getActiveOpcodes()
this._precompiles = getActivePrecompiles(this.common, this._customPrecompiles)

// Precompile crypto libraries
if (this.common.isActivatedEIP(2537)) {
this._bls = opts.bls ?? new NobleBLS()
this._bls.init?.()
}
this._bn254 = opts.bn254!

this._emit = async (topic: string, data: any): Promise<void> => {
return new Promise((resolve) => this.events.emit(topic as keyof EVMEvents, data, resolve))
Expand Down Expand Up @@ -1085,7 +1086,7 @@ export class EVM implements EVMInterface {
stateManager: this.stateManager.shallowCopy(),
}
;(opts.stateManager as any).common = common
return new EVM(opts, this._bn128)
return new EVM(opts)
}

public getPerformanceLogs() {
Expand Down
3 changes: 3 additions & 0 deletions packages/evm/src/exceptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ export enum ERROR {
BLS_12_381_INPUT_EMPTY = 'input is empty',
BLS_12_381_FP_NOT_IN_FIELD = 'fp point not in field',

// BN254 errors
BN254_FP_NOT_IN_FIELD = 'fp point not in field',

// Point Evaluation Errors
INVALID_COMMITMENT = 'kzg commitment does not match versioned hash',
INVALID_INPUTS = 'kzg inputs invalid',
Expand Down
10 changes: 8 additions & 2 deletions packages/evm/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,25 +6,29 @@ import { getOpcodesForHF } from './opcodes/index.js'
import {
MCLBLS,
NobleBLS,
NobleBN254,
type PrecompileInput,
RustBN254,
getActivePrecompiles,
} from './precompiles/index.js'

import type { InterpreterStep } from './interpreter.js'
import type {
EVMBLSInterface,
EVMBN254Interface,
EVMInterface,
EVMOpts,
EVMResult,
EVMRunCallOpts,
EVMRunCodeOpts,
ExecResult,
Log,
bn128,
} from './types.js'
export * from './logger.js'

export type {
bn128,
EVMBLSInterface,
EVMBN254Interface,
EVMInterface,
EVMOpts,
EVMResult,
Expand All @@ -46,6 +50,8 @@ export {
MCLBLS,
Message,
NobleBLS,
NobleBN254,
RustBN254,
validateEOF,
}

Expand Down
20 changes: 15 additions & 5 deletions packages/evm/src/precompiles/06-ecadd.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
import { bytesToHex, bytesToUnprefixedHex, hexToBytes, short } from '@ethereumjs/util'
import { bytesToHex, setLengthRight, short } from '@ethereumjs/util'

import { OOGResult } from '../evm.js'
import { EvmErrorResult, OOGResult } from '../evm.js'

import type { EVM } from '../evm.js'
import type { ExecResult } from '../types.js'
import type { PrecompileInput } from './types.js'

export function precompile06(opts: PrecompileInput): ExecResult {
const inputData = bytesToUnprefixedHex(opts.data.subarray(0, 128))

const gasUsed = opts.common.param('ecAddGas')
if (opts._debug !== undefined) {
opts._debug(
Expand All @@ -24,7 +22,19 @@ export function precompile06(opts: PrecompileInput): ExecResult {
return OOGResult(opts.gasLimit)
}

const returnData = hexToBytes((opts._EVM as EVM)['_bn128'].ec_add(inputData))
// > 128 bytes: chop off extra bytes
// < 128 bytes: right-pad with 0-s
const input = setLengthRight(opts.data.subarray(0, 128), 128)

let returnData
try {
returnData = (opts._EVM as EVM)['_bn254'].add(input)
} catch (e: any) {
if (opts._debug !== undefined) {
opts._debug(`ECADD (0x06) failed: ${e.message}`)
}
return EvmErrorResult(e, opts.gasLimit)
}

// check ecadd success or failure by comparing the output length
if (returnData.length !== 64) {
Expand Down
19 changes: 15 additions & 4 deletions packages/evm/src/precompiles/07-ecmul.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import { bytesToHex, bytesToUnprefixedHex, hexToBytes, short } from '@ethereumjs/util'
import { bytesToHex, setLengthRight, short } from '@ethereumjs/util'

import { OOGResult } from '../evm.js'
import { EvmErrorResult, OOGResult } from '../evm.js'

import type { EVM } from '../evm.js'
import type { ExecResult } from '../types.js'
import type { PrecompileInput } from './types.js'

export function precompile07(opts: PrecompileInput): ExecResult {
const inputData = bytesToUnprefixedHex(opts.data.subarray(0, 128))
const gasUsed = opts.common.param('ecMulGas')
if (opts._debug !== undefined) {
opts._debug(
Expand All @@ -24,7 +23,19 @@ export function precompile07(opts: PrecompileInput): ExecResult {
return OOGResult(opts.gasLimit)
}

const returnData = hexToBytes((opts._EVM as EVM)['_bn128'].ec_mul(inputData))
// > 128 bytes: chop off extra bytes
// < 128 bytes: right-pad with 0-s
const input = setLengthRight(opts.data.subarray(0, 128), 128)

let returnData
try {
returnData = (opts._EVM as EVM)['_bn254'].mul(input)
} catch (e: any) {
if (opts._debug !== undefined) {
opts._debug(`ECMUL (0x07) failed: ${e.message}`)
}
return EvmErrorResult(e, opts.gasLimit)
}

// check ecmul success or failure by comparing the output length
if (returnData.length !== 64) {
Expand Down
Loading

0 comments on commit 6021203

Please sign in to comment.