@polygon-hermez/vm
Version:
An Ethereum VM implementation
161 lines • 5.98 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const ethereumjs_util_1 = require("ethereumjs-util");
const evm_1 = require("../evm");
const exceptions_1 = require("../../exceptions");
const assert = require('assert');
const MAX_SIZE_MODEXP = new ethereumjs_util_1.BN(1024);
function multComplexity(x) {
let fac1;
let fac2;
if (x.lten(64)) {
return x.sqr();
}
else if (x.lten(1024)) {
// return Math.floor(Math.pow(x, 2) / 4) + 96 * x - 3072
fac1 = x.sqr().divn(4);
fac2 = x.muln(96);
return fac1.add(fac2).subn(3072);
}
else {
// return Math.floor(Math.pow(x, 2) / 16) + 480 * x - 199680
fac1 = x.sqr().divn(16);
fac2 = x.muln(480);
return fac1.add(fac2).subn(199680);
}
}
function multComplexityEIP2565(x) {
const words = x.addn(7).divn(8);
return words.mul(words);
}
function getAdjustedExponentLength(data) {
let expBytesStart;
try {
const baseLen = new ethereumjs_util_1.BN(data.slice(0, 32)).toNumber();
expBytesStart = 96 + baseLen; // 96 for base length, then exponent length, and modulus length, then baseLen for the base data, then exponent bytes start
}
catch (e) {
expBytesStart = Number.MAX_SAFE_INTEGER - 32;
}
const expLen = new ethereumjs_util_1.BN(data.slice(32, 64));
let firstExpBytes = Buffer.from(data.slice(expBytesStart, expBytesStart + 32)); // first word of the exponent data
firstExpBytes = (0, ethereumjs_util_1.setLengthRight)(firstExpBytes, 32); // reading past the data reads virtual zeros
let firstExpBN = new ethereumjs_util_1.BN(firstExpBytes);
let max32expLen = 0;
if (expLen.ltn(32)) {
max32expLen = 32 - expLen.toNumber();
}
firstExpBN = firstExpBN.shrn(8 * Math.max(max32expLen, 0));
let bitLen = -1;
while (firstExpBN.gtn(0)) {
bitLen = bitLen + 1;
firstExpBN = firstExpBN.ushrn(1);
}
let expLenMinus32OrZero = expLen.subn(32);
if (expLenMinus32OrZero.ltn(0)) {
expLenMinus32OrZero = new ethereumjs_util_1.BN(0);
}
const eightTimesExpLenMinus32OrZero = expLenMinus32OrZero.muln(8);
const adjustedExpLen = eightTimesExpLenMinus32OrZero;
if (bitLen > 0) {
adjustedExpLen.iaddn(bitLen);
}
return adjustedExpLen;
}
function expmod(B, E, M) {
if (E.isZero())
return new ethereumjs_util_1.BN(1).mod(M);
// Red asserts M > 1
if (M.lten(1))
return new ethereumjs_util_1.BN(0);
const red = ethereumjs_util_1.BN.red(M);
const redB = B.toRed(red);
const res = redB.redPow(E);
return res.fromRed();
}
function default_1(opts) {
assert(opts.data);
const data = opts.data;
let adjustedELen = getAdjustedExponentLength(data);
if (adjustedELen.ltn(1)) {
adjustedELen = new ethereumjs_util_1.BN(1);
}
const bLen = new ethereumjs_util_1.BN(data.slice(0, 32));
const eLen = new ethereumjs_util_1.BN(data.slice(32, 64));
const mLen = new ethereumjs_util_1.BN(data.slice(64, 96));
let maxLen = bLen;
if (maxLen.lt(mLen)) {
maxLen = mLen;
}
const Gquaddivisor = opts._common.param('gasPrices', 'modexpGquaddivisor');
let gasUsed;
const bStart = new ethereumjs_util_1.BN(96);
const bEnd = bStart.add(bLen);
const eStart = bEnd;
const eEnd = eStart.add(eLen);
const mStart = eEnd;
const mEnd = mStart.add(mLen);
if (!opts._common.isActivatedEIP(2565)) {
gasUsed = adjustedELen.mul(multComplexity(maxLen)).divn(Gquaddivisor);
}
else {
gasUsed = adjustedELen.mul(multComplexityEIP2565(maxLen)).divn(Gquaddivisor);
if (gasUsed.ltn(200)) {
gasUsed = new ethereumjs_util_1.BN(200);
}
}
if (opts.gasLimit.lt(gasUsed)) {
return (0, evm_1.OOGResult)(opts.gasLimit);
}
if (bLen.isZero()) {
return {
gasUsed,
returnValue: new ethereumjs_util_1.BN(0).toArrayLike(Buffer, 'be', mLen.toNumber()),
};
}
if (mLen.isZero()) {
return {
gasUsed,
returnValue: Buffer.alloc(0),
};
}
// If base values are greater than max supported at zkEVM, return OOG
if (bLen.gt(MAX_SIZE_MODEXP) || eLen.gt(MAX_SIZE_MODEXP) || mLen.gt(MAX_SIZE_MODEXP)) {
return (0, evm_1.VmErrorResult)(new exceptions_1.VmError(exceptions_1.ERROR.MAX_SIZE_MODEXP), new ethereumjs_util_1.BN(0));
}
const maxInt = new ethereumjs_util_1.BN(Number.MAX_SAFE_INTEGER);
const maxSize = new ethereumjs_util_1.BN(2147483647); // ethereumjs-util setLengthRight limitation
if (bLen.gt(maxSize) || eLen.gt(maxSize) || mLen.gt(maxSize)) {
return (0, evm_1.OOGResult)(opts.gasLimit);
}
const B = new ethereumjs_util_1.BN((0, ethereumjs_util_1.setLengthRight)(data.slice(bStart.toNumber(), bEnd.toNumber()), bLen.toNumber()));
const E = new ethereumjs_util_1.BN((0, ethereumjs_util_1.setLengthRight)(data.slice(eStart.toNumber(), eEnd.toNumber()), eLen.toNumber()));
const M = new ethereumjs_util_1.BN((0, ethereumjs_util_1.setLengthRight)(data.slice(mStart.toNumber(), mEnd.toNumber()), mLen.toNumber()));
if (mEnd.gt(maxInt)) {
return (0, evm_1.OOGResult)(opts.gasLimit);
}
// Reduce counters
opts._VM.vcm.computeFunctionCounters('preModExp', {
bLen: bLen.toNumber(),
eLen: eLen.toNumber(),
mLen: mLen.toNumber(),
base: B.toString('hex'),
exponent: E.toString('hex'),
modulus: M.toString('hex'),
calldataLength: data.length,
returnDataLength: opts.outLength.toNumber(),
});
let R;
if (M.isZero()) {
R = new ethereumjs_util_1.BN(0);
}
else {
R = expmod(B, E, M);
}
return {
gasUsed,
returnValue: R.toArrayLike(Buffer, 'be', mLen.toNumber()),
};
}
exports.default = default_1;
//# sourceMappingURL=05-modexp.js.map