From 47db32f033e282283440e91401a3821c3ef13c58 Mon Sep 17 00:00:00 2001 From: Holger Drewes Date: Tue, 6 Aug 2024 12:04:07 +0200 Subject: [PATCH 01/30] Rename BN254 (alt_BN128) interface to match the more generic naming scheme started with BLS --- packages/evm/src/constructors.ts | 10 +++++----- packages/evm/src/evm.ts | 6 +++--- packages/evm/src/index.ts | 4 ++-- packages/evm/src/types.ts | 23 ++++++++++++++--------- 4 files changed, 24 insertions(+), 19 deletions(-) diff --git a/packages/evm/src/constructors.ts b/packages/evm/src/constructors.ts index 16c6ecf607..320ffbafcb 100644 --- a/packages/evm/src/constructors.ts +++ b/packages/evm/src/constructors.ts @@ -6,9 +6,9 @@ import { DefaultBlockchain } from './types.js' import { EVM } from './index.js' -import type { EVMOpts, bn128 } from './index.js' +import type { EVMBN254Interface, EVMOpts } from './index.js' -let initializedRustBN: bn128 | undefined = undefined +let initializedRustBN: EVMBN254Interface | undefined = undefined /** * Use this async static constructor for the initialization @@ -19,8 +19,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 + const bn254 = initializedRustBN ?? ((await initRustBN()) as EVMBN254Interface) + initializedRustBN = bn254 if (opts.common === undefined) { opts.common = new Common({ chain: Mainnet }) @@ -34,5 +34,5 @@ export async function createEVM(createOpts?: EVMOpts) { opts.stateManager = new SimpleStateManager() } - return new EVM(opts, bn128) + return new EVM(opts, bn254) } diff --git a/packages/evm/src/evm.ts b/packages/evm/src/evm.ts index 093339bd10..dd1f64c724 100644 --- a/packages/evm/src/evm.ts +++ b/packages/evm/src/evm.ts @@ -42,6 +42,7 @@ import type { Blockchain, CustomOpcode, EVMBLSInterface, + EVMBN254Interface, EVMEvents, EVMInterface, EVMOpts, @@ -49,7 +50,6 @@ import type { EVMRunCallOpts, EVMRunCodeOpts, ExecResult, - bn128, } from './types.js' import type { Common, StateManagerInterface } from '@ethereumjs/common' @@ -143,7 +143,7 @@ export class EVM implements EVMInterface { protected readonly _emit: (topic: string, data: any) => Promise - private _bn128: bn128 + private _bn128: EVMBN254Interface /** * @@ -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, bn128: EVMBN254Interface) { this.common = opts.common! this.blockchain = opts.blockchain! this.stateManager = opts.stateManager! diff --git a/packages/evm/src/index.ts b/packages/evm/src/index.ts index dc79d5c626..5313106c43 100644 --- a/packages/evm/src/index.ts +++ b/packages/evm/src/index.ts @@ -11,6 +11,7 @@ import { import type { InterpreterStep } from './interpreter.js' import type { + EVMBN254Interface, EVMInterface, EVMOpts, EVMResult, @@ -18,12 +19,11 @@ import type { EVMRunCodeOpts, ExecResult, Log, - bn128, } from './types.js' export * from './logger.js' export type { - bn128, + EVMBN254Interface, EVMInterface, EVMOpts, EVMResult, diff --git a/packages/evm/src/types.ts b/packages/evm/src/types.ts index 109b79129d..43f460884d 100644 --- a/packages/evm/src/types.ts +++ b/packages/evm/src/types.ts @@ -382,6 +382,10 @@ export interface ExecResult { blobGasUsed?: bigint } +/** + * High level wrapper for BLS libraries used + * for the BLS precompiles + */ export type EVMBLSInterface = { init?(): void addG1(input: Uint8Array): Uint8Array @@ -395,6 +399,16 @@ export type EVMBLSInterface = { pairingCheck(input: Uint8Array): Uint8Array } +/** + * High level wrapper for BN254 (alt_BN128) libraries + * used for the BN254 (alt_BN128) EC precompiles + */ +export type EVMBN254Interface = { + ec_pairing: (input_str: string) => PrefixedHexString + ec_add: (input_str: string) => PrefixedHexString + ec_mul: (input_hex: string) => PrefixedHexString +} + /** * Log that the contract emits. */ @@ -446,15 +460,6 @@ export class DefaultBlockchain implements Blockchain { } } -/** - * The BN128 curve package (`rustbn-wasm`) - */ -export interface bn128 { - ec_pairing: (input_str: string) => PrefixedHexString - ec_add: (input_str: string) => PrefixedHexString - ec_mul: (input_hex: string) => PrefixedHexString -} - // EOF type which holds the execution-related data for EOF export type EOFEnv = { container: EOFContainer From 2cb20a3f2cd390f86120b3d0db8048613ea3755b Mon Sep 17 00:00:00 2001 From: Holger Drewes Date: Tue, 6 Aug 2024 12:23:34 +0200 Subject: [PATCH 02/30] Add bn254 EVM constructor option, use option for existing rustbn.js passing instead of separate constructor parameter --- packages/evm/src/constructors.ts | 7 ++---- packages/evm/src/evm.ts | 9 +++---- packages/evm/src/precompiles/06-ecadd.ts | 2 +- packages/evm/src/precompiles/07-ecmul.ts | 2 +- packages/evm/src/precompiles/08-ecpairing.ts | 2 +- packages/evm/src/types.ts | 25 +++++++++++++++++++- 6 files changed, 34 insertions(+), 13 deletions(-) diff --git a/packages/evm/src/constructors.ts b/packages/evm/src/constructors.ts index 320ffbafcb..3b802134af 100644 --- a/packages/evm/src/constructors.ts +++ b/packages/evm/src/constructors.ts @@ -8,8 +8,6 @@ import { EVM } from './index.js' import type { EVMBN254Interface, EVMOpts } from './index.js' -let initializedRustBN: EVMBN254Interface | undefined = undefined - /** * Use this async static constructor for the initialization * of an EVM object @@ -19,8 +17,7 @@ let initializedRustBN: EVMBN254Interface | undefined = undefined */ export async function createEVM(createOpts?: EVMOpts) { const opts = createOpts ?? ({} as EVMOpts) - const bn254 = initializedRustBN ?? ((await initRustBN()) as EVMBN254Interface) - initializedRustBN = bn254 + opts.bn254 = (await initRustBN()) as EVMBN254Interface if (opts.common === undefined) { opts.common = new Common({ chain: Mainnet }) @@ -34,5 +31,5 @@ export async function createEVM(createOpts?: EVMOpts) { opts.stateManager = new SimpleStateManager() } - return new EVM(opts, bn254) + return new EVM(opts) } diff --git a/packages/evm/src/evm.ts b/packages/evm/src/evm.ts index dd1f64c724..c39855664f 100644 --- a/packages/evm/src/evm.ts +++ b/packages/evm/src/evm.ts @@ -143,7 +143,7 @@ export class EVM implements EVMInterface { protected readonly _emit: (topic: string, data: any) => Promise - private _bn128: EVMBN254Interface + private _bn254: EVMBN254Interface /** * @@ -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: EVMBN254Interface) { + constructor(opts: EVMOpts) { this.common = opts.common! this.blockchain = opts.blockchain! this.stateManager = opts.stateManager! @@ -172,7 +172,6 @@ export class EVM implements EVMInterface { } } - this._bn128 = bn128 this.events = new AsyncEventEmitter() this._optsCached = opts @@ -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 => { return new Promise((resolve) => this.events.emit(topic as keyof EVMEvents, data, resolve)) @@ -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() { diff --git a/packages/evm/src/precompiles/06-ecadd.ts b/packages/evm/src/precompiles/06-ecadd.ts index f66858f280..f227154264 100644 --- a/packages/evm/src/precompiles/06-ecadd.ts +++ b/packages/evm/src/precompiles/06-ecadd.ts @@ -24,7 +24,7 @@ export function precompile06(opts: PrecompileInput): ExecResult { return OOGResult(opts.gasLimit) } - const returnData = hexToBytes((opts._EVM as EVM)['_bn128'].ec_add(inputData)) + const returnData = hexToBytes((opts._EVM as EVM)['_bn254'].ec_add(inputData)) // check ecadd success or failure by comparing the output length if (returnData.length !== 64) { diff --git a/packages/evm/src/precompiles/07-ecmul.ts b/packages/evm/src/precompiles/07-ecmul.ts index 6f269f1d41..865dba1052 100644 --- a/packages/evm/src/precompiles/07-ecmul.ts +++ b/packages/evm/src/precompiles/07-ecmul.ts @@ -24,7 +24,7 @@ export function precompile07(opts: PrecompileInput): ExecResult { return OOGResult(opts.gasLimit) } - const returnData = hexToBytes((opts._EVM as EVM)['_bn128'].ec_mul(inputData)) + const returnData = hexToBytes((opts._EVM as EVM)['_bn254'].ec_mul(inputData)) // check ecmul success or failure by comparing the output length if (returnData.length !== 64) { diff --git a/packages/evm/src/precompiles/08-ecpairing.ts b/packages/evm/src/precompiles/08-ecpairing.ts index aeda2b96ca..a6404274bc 100644 --- a/packages/evm/src/precompiles/08-ecpairing.ts +++ b/packages/evm/src/precompiles/08-ecpairing.ts @@ -28,7 +28,7 @@ export function precompile08(opts: PrecompileInput): ExecResult { } const returnData = hexToBytes( - (opts._EVM as EVM)['_bn128'].ec_pairing(bytesToUnprefixedHex(inputData)), + (opts._EVM as EVM)['_bn254'].ec_pairing(bytesToUnprefixedHex(inputData)), ) // check ecpairing success or failure by comparing the output length diff --git a/packages/evm/src/types.ts b/packages/evm/src/types.ts index 43f460884d..67ddad937c 100644 --- a/packages/evm/src/types.ts +++ b/packages/evm/src/types.ts @@ -284,7 +284,7 @@ export interface EVMOpts { /** * For the EIP-2935 BLS precompiles, the native JS `@noble/curves` * https://github.com/paulmillr/noble-curves BLS12-381 curve implementation - * is used (see `noble.ts` file in the `precompiles` folder). + * is used (see `noble.ts` file in the `precompiles/bls12_381/` folder). * * To use an alternative implementation this option can be used by passing * in a wrapper implementation integrating the desired library and adhering @@ -303,6 +303,29 @@ export interface EVMOpts { */ bls?: EVMBLSInterface + /** + * For the EIP-196/EIP-197 BN254 (alt_BN128) EC precompiles, the native JS `@noble/curves` + * https://github.com/paulmillr/noble-curves BN254 curve implementation + * is used (see `noble.ts` file in the `precompiles/bn254/` folder). + * + * To use an alternative implementation this option can be used by passing + * in a wrapper implementation integrating the desired library and adhering + * to the `EVMBN254Interface` specification. + * + * An interface for a WASM wrapper https://github.com/ethereumjs/rustbn.js around the + * Parity fork of the Zcash bn pairing cryptography library is shipped with this library + * which can be used as follows (with `rustbn.js` being explicitly added to the set of + * dependencies): + * + * ```ts + * import { initRustBN } from 'rustbn-wasm' + * + * const bn254 = await initRustBN() + * const evm = await createEVM({ bn254: new RustBN254(bn254) }) + * ``` + */ + bn254?: EVMBN254Interface + /* * The EVM comes with a basic dependency-minimized `SimpleStateManager` implementation * which serves most code execution use cases and which is included in the From 59766c90a5b6abcd7bf1ed8435f817ac706943f4 Mon Sep 17 00:00:00 2001 From: Holger Drewes Date: Tue, 6 Aug 2024 12:35:27 +0200 Subject: [PATCH 03/30] Add a thin wrapper interface around pure rustbn, use interface within createEVM() method --- packages/evm/src/constructors.ts | 6 +++-- packages/evm/src/precompiles/bn254/index.ts | 1 + packages/evm/src/precompiles/bn254/rustbn.ts | 28 ++++++++++++++++++++ packages/evm/src/precompiles/index.ts | 2 ++ packages/evm/src/types.ts | 2 +- 5 files changed, 36 insertions(+), 3 deletions(-) create mode 100644 packages/evm/src/precompiles/bn254/index.ts create mode 100644 packages/evm/src/precompiles/bn254/rustbn.ts diff --git a/packages/evm/src/constructors.ts b/packages/evm/src/constructors.ts index 3b802134af..73dcf82087 100644 --- a/packages/evm/src/constructors.ts +++ b/packages/evm/src/constructors.ts @@ -2,11 +2,12 @@ import { Common, Mainnet } from '@ethereumjs/common' import { SimpleStateManager } from '@ethereumjs/statemanager' import { initRustBN } from 'rustbn-wasm' +import { RustBN254 } from './precompiles/index.js' import { DefaultBlockchain } from './types.js' import { EVM } from './index.js' -import type { EVMBN254Interface, EVMOpts } from './index.js' +import type { EVMOpts } from './index.js' /** * Use this async static constructor for the initialization @@ -17,7 +18,8 @@ import type { EVMBN254Interface, EVMOpts } from './index.js' */ export async function createEVM(createOpts?: EVMOpts) { const opts = createOpts ?? ({} as EVMOpts) - opts.bn254 = (await initRustBN()) as EVMBN254Interface + const rustbn = await initRustBN() + opts.bn254 = new RustBN254(rustbn) if (opts.common === undefined) { opts.common = new Common({ chain: Mainnet }) diff --git a/packages/evm/src/precompiles/bn254/index.ts b/packages/evm/src/precompiles/bn254/index.ts new file mode 100644 index 0000000000..afd643c949 --- /dev/null +++ b/packages/evm/src/precompiles/bn254/index.ts @@ -0,0 +1 @@ +export { RustBN254 } from './rustbn.js' diff --git a/packages/evm/src/precompiles/bn254/rustbn.ts b/packages/evm/src/precompiles/bn254/rustbn.ts new file mode 100644 index 0000000000..3d52f7b3f0 --- /dev/null +++ b/packages/evm/src/precompiles/bn254/rustbn.ts @@ -0,0 +1,28 @@ +import type { EVMBN254Interface } from '../../types.js' +import type { PrefixedHexString } from '@ethereumjs/util' + +/** + * Implementation of the `EVMBN254Interface` using a WASM wrapper https://github.com/ethereumjs/rustbn.js + * around the Parity fork of the Zcash bn pairing cryptography library. + * + * This can be optionally used to replace the build-in Noble implementation (`NobleBN254`) with + * a more performant WASM variant. See EVM `bls` constructor option on how to use. + */ +export class RustBN254 implements EVMBN254Interface { + protected readonly _rustbn: any + + constructor(rustbn: any) { + this._rustbn = rustbn + } + + ec_add(input_str: string): PrefixedHexString { + return this._rustbn.ec_add(input_str) + } + + ec_mul(input_hex: string): PrefixedHexString { + return this._rustbn.ec_mul(input_hex) + } + ec_pairing(input_str: string): PrefixedHexString { + return this._rustbn.ec_pairing(input_str) + } +} diff --git a/packages/evm/src/precompiles/index.ts b/packages/evm/src/precompiles/index.ts index c84f429948..0af1642861 100644 --- a/packages/evm/src/precompiles/index.ts +++ b/packages/evm/src/precompiles/index.ts @@ -21,6 +21,7 @@ import { precompile11 } from './11-bls12-pairing.js' import { precompile12 } from './12-bls12-map-fp-to-g1.js' import { precompile13 } from './13-bls12-map-fp2-to-g2.js' import { MCLBLS, NobleBLS } from './bls12_381/index.js' +import { RustBN254 } from './bn254/index.js' import type { PrecompileFunc, PrecompileInput } from './types.js' import type { Common } from '@ethereumjs/common' @@ -310,6 +311,7 @@ export { precompileEntries, precompiles, ripemdPrecompileAddress, + RustBN254, } export type { AddPrecompile, CustomPrecompile, DeletePrecompile, PrecompileFunc, PrecompileInput } diff --git a/packages/evm/src/types.ts b/packages/evm/src/types.ts index 67ddad937c..e4b75fedca 100644 --- a/packages/evm/src/types.ts +++ b/packages/evm/src/types.ts @@ -427,9 +427,9 @@ export type EVMBLSInterface = { * used for the BN254 (alt_BN128) EC precompiles */ export type EVMBN254Interface = { - ec_pairing: (input_str: string) => PrefixedHexString ec_add: (input_str: string) => PrefixedHexString ec_mul: (input_hex: string) => PrefixedHexString + ec_pairing: (input_str: string) => PrefixedHexString } /** From fb2f4a77ff41ac4c5ff4558f23e7e621bd7f5d4f Mon Sep 17 00:00:00 2001 From: Holger Drewes Date: Tue, 6 Aug 2024 12:39:28 +0200 Subject: [PATCH 04/30] Naming adjustments --- packages/evm/src/precompiles/06-ecadd.ts | 2 +- packages/evm/src/precompiles/07-ecmul.ts | 2 +- packages/evm/src/precompiles/08-ecpairing.ts | 2 +- packages/evm/src/precompiles/bn254/rustbn.ts | 12 ++++++------ packages/evm/src/types.ts | 6 +++--- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/packages/evm/src/precompiles/06-ecadd.ts b/packages/evm/src/precompiles/06-ecadd.ts index f227154264..91e65a726d 100644 --- a/packages/evm/src/precompiles/06-ecadd.ts +++ b/packages/evm/src/precompiles/06-ecadd.ts @@ -24,7 +24,7 @@ export function precompile06(opts: PrecompileInput): ExecResult { return OOGResult(opts.gasLimit) } - const returnData = hexToBytes((opts._EVM as EVM)['_bn254'].ec_add(inputData)) + const returnData = hexToBytes((opts._EVM as EVM)['_bn254'].add(inputData)) // check ecadd success or failure by comparing the output length if (returnData.length !== 64) { diff --git a/packages/evm/src/precompiles/07-ecmul.ts b/packages/evm/src/precompiles/07-ecmul.ts index 865dba1052..3adc49c305 100644 --- a/packages/evm/src/precompiles/07-ecmul.ts +++ b/packages/evm/src/precompiles/07-ecmul.ts @@ -24,7 +24,7 @@ export function precompile07(opts: PrecompileInput): ExecResult { return OOGResult(opts.gasLimit) } - const returnData = hexToBytes((opts._EVM as EVM)['_bn254'].ec_mul(inputData)) + const returnData = hexToBytes((opts._EVM as EVM)['_bn254'].mul(inputData)) // check ecmul success or failure by comparing the output length if (returnData.length !== 64) { diff --git a/packages/evm/src/precompiles/08-ecpairing.ts b/packages/evm/src/precompiles/08-ecpairing.ts index a6404274bc..7a65de37b9 100644 --- a/packages/evm/src/precompiles/08-ecpairing.ts +++ b/packages/evm/src/precompiles/08-ecpairing.ts @@ -28,7 +28,7 @@ export function precompile08(opts: PrecompileInput): ExecResult { } const returnData = hexToBytes( - (opts._EVM as EVM)['_bn254'].ec_pairing(bytesToUnprefixedHex(inputData)), + (opts._EVM as EVM)['_bn254'].pairing(bytesToUnprefixedHex(inputData)), ) // check ecpairing success or failure by comparing the output length diff --git a/packages/evm/src/precompiles/bn254/rustbn.ts b/packages/evm/src/precompiles/bn254/rustbn.ts index 3d52f7b3f0..a27c1889db 100644 --- a/packages/evm/src/precompiles/bn254/rustbn.ts +++ b/packages/evm/src/precompiles/bn254/rustbn.ts @@ -15,14 +15,14 @@ export class RustBN254 implements EVMBN254Interface { this._rustbn = rustbn } - ec_add(input_str: string): PrefixedHexString { - return this._rustbn.ec_add(input_str) + add(inputStr: string): PrefixedHexString { + return this._rustbn.ec_add(inputStr) } - ec_mul(input_hex: string): PrefixedHexString { - return this._rustbn.ec_mul(input_hex) + mul(inputHex: string): PrefixedHexString { + return this._rustbn.ec_mul(inputHex) } - ec_pairing(input_str: string): PrefixedHexString { - return this._rustbn.ec_pairing(input_str) + pairing(inputStr: string): PrefixedHexString { + return this._rustbn.ec_pairing(inputStr) } } diff --git a/packages/evm/src/types.ts b/packages/evm/src/types.ts index e4b75fedca..293ff83979 100644 --- a/packages/evm/src/types.ts +++ b/packages/evm/src/types.ts @@ -427,9 +427,9 @@ export type EVMBLSInterface = { * used for the BN254 (alt_BN128) EC precompiles */ export type EVMBN254Interface = { - ec_add: (input_str: string) => PrefixedHexString - ec_mul: (input_hex: string) => PrefixedHexString - ec_pairing: (input_str: string) => PrefixedHexString + add: (input_str: string) => PrefixedHexString + mul: (input_hex: string) => PrefixedHexString + pairing: (input_str: string) => PrefixedHexString } /** From ef4e8dc079c4f4a5cd1e09dc8969a1748323744e Mon Sep 17 00:00:00 2001 From: Holger Drewes Date: Tue, 6 Aug 2024 12:50:03 +0200 Subject: [PATCH 05/30] Switch over to use Uint8Array as input and output values for the interface, encapsule string conversions for rustbn --- packages/evm/src/precompiles/06-ecadd.ts | 6 ++---- packages/evm/src/precompiles/07-ecmul.ts | 5 ++--- packages/evm/src/precompiles/08-ecpairing.ts | 9 +++------ packages/evm/src/precompiles/bn254/rustbn.ts | 18 +++++++++++------- packages/evm/src/types.ts | 6 +++--- 5 files changed, 21 insertions(+), 23 deletions(-) diff --git a/packages/evm/src/precompiles/06-ecadd.ts b/packages/evm/src/precompiles/06-ecadd.ts index 91e65a726d..dde0cac5af 100644 --- a/packages/evm/src/precompiles/06-ecadd.ts +++ b/packages/evm/src/precompiles/06-ecadd.ts @@ -1,4 +1,4 @@ -import { bytesToHex, bytesToUnprefixedHex, hexToBytes, short } from '@ethereumjs/util' +import { bytesToHex, short } from '@ethereumjs/util' import { OOGResult } from '../evm.js' @@ -7,8 +7,6 @@ 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( @@ -24,7 +22,7 @@ export function precompile06(opts: PrecompileInput): ExecResult { return OOGResult(opts.gasLimit) } - const returnData = hexToBytes((opts._EVM as EVM)['_bn254'].add(inputData)) + const returnData = (opts._EVM as EVM)['_bn254'].add(opts.data.subarray(0, 128)) // check ecadd success or failure by comparing the output length if (returnData.length !== 64) { diff --git a/packages/evm/src/precompiles/07-ecmul.ts b/packages/evm/src/precompiles/07-ecmul.ts index 3adc49c305..056de08c29 100644 --- a/packages/evm/src/precompiles/07-ecmul.ts +++ b/packages/evm/src/precompiles/07-ecmul.ts @@ -1,4 +1,4 @@ -import { bytesToHex, bytesToUnprefixedHex, hexToBytes, short } from '@ethereumjs/util' +import { bytesToHex, short } from '@ethereumjs/util' import { OOGResult } from '../evm.js' @@ -7,7 +7,6 @@ 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( @@ -24,7 +23,7 @@ export function precompile07(opts: PrecompileInput): ExecResult { return OOGResult(opts.gasLimit) } - const returnData = hexToBytes((opts._EVM as EVM)['_bn254'].mul(inputData)) + const returnData = (opts._EVM as EVM)['_bn254'].mul(opts.data.subarray(0, 128)) // check ecmul success or failure by comparing the output length if (returnData.length !== 64) { diff --git a/packages/evm/src/precompiles/08-ecpairing.ts b/packages/evm/src/precompiles/08-ecpairing.ts index 7a65de37b9..7481ca416f 100644 --- a/packages/evm/src/precompiles/08-ecpairing.ts +++ b/packages/evm/src/precompiles/08-ecpairing.ts @@ -1,4 +1,4 @@ -import { bytesToHex, bytesToUnprefixedHex, hexToBytes, short } from '@ethereumjs/util' +import { bytesToHex, short } from '@ethereumjs/util' import { OOGResult } from '../evm.js' @@ -7,9 +7,8 @@ import type { ExecResult } from '../types.js' import type { PrecompileInput } from './types.js' export function precompile08(opts: PrecompileInput): ExecResult { - const inputData = opts.data // no need to care about non-divisible-by-192, because bn128.pairing will properly fail in that case - const inputDataSize = BigInt(Math.floor(inputData.length / 192)) + const inputDataSize = BigInt(Math.floor(opts.data.length / 192)) const gasUsed = opts.common.param('ecPairingGas') + inputDataSize * opts.common.param('ecPairingWordGas') if (opts._debug !== undefined) { @@ -27,9 +26,7 @@ export function precompile08(opts: PrecompileInput): ExecResult { return OOGResult(opts.gasLimit) } - const returnData = hexToBytes( - (opts._EVM as EVM)['_bn254'].pairing(bytesToUnprefixedHex(inputData)), - ) + const returnData = (opts._EVM as EVM)['_bn254'].pairing(opts.data) // check ecpairing success or failure by comparing the output length if (returnData.length !== 32) { diff --git a/packages/evm/src/precompiles/bn254/rustbn.ts b/packages/evm/src/precompiles/bn254/rustbn.ts index a27c1889db..a6a9877d77 100644 --- a/packages/evm/src/precompiles/bn254/rustbn.ts +++ b/packages/evm/src/precompiles/bn254/rustbn.ts @@ -1,5 +1,6 @@ +import { bytesToUnprefixedHex, hexToBytes } from '@ethereumjs/util' + import type { EVMBN254Interface } from '../../types.js' -import type { PrefixedHexString } from '@ethereumjs/util' /** * Implementation of the `EVMBN254Interface` using a WASM wrapper https://github.com/ethereumjs/rustbn.js @@ -15,14 +16,17 @@ export class RustBN254 implements EVMBN254Interface { this._rustbn = rustbn } - add(inputStr: string): PrefixedHexString { - return this._rustbn.ec_add(inputStr) + add(input: Uint8Array): Uint8Array { + const inputStr = bytesToUnprefixedHex(input) + return hexToBytes(this._rustbn.ec_add(inputStr)) } - mul(inputHex: string): PrefixedHexString { - return this._rustbn.ec_mul(inputHex) + mul(input: Uint8Array): Uint8Array { + const inputHex = bytesToUnprefixedHex(input) + return hexToBytes(this._rustbn.ec_mul(inputHex)) } - pairing(inputStr: string): PrefixedHexString { - return this._rustbn.ec_pairing(inputStr) + pairing(input: Uint8Array): Uint8Array { + const inputStr = bytesToUnprefixedHex(input) + return hexToBytes(this._rustbn.ec_pairing(inputStr)) } } diff --git a/packages/evm/src/types.ts b/packages/evm/src/types.ts index 293ff83979..752836afc0 100644 --- a/packages/evm/src/types.ts +++ b/packages/evm/src/types.ts @@ -427,9 +427,9 @@ export type EVMBLSInterface = { * used for the BN254 (alt_BN128) EC precompiles */ export type EVMBN254Interface = { - add: (input_str: string) => PrefixedHexString - mul: (input_hex: string) => PrefixedHexString - pairing: (input_str: string) => PrefixedHexString + add: (input: Uint8Array) => Uint8Array + mul: (input: Uint8Array) => Uint8Array + pairing: (input: Uint8Array) => Uint8Array } /** From cfe2f3243cdc6d7d6b86cd73ac11b38422693321 Mon Sep 17 00:00:00 2001 From: Holger Drewes Date: Tue, 6 Aug 2024 15:01:51 +0200 Subject: [PATCH 06/30] One-time WASM initialization fix --- packages/evm/src/constructors.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/evm/src/constructors.ts b/packages/evm/src/constructors.ts index 73dcf82087..3324472b19 100644 --- a/packages/evm/src/constructors.ts +++ b/packages/evm/src/constructors.ts @@ -9,6 +9,10 @@ import { EVM } from './index.js' import type { EVMOpts } from './index.js' +// Hack to ensure one-time instantiation from rustbn.js since +// otherwise "unreachable" error occurs during WASM access +let initializedRustBN: any | undefined = undefined + /** * Use this async static constructor for the initialization * of an EVM object @@ -18,8 +22,8 @@ import type { EVMOpts } from './index.js' */ export async function createEVM(createOpts?: EVMOpts) { const opts = createOpts ?? ({} as EVMOpts) - const rustbn = await initRustBN() - opts.bn254 = new RustBN254(rustbn) + initializedRustBN = initializedRustBN ?? (await initRustBN()) + opts.bn254 = new RustBN254(initializedRustBN) if (opts.common === undefined) { opts.common = new Common({ chain: Mainnet }) From 1743872cbd66d0b62b394e32abf4bee3b36df6be Mon Sep 17 00:00:00 2001 From: Holger Drewes Date: Tue, 6 Aug 2024 15:11:02 +0200 Subject: [PATCH 07/30] Add dummy Noble interface --- packages/evm/src/constructors.ts | 4 +-- packages/evm/src/precompiles/bn254/index.ts | 1 + packages/evm/src/precompiles/bn254/noble.ts | 31 +++++++++++++++++++++ packages/evm/src/precompiles/index.ts | 3 +- 4 files changed, 36 insertions(+), 3 deletions(-) create mode 100644 packages/evm/src/precompiles/bn254/noble.ts diff --git a/packages/evm/src/constructors.ts b/packages/evm/src/constructors.ts index 3324472b19..aa452cb9c0 100644 --- a/packages/evm/src/constructors.ts +++ b/packages/evm/src/constructors.ts @@ -2,7 +2,7 @@ import { Common, Mainnet } from '@ethereumjs/common' import { SimpleStateManager } from '@ethereumjs/statemanager' import { initRustBN } from 'rustbn-wasm' -import { RustBN254 } from './precompiles/index.js' +import { NobleBN254 } from './precompiles/index.js' import { DefaultBlockchain } from './types.js' import { EVM } from './index.js' @@ -23,7 +23,7 @@ let initializedRustBN: any | undefined = undefined export async function createEVM(createOpts?: EVMOpts) { const opts = createOpts ?? ({} as EVMOpts) initializedRustBN = initializedRustBN ?? (await initRustBN()) - opts.bn254 = new RustBN254(initializedRustBN) + opts.bn254 = new NobleBN254(initializedRustBN) if (opts.common === undefined) { opts.common = new Common({ chain: Mainnet }) diff --git a/packages/evm/src/precompiles/bn254/index.ts b/packages/evm/src/precompiles/bn254/index.ts index afd643c949..d54b65c7e0 100644 --- a/packages/evm/src/precompiles/bn254/index.ts +++ b/packages/evm/src/precompiles/bn254/index.ts @@ -1 +1,2 @@ +export { NobleBN254 } from './noble.js' export { RustBN254 } from './rustbn.js' diff --git a/packages/evm/src/precompiles/bn254/noble.ts b/packages/evm/src/precompiles/bn254/noble.ts new file mode 100644 index 0000000000..422c5b3983 --- /dev/null +++ b/packages/evm/src/precompiles/bn254/noble.ts @@ -0,0 +1,31 @@ +import { bytesToUnprefixedHex, hexToBytes } from '@ethereumjs/util' + +import type { EVMBN254Interface } from '../../types.js' + +/** + * Implementation of the `EVMBN254Interface` using the `@noble/curves` JS library, + * see https://github.com/paulmillr/noble-curves. + * + * This is the EVM default implementation. + */ +export class NobleBN254 implements EVMBN254Interface { + protected readonly _rustbn: any + + constructor(rustbn: any) { + this._rustbn = rustbn + } + + add(input: Uint8Array): Uint8Array { + const inputStr = bytesToUnprefixedHex(input) + return hexToBytes(this._rustbn.ec_add(inputStr)) + } + + mul(input: Uint8Array): Uint8Array { + const inputHex = bytesToUnprefixedHex(input) + return hexToBytes(this._rustbn.ec_mul(inputHex)) + } + pairing(input: Uint8Array): Uint8Array { + const inputStr = bytesToUnprefixedHex(input) + return hexToBytes(this._rustbn.ec_pairing(inputStr)) + } +} diff --git a/packages/evm/src/precompiles/index.ts b/packages/evm/src/precompiles/index.ts index 0af1642861..c2ec81b8dc 100644 --- a/packages/evm/src/precompiles/index.ts +++ b/packages/evm/src/precompiles/index.ts @@ -21,7 +21,7 @@ import { precompile11 } from './11-bls12-pairing.js' import { precompile12 } from './12-bls12-map-fp-to-g1.js' import { precompile13 } from './13-bls12-map-fp2-to-g2.js' import { MCLBLS, NobleBLS } from './bls12_381/index.js' -import { RustBN254 } from './bn254/index.js' +import { NobleBN254, RustBN254 } from './bn254/index.js' import type { PrecompileFunc, PrecompileInput } from './types.js' import type { Common } from '@ethereumjs/common' @@ -308,6 +308,7 @@ export { getPrecompileName, MCLBLS, NobleBLS, + NobleBN254, precompileEntries, precompiles, ripemdPrecompileAddress, From 978335f031d41e033e96ebed7de8425b69649470 Mon Sep 17 00:00:00 2001 From: Holger Drewes Date: Tue, 6 Aug 2024 15:38:45 +0200 Subject: [PATCH 08/30] Add custom (temporary) @noble/curves build to EVM package.json --- packages/evm/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/evm/package.json b/packages/evm/package.json index bdd1015585..c2d83f3421 100644 --- a/packages/evm/package.json +++ b/packages/evm/package.json @@ -63,7 +63,7 @@ "@types/debug": "^4.1.9", "debug": "^4.3.3", "ethereum-cryptography": "^2.2.1", - "@noble/curves": "^1.4.2", + "@noble/curves": "git://github.com/holgerd77/noble-curves.git#build-3ed792f ", "rustbn-wasm": "^0.4.0" }, "devDependencies": { From ec29aafbe88ddd95b7d229e4ca879d8b32f62ffa Mon Sep 17 00:00:00 2001 From: Holger Drewes Date: Tue, 6 Aug 2024 15:39:18 +0200 Subject: [PATCH 09/30] Rebuild package-lock.json --- package-lock.json | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index efe96a9f05..d4e0298460 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2159,8 +2159,7 @@ }, "node_modules/@noble/curves": { "version": "1.4.2", - "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.4.2.tgz", - "integrity": "sha512-TavHr8qycMChk8UwMld0ZDRvatedkzWfH8IiaeGCfymOP5i0hSCozz9vHOL0nkwk7HRMlFnAiKpS2jrUmSybcw==", + "resolved": "git+ssh://git@github.com/holgerd77/noble-curves.git#d4ccba71cfa549637d5c10322639d5467397c0fb", "dependencies": { "@noble/hashes": "1.4.0" }, @@ -16821,7 +16820,7 @@ "@ethereumjs/statemanager": "^2.3.0", "@ethereumjs/tx": "^5.3.0", "@ethereumjs/util": "^9.0.3", - "@noble/curves": "^1.4.2", + "@noble/curves": "git://github.com/holgerd77/noble-curves.git#build-3ed792f ", "@types/debug": "^4.1.9", "debug": "^4.3.3", "ethereum-cryptography": "^2.2.1", From b7fdd498411ea29a93498f933f597f739d64034c Mon Sep 17 00:00:00 2001 From: Holger Drewes Date: Tue, 6 Aug 2024 15:39:35 +0200 Subject: [PATCH 10/30] Temporary fix for conflicting @noble/curves versions --- packages/evm/src/precompiles/bls12_381/noble.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/evm/src/precompiles/bls12_381/noble.ts b/packages/evm/src/precompiles/bls12_381/noble.ts index 4a39590785..b9bf5c163b 100644 --- a/packages/evm/src/precompiles/bls12_381/noble.ts +++ b/packages/evm/src/precompiles/bls12_381/noble.ts @@ -66,7 +66,8 @@ function BLS12_381_FromG1Point(input: AffinePoint): Uint8Array { * @param input Input Uint8Array. Should be 256 bytes * @returns Noble G2 point */ -function BLS12_381_ToG2Point(input: Uint8Array) { +function BLS12_381_ToG2Point(input: Uint8Array): any { + // TODO: remove any type, temporary fix due to conflicing @noble/curves versions if (equalsBytes(input, BLS_G2_INFINITY_POINT_BYTES)) { return bls12_381.G2.ProjectivePoint.ZERO } @@ -141,7 +142,8 @@ function BLS12_381_ToFpPoint(fpCoordinate: Uint8Array) { return FP } -function BLS12_381_ToFp2Point(fpXCoordinate: Uint8Array, fpYCoordinate: Uint8Array) { +function BLS12_381_ToFp2Point(fpXCoordinate: Uint8Array, fpYCoordinate: Uint8Array): any { + // TODO: remove any type, temporary fix due to conflicing @noble/curves versions // check if the coordinates are in the field if (bytesToBigInt(fpXCoordinate) >= BLS_FIELD_MODULUS) { throw new EvmError(ERROR.BLS_12_381_FP_NOT_IN_FIELD) From 4e0be50c0b0091e7f5c929a25dd8c837fe2ba396 Mon Sep 17 00:00:00 2001 From: Holger Drewes Date: Tue, 6 Aug 2024 16:14:27 +0200 Subject: [PATCH 11/30] Integrate Noble usage for multiplication --- packages/evm/src/precompiles/bn254/noble.ts | 66 ++++++++++++++++++++- 1 file changed, 63 insertions(+), 3 deletions(-) diff --git a/packages/evm/src/precompiles/bn254/noble.ts b/packages/evm/src/precompiles/bn254/noble.ts index 422c5b3983..0e39bd6f97 100644 --- a/packages/evm/src/precompiles/bn254/noble.ts +++ b/packages/evm/src/precompiles/bn254/noble.ts @@ -1,6 +1,58 @@ -import { bytesToUnprefixedHex, hexToBytes } from '@ethereumjs/util' +import { + bigIntToBytes, + bytesToBigInt, + bytesToUnprefixedHex, + concatBytes, + equalsBytes, + hexToBytes, + setLengthLeft, +} from '@ethereumjs/util' +import { bytesToHex } from '@noble/curves/abstract/utils' +import { bn254 } from '@noble/curves/bn254' import type { EVMBN254Interface } from '../../types.js' +import type { AffinePoint } from '@noble/curves/abstract/weierstrass' + +const BN254_G1_INFINITY_POINT_BYTES = new Uint8Array(32) + +/** + * Converts an Uint8Array to a Noble G1 point. + * @param input Input Uint8Array. Should be 64 bytes + * @returns Noble G1 point + */ +function toG1Point(input: Uint8Array) { + if (equalsBytes(input, BN254_G1_INFINITY_POINT_BYTES)) { + return bn254.G1.ProjectivePoint.ZERO + } + + const x = bytesToBigInt(input.subarray(0, 32)) + const y = bytesToBigInt(input.subarray(32, 64)) + + const G1 = bn254.G1.ProjectivePoint.fromAffine({ + x, + y, + }) + + return G1 +} + +// input: a 32-byte hex scalar Uint8Array +// output: a Noble Fr point + +function toFrPoint(input: Uint8Array): bigint { + const Fr = bn254.fields.Fr.fromBytes(input) + if (Fr > bn254.fields.Fr.ORDER) { + return Fr % bn254.fields.Fr.ORDER + } + return Fr +} + +function fromG1Point(input: AffinePoint): Uint8Array { + const xBytes = setLengthLeft(bigIntToBytes(input.x), 32) + const yBytes = setLengthLeft(bigIntToBytes(input.y), 32) + + return concatBytes(xBytes, yBytes) +} /** * Implementation of the `EVMBN254Interface` using the `@noble/curves` JS library, @@ -21,8 +73,16 @@ export class NobleBN254 implements EVMBN254Interface { } mul(input: Uint8Array): Uint8Array { - const inputHex = bytesToUnprefixedHex(input) - return hexToBytes(this._rustbn.ec_mul(inputHex)) + const G1 = toG1Point(input.slice(0, 64)) + const scalar = toFrPoint(input.slice(64, 96)) + const result = fromG1Point(G1.multiply(scalar)) + //console.log(bytesToHex(result)) + return result + + /*const inputHex = bytesToUnprefixedHex(input) + const resultRBN = this._rustbn.ec_mul(inputHex) + console.log(resultRBN) + return hexToBytes(resultRBN)*/ } pairing(input: Uint8Array): Uint8Array { const inputStr = bytesToUnprefixedHex(input) From d61aa4784812746a7a4ecd524a99d6aee3f14227 Mon Sep 17 00:00:00 2001 From: Holger Drewes Date: Tue, 6 Aug 2024 16:30:18 +0200 Subject: [PATCH 12/30] Add generic equalityLengthCheck, moduloLengthCheck methods from BLS utils to precompile utils --- .../evm/src/precompiles/0b-bls12-g1add.ts | 3 +- .../evm/src/precompiles/0c-bls12-g1mul.ts | 3 +- .../evm/src/precompiles/0d-bls12-g1msm.ts | 8 +--- .../evm/src/precompiles/0e-bls12-g2add.ts | 3 +- .../evm/src/precompiles/0f-bls12-g2mul.ts | 3 +- .../evm/src/precompiles/10-bls12-g2msm.ts | 8 +--- .../evm/src/precompiles/11-bls12-pairing.ts | 3 +- .../src/precompiles/12-bls12-map-fp-to-g1.ts | 3 +- .../src/precompiles/13-bls12-map-fp2-to-g2.ts | 3 +- .../evm/src/precompiles/bls12_381/util.ts | 41 ------------------ packages/evm/src/precompiles/util.ts | 42 +++++++++++++++++++ 11 files changed, 60 insertions(+), 60 deletions(-) create mode 100644 packages/evm/src/precompiles/util.ts diff --git a/packages/evm/src/precompiles/0b-bls12-g1add.ts b/packages/evm/src/precompiles/0b-bls12-g1add.ts index cad13c876e..34be7c5309 100644 --- a/packages/evm/src/precompiles/0b-bls12-g1add.ts +++ b/packages/evm/src/precompiles/0b-bls12-g1add.ts @@ -3,7 +3,8 @@ import { bytesToHex } from '@ethereumjs/util' import { EvmErrorResult, OOGResult } from '../evm.js' import { ERROR, EvmError } from '../exceptions.js' -import { equalityLengthCheck, gasCheck, leading16ZeroBytesCheck } from './bls12_381/index.js' +import { gasCheck, leading16ZeroBytesCheck } from './bls12_381/index.js' +import { equalityLengthCheck } from './util.js' import type { EVMBLSInterface, ExecResult } from '../types.js' import type { PrecompileInput } from './types.js' diff --git a/packages/evm/src/precompiles/0c-bls12-g1mul.ts b/packages/evm/src/precompiles/0c-bls12-g1mul.ts index 3302c77295..92dd9ab8ab 100644 --- a/packages/evm/src/precompiles/0c-bls12-g1mul.ts +++ b/packages/evm/src/precompiles/0c-bls12-g1mul.ts @@ -3,7 +3,8 @@ import { bytesToHex } from '@ethereumjs/util' import { EvmErrorResult, OOGResult } from '../evm.js' import { ERROR, EvmError } from '../exceptions.js' -import { equalityLengthCheck, gasCheck, leading16ZeroBytesCheck } from './bls12_381/index.js' +import { gasCheck, leading16ZeroBytesCheck } from './bls12_381/index.js' +import { equalityLengthCheck } from './util.js' import type { EVMBLSInterface, ExecResult } from '../types.js' import type { PrecompileInput } from './types.js' diff --git a/packages/evm/src/precompiles/0d-bls12-g1msm.ts b/packages/evm/src/precompiles/0d-bls12-g1msm.ts index e1b00709c0..257265cb27 100644 --- a/packages/evm/src/precompiles/0d-bls12-g1msm.ts +++ b/packages/evm/src/precompiles/0d-bls12-g1msm.ts @@ -3,12 +3,8 @@ import { bytesToHex } from '@ethereumjs/util' import { EvmErrorResult, OOGResult } from '../evm.js' import { ERROR, EvmError } from '../exceptions.js' -import { - gasCheck, - leading16ZeroBytesCheck, - moduloLengthCheck, - msmGasUsed, -} from './bls12_381/index.js' +import { gasCheck, leading16ZeroBytesCheck, msmGasUsed } from './bls12_381/index.js' +import { moduloLengthCheck } from './util.js' import type { EVMBLSInterface, ExecResult } from '../types.js' import type { PrecompileInput } from './types.js' diff --git a/packages/evm/src/precompiles/0e-bls12-g2add.ts b/packages/evm/src/precompiles/0e-bls12-g2add.ts index 12bc94804f..019f815075 100644 --- a/packages/evm/src/precompiles/0e-bls12-g2add.ts +++ b/packages/evm/src/precompiles/0e-bls12-g2add.ts @@ -3,7 +3,8 @@ import { bytesToHex } from '@ethereumjs/util' import { EvmErrorResult, OOGResult } from '../evm.js' import { ERROR, EvmError } from '../exceptions.js' -import { equalityLengthCheck, gasCheck, leading16ZeroBytesCheck } from './bls12_381/index.js' +import { gasCheck, leading16ZeroBytesCheck } from './bls12_381/index.js' +import { equalityLengthCheck } from './util.js' import type { EVMBLSInterface, ExecResult } from '../types.js' import type { PrecompileInput } from './types.js' diff --git a/packages/evm/src/precompiles/0f-bls12-g2mul.ts b/packages/evm/src/precompiles/0f-bls12-g2mul.ts index ed4c8553ff..a8817e0a65 100644 --- a/packages/evm/src/precompiles/0f-bls12-g2mul.ts +++ b/packages/evm/src/precompiles/0f-bls12-g2mul.ts @@ -3,7 +3,8 @@ import { bytesToHex } from '@ethereumjs/util' import { EvmErrorResult, OOGResult } from '../evm.js' import { ERROR, EvmError } from '../exceptions.js' -import { equalityLengthCheck, gasCheck, leading16ZeroBytesCheck } from './bls12_381/index.js' +import { gasCheck, leading16ZeroBytesCheck } from './bls12_381/index.js' +import { equalityLengthCheck } from './util.js' import type { EVMBLSInterface, ExecResult } from '../types.js' import type { PrecompileInput } from './types.js' diff --git a/packages/evm/src/precompiles/10-bls12-g2msm.ts b/packages/evm/src/precompiles/10-bls12-g2msm.ts index d660b1521d..f637959f38 100644 --- a/packages/evm/src/precompiles/10-bls12-g2msm.ts +++ b/packages/evm/src/precompiles/10-bls12-g2msm.ts @@ -3,12 +3,8 @@ import { bytesToHex } from '@ethereumjs/util' import { EvmErrorResult, OOGResult } from '../evm.js' import { ERROR, EvmError } from '../exceptions.js' -import { - gasCheck, - leading16ZeroBytesCheck, - moduloLengthCheck, - msmGasUsed, -} from './bls12_381/index.js' +import { gasCheck, leading16ZeroBytesCheck, msmGasUsed } from './bls12_381/index.js' +import { moduloLengthCheck } from './util.js' import type { EVMBLSInterface, ExecResult } from '../types.js' import type { PrecompileInput } from './types.js' diff --git a/packages/evm/src/precompiles/11-bls12-pairing.ts b/packages/evm/src/precompiles/11-bls12-pairing.ts index b36cf33049..7d7eb44eed 100644 --- a/packages/evm/src/precompiles/11-bls12-pairing.ts +++ b/packages/evm/src/precompiles/11-bls12-pairing.ts @@ -3,7 +3,8 @@ import { bytesToHex } from '@ethereumjs/util' import { EvmErrorResult, OOGResult } from '../evm.js' import { ERROR, EvmError } from '../exceptions.js' -import { gasCheck, leading16ZeroBytesCheck, moduloLengthCheck } from './bls12_381/index.js' +import { gasCheck, leading16ZeroBytesCheck } from './bls12_381/index.js' +import { moduloLengthCheck } from './util.js' import type { EVMBLSInterface, ExecResult } from '../types.js' import type { PrecompileInput } from './types.js' diff --git a/packages/evm/src/precompiles/12-bls12-map-fp-to-g1.ts b/packages/evm/src/precompiles/12-bls12-map-fp-to-g1.ts index 28eb2b9168..f95792d895 100644 --- a/packages/evm/src/precompiles/12-bls12-map-fp-to-g1.ts +++ b/packages/evm/src/precompiles/12-bls12-map-fp-to-g1.ts @@ -3,7 +3,8 @@ import { bytesToHex } from '@ethereumjs/util' import { EvmErrorResult, OOGResult } from '../evm.js' import { ERROR, EvmError } from '../exceptions.js' -import { equalityLengthCheck, gasCheck, leading16ZeroBytesCheck } from './bls12_381/index.js' +import { gasCheck, leading16ZeroBytesCheck } from './bls12_381/index.js' +import { equalityLengthCheck } from './util.js' import type { EVMBLSInterface, ExecResult } from '../types.js' import type { PrecompileInput } from './types.js' diff --git a/packages/evm/src/precompiles/13-bls12-map-fp2-to-g2.ts b/packages/evm/src/precompiles/13-bls12-map-fp2-to-g2.ts index b774cb098f..2e49d12a18 100644 --- a/packages/evm/src/precompiles/13-bls12-map-fp2-to-g2.ts +++ b/packages/evm/src/precompiles/13-bls12-map-fp2-to-g2.ts @@ -3,7 +3,8 @@ import { bytesToHex } from '@ethereumjs/util' import { EvmErrorResult, OOGResult } from '../evm.js' import { ERROR, EvmError } from '../exceptions.js' -import { equalityLengthCheck, gasCheck, leading16ZeroBytesCheck } from './bls12_381/index.js' +import { gasCheck, leading16ZeroBytesCheck } from './bls12_381/index.js' +import { equalityLengthCheck } from './util.js' import type { EVMBLSInterface, ExecResult } from '../types.js' import type { PrecompileInput } from './types.js' diff --git a/packages/evm/src/precompiles/bls12_381/util.ts b/packages/evm/src/precompiles/bls12_381/util.ts index 618acca0a7..9d1b6b2a94 100644 --- a/packages/evm/src/precompiles/bls12_381/util.ts +++ b/packages/evm/src/precompiles/bls12_381/util.ts @@ -56,47 +56,6 @@ export const msmGasUsed = (numPairs: number, gasUsedPerPair: bigint) => { return (BigInt(numPairs) * gasUsedPerPair * BigInt(gasDiscountMultiplier)) / BigInt(1000) } -/** - * Checks that the length of the provided data is equal to `length`. - * - * @param opts - * @param length - * @param pName - * @returns - */ -export const equalityLengthCheck = (opts: PrecompileInput, length: number, pName: string) => { - if (opts.data.length !== length) { - if (opts._debug !== undefined) { - opts._debug( - `${pName} failed: Invalid input length length=${opts.data.length} (expected: ${length})`, - ) - } - return false - } - return true -} - -/** - * Checks that the total length of the provided data input can be subdivided into k equal parts - * with `length` (without leaving some remainder bytes). - * - * @param opts - * @param length - * @param pName - * @returns - */ -export const moduloLengthCheck = (opts: PrecompileInput, length: number, pName: string) => { - if (opts.data.length % length !== 0) { - if (opts._debug !== undefined) { - opts._debug( - `${pName} failed: Invalid input length length=${opts.data.length} (expected: ${length}*k bytes)`, - ) - } - return false - } - return true -} - /** * BLS-specific zero check to check that the top 16 bytes of a 64 byte field element provided * are always zero (see EIP notes on field element encoding). diff --git a/packages/evm/src/precompiles/util.ts b/packages/evm/src/precompiles/util.ts new file mode 100644 index 0000000000..e8dc82d306 --- /dev/null +++ b/packages/evm/src/precompiles/util.ts @@ -0,0 +1,42 @@ +import type { PrecompileInput } from './index.js' + +/** + * Checks that the length of the provided data is equal to `length`. + * + * @param opts + * @param length + * @param pName + * @returns + */ +export const equalityLengthCheck = (opts: PrecompileInput, length: number, pName: string) => { + if (opts.data.length !== length) { + if (opts._debug !== undefined) { + opts._debug( + `${pName} failed: Invalid input length length=${opts.data.length} (expected: ${length})`, + ) + } + return false + } + return true +} + +/** + * Checks that the total length of the provided data input can be subdivided into k equal parts + * with `length` (without leaving some remainder bytes). + * + * @param opts + * @param length + * @param pName + * @returns + */ +export const moduloLengthCheck = (opts: PrecompileInput, length: number, pName: string) => { + if (opts.data.length % length !== 0) { + if (opts._debug !== undefined) { + opts._debug( + `${pName} failed: Invalid input length length=${opts.data.length} (expected: ${length}*k bytes)`, + ) + } + return false + } + return true +} From 610852b003e021c55838b0ede4cb02c48aef39e9 Mon Sep 17 00:00:00 2001 From: Holger Drewes Date: Tue, 6 Aug 2024 16:41:07 +0200 Subject: [PATCH 13/30] Fix test --- packages/evm/src/precompiles/07-ecmul.ts | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/packages/evm/src/precompiles/07-ecmul.ts b/packages/evm/src/precompiles/07-ecmul.ts index 056de08c29..c6f3877c86 100644 --- a/packages/evm/src/precompiles/07-ecmul.ts +++ b/packages/evm/src/precompiles/07-ecmul.ts @@ -1,6 +1,9 @@ import { bytesToHex, short } from '@ethereumjs/util' -import { OOGResult } from '../evm.js' +import { EvmErrorResult, OOGResult } from '../evm.js' +import { ERROR, EvmError } from '../exceptions.js' + +import { equalityLengthCheck } from './util.js' import type { EVM } from '../evm.js' import type { ExecResult } from '../types.js' @@ -23,6 +26,14 @@ export function precompile07(opts: PrecompileInput): ExecResult { return OOGResult(opts.gasLimit) } + if (!equalityLengthCheck(opts, 128, 'ECMUL (0x07)')) { + // TODO: I changed opts.gasLimit (as being used for BLS) to gasUsed + // This passes the local npx vitest run test/precompiles/07-ecmul.spec.ts test, + // getting to the expected 40000 (instead of 65535), a bit spooky though. + // Should be analyzed if both cases (BLS and BN254) are correctly applied. + return EvmErrorResult(new EvmError(ERROR.INVALID_INPUT_LENGTH), gasUsed) + } + const returnData = (opts._EVM as EVM)['_bn254'].mul(opts.data.subarray(0, 128)) // check ecmul success or failure by comparing the output length From 69bca4c5df7334048f18b38360600e7256ad2c35 Mon Sep 17 00:00:00 2001 From: Holger Drewes Date: Tue, 6 Aug 2024 16:41:58 +0200 Subject: [PATCH 14/30] Minor --- packages/evm/src/precompiles/bn254/noble.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/evm/src/precompiles/bn254/noble.ts b/packages/evm/src/precompiles/bn254/noble.ts index 0e39bd6f97..f1f41f0511 100644 --- a/packages/evm/src/precompiles/bn254/noble.ts +++ b/packages/evm/src/precompiles/bn254/noble.ts @@ -7,7 +7,6 @@ import { hexToBytes, setLengthLeft, } from '@ethereumjs/util' -import { bytesToHex } from '@noble/curves/abstract/utils' import { bn254 } from '@noble/curves/bn254' import type { EVMBN254Interface } from '../../types.js' From d3246234c7d4c7f0e2e30103676a84dc9d8f6c53 Mon Sep 17 00:00:00 2001 From: Holger Drewes Date: Tue, 6 Aug 2024 17:50:07 +0200 Subject: [PATCH 15/30] Fixes --- packages/evm/src/precompiles/07-ecmul.ts | 10 +++++++++- packages/evm/src/precompiles/bn254/noble.ts | 6 ++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/packages/evm/src/precompiles/07-ecmul.ts b/packages/evm/src/precompiles/07-ecmul.ts index c6f3877c86..e548fb7663 100644 --- a/packages/evm/src/precompiles/07-ecmul.ts +++ b/packages/evm/src/precompiles/07-ecmul.ts @@ -34,7 +34,15 @@ export function precompile07(opts: PrecompileInput): ExecResult { return EvmErrorResult(new EvmError(ERROR.INVALID_INPUT_LENGTH), gasUsed) } - const returnData = (opts._EVM as EVM)['_bn254'].mul(opts.data.subarray(0, 128)) + let returnData + try { + returnData = (opts._EVM as EVM)['_bn254'].mul(opts.data.subarray(0, 128)) + } 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) { diff --git a/packages/evm/src/precompiles/bn254/noble.ts b/packages/evm/src/precompiles/bn254/noble.ts index f1f41f0511..67fafcf063 100644 --- a/packages/evm/src/precompiles/bn254/noble.ts +++ b/packages/evm/src/precompiles/bn254/noble.ts @@ -1,4 +1,5 @@ import { + BIGINT_0, bigIntToBytes, bytesToBigInt, bytesToUnprefixedHex, @@ -74,6 +75,11 @@ export class NobleBN254 implements EVMBN254Interface { mul(input: Uint8Array): Uint8Array { const G1 = toG1Point(input.slice(0, 64)) const scalar = toFrPoint(input.slice(64, 96)) + + if (scalar === BIGINT_0) { + return BN254_G1_INFINITY_POINT_BYTES + } + const result = fromG1Point(G1.multiply(scalar)) //console.log(bytesToHex(result)) return result From ef7ab69f96107a65154046c10490b5474a87b2a0 Mon Sep 17 00:00:00 2001 From: Holger Drewes Date: Wed, 7 Aug 2024 09:45:02 +0200 Subject: [PATCH 16/30] Add validity assertion for G1 point --- packages/evm/src/precompiles/bn254/noble.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/evm/src/precompiles/bn254/noble.ts b/packages/evm/src/precompiles/bn254/noble.ts index 67fafcf063..c67be579c3 100644 --- a/packages/evm/src/precompiles/bn254/noble.ts +++ b/packages/evm/src/precompiles/bn254/noble.ts @@ -32,7 +32,7 @@ function toG1Point(input: Uint8Array) { x, y, }) - + G1.assertValidity() return G1 } From 484a859d9a05afabf621c2327b12f21e496385ac Mon Sep 17 00:00:00 2001 From: Holger Drewes Date: Wed, 7 Aug 2024 10:15:07 +0200 Subject: [PATCH 17/30] Replace equality length check with byte length correction (chop off or right-pad) as stated in EIP --- packages/evm/src/precompiles/07-ecmul.ts | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/packages/evm/src/precompiles/07-ecmul.ts b/packages/evm/src/precompiles/07-ecmul.ts index e548fb7663..a1d3dda857 100644 --- a/packages/evm/src/precompiles/07-ecmul.ts +++ b/packages/evm/src/precompiles/07-ecmul.ts @@ -1,9 +1,6 @@ -import { bytesToHex, short } from '@ethereumjs/util' +import { bytesToHex, setLengthRight, short } from '@ethereumjs/util' import { EvmErrorResult, OOGResult } from '../evm.js' -import { ERROR, EvmError } from '../exceptions.js' - -import { equalityLengthCheck } from './util.js' import type { EVM } from '../evm.js' import type { ExecResult } from '../types.js' @@ -26,17 +23,13 @@ export function precompile07(opts: PrecompileInput): ExecResult { return OOGResult(opts.gasLimit) } - if (!equalityLengthCheck(opts, 128, 'ECMUL (0x07)')) { - // TODO: I changed opts.gasLimit (as being used for BLS) to gasUsed - // This passes the local npx vitest run test/precompiles/07-ecmul.spec.ts test, - // getting to the expected 40000 (instead of 65535), a bit spooky though. - // Should be analyzed if both cases (BLS and BN254) are correctly applied. - return EvmErrorResult(new EvmError(ERROR.INVALID_INPUT_LENGTH), gasUsed) - } + // > 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(opts.data.subarray(0, 128)) + returnData = (opts._EVM as EVM)['_bn254'].mul(input) } catch (e: any) { if (opts._debug !== undefined) { opts._debug(`ECMUL (0x07) failed: ${e.message}`) From 69f387e24a3fb8bbcd747f4a78b77ce7db1f6b89 Mon Sep 17 00:00:00 2001 From: Holger Drewes Date: Wed, 7 Aug 2024 10:29:18 +0200 Subject: [PATCH 18/30] Fixes --- packages/evm/src/precompiles/bn254/noble.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/evm/src/precompiles/bn254/noble.ts b/packages/evm/src/precompiles/bn254/noble.ts index c67be579c3..62295d7a0c 100644 --- a/packages/evm/src/precompiles/bn254/noble.ts +++ b/packages/evm/src/precompiles/bn254/noble.ts @@ -13,7 +13,7 @@ import { bn254 } from '@noble/curves/bn254' import type { EVMBN254Interface } from '../../types.js' import type { AffinePoint } from '@noble/curves/abstract/weierstrass' -const BN254_G1_INFINITY_POINT_BYTES = new Uint8Array(32) +const BN254_G1_INFINITY_POINT_BYTES = new Uint8Array(64) /** * Converts an Uint8Array to a Noble G1 point. @@ -41,7 +41,7 @@ function toG1Point(input: Uint8Array) { function toFrPoint(input: Uint8Array): bigint { const Fr = bn254.fields.Fr.fromBytes(input) - if (Fr > bn254.fields.Fr.ORDER) { + if (Fr >= bn254.fields.Fr.ORDER) { return Fr % bn254.fields.Fr.ORDER } return Fr From 09bbab25ee20e92a6dd4c973f750bd6749035d9d Mon Sep 17 00:00:00 2001 From: Holger Drewes Date: Wed, 7 Aug 2024 11:01:11 +0200 Subject: [PATCH 19/30] Add BN254 add implementation --- packages/evm/src/precompiles/06-ecadd.ts | 18 +++++++++++++++--- packages/evm/src/precompiles/bn254/noble.ts | 17 +++++++---------- 2 files changed, 22 insertions(+), 13 deletions(-) diff --git a/packages/evm/src/precompiles/06-ecadd.ts b/packages/evm/src/precompiles/06-ecadd.ts index dde0cac5af..0c498f15f1 100644 --- a/packages/evm/src/precompiles/06-ecadd.ts +++ b/packages/evm/src/precompiles/06-ecadd.ts @@ -1,6 +1,6 @@ -import { bytesToHex, 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' @@ -22,7 +22,19 @@ export function precompile06(opts: PrecompileInput): ExecResult { return OOGResult(opts.gasLimit) } - const returnData = (opts._EVM as EVM)['_bn254'].add(opts.data.subarray(0, 128)) + // > 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) { diff --git a/packages/evm/src/precompiles/bn254/noble.ts b/packages/evm/src/precompiles/bn254/noble.ts index 62295d7a0c..d5801356de 100644 --- a/packages/evm/src/precompiles/bn254/noble.ts +++ b/packages/evm/src/precompiles/bn254/noble.ts @@ -68,26 +68,23 @@ export class NobleBN254 implements EVMBN254Interface { } add(input: Uint8Array): Uint8Array { - const inputStr = bytesToUnprefixedHex(input) - return hexToBytes(this._rustbn.ec_add(inputStr)) + const p1 = toG1Point(input.slice(0, 64)) + const p2 = toG1Point(input.slice(64, 128)) + + const result = fromG1Point(p1.add(p2)) + return result } mul(input: Uint8Array): Uint8Array { - const G1 = toG1Point(input.slice(0, 64)) + const p1 = toG1Point(input.slice(0, 64)) const scalar = toFrPoint(input.slice(64, 96)) if (scalar === BIGINT_0) { return BN254_G1_INFINITY_POINT_BYTES } - const result = fromG1Point(G1.multiply(scalar)) - //console.log(bytesToHex(result)) + const result = fromG1Point(p1.multiply(scalar)) return result - - /*const inputHex = bytesToUnprefixedHex(input) - const resultRBN = this._rustbn.ec_mul(inputHex) - console.log(resultRBN) - return hexToBytes(resultRBN)*/ } pairing(input: Uint8Array): Uint8Array { const inputStr = bytesToUnprefixedHex(input) From 29faa84a4541fbc668244ba6c07f4c8f5d33b1e6 Mon Sep 17 00:00:00 2001 From: Holger Drewes Date: Wed, 7 Aug 2024 12:10:07 +0200 Subject: [PATCH 20/30] Add additional pairing modulo length check since not safe to rely on implementation --- packages/evm/src/precompiles/08-ecpairing.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/evm/src/precompiles/08-ecpairing.ts b/packages/evm/src/precompiles/08-ecpairing.ts index 7481ca416f..3f66243314 100644 --- a/packages/evm/src/precompiles/08-ecpairing.ts +++ b/packages/evm/src/precompiles/08-ecpairing.ts @@ -1,13 +1,19 @@ import { bytesToHex, short } from '@ethereumjs/util' -import { OOGResult } from '../evm.js' +import { EvmErrorResult, OOGResult } from '../evm.js' +import { ERROR, EvmError } from '../exceptions.js' + +import { moduloLengthCheck } from './util.js' import type { EVM } from '../evm.js' import type { ExecResult } from '../types.js' import type { PrecompileInput } from './types.js' export function precompile08(opts: PrecompileInput): ExecResult { - // no need to care about non-divisible-by-192, because bn128.pairing will properly fail in that case + if (!moduloLengthCheck(opts, 192, 'ECPAIRING (0x08)')) { + return EvmErrorResult(new EvmError(ERROR.INVALID_INPUT_LENGTH), opts.gasLimit) + } + const inputDataSize = BigInt(Math.floor(opts.data.length / 192)) const gasUsed = opts.common.param('ecPairingGas') + inputDataSize * opts.common.param('ecPairingWordGas') From ea1780de984947c5422debe79c67856754ae3259 Mon Sep 17 00:00:00 2001 From: Holger Drewes Date: Wed, 7 Aug 2024 12:12:47 +0200 Subject: [PATCH 21/30] Some basic alignment --- packages/evm/src/precompiles/08-ecpairing.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/evm/src/precompiles/08-ecpairing.ts b/packages/evm/src/precompiles/08-ecpairing.ts index 3f66243314..3715d5082b 100644 --- a/packages/evm/src/precompiles/08-ecpairing.ts +++ b/packages/evm/src/precompiles/08-ecpairing.ts @@ -32,7 +32,15 @@ export function precompile08(opts: PrecompileInput): ExecResult { return OOGResult(opts.gasLimit) } - const returnData = (opts._EVM as EVM)['_bn254'].pairing(opts.data) + let returnData + try { + returnData = (opts._EVM as EVM)['_bn254'].pairing(opts.data) + } catch (e: any) { + if (opts._debug !== undefined) { + opts._debug(`ECPAIRING (0x08) failed: ${e.message}`) + } + return EvmErrorResult(e, opts.gasLimit) + } // check ecpairing success or failure by comparing the output length if (returnData.length !== 32) { From ce4eca573ea311ba64261d80b2a01e3ee66c9cec Mon Sep 17 00:00:00 2001 From: Holger Drewes Date: Wed, 7 Aug 2024 13:36:12 +0200 Subject: [PATCH 22/30] First try on pairing (not working yet) --- packages/evm/src/exceptions.ts | 3 + .../evm/src/precompiles/bls12_381/noble.ts | 1 + packages/evm/src/precompiles/bn254/noble.ts | 136 +++++++++++++++--- 3 files changed, 124 insertions(+), 16 deletions(-) diff --git a/packages/evm/src/exceptions.ts b/packages/evm/src/exceptions.ts index 9959f38dfd..abc2ab269b 100644 --- a/packages/evm/src/exceptions.ts +++ b/packages/evm/src/exceptions.ts @@ -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', diff --git a/packages/evm/src/precompiles/bls12_381/noble.ts b/packages/evm/src/precompiles/bls12_381/noble.ts index b9bf5c163b..31737d5d40 100644 --- a/packages/evm/src/precompiles/bls12_381/noble.ts +++ b/packages/evm/src/precompiles/bls12_381/noble.ts @@ -293,6 +293,7 @@ export class NobleBLS implements EVMBLSInterface { } pairingCheck(input: Uint8Array): Uint8Array { + // Extract the pairs from the input const pairLength = 384 const pairs = [] for (let k = 0; k < input.length / pairLength; k++) { diff --git a/packages/evm/src/precompiles/bn254/noble.ts b/packages/evm/src/precompiles/bn254/noble.ts index d5801356de..3a77658225 100644 --- a/packages/evm/src/precompiles/bn254/noble.ts +++ b/packages/evm/src/precompiles/bn254/noble.ts @@ -2,7 +2,6 @@ import { BIGINT_0, bigIntToBytes, bytesToBigInt, - bytesToUnprefixedHex, concatBytes, equalsBytes, hexToBytes, @@ -10,10 +9,25 @@ import { } from '@ethereumjs/util' import { bn254 } from '@noble/curves/bn254' +import { ERROR, EvmError } from '../../exceptions.js' + import type { EVMBN254Interface } from '../../types.js' import type { AffinePoint } from '@noble/curves/abstract/weierstrass' -const BN254_G1_INFINITY_POINT_BYTES = new Uint8Array(64) +// Copied from @noble/curves (only local declaration) +/*type Fp2 = { + c0: bigint + c1: bigint +}*/ + +const G1_INFINITY_POINT_BYTES = new Uint8Array(64) +const G2_INFINITY_POINT_BYTES = new Uint8Array(128) +const G1_POINT_BYTE_LENGTH = 64 +const G1_ELEMENT_BYTE_LENGTH = 32 +const G2_POINT_BYTE_LENGTH = 128 + +const ZERO_BUFFER = new Uint8Array(32) +const ONE_BUFFER = concatBytes(new Uint8Array(31), hexToBytes('0x01')) /** * Converts an Uint8Array to a Noble G1 point. @@ -21,12 +35,12 @@ const BN254_G1_INFINITY_POINT_BYTES = new Uint8Array(64) * @returns Noble G1 point */ function toG1Point(input: Uint8Array) { - if (equalsBytes(input, BN254_G1_INFINITY_POINT_BYTES)) { + if (equalsBytes(input, G1_INFINITY_POINT_BYTES)) { return bn254.G1.ProjectivePoint.ZERO } - const x = bytesToBigInt(input.subarray(0, 32)) - const y = bytesToBigInt(input.subarray(32, 64)) + const x = bytesToBigInt(input.subarray(0, G1_ELEMENT_BYTE_LENGTH)) + const y = bytesToBigInt(input.subarray(G1_ELEMENT_BYTE_LENGTH, G1_POINT_BYTE_LENGTH)) const G1 = bn254.G1.ProjectivePoint.fromAffine({ x, @@ -36,6 +50,13 @@ function toG1Point(input: Uint8Array) { return G1 } +function fromG1Point(input: AffinePoint): Uint8Array { + const xBytes = setLengthLeft(bigIntToBytes(input.x), G1_ELEMENT_BYTE_LENGTH) + const yBytes = setLengthLeft(bigIntToBytes(input.y), G1_ELEMENT_BYTE_LENGTH) + + return concatBytes(xBytes, yBytes) +} + // input: a 32-byte hex scalar Uint8Array // output: a Noble Fr point @@ -47,11 +68,50 @@ function toFrPoint(input: Uint8Array): bigint { return Fr } -function fromG1Point(input: AffinePoint): Uint8Array { - const xBytes = setLengthLeft(bigIntToBytes(input.x), 32) - const yBytes = setLengthLeft(bigIntToBytes(input.y), 32) +/** + * Converts an Uint8Array to a Noble G2 point. Raises errors if the point is not on the curve + * and (if activated) if the point is in the subgroup / order check. + * @param input Input Uint8Array. Should be 256 bytes + * @returns Noble G2 point + */ +function toG2Point(input: Uint8Array): any { + // TODO: remove any type, temporary fix due to conflicing @noble/curves versions + if (equalsBytes(input, G2_INFINITY_POINT_BYTES)) { + return bn254.G2.ProjectivePoint.ZERO + } - return concatBytes(xBytes, yBytes) + const p_x_1 = input.subarray(0, G1_ELEMENT_BYTE_LENGTH) + const p_x_2 = input.subarray(G1_ELEMENT_BYTE_LENGTH, G1_ELEMENT_BYTE_LENGTH * 2) + const start2 = G1_ELEMENT_BYTE_LENGTH * 2 + const p_y_1 = input.subarray(start2, start2 + G1_ELEMENT_BYTE_LENGTH) + const p_y_2 = input.subarray(start2 + G1_ELEMENT_BYTE_LENGTH, start2 + G1_ELEMENT_BYTE_LENGTH * 2) + + const Fp2X = toFp2Point(p_x_1, p_x_2) + const Fp2Y = toFp2Point(p_y_1, p_y_2) + + const pG2 = bn254.G2.ProjectivePoint.fromAffine({ + x: Fp2X, + y: Fp2Y, + }) + pG2.assertValidity() + + return pG2 +} + +function toFp2Point(fpXCoordinate: Uint8Array, fpYCoordinate: Uint8Array): any { + // TODO: remove any type, temporary fix due to conflicing @noble/curves versions + // check if the coordinates are in the field + if (bytesToBigInt(fpXCoordinate) >= bn254.fields.Fp2.ORDER) { + throw new EvmError(ERROR.BN254_FP_NOT_IN_FIELD) + } + if (bytesToBigInt(fpYCoordinate) >= bn254.fields.Fp2.ORDER) { + throw new EvmError(ERROR.BLS_12_381_FP_NOT_IN_FIELD) + } + + const fpBytes = concatBytes(fpXCoordinate, fpYCoordinate) + + const FP = bn254.fields.Fp2.fromBytes(fpBytes) + return FP } /** @@ -68,26 +128,70 @@ export class NobleBN254 implements EVMBN254Interface { } add(input: Uint8Array): Uint8Array { - const p1 = toG1Point(input.slice(0, 64)) - const p2 = toG1Point(input.slice(64, 128)) + const p1 = toG1Point(input.slice(0, G1_POINT_BYTE_LENGTH)) + const p2 = toG1Point(input.slice(G1_POINT_BYTE_LENGTH, G1_POINT_BYTE_LENGTH * 2)) const result = fromG1Point(p1.add(p2)) return result } mul(input: Uint8Array): Uint8Array { - const p1 = toG1Point(input.slice(0, 64)) - const scalar = toFrPoint(input.slice(64, 96)) + const p1 = toG1Point(input.slice(0, G1_POINT_BYTE_LENGTH)) + const scalar = toFrPoint(input.slice(G1_POINT_BYTE_LENGTH, 96)) if (scalar === BIGINT_0) { - return BN254_G1_INFINITY_POINT_BYTES + return G1_INFINITY_POINT_BYTES } const result = fromG1Point(p1.multiply(scalar)) return result } pairing(input: Uint8Array): Uint8Array { - const inputStr = bytesToUnprefixedHex(input) - return hexToBytes(this._rustbn.ec_pairing(inputStr)) + // Extract the pairs from the input + const pairLength = 192 + const pairs = [] + for (let k = 0; k < input.length / pairLength; k++) { + const pairStart = pairLength * k + const G1 = toG1Point(input.subarray(pairStart, pairStart + G1_POINT_BYTE_LENGTH)) + + const g2start = pairStart + G1_POINT_BYTE_LENGTH + const G2 = toG2Point(input.subarray(g2start, g2start + G2_POINT_BYTE_LENGTH)) + + // EIP: "If any input is the infinity point, pairing result will be 1" + if (G1 === bn254.G1.ProjectivePoint.ZERO || G2 === bn254.G2.ProjectivePoint.ZERO) { + return ONE_BUFFER + } + + pairs.push({ g1: G1, g2: G2 }) + } + + bn254.pairingBatch(pairs) + return ZERO_BUFFER + + // run the pairing check + // reference (Nethermind): https://github.com/NethermindEth/nethermind/blob/374b036414722b9c8ad27e93d64840b8f63931b9/src/Nethermind/Nethermind.Evm/Precompiles/Bls/Mcl/PairingPrecompile.cs#L93 + /*let GT: any // Fp12 type not exported, eventually too complex + for (let index = 0; index < pairs.length; index++) { + const pair = pairs[index] + const G1 = pair[0] as ProjPointType + const G2 = pair[1] as ProjPointType + + if (index === 0) { + GT = bn254.pairing(G1, G2) + } else { + GT = bn254.fields.Fp12.mul(GT!, bn254.pairing(G1, G2)) + } + } + + const FP12 = bn254.fields.Fp12.finalExponentiate(GT!) + if (bn254.fields.Fp12.eql(FP12, bn254.fields.Fp12.ONE)) { + console.log(bytesToHex(ONE_BUFFER)) + return ONE_BUFFER + } else { + return ZERO_BUFFER + }*/ + + /*const inputStr = bytesToUnprefixedHex(input) + return hexToBytes(this._rustbn.ec_pairing(inputStr))*/ } } From 50919fcb045ab0ca318abc95dd86d072ca997054 Mon Sep 17 00:00:00 2001 From: Holger Drewes Date: Wed, 7 Aug 2024 15:15:46 +0200 Subject: [PATCH 23/30] First pairing tests passing --- packages/evm/src/precompiles/bn254/noble.ts | 37 +++++---------------- 1 file changed, 8 insertions(+), 29 deletions(-) diff --git a/packages/evm/src/precompiles/bn254/noble.ts b/packages/evm/src/precompiles/bn254/noble.ts index 3a77658225..0546d3a0da 100644 --- a/packages/evm/src/precompiles/bn254/noble.ts +++ b/packages/evm/src/precompiles/bn254/noble.ts @@ -80,11 +80,11 @@ function toG2Point(input: Uint8Array): any { return bn254.G2.ProjectivePoint.ZERO } - const p_x_1 = input.subarray(0, G1_ELEMENT_BYTE_LENGTH) - const p_x_2 = input.subarray(G1_ELEMENT_BYTE_LENGTH, G1_ELEMENT_BYTE_LENGTH * 2) + const p_x_2 = input.subarray(0, G1_ELEMENT_BYTE_LENGTH) + const p_x_1 = input.subarray(G1_ELEMENT_BYTE_LENGTH, G1_ELEMENT_BYTE_LENGTH * 2) const start2 = G1_ELEMENT_BYTE_LENGTH * 2 - const p_y_1 = input.subarray(start2, start2 + G1_ELEMENT_BYTE_LENGTH) - const p_y_2 = input.subarray(start2 + G1_ELEMENT_BYTE_LENGTH, start2 + G1_ELEMENT_BYTE_LENGTH * 2) + const p_y_2 = input.subarray(start2, start2 + G1_ELEMENT_BYTE_LENGTH) + const p_y_1 = input.subarray(start2 + G1_ELEMENT_BYTE_LENGTH, start2 + G1_ELEMENT_BYTE_LENGTH * 2) const Fp2X = toFp2Point(p_x_1, p_x_2) const Fp2Y = toFp2Point(p_y_1, p_y_2) @@ -93,6 +93,7 @@ function toG2Point(input: Uint8Array): any { x: Fp2X, y: Fp2Y, }) + pG2.assertValidity() return pG2 @@ -165,33 +166,11 @@ export class NobleBN254 implements EVMBN254Interface { pairs.push({ g1: G1, g2: G2 }) } - bn254.pairingBatch(pairs) - return ZERO_BUFFER - - // run the pairing check - // reference (Nethermind): https://github.com/NethermindEth/nethermind/blob/374b036414722b9c8ad27e93d64840b8f63931b9/src/Nethermind/Nethermind.Evm/Precompiles/Bls/Mcl/PairingPrecompile.cs#L93 - /*let GT: any // Fp12 type not exported, eventually too complex - for (let index = 0; index < pairs.length; index++) { - const pair = pairs[index] - const G1 = pair[0] as ProjPointType - const G2 = pair[1] as ProjPointType - - if (index === 0) { - GT = bn254.pairing(G1, G2) - } else { - GT = bn254.fields.Fp12.mul(GT!, bn254.pairing(G1, G2)) - } - } - - const FP12 = bn254.fields.Fp12.finalExponentiate(GT!) - if (bn254.fields.Fp12.eql(FP12, bn254.fields.Fp12.ONE)) { - console.log(bytesToHex(ONE_BUFFER)) + const res = bn254.pairingBatch(pairs) + if (bn254.fields.Fp12.eql(res, bn254.fields.Fp12.ONE) === true) { return ONE_BUFFER } else { return ZERO_BUFFER - }*/ - - /*const inputStr = bytesToUnprefixedHex(input) - return hexToBytes(this._rustbn.ec_pairing(inputStr))*/ + } } } From 8e41321a77a31ebd600ea317209c17ee4107aea1 Mon Sep 17 00:00:00 2001 From: Holger Drewes Date: Wed, 7 Aug 2024 15:57:50 +0200 Subject: [PATCH 24/30] Fixes --- packages/evm/src/precompiles/bn254/noble.ts | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/packages/evm/src/precompiles/bn254/noble.ts b/packages/evm/src/precompiles/bn254/noble.ts index 0546d3a0da..c42f43b1ae 100644 --- a/packages/evm/src/precompiles/bn254/noble.ts +++ b/packages/evm/src/precompiles/bn254/noble.ts @@ -86,6 +86,13 @@ function toG2Point(input: Uint8Array): any { const p_y_2 = input.subarray(start2, start2 + G1_ELEMENT_BYTE_LENGTH) const p_y_1 = input.subarray(start2 + G1_ELEMENT_BYTE_LENGTH, start2 + G1_ELEMENT_BYTE_LENGTH * 2) + for (const p of [p_x_1, p_x_2, p_y_1, p_y_2]) { + const pB = bytesToBigInt(p) + if (bn254.fields.Fp.create(pB) !== pB) { + throw new EvmError(ERROR.BN254_FP_NOT_IN_FIELD) + } + } + const Fp2X = toFp2Point(p_x_1, p_x_2) const Fp2Y = toFp2Point(p_y_1, p_y_2) @@ -99,14 +106,12 @@ function toG2Point(input: Uint8Array): any { return pG2 } -function toFp2Point(fpXCoordinate: Uint8Array, fpYCoordinate: Uint8Array): any { - // TODO: remove any type, temporary fix due to conflicing @noble/curves versions - // check if the coordinates are in the field +function toFp2Point(fpXCoordinate: Uint8Array, fpYCoordinate: Uint8Array) { if (bytesToBigInt(fpXCoordinate) >= bn254.fields.Fp2.ORDER) { throw new EvmError(ERROR.BN254_FP_NOT_IN_FIELD) } if (bytesToBigInt(fpYCoordinate) >= bn254.fields.Fp2.ORDER) { - throw new EvmError(ERROR.BLS_12_381_FP_NOT_IN_FIELD) + throw new EvmError(ERROR.BN254_FP_NOT_IN_FIELD) } const fpBytes = concatBytes(fpXCoordinate, fpYCoordinate) @@ -158,9 +163,8 @@ export class NobleBN254 implements EVMBN254Interface { const g2start = pairStart + G1_POINT_BYTE_LENGTH const G2 = toG2Point(input.subarray(g2start, g2start + G2_POINT_BYTE_LENGTH)) - // EIP: "If any input is the infinity point, pairing result will be 1" if (G1 === bn254.G1.ProjectivePoint.ZERO || G2 === bn254.G2.ProjectivePoint.ZERO) { - return ONE_BUFFER + continue } pairs.push({ g1: G1, g2: G2 }) From d978474bb70285a98907eaef7e9c5a3dc8dc9869 Mon Sep 17 00:00:00 2001 From: Holger Drewes Date: Thu, 8 Aug 2024 11:40:55 +0200 Subject: [PATCH 25/30] Update @noble/curves to final v1.5.0 release --- packages/evm/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/evm/package.json b/packages/evm/package.json index 1c0db9d90d..ee1c554082 100644 --- a/packages/evm/package.json +++ b/packages/evm/package.json @@ -60,7 +60,7 @@ "@ethereumjs/statemanager": "^2.3.0", "@ethereumjs/tx": "^5.3.0", "@ethereumjs/util": "^9.0.3", - "@noble/curves": "git://github.com/holgerd77/noble-curves.git#build-3ed792f", + "@noble/curves": "^1.5.0", "@types/debug": "^4.1.9", "debug": "^4.3.3", "ethereum-cryptography": "^2.2.1", From cfe48cfc438fcbfc683dcbf146e98568b59bbe9f Mon Sep 17 00:00:00 2001 From: Holger Drewes Date: Thu, 8 Aug 2024 11:42:13 +0200 Subject: [PATCH 26/30] Rebuild package-lock.json --- package-lock.json | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/package-lock.json b/package-lock.json index 4970bcfffa..53922aaf67 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16832,7 +16832,7 @@ "@ethereumjs/statemanager": "^2.3.0", "@ethereumjs/tx": "^5.3.0", "@ethereumjs/util": "^9.0.3", - "@noble/curves": "git://github.com/holgerd77/noble-curves.git#build-3ed792f ", + "@noble/curves": "^1.5.0", "@types/debug": "^4.1.9", "debug": "^4.3.3", "ethereum-cryptography": "^2.2.1", @@ -16860,6 +16860,17 @@ "node": ">=18" } }, + "packages/evm/node_modules/@noble/curves": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.5.0.tgz", + "integrity": "sha512-J5EKamIHnKPyClwVrzmaf5wSdQXgdHcPZIZLu3bwnbeCx8/7NPK5q2ZBWF+5FvYGByjiQQsJYX6jfgB2wDPn3A==", + "dependencies": { + "@noble/hashes": "1.4.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "packages/genesis": { "name": "@ethereumjs/genesis", "version": "0.2.2", From c187705367adaeb401fee210634316b180f802c0 Mon Sep 17 00:00:00 2001 From: Holger Drewes Date: Thu, 8 Aug 2024 14:30:55 +0200 Subject: [PATCH 27/30] Use plain Noble BN254 by default, clean-up, interface type exports, new --bn254 option for VM test runners (default: rustbn.js) --- packages/evm/src/constructors.ts | 9 ++------- packages/evm/src/index.ts | 6 ++++++ packages/evm/src/precompiles/bn254/noble.ts | 12 ----------- packages/vm/test/tester/index.ts | 20 ++++++++++++++----- .../tester/runners/BlockchainTestsRunner.ts | 10 ++++------ .../tester/runners/GeneralStateTestsRunner.ts | 1 + 6 files changed, 28 insertions(+), 30 deletions(-) diff --git a/packages/evm/src/constructors.ts b/packages/evm/src/constructors.ts index aa452cb9c0..381a542806 100644 --- a/packages/evm/src/constructors.ts +++ b/packages/evm/src/constructors.ts @@ -1,6 +1,5 @@ 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' @@ -9,10 +8,6 @@ import { EVM } from './index.js' import type { EVMOpts } from './index.js' -// Hack to ensure one-time instantiation from rustbn.js since -// otherwise "unreachable" error occurs during WASM access -let initializedRustBN: any | undefined = undefined - /** * Use this async static constructor for the initialization * of an EVM object @@ -22,8 +17,8 @@ let initializedRustBN: any | undefined = undefined */ export async function createEVM(createOpts?: EVMOpts) { const opts = createOpts ?? ({} as EVMOpts) - initializedRustBN = initializedRustBN ?? (await initRustBN()) - opts.bn254 = new NobleBN254(initializedRustBN) + + opts.bn254 = new NobleBN254() if (opts.common === undefined) { opts.common = new Common({ chain: Mainnet }) diff --git a/packages/evm/src/index.ts b/packages/evm/src/index.ts index db5bd51d20..0733f6c8ba 100644 --- a/packages/evm/src/index.ts +++ b/packages/evm/src/index.ts @@ -6,12 +6,15 @@ 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, @@ -24,6 +27,7 @@ import type { export * from './logger.js' export type { + EVMBLSInterface, EVMBN254Interface, EVMInterface, EVMOpts, @@ -46,6 +50,8 @@ export { MCLBLS, Message, NobleBLS, + NobleBN254, + RustBN254, validateEOF, } diff --git a/packages/evm/src/precompiles/bn254/noble.ts b/packages/evm/src/precompiles/bn254/noble.ts index c42f43b1ae..5f3396602d 100644 --- a/packages/evm/src/precompiles/bn254/noble.ts +++ b/packages/evm/src/precompiles/bn254/noble.ts @@ -14,12 +14,6 @@ import { ERROR, EvmError } from '../../exceptions.js' import type { EVMBN254Interface } from '../../types.js' import type { AffinePoint } from '@noble/curves/abstract/weierstrass' -// Copied from @noble/curves (only local declaration) -/*type Fp2 = { - c0: bigint - c1: bigint -}*/ - const G1_INFINITY_POINT_BYTES = new Uint8Array(64) const G2_INFINITY_POINT_BYTES = new Uint8Array(128) const G1_POINT_BYTE_LENGTH = 64 @@ -127,12 +121,6 @@ function toFp2Point(fpXCoordinate: Uint8Array, fpYCoordinate: Uint8Array) { * This is the EVM default implementation. */ export class NobleBN254 implements EVMBN254Interface { - protected readonly _rustbn: any - - constructor(rustbn: any) { - this._rustbn = rustbn - } - add(input: Uint8Array): Uint8Array { const p1 = toG1Point(input.slice(0, G1_POINT_BYTE_LENGTH)) const p2 = toG1Point(input.slice(G1_POINT_BYTE_LENGTH, G1_POINT_BYTE_LENGTH * 2)) diff --git a/packages/vm/test/tester/index.ts b/packages/vm/test/tester/index.ts index 864c22eaac..13061b2e60 100755 --- a/packages/vm/test/tester/index.ts +++ b/packages/vm/test/tester/index.ts @@ -1,4 +1,4 @@ -import { MCLBLS, NobleBLS, type bn128 } from '@ethereumjs/evm' +import { MCLBLS, NobleBLS, NobleBN254, RustBN254 } from '@ethereumjs/evm' import { loadKZG } from 'kzg-wasm' import * as mcl from 'mcl-wasm' import * as minimist from 'minimist' @@ -21,7 +21,7 @@ import { runStateTest } from './runners/GeneralStateTestsRunner.js' import { getTestFromSource, getTestsFromArgs } from './testLoader.js' import type { Common } from '@ethereumjs/common' -import type { EVMBLSInterface } from '@ethereumjs/evm/dist/cjs/types' +import type { EVMBLSInterface, EVMBN254Interface } from '@ethereumjs/evm' /** * Test runner @@ -48,6 +48,7 @@ import type { EVMBLSInterface } from '@ethereumjs/evm/dist/cjs/types' * --verify-test-amount-alltests: number. If passed, get the expected amount from tests and verify afterwards if this is the count of tests (expects tests are ran with default settings) * --reps: number. If passed, each test case will be run the number of times indicated * --bls: string. BLS library being used, choices: Noble, MCL (default: MCL) + * --bn254: string. BN254 (alt_BN128) library being used, choices: Noble, RustBN (default: RustBN) * --profile If this flag is passed, the state/blockchain tests will profile */ @@ -112,11 +113,20 @@ async function runTests() { console.log('BLS library used: MCL (WASM)') } + let bn254: EVMBN254Interface + if (argv.bn254 !== undefined && argv.bn254.toLowerCase() === 'noble') { + console.log('BN254 (alt_BN128) library used: Noble (JavaScript)') + bn254 = new NobleBN254() + } else { + const rustBN = await initRustBN() + bn254 = new RustBN254(rustBN) + console.log('BN254 (alt_BN128) library used: rustbn.js (WASM)') + } + /** * Run-time configuration */ const kzg = await loadKZG() - const bn128 = (await initRustBN()) as bn128 const runnerArgs: { forkConfigVM: string forkConfigTestSuite: string @@ -130,7 +140,7 @@ async function runTests() { reps?: number profile: boolean bls: EVMBLSInterface - bn128: bn128 + bn254: EVMBN254Interface } = { forkConfigVM: FORK_CONFIG_VM, forkConfigTestSuite: FORK_CONFIG_TEST_SUITE, @@ -144,7 +154,7 @@ async function runTests() { reps: argv.reps, // test repetitions bls, profile: RUN_PROFILER, - bn128, + bn254, } /** diff --git a/packages/vm/test/tester/runners/BlockchainTestsRunner.ts b/packages/vm/test/tester/runners/BlockchainTestsRunner.ts index 013bfd3582..92e20239fd 100644 --- a/packages/vm/test/tester/runners/BlockchainTestsRunner.ts +++ b/packages/vm/test/tester/runners/BlockchainTestsRunner.ts @@ -92,6 +92,7 @@ export async function runBlockchainTest(options: any, testData: any, t: tape.Tes const evmOpts = { bls: options.bls, + bn254: options.bn254, } let vm = await VM.create({ stateManager, @@ -147,17 +148,14 @@ export async function runBlockchainTest(options: any, testData: any, t: tape.Tes try { const blockRlp = hexToBytes(raw.rlp as PrefixedHexString) // Update common HF - let TD: bigint | undefined = undefined let timestamp: bigint | undefined = undefined try { const decoded: any = RLP.decode(blockRlp) - const parentHash = decoded[0][0] - TD = await blockchain.getTotalDifficulty(parentHash) timestamp = bytesToBigInt(decoded[0][11]) // eslint-disable-next-line no-empty } catch (e) {} - common.setHardforkBy({ blockNumber: currentBlock, td: TD, timestamp }) + common.setHardforkBy({ blockNumber: currentBlock, timestamp }) // transactionSequence is provided when txs are expected to be rejected. // To run this field we try to import them on the current state. @@ -191,7 +189,7 @@ export async function runBlockchainTest(options: any, testData: any, t: tape.Tes await blockBuilder.revert() // will only revert if checkpointed } - const block = createBlockFromRLPSerializedBlock(blockRlp, { common, setHardfork: TD }) + const block = createBlockFromRLPSerializedBlock(blockRlp, { common }) await blockchain.putBlock(block) // This is a trick to avoid generating the canonical genesis @@ -206,7 +204,7 @@ export async function runBlockchainTest(options: any, testData: any, t: tape.Tes const parentState = parentBlock.header.stateRoot // run block, update head if valid try { - await runBlock(vm, { block, root: parentState, setHardfork: TD }) + await runBlock(vm, { block, root: parentState }) // set as new head block } catch (error: any) { // remove invalid block diff --git a/packages/vm/test/tester/runners/GeneralStateTestsRunner.ts b/packages/vm/test/tester/runners/GeneralStateTestsRunner.ts index 19219e473e..992cc6c11c 100644 --- a/packages/vm/test/tester/runners/GeneralStateTestsRunner.ts +++ b/packages/vm/test/tester/runners/GeneralStateTestsRunner.ts @@ -87,6 +87,7 @@ async function runTestCase(options: any, testData: any, t: tape.Test) { const evmOpts = { bls: options.bls, + bn254: options.bn254, } const vm = await VM.create({ stateManager, From f35850cbbc959c370ad7bc9e44c18119da72f4a1 Mon Sep 17 00:00:00 2001 From: Holger Drewes Date: Thu, 8 Aug 2024 14:33:45 +0200 Subject: [PATCH 28/30] Move rustbn-wasm dependency over to EVM dev dependencies, add to client dependencies --- packages/client/package.json | 1 + packages/evm/package.json | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/client/package.json b/packages/client/package.json index 9438ffa403..96a12eff64 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -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", diff --git a/packages/evm/package.json b/packages/evm/package.json index ee1c554082..a6cc3f5d97 100644 --- a/packages/evm/package.json +++ b/packages/evm/package.json @@ -63,8 +63,7 @@ "@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", @@ -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" }, From 33cc3e9126d229232cbba4bdd71857cd34fb6320 Mon Sep 17 00:00:00 2001 From: Holger Drewes Date: Thu, 8 Aug 2024 14:35:20 +0200 Subject: [PATCH 29/30] Rebuild package-lock.json --- package-lock.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 53922aaf67..f60e95c3e6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16674,6 +16674,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", @@ -16835,8 +16836,7 @@ "@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", @@ -16853,6 +16853,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" }, From 3b3e32431431f59325d246e7fb18f98b203c35de Mon Sep 17 00:00:00 2001 From: Holger Drewes Date: Thu, 8 Aug 2024 14:38:39 +0200 Subject: [PATCH 30/30] Integrate in client --- packages/client/src/execution/vmexecution.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/client/src/execution/vmexecution.ts b/packages/client/src/execution/vmexecution.ts index 8b45786f67..2d7a0090b1 100644 --- a/packages/client/src/execution/vmexecution.ts +++ b/packages/client/src/execution/vmexecution.ts @@ -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' @@ -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' @@ -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, }) @@ -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, })