@ethereumjs/tx
Version:
Implementation of the various Ethereum Transaction Types
254 lines • 9.06 kB
JavaScript
;
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