diff --git a/.github/workflows/nimbus_verified_proxy.yml b/.github/workflows/nimbus_verified_proxy.yml index 8a7fb21eb8..e7b9790ebb 100644 --- a/.github/workflows/nimbus_verified_proxy.yml +++ b/.github/workflows/nimbus_verified_proxy.yml @@ -34,6 +34,8 @@ on: - 'Makefile' - 'nimbus.nimble' + workflow_dispatch: + concurrency: # Cancel stale PR builds (but not push builds) group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }} cancel-in-progress: true @@ -67,28 +69,9 @@ jobs: rocksdb-cache: true eest-cache: false - - name: Run verified proxy tests (Windows) - if: runner.os == 'Windows' + - name: Run verified proxy tests run: | gcc --version - DEFAULT_MAKE_FLAGS="-j${ncpu}" - mingw32-make ${DEFAULT_MAKE_FLAGS} nimbus_verified_proxy libverifproxy - build/nimbus_verified_proxy.exe --help - mingw32-make ${DEFAULT_MAKE_FLAGS} nimbus-verified-proxy-test - - - name: Run verified proxy tests (Linux) - if: runner.os == 'Linux' - run: | - export LD_LIBRARY_PATH="${LD_LIBRARY_PATH}:/usr/local/lib" - DEFAULT_MAKE_FLAGS="-j${ncpu}" - env CC=gcc make ${DEFAULT_MAKE_FLAGS} nimbus_verified_proxy libverifproxy - build/nimbus_verified_proxy --help - # CC is needed to select correct compiler 32/64 bit - env CC=gcc CXX=g++ make ${DEFAULT_MAKE_FLAGS} nimbus-verified-proxy-test - - - name: Run verified proxy tests (Macos) - if: runner.os == 'Macos' - run: | DEFAULT_MAKE_FLAGS="-j${ncpu}" make ${DEFAULT_MAKE_FLAGS} nimbus_verified_proxy libverifproxy build/nimbus_verified_proxy --help diff --git a/.gitmodules b/.gitmodules index 289491594b..328b9c796f 100644 --- a/.gitmodules +++ b/.gitmodules @@ -226,3 +226,7 @@ path = vendor/nim-minilru url = https://github.com/status-im/nim-minilru.git branch = master +[submodule "vendor/nim-mcl"] + path = vendor/nim-mcl + url = https://github.com/status-im/nim-mcl + branch = main diff --git a/config.nims b/config.nims index 18a9b90ee8..4e099acda7 100644 --- a/config.nims +++ b/config.nims @@ -94,6 +94,10 @@ elif defined(riscv64): # and seems to be the minimum extensions needed to build. switch("passC", "-march=rv64gc") switch("passL", "-march=rv64gc") +elif defined(linux) and defined(arm64): + # clang can't handle "-march=native" + switch("passC", "-march=armv8-a") + switch("passL", "-march=armv8-a") else: switch("passC", "-march=native") switch("passL", "-march=native") diff --git a/execution_chain/compile_info.nim b/execution_chain/compile_info.nim index 6aa9e58f74..37d2faeb1d 100644 --- a/execution_chain/compile_info.nim +++ b/execution_chain/compile_info.nim @@ -14,3 +14,7 @@ const chronicles_line_numbers {.strdefine.} = "0" when chronicles_line_numbers notin ["0", "off"]: {.hint: "*** Compiling with logger line numbers enabled".} + +const enable_mcl_lib* {.booldefine.} = true +when enable_mcl_lib: + {.hint: "*** Compiling with mcl library".} diff --git a/execution_chain/evm/bncurve_mcl.nim b/execution_chain/evm/bncurve_mcl.nim new file mode 100644 index 0000000000..8debbde77c --- /dev/null +++ b/execution_chain/evm/bncurve_mcl.nim @@ -0,0 +1,174 @@ +# nimbus-execution-client +# Copyright (c) 2025 Status Research & Development GmbH +# Licensed under either of +# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE)) +# * MIT license ([LICENSE-MIT](LICENSE-MIT)) +# at your option. +# This file may not be copied, modified, or distributed except according to +# those terms + +{.push raises: [].} + +import + results, + stew/assign2, + bncurve/arith, + mcl/bn_abi, + ./evm_errors, + ./types + +# one time initialization +doAssert(mclBn_init(MCL_BN_SNARK1, MCLBN_COMPILED_TIME_VAR) == 0.cint) + +const + ioMode = MCLBN_IO_SERIALIZE or MCLBN_IO_BIG_ENDIAN + +func isAllZero(data: openArray[byte]): bool = + for c in data: + if c != 0: return false + true + +# deserialize Fp from 32 byte big-endian number. +func deserialize(x: var BnFp, buf: openArray[byte]): bool = + mclBnFp_setStr(x.addr, cast[ptr char](buf[0].addr), 32, ioMode) == 0.cint + +func deserialize(x: var BnFp2, buf: openArray[byte]): bool = + deserialize(x.d[1], buf) and deserialize(x.d[0], buf.toOpenArray(32, buf.len-1)) + +func deserialize(x: var BnFr, buf: openArray[byte]): bool = + mclBnFr_setBigEndianMod(x.addr, buf[0].addr, 32.mclSize) == 0 + +func deserialize(P: var BnG1, buf: openArray[byte]): bool = + if buf.isAllZero: + mclBnG1_clear(P.addr) + return true + + if not deserialize(P.x, buf) or not deserialize(P.y, buf.toOpenArray(32, buf.len-1)): + return false + + mclBnFp_setInt32(P.z.addr, 1.cint) + mclBnG1_isValid(P.addr) == 1.cint + +func deserialize(P: var BnG2, buf: openArray[byte]): bool = + if buf.isAllZero: + mclBnG2_clear(P.addr) + return true + + if not deserialize(P.x, buf) or not deserialize(P.y, buf.toOpenArray(64, buf.len-1)): + return false + + mclBnFp_setInt32(P.z.d[0].addr, 1.cint) + mclBnFp_clear(P.z.d[1].addr) + mclBnG2_isValid(P.addr) == 1.cint + +# serialize Fp as 32 byte big-endian number. +func serialize(buf: var openArray[byte], x: BnFp): bool = + # sigh, getStr output buf is zero terminated + var tmp: array[33, byte] + result = mclBnFp_getStr(cast[ptr char](tmp[0].addr), 32, x.addr, ioMode) == 32.mclSize + assign(buf.toOpenArray(0, 31), tmp.toOpenArray(0, 31)) + +# Serialize P.x|P.y. +# Set _buf to all zeros if P == 0. +func serialize(buf: var openArray[byte], P: BnG1): bool = + if mclBnG1_isZero(P.addr) == 1.cint: + zeroMem(buf[0].addr, 64) + return true + + var Pn {.noinit.}: BnG1 + mclBnG1_normalize(Pn.addr, P.addr) + serialize(buf, Pn.x) and serialize(buf.toOpenArray(32, buf.len-1), Pn.y) + +func bn256ecAddImpl*(c: Computation): EvmResultVoid = + var + input: array[128, byte] + p1 {.noinit.}: BnG1 + p2 {.noinit.}: BnG1 + apo {.noinit.}: BnG1 + + # Padding data + let len = min(c.msg.data.len, 128) - 1 + assign(input.toOpenArray(0, len), c.msg.data.toOpenArray(0, len)) + + if not p1.deserialize(input.toOpenArray(0, 63)): + return err(prcErr(PrcInvalidPoint)) + + if not p2.deserialize(input.toOpenArray(64, 127)): + return err(prcErr(PrcInvalidPoint)) + + mclBnG1_add(apo.addr, p1.addr, p2.addr) + + c.output.setLen(64) + if not serialize(c.output, apo): + zeroMem(c.output[0].addr, 64) + + ok() + +func bn256ecMulImpl*(c: Computation): EvmResultVoid = + var + input: array[96, byte] + p1 {.noinit.}: BnG1 + fr {.noinit.}: BnFr + apo {.noinit.}: BnG1 + + # Padding data + let len = min(c.msg.data.len, 96) - 1 + assign(input.toOpenArray(0, len), c.msg.data.toOpenArray(0, len)) + + if not p1.deserialize(input.toOpenArray(0, 63)): + return err(prcErr(PrcInvalidPoint)) + + if not fr.deserialize(input.toOpenArray(64, 95)): + return err(prcErr(PrcInvalidPoint)) + + mclBnG1_mul(apo.addr, p1.addr, fr.addr) + + c.output.setLen(64) + if not serialize(c.output, apo): + zeroMem(c.output[0].addr, 64) + + ok() + +func bn256ecPairingImpl*(c: Computation): EvmResultVoid = + let msglen = c.msg.data.len + if msglen == 0: + # we can discard here because we supply buffer of proper size + c.output.setLen(32) + discard BNU256.one().toBytesBE(c.output) + else: + # Calculate number of pairing pairs + let count = msglen div 192 + # Pairing accumulator + var + acc {.noinit.}: BnGT + one {.noinit.}: BnGT + tmp {.noinit.}: BnGT + + mclBnGT_setInt(acc.addr, 1.mclInt) + mclBnGT_setInt(one.addr, 1.mclInt) + + var + p1 {.noinit.}: BnG1 + p2 {.noinit.}: BnG2 + + for i in 0.. modulus` + result = false + if dst.c1.fromBytes(src.toOpenArray(0, 31)) and + dst.c0.fromBytes(src.toOpenArray(32, 63)): + result = true + +template simpleDecode(dst: var FQ, src: openArray[byte]): bool = + fromBytes(dst, src) + +func getPoint[T: G1|G2](_: typedesc[T], data: openArray[byte]): EvmResult[Point[T]] = + when T is G1: + const nextOffset = 32 + var px, py: FQ + else: + const nextOffset = 64 + var px, py: FQ2 + + if not px.simpleDecode(data.toOpenArray(0, nextOffset - 1)): + return err(prcErr(PrcInvalidPoint)) + if not py.simpleDecode(data.toOpenArray(nextOffset, nextOffset * 2 - 1)): + return err(prcErr(PrcInvalidPoint)) + + if px.isZero() and py.isZero(): + ok(T.zero()) + else: + var ap: AffinePoint[T] + if not ap.init(px, py): + return err(prcErr(PrcInvalidPoint)) + ok(ap.toJacobian()) + +func getFR(data: openArray[byte]): EvmResult[FR] = + var res: FR + if not res.fromBytes2(data): + return err(prcErr(PrcInvalidPoint)) + ok(res) + +func bn256ecAddImpl*(c: Computation): EvmResultVoid = + var + input: array[128, byte] + # Padding data + let len = min(c.msg.data.len, 128) - 1 + assign(input.toOpenArray(0, len), c.msg.data.toOpenArray(0, len)) + let + p1 = ? G1.getPoint(input.toOpenArray(0, 63)) + p2 = ? G1.getPoint(input.toOpenArray(64, 127)) + apo = (p1 + p2).toAffine() + + c.output.setLen(64) + if isSome(apo): + # we can discard here because we supply proper buffer + discard apo.get().toBytes(c.output) + + ok() + +func bn256ecMulImpl*(c: Computation): EvmResultVoid = + var + input: array[96, byte] + # Padding data + let len = min(c.msg.data.len, 96) - 1 + assign(input.toOpenArray(0, len), c.msg.data.toOpenArray(0, len)) + let + p1 = ? G1.getPoint(input.toOpenArray(0, 63)) + fr = ? getFR(input.toOpenArray(64, 95)) + apo = (p1 * fr).toAffine() + + c.output.setLen(64) + if isSome(apo): + # we can discard here because we supply buffer of proper size + discard apo.get().toBytes(c.output) + + ok() + +func bn256ecPairingImpl*(c: Computation): EvmResultVoid = + let msglen = c.msg.data.len + if msglen == 0: + # we can discard here because we supply buffer of proper size + c.output.setLen(32) + discard BNU256.one().toBytesBE(c.output) + else: + # Calculate number of pairing pairs + let count = msglen div 192 + # Pairing accumulator + var acc = FQ12.one() + + for i in 0.. modulus` - result = false - if dst.c1.fromBytes(src.toOpenArray(0, 31)) and - dst.c0.fromBytes(src.toOpenArray(32, 63)): - result = true - -template simpleDecode(dst: var FQ, src: openArray[byte]): bool = - fromBytes(dst, src) - -func getPoint[T: G1|G2](_: typedesc[T], data: openArray[byte]): EvmResult[Point[T]] = - when T is G1: - const nextOffset = 32 - var px, py: FQ - else: - const nextOffset = 64 - var px, py: FQ2 - - if not px.simpleDecode(data.toOpenArray(0, nextOffset - 1)): - return err(prcErr(PrcInvalidPoint)) - if not py.simpleDecode(data.toOpenArray(nextOffset, nextOffset * 2 - 1)): - return err(prcErr(PrcInvalidPoint)) - - if px.isZero() and py.isZero(): - ok(T.zero()) - else: - var ap: AffinePoint[T] - if not ap.init(px, py): - return err(prcErr(PrcInvalidPoint)) - ok(ap.toJacobian()) - -func getFR(data: openArray[byte]): EvmResult[FR] = - var res: FR - if not res.fromBytes2(data): - return err(prcErr(PrcInvalidPoint)) - ok(res) - # ------------------------------------------------------------------------------ # Precompiles functions # ------------------------------------------------------------------------------ @@ -384,43 +351,12 @@ func modExp(c: Computation, fork: EVMFork = FkByzantium): EvmResultVoid = func bn256ecAdd(c: Computation, fork: EVMFork = FkByzantium): EvmResultVoid = let gasFee = if fork < FkIstanbul: GasECAdd else: GasECAddIstanbul ? c.gasMeter.consumeGas(gasFee, reason = "ecAdd Precompile") - - var - input: array[128, byte] - # Padding data - let len = min(c.msg.data.len, 128) - 1 - input[0..len] = c.msg.data[0..len] - var p1 = ? G1.getPoint(input.toOpenArray(0, 63)) - var p2 = ? G1.getPoint(input.toOpenArray(64, 127)) - var apo = (p1 + p2).toAffine() - - c.output.setLen(64) - if isSome(apo): - # we can discard here because we supply proper buffer - discard apo.get().toBytes(c.output) - - ok() + bn256ecAddImpl(c) func bn256ecMul(c: Computation, fork: EVMFork = FkByzantium): EvmResultVoid = let gasFee = if fork < FkIstanbul: GasECMul else: GasECMulIstanbul ? c.gasMeter.consumeGas(gasFee, reason="ecMul Precompile") - - var - input: array[96, byte] - - # Padding data - let len = min(c.msg.data.len, 96) - 1 - assign(input.toOpenArray(0, len), c.msg.data.toOpenArray(0, len)) - var p1 = ? G1.getPoint(input.toOpenArray(0, 63)) - var fr = ? getFR(input.toOpenArray(64, 95)) - var apo = (p1 * fr).toAffine() - - c.output.setLen(64) - if isSome(apo): - # we can discard here because we supply buffer of proper size - discard apo.get().toBytes(c.output) - - ok() + bn256ecMulImpl(c) func bn256ecPairing(c: Computation, fork: EVMFork = FkByzantium): EvmResultVoid = let msglen = c.msg.data.len @@ -433,32 +369,7 @@ func bn256ecPairing(c: Computation, fork: EVMFork = FkByzantium): EvmResultVoid else: GasECPairingBaseIstanbul + numPoints * GasECPairingPerPointIstanbul ? c.gasMeter.consumeGas(gasFee, reason="ecPairing Precompile") - - if msglen == 0: - # we can discard here because we supply buffer of proper size - c.output.setLen(32) - discard BNU256.one().toBytesBE(c.output) - else: - # Calculate number of pairing pairs - let count = msglen div 192 - # Pairing accumulator - var acc = FQ12.one() - - for i in 0..