UNPKG

@ethereumjs/tx

Version:
254 lines 9.06 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.errorMsg = errorMsg; exports.isSigned = isSigned; exports.getDataGas = getDataGas; exports.getIntrinsicGas = getIntrinsicGas; exports.toCreationAddress = toCreationAddress; exports.hash = hash; exports.validateHighS = validateHighS; exports.getSenderPublicKey = getSenderPublicKey; exports.getEffectivePriorityFee = getEffectivePriorityFee; exports.getValidationErrors = getValidationErrors; exports.isValid = isValid; exports.verifySignature = verifySignature; exports.getSenderAddress = getSenderAddress; exports.sign = sign; exports.getSharedErrorPostfix = getSharedErrorPostfix; const util_1 = require("@ethereumjs/util"); const keccak_js_1 = require("ethereum-cryptography/keccak.js"); const types_ts_1 = require("../types.js"); const secp256k1_1 = require("ethereum-cryptography/secp256k1"); function errorMsg(tx, msg) { return `${msg} (${tx.errorStr()})`; } function isSigned(tx) { const { v, r, s } = tx; if (v === undefined || r === undefined || s === undefined) { return false; } else { return true; } } /** * The amount of gas paid for the data in this tx */ function getDataGas(tx) { if (tx.cache.dataFee && tx.cache.dataFee.hardfork === tx.common.hardfork()) { return tx.cache.dataFee.value; } const txDataZero = tx.common.param('txDataZeroGas'); const txDataNonZero = tx.common.param('txDataNonZeroGas'); let cost = util_1.BIGINT_0; for (let i = 0; i < tx.data.length; i++) { tx.data[i] === 0 ? (cost += txDataZero) : (cost += txDataNonZero); } if ((tx.to === undefined || tx.to === null) && tx.common.isActivatedEIP(3860)) { const dataLength = BigInt(Math.ceil(tx.data.length / 32)); const initCodeCost = tx.common.param('initCodeWordGas') * dataLength; cost += initCodeCost; } if (Object.isFrozen(tx)) { tx.cache.dataFee = { value: cost, hardfork: tx.common.hardfork(), }; } return cost; } /** * The minimum gas limit which the tx to have to be valid. * This covers costs as the standard fee (21000 gas), the data fee (paid for each calldata byte), * the optional creation fee (if the transaction creates a contract), and if relevant the gas * to be paid for access lists (EIP-2930) and authority lists (EIP-7702). */ function getIntrinsicGas(tx) { const txFee = tx.common.param('txGas'); let fee = tx.getDataGas(); if (txFee) fee += txFee; if (tx.common.gteHardfork('homestead') && tx.toCreationAddress()) { const txCreationFee = tx.common.param('txCreationGas'); if (txCreationFee) fee += txCreationFee; } return fee; } function toCreationAddress(tx) { return tx.to === undefined || tx.to.bytes.length === 0; } function hash(tx) { if (!tx.isSigned()) { const msg = errorMsg(tx, 'Cannot call hash method if transaction is not signed'); throw (0, util_1.EthereumJSErrorWithoutCode)(msg); } const keccakFunction = tx.common.customCrypto.keccak256 ?? keccak_js_1.keccak256; if (Object.isFrozen(tx)) { if (!tx.cache.hash) { tx.cache.hash = keccakFunction(tx.serialize()); } return tx.cache.hash; } return keccakFunction(tx.serialize()); } /** * EIP-2: All transaction signatures whose s-value is greater than secp256k1n/2are considered invalid. * Reasoning: https://ethereum.stackexchange.com/a/55728 */ function validateHighS(tx) { const { s } = tx; if (tx.common.gteHardfork('homestead') && s !== undefined && s > util_1.SECP256K1_ORDER_DIV_2) { const msg = errorMsg(tx, 'Invalid Signature: s-values greater than secp256k1n/2 are considered invalid'); throw (0, util_1.EthereumJSErrorWithoutCode)(msg); } } function getSenderPublicKey(tx) { if (tx.cache.senderPubKey !== undefined) { return tx.cache.senderPubKey; } const msgHash = tx.getMessageToVerifySignature(); const { v, r, s } = tx; validateHighS(tx); try { const ecrecoverFunction = tx.common.customCrypto.ecrecover ?? util_1.ecrecover; const sender = ecrecoverFunction(msgHash, v, (0, util_1.bigIntToUnpaddedBytes)(r), (0, util_1.bigIntToUnpaddedBytes)(s), tx.supports(types_ts_1.Capability.EIP155ReplayProtection) ? tx.common.chainId() : undefined); if (Object.isFrozen(tx)) { tx.cache.senderPubKey = sender; } return sender; } catch { const msg = errorMsg(tx, 'Invalid Signature'); throw (0, util_1.EthereumJSErrorWithoutCode)(msg); } } function getEffectivePriorityFee(gasPrice, baseFee) { if (baseFee !== undefined && baseFee > gasPrice) { throw (0, util_1.EthereumJSErrorWithoutCode)('Tx cannot pay baseFee'); } if (baseFee === undefined) { return gasPrice; } return gasPrice - baseFee; } /** * Validates the transaction signature and minimum gas requirements. * @returns {string[]} an array of error strings */ function getValidationErrors(tx) { const errors = []; if (tx.isSigned() && !tx.verifySignature()) { errors.push('Invalid Signature'); } let intrinsicGas = tx.getIntrinsicGas(); if (tx.common.isActivatedEIP(7623)) { let tokens = 0; for (let i = 0; i < tx.data.length; i++) { tokens += tx.data[i] === 0 ? 1 : 4; } const floorCost = tx.common.param('txGas') + tx.common.param('totalCostFloorPerToken') * BigInt(tokens); intrinsicGas = (0, util_1.bigIntMax)(intrinsicGas, floorCost); } if (intrinsicGas > tx.gasLimit) { errors.push(`gasLimit is too low. The gasLimit is lower than the minimum gas limit of ${tx.getIntrinsicGas()}, the gas limit is: ${tx.gasLimit}`); } return errors; } /** * Validates the transaction signature and minimum gas requirements. * @returns {boolean} true if the transaction is valid, false otherwise */ function isValid(tx) { const errors = tx.getValidationErrors(); return errors.length === 0; } /** * Determines if the signature is valid */ function verifySignature(tx) { try { // Main signature verification is done in `getSenderPublicKey()` const publicKey = tx.getSenderPublicKey(); return (0, util_1.unpadBytes)(publicKey).length !== 0; } catch { return false; } } /** * Returns the sender's address */ function getSenderAddress(tx) { return new util_1.Address((0, util_1.publicToAddress)(tx.getSenderPublicKey())); } /** * Signs a transaction. * * Note that the signed tx is returned as a new object, * use as follows: * ```javascript * const signedTx = tx.sign(privateKey) * ``` */ function sign(tx, privateKey, extraEntropy = true) { if (privateKey.length !== 32) { // TODO figure out this errorMsg logic how this diverges on other txs const msg = errorMsg(tx, 'Private key must be 32 bytes in length.'); throw (0, util_1.EthereumJSErrorWithoutCode)(msg); } // TODO (Jochem, 05 nov 2024): figure out what this hack does and clean it up // Hack for the constellation that we have got a legacy tx after spuriousDragon with a non-EIP155 conforming signature // and want to recreate a signature (where EIP155 should be applied) // Leaving this hack lets the legacy.spec.ts -> sign(), verifySignature() test fail // 2021-06-23 let hackApplied = false; if (tx.type === types_ts_1.TransactionType.Legacy && tx.common.gteHardfork('spuriousDragon') && !tx.supports(types_ts_1.Capability.EIP155ReplayProtection)) { ; tx['activeCapabilities'].push(types_ts_1.Capability.EIP155ReplayProtection); hackApplied = true; } const msgHash = tx.getHashedMessageToSign(); const ecSignFunction = tx.common.customCrypto?.ecsign ?? secp256k1_1.secp256k1.sign; const { recovery, r, s } = ecSignFunction(msgHash, privateKey, { extraEntropy }); const signedTx = tx.addSignature(BigInt(recovery), r, s, true); // Hack part 2 if (hackApplied) { const index = tx['activeCapabilities'].indexOf(types_ts_1.Capability.EIP155ReplayProtection); if (index > -1) { ; tx['activeCapabilities'].splice(index, 1); } } return signedTx; } // TODO maybe move this to shared methods (util.ts in features) function getSharedErrorPostfix(tx) { let hash = ''; try { hash = tx.isSigned() ? (0, util_1.bytesToHex)(tx.hash()) : 'not available (unsigned)'; } catch { hash = 'error'; } let isSigned = ''; try { isSigned = tx.isSigned().toString(); } catch { hash = 'error'; } let hf = ''; try { hf = tx.common.hardfork(); } catch { hf = 'error'; } let postfix = `tx type=${tx.type} hash=${hash} nonce=${tx.nonce} value=${tx.value} `; postfix += `signed=${isSigned} hf=${hf}`; return postfix; } //# sourceMappingURL=legacy.js.map