UNPKG

@celo/wallet-base

Version:

Wallet base implementation

688 lines 34.3 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.assertLength = exports.handleData = exports.handleHexString = exports.handleBigInt = exports.handleStringNumber = exports.handleNumber = exports.signTransaction = exports.extractPublicKeyFromHashAndSignature = exports.decodeSig = exports.verifySignatureWithoutPrefix = exports.verifyEIP712TypedDataSigner = exports.recoverMessageSigner = exports.determineTXType = exports.getSignerFromTxEIP2718TX = exports.recoverTransaction = exports.extractSignature = exports.encodeTransaction = exports.isCIP64 = exports.isCIP66 = exports.isEIP1559 = exports.isPriceToLow = exports.rlpEncodedTx = exports.stringNumberOrBNToHex = exports.getHashFromEncoded = exports.chainIdTransformationForSigning = exports.thirtyTwo = exports.sixtyFour = exports.publicKeyPrefix = void 0; const address_1 = require("@celo/base/lib/address"); const connect_1 = require("@celo/connect"); const formatter_1 = require("@celo/connect/lib/utils/formatter"); const address_2 = require("@celo/utils/lib/address"); const sign_typed_data_utils_1 = require("@celo/utils/lib/sign-typed-data-utils"); const signatureUtils_1 = require("@celo/utils/lib/signatureUtils"); const RLP = __importStar(require("@ethereumjs/rlp")); const ethUtil = __importStar(require("@ethereumjs/util")); const secp256k1_1 = require("@noble/curves/secp256k1"); const sha3_1 = require("@noble/hashes/sha3"); const utils_1 = require("@noble/hashes/utils"); const debug_1 = __importDefault(require("debug")); const web3_1 = __importDefault(require("web3")); // TODO try to do this without web3 direct const { ecrecover, fromRpcSig, hashPersonalMessage, toBuffer } = ethUtil; const debug = (0, debug_1.default)('wallet-base:tx:sign'); // Original code taken from // https://github.com/ethereum/web3.js/blob/1.x/packages/web3-eth-accounts/src/index.js // 0x04 prefix indicates that the key is not compressed // https://tools.ietf.org/html/rfc5480#section-2.2 exports.publicKeyPrefix = 0x04; exports.sixtyFour = 64; exports.thirtyTwo = 32; const Y_PARITY_EIP_2098 = 27; function rlpEncodeHex(value) { return (0, address_1.ensureLeading0x)(Buffer.from(RLP.encode(value)).toString('hex')); } function isNullOrUndefined(value) { return value === null || value === undefined; } // Simple replay attack protection // https://github.com/ethereum/EIPs/blob/master/EIPS/eip-155.md function chainIdTransformationForSigning(chainId) { return chainId * 2 + 35; } exports.chainIdTransformationForSigning = chainIdTransformationForSigning; function getHashFromEncoded(rlpEncode) { const rlpBytes = (0, utils_1.hexToBytes)((0, address_1.trimLeading0x)(rlpEncode)); const hash = Buffer.from((0, sha3_1.keccak_256)(rlpBytes)); return `0x${hash.toString('hex')}`; } exports.getHashFromEncoded = getHashFromEncoded; function trimLeadingZero(hex) { while (hex && hex.startsWith('0x0')) { hex = (0, address_1.ensureLeading0x)(hex.slice(3)); } return hex; } function makeEven(hex) { if (hex.length % 2 === 1) { hex = hex.replace('0x', '0x0'); } return hex; } function signatureFormatter(signature, type) { let v = signature.v; if (type !== 'celo-legacy' && type !== 'ethereum-legacy') { const vn = BigInt(signature.v); if (vn === BigInt(Y_PARITY_EIP_2098) || vn === BigInt(0)) { v = 0; } else if (vn === BigInt(Y_PARITY_EIP_2098 + 1) || vn === BigInt(1)) { v = 1; } else { throw new Error('Invalid v ' + signature.v); } } return { v: (0, address_1.trimLeading0x)(stringNumberToHex(v)), r: (0, address_1.trimLeading0x)(makeEven(trimLeadingZero((0, address_1.ensureLeading0x)(signature.r.toString('hex'))))), s: (0, address_1.trimLeading0x)(makeEven(trimLeadingZero((0, address_1.ensureLeading0x)(signature.s.toString('hex'))))), }; } function stringNumberOrBNToHex(num) { if (typeof num === 'string' || typeof num === 'number' || num === undefined) { return stringNumberToHex(num); } else { return makeEven(`0x` + num.toString(16)); } } exports.stringNumberOrBNToHex = stringNumberOrBNToHex; function stringNumberToHex(num) { const auxNumber = Number(num); if (num === '0x' || num === undefined || auxNumber === 0) { return '0x'; } if (typeof num === 'bigint') { return makeEven(`0x` + num.toString(16)); } return makeEven(web3_1.default.utils.numberToHex(num)); } function rlpEncodedTx(tx) { assertSerializableTX(tx); const transaction = (0, formatter_1.inputCeloTxFormatter)(tx); transaction.to = (0, address_1.ensureLeading0x)((tx.to || '0x').toLowerCase()); transaction.nonce = Number((tx.nonce !== '0x' ? tx.nonce : 0) || 0); transaction.data = (tx.data || '0x').toLowerCase(); transaction.value = stringNumberOrBNToHex(tx.value); transaction.gas = stringNumberOrBNToHex(tx.gas); transaction.chainId = tx.chainId || 1; // Celo Specific transaction.feeCurrency = (0, address_1.ensureLeading0x)((tx.feeCurrency || '0x').toLowerCase()); // Legacy transaction.gasPrice = stringNumberOrBNToHex(tx.gasPrice); // EIP1559 / CIP42 transaction.maxFeePerGas = stringNumberOrBNToHex(tx.maxFeePerGas); transaction.maxPriorityFeePerGas = stringNumberOrBNToHex(tx.maxPriorityFeePerGas); let rlpEncode; if (isCIP66(tx)) { transaction.maxFeeInFeeCurrency = stringNumberOrBNToHex(tx.maxFeeInFeeCurrency); // https://github.com/celo-org/celo-proposals/blob/master/CIPs/cip-0064.md // 0x7b || rlp([chainId, nonce, maxPriorityFeePerGas, maxFeePerGas, gasLimit, to, value, data, accessList, feeCurrency, signatureYParity, signatureR, signatureS]). rlpEncode = rlpEncodeHex([ stringNumberToHex(transaction.chainId), stringNumberToHex(transaction.nonce), transaction.maxPriorityFeePerGas || '0x', transaction.maxFeePerGas || '0x', transaction.gas || '0x', transaction.to || '0x', transaction.value || '0x', transaction.data || '0x', transaction.accessList || [], transaction.feeCurrency || '0x', transaction.maxFeeInFeeCurrency || '0x', ]); delete transaction.gasPrice; return { transaction, rlpEncode: concatHex([TxTypeToPrefix.cip66, rlpEncode]), type: 'cip66' }; } else if (isCIP64(tx)) { // https://github.com/celo-org/celo-proposals/blob/master/CIPs/cip-0064.md // 0x7b || rlp([chainId, nonce, maxPriorityFeePerGas, maxFeePerGas, gasLimit, to, value, data, accessList, feeCurrency, signatureYParity, signatureR, signatureS]). rlpEncode = rlpEncodeHex([ stringNumberToHex(transaction.chainId), stringNumberToHex(transaction.nonce), transaction.maxPriorityFeePerGas || '0x', transaction.maxFeePerGas || '0x', transaction.gas || '0x', transaction.to || '0x', transaction.value || '0x', transaction.data || '0x', transaction.accessList || [], transaction.feeCurrency || '0x', ]); delete transaction.gasPrice; return { transaction, rlpEncode: concatHex([TxTypeToPrefix.cip64, rlpEncode]), type: 'cip64' }; } else if (isEIP1559(tx)) { // https://eips.ethereum.org/EIPS/eip-1559 // 0x02 || rlp([chain_id, nonce, max_priority_fee_per_gas, max_fee_per_gas, gas_limit, destination, amount, data, access_list, signature_y_parity, signature_r, signature_s]). rlpEncode = rlpEncodeHex([ stringNumberToHex(transaction.chainId), stringNumberToHex(transaction.nonce), transaction.maxPriorityFeePerGas || '0x', transaction.maxFeePerGas || '0x', transaction.gas || '0x', transaction.to || '0x', transaction.value || '0x', transaction.data || '0x', transaction.accessList || [], ]); delete transaction.feeCurrency; delete transaction.gasPrice; return { transaction, rlpEncode: concatHex([TxTypeToPrefix.eip1559, rlpEncode]), type: 'eip1559', }; } else { // https://github.com/celo-org/celo-proposals/blob/master/CIPs/cip-0035.md // rlp([nonce, gasprice, gaslimit, recipient, amount, data, v, r, s]) rlpEncode = rlpEncodeHex([ stringNumberToHex(transaction.nonce), transaction.gasPrice, transaction.gas, transaction.to, transaction.value, transaction.data, stringNumberToHex(transaction.chainId), '0x', '0x', ]); delete transaction.feeCurrency; return { transaction, rlpEncode, type: 'ethereum-legacy' }; } } exports.rlpEncodedTx = rlpEncodedTx; var TxTypeToPrefix; (function (TxTypeToPrefix) { TxTypeToPrefix["ethereum-legacy"] = ""; TxTypeToPrefix["celo-legacy"] = ""; TxTypeToPrefix["cip42"] = "0x7c"; TxTypeToPrefix["cip64"] = "0x7b"; TxTypeToPrefix["cip66"] = "0x7a"; TxTypeToPrefix["eip1559"] = "0x02"; })(TxTypeToPrefix || (TxTypeToPrefix = {})); function concatTypePrefixHex(rawTransaction, txType) { const prefix = TxTypeToPrefix[txType]; if (prefix) { return concatHex([prefix, rawTransaction]); } return rawTransaction; } function assertSerializableTX(tx) { if (!tx.gas) { throw new Error('"gas" is missing'); } // ensure at least gasPrice or maxFeePerGas and maxPriorityFeePerGas are set if (!(0, connect_1.isPresent)(tx.gasPrice) && (!(0, connect_1.isPresent)(tx.maxFeePerGas) || !(0, connect_1.isPresent)(tx.maxPriorityFeePerGas))) { throw new Error('"gasPrice" or "maxFeePerGas" and "maxPriorityFeePerGas" are missing'); } // ensure that gasPrice and maxFeePerGas are not set at the same time if ((0, connect_1.isPresent)(tx.gasPrice) && ((0, connect_1.isPresent)(tx.maxFeePerGas) || (0, connect_1.isPresent)(tx.maxPriorityFeePerGas))) { throw new Error('when "maxFeePerGas" or "maxPriorityFeePerGas" are set, "gasPrice" must not be set'); } if (isNullOrUndefined(tx.nonce) || isNullOrUndefined(tx.chainId)) { throw new Error('One of the values "chainId" or "nonce" couldn\'t be fetched: ' + JSON.stringify({ chainId: tx.chainId, nonce: tx.nonce })); } if (isLessThanZero(tx.nonce) || isLessThanZero(tx.gas) || isLessThanZero(tx.chainId)) { throw new Error('Gas, nonce or chainId is less than than 0'); } isPriceToLow(tx); } function isPriceToLow(tx) { const prices = [tx.gasPrice, tx.maxFeePerGas, tx.maxPriorityFeePerGas].filter((price) => price !== undefined); const isLow = false; for (const price of prices) { if (isLessThanZero(price)) { throw new Error('GasPrice or maxFeePerGas or maxPriorityFeePerGas is less than than 0'); } } return isLow; } exports.isPriceToLow = isPriceToLow; function isEIP1559(tx) { return (0, connect_1.isPresent)(tx.maxFeePerGas) && (0, connect_1.isPresent)(tx.maxPriorityFeePerGas); } exports.isEIP1559 = isEIP1559; function isCIP66(tx) { return (0, connect_1.isPresent)(tx.feeCurrency) && (0, connect_1.isPresent)(tx.maxFeeInFeeCurrency); } exports.isCIP66 = isCIP66; function isCIP64(tx) { return isEIP1559(tx) && (0, connect_1.isPresent)(tx.feeCurrency); } exports.isCIP64 = isCIP64; function concatHex(values) { return `0x${values.reduce((acc, x) => acc + x.replace('0x', ''), '')}`; } function isLessThanZero(value) { if (isNullOrUndefined(value)) { return true; } switch (typeof value) { case 'string': case 'number': return Number(value) < 0; default: return (value === null || value === void 0 ? void 0 : value.lt(web3_1.default.utils.toBN(0))) || false; } } function encodeTransaction(rlpEncoded, signature) { return __awaiter(this, void 0, void 0, function* () { const sanitizedSignature = signatureFormatter(signature, rlpEncoded.type); const { v, r, s } = sanitizedSignature; const decodedTX = prefixAwareRLPDecode(rlpEncoded.rlpEncode, rlpEncoded.type); // for legacy tx we need to slice but for new ones we do not want to do that let decodedFields; if (rlpEncoded.type === 'celo-legacy') { decodedFields = decodedTX.slice(0, 9); } else if (rlpEncoded.type === 'ethereum-legacy') { decodedFields = decodedTX.slice(0, 6); } else { decodedFields = decodedTX; } const rawTx = decodedFields.concat([(0, utils_1.hexToBytes)(v), (0, utils_1.hexToBytes)(r), (0, utils_1.hexToBytes)(s)]); // After signing, the transaction is encoded again and type prefix added const rawTransaction = concatTypePrefixHex(rlpEncodeHex(rawTx), rlpEncoded.type); const hash = getHashFromEncoded(rawTransaction); const baseTX = { nonce: rlpEncoded.transaction.nonce.toString(), gas: rlpEncoded.transaction.gas.toString(), to: rlpEncoded.transaction.to.toString(), value: rlpEncoded.transaction.value.toString(), input: rlpEncoded.transaction.data, v: (0, address_1.ensureLeading0x)(v), r: (0, address_1.ensureLeading0x)(r), s: (0, address_1.ensureLeading0x)(s), hash, }; let tx = baseTX; if (rlpEncoded.type === 'eip1559' || rlpEncoded.type === 'cip64') { tx = Object.assign(Object.assign({}, tx), { // @ts-expect-error -- just a matter of how this tx is built maxFeePerGas: rlpEncoded.transaction.maxFeePerGas.toString(), maxPriorityFeePerGas: rlpEncoded.transaction.maxPriorityFeePerGas.toString(), accessList: (0, formatter_1.parseAccessList)(rlpEncoded.transaction.accessList || []) }); } if (rlpEncoded.type === 'cip64' || rlpEncoded.type === 'celo-legacy') { tx = Object.assign(Object.assign({}, tx), { // @ts-expect-error -- just a matter of how this tx is built feeCurrency: rlpEncoded.transaction.feeCurrency.toString() }); } if (rlpEncoded.type === 'ethereum-legacy') { tx = Object.assign(Object.assign({}, tx), { // @ts-expect-error -- just a matter of how this tx is built gasPrice: rlpEncoded.transaction.gasPrice.toString() }); } const result = { tx: tx, raw: rawTransaction, type: rlpEncoded.type, }; return result; }); } exports.encodeTransaction = encodeTransaction; // new types have prefix but legacy does not function prefixAwareRLPDecode(rlpEncode, type) { if (type === 'celo-legacy' || type === 'ethereum-legacy') { return RLP.decode(rlpEncode); } return RLP.decode(`0x${rlpEncode.slice(4)}`); } function correctLengthOf(type, includeSig = true) { switch (type) { case 'cip66': { return includeSig ? 14 : 11; } case 'cip64': { return includeSig ? 13 : 10; } case 'cip42': return includeSig ? 15 : 12; case 'ethereum-legacy': return 9; case 'celo-legacy': case 'eip1559': return 12; } } // Based on the return type of ensureLeading0x this was not a Buffer function extractSignature(rawTx) { const type = determineTXType(rawTx); const rawValues = prefixAwareRLPDecode(rawTx, type); const length = rawValues.length; if (correctLengthOf(type) !== length) { throw new Error(`@extractSignature: provided transaction has ${length} elements but ${type} txs with a signature have ${correctLengthOf(type)} ${JSON.stringify(rawValues)}`); } return extractSignatureFromDecoded(rawValues); } exports.extractSignature = extractSignature; function extractSignatureFromDecoded(rawValues) { // signature is always (for the tx we support so far) the last three elements of the array in order v, r, s, const v = rawValues.at(-3); const r = rawValues.at(-2); const s = rawValues.at(-1); // https://github.com/wagmi-dev/viem/blob/993321689b3e2220976504e7e170fe47731297ce/src/utils/transaction/parseTransaction.ts#L281 // Account.recover cannot handle canonicalized signatures // A canonicalized signature may have the first byte removed if its value is 0 return { v: handleHexString(v), r: (0, address_1.ensureLeading0x)((0, utils_1.bytesToHex)(r).padStart(64, '0')), s: (0, address_1.ensureLeading0x)((0, utils_1.bytesToHex)(s).padStart(64, '0')), }; } // Recover transaction and sender address from a raw transaction. // This is used for testing. function recoverTransaction(rawTx) { if (!rawTx.startsWith('0x')) { throw new Error('rawTx must start with 0x'); } switch (determineTXType(rawTx)) { case 'cip66': return recoverTransactionCIP66(rawTx); case 'cip64': return recoverTransactionCIP64(rawTx); case 'cip42': return recoverTransactionCIP42(rawTx); case 'eip1559': return recoverTransactionEIP1559(rawTx); case 'celo-legacy': return recoverCeloLegacy(rawTx); default: return recoverEthereumLegacy(rawTx); } } exports.recoverTransaction = recoverTransaction; // inspired by @ethereumjs/tx function getPublicKeyofSignerFromTx(transactionArray, type) { // this needs to be 10 for cip64, 12 for cip42 and eip1559 const base = transactionArray.slice(0, correctLengthOf(type, false)); const message = concatHex([TxTypeToPrefix[type], rlpEncodeHex(base).slice(2)]); const msgHash = (0, sha3_1.keccak_256)((0, utils_1.hexToBytes)((0, address_1.trimLeading0x)(message))); const { v, r, s } = extractSignatureFromDecoded(transactionArray); try { return ecrecover(Buffer.from(msgHash), v === '0x' || v === undefined ? BigInt(0) : BigInt(1), toBuffer(r), toBuffer(s)); } catch (e) { throw new Error(e); } } function getSignerFromTxEIP2718TX(serializedTransaction) { const transactionArray = RLP.decode(`0x${serializedTransaction.slice(4)}`); const signer = getPublicKeyofSignerFromTx(transactionArray, determineTXType(serializedTransaction)); return (0, address_2.publicKeyToAddress)(signer.toString('hex')); } exports.getSignerFromTxEIP2718TX = getSignerFromTxEIP2718TX; function determineTXType(serializedTransaction) { const prefix = serializedTransaction.slice(0, 4); if (prefix === TxTypeToPrefix.eip1559) { return 'eip1559'; } else if (prefix === TxTypeToPrefix.cip42) { return 'cip42'; } else if (prefix === TxTypeToPrefix.cip64) { return 'cip64'; } else if (prefix === TxTypeToPrefix.cip66) { return 'cip66'; } // it is one of the legacy types (Celo or Ethereum), to differentiate between // legacy tx types we have to check the numberof fields const rawValues = RLP.decode(serializedTransaction); const length = rawValues.length; return correctLengthOf('celo-legacy') === length ? 'celo-legacy' : 'ethereum-legacy'; } exports.determineTXType = determineTXType; function vrsForRecovery(vRaw, r, s) { const v = vRaw === '0x' || (0, formatter_1.hexToNumber)(vRaw) === 0 || (0, formatter_1.hexToNumber)(vRaw) === 27 ? Y_PARITY_EIP_2098 : Y_PARITY_EIP_2098 + 1; return { v, r, s, yParity: (v - Y_PARITY_EIP_2098), }; } function recoverTransactionCIP42(serializedTransaction) { const type = 'cip42'; const transactionArray = prefixAwareRLPDecode(serializedTransaction, type); debug(`signing-utils@recoverTransaction${type.toUpperCase()}: values are %s`, transactionArray); assertLength(transactionArray, type); const [chainId, nonce, maxPriorityFeePerGas, maxFeePerGas, gas, feeCurrency, gatewayFeeRecipient, gatewayFee, to, value, data, accessList, vRaw, r, s,] = transactionArray; const celoTX = Object.assign({ type, nonce: handleNumber(nonce), maxPriorityFeePerGas: handleNumber(maxPriorityFeePerGas), maxFeePerGas: handleNumber(maxFeePerGas), gas: handleNumber(gas), feeCurrency: handleHexString(feeCurrency), // @ts-expect-error remove from type bc its not valid but we still should be able to recover/deserialize old txns gatewayFeeRecipient: handleHexString(gatewayFeeRecipient), gatewayFee: handleHexString(gatewayFee), to: handleHexString(to), value: handleNumber(value), data: handleData(data), chainId: handleNumber(chainId), accessList: (0, formatter_1.parseAccessList)(accessList) }, vrsForRecovery(handleHexString(vRaw), handleHexString(r), handleHexString(s))); const signer = transactionArray.length === correctLengthOf(type) ? getSignerFromTxEIP2718TX(serializedTransaction) : 'unsigned'; return [celoTX, signer]; } function recoverTransactionCIP64(serializedTransaction) { const type = 'cip64'; const transactionArray = prefixAwareRLPDecode(serializedTransaction, type); debug(`signing-utils@recoverTransaction${type.toUpperCase()}: values are %s`, transactionArray); assertLength(transactionArray, type); const [chainId, nonce, maxPriorityFeePerGas, maxFeePerGas, gas, to, value, data, accessList, feeCurrency, vRaw, r, s,] = transactionArray; const celoTX = Object.assign({ type, nonce: handleNumber(nonce), maxPriorityFeePerGas: handleNumber(maxPriorityFeePerGas), maxFeePerGas: handleNumber(maxFeePerGas), gas: handleNumber(gas), feeCurrency: handleHexString(feeCurrency), to: handleHexString(to), value: handleNumber(value), data: handleData(data), chainId: handleNumber(chainId), accessList: (0, formatter_1.parseAccessList)(accessList) }, vrsForRecovery(handleHexString(vRaw), handleHexString(r), handleHexString(s))); const signer = transactionArray.length === correctLengthOf(type, true) ? getSignerFromTxEIP2718TX(serializedTransaction) : 'unsigned'; return [celoTX, signer]; } function recoverTransactionCIP66(serializedTransaction) { const type = 'cip66'; const transactionArray = prefixAwareRLPDecode(serializedTransaction, 'cip66'); debug(`signing-utils@recoverTransaction${type.toUpperCase()}: values are %s`, transactionArray); assertLength(transactionArray, type); const [chainId, nonce, maxPriorityFeePerGas, maxFeePerGas, gas, to, value, data, accessList, feeCurrency, maxFeeInFeeCurrency, vRaw, r, s,] = transactionArray; const celoTX = Object.assign({ type, nonce: handleNumber(nonce), maxPriorityFeePerGas: handleStringNumber(maxPriorityFeePerGas), maxFeePerGas: handleStringNumber(maxFeePerGas), gas: handleStringNumber(gas), feeCurrency: handleHexString(feeCurrency), maxFeeInFeeCurrency: handleHexString(maxFeeInFeeCurrency), to: handleHexString(to), value: handleStringNumber(value), data: handleData(data), chainId: handleNumber(chainId), accessList: (0, formatter_1.parseAccessList)(accessList) }, vrsForRecovery(handleHexString(vRaw), handleHexString(r), handleHexString(s))); const signer = transactionArray.length === correctLengthOf(type, true) ? getSignerFromTxEIP2718TX(serializedTransaction) : 'unsigned'; return [celoTX, signer]; } function recoverTransactionEIP1559(serializedTransaction) { const type = 'eip1559'; const transactionArray = prefixAwareRLPDecode(serializedTransaction, type); debug(`signing-utils@recoverTransaction${type.toUpperCase()}: values are %s`, transactionArray); const [chainId, nonce, maxPriorityFeePerGas, maxFeePerGas, gas, to, value, data, accessList, vRaw, r, s,] = transactionArray; const vrs = vrsForRecovery(handleHexString(vRaw), handleHexString(r), handleHexString(s)); const celoTx = Object.assign({ type, nonce: handleNumber(nonce), gas: handleNumber(gas), maxPriorityFeePerGas: handleNumber(maxPriorityFeePerGas), maxFeePerGas: handleNumber(maxFeePerGas), to: handleHexString(to), value: handleNumber(value), data: handleData(data), chainId: handleNumber(chainId), accessList: (0, formatter_1.parseAccessList)(accessList) }, vrs); const publicKey = extractPublicKeyFromHashAndSignature(vrs, transactionArray.slice(0, -3), TxTypeToPrefix.eip1559); return [celoTx, (0, address_2.publicKeyToAddress)(publicKey)]; } function recoverCeloLegacy(serializedTransaction) { const rawValues = RLP.decode(serializedTransaction); debug('signing-utils@recoverTransaction: values are %s', rawValues); const recovery = handleNumber(rawValues[9]); const chainId = (recovery - 35) >> 1; const celoTx = { type: 'celo-legacy', nonce: handleNumber(rawValues[0]), // NOTE: I used `handleNumber` to make it match the snapshot but we may // lose accuracy, should use `handleHexString` gasPrice: handleNumber(rawValues[1]), // NOTE: I used `handleNumber` to make it match the snapshot but we may // lose accuracy, should use `handleHexString` gas: handleNumber(rawValues[2]), feeCurrency: handleHexString(rawValues[3]), gatewayFeeRecipient: handleHexString(rawValues[4]), gatewayFee: handleHexString(rawValues[5]), to: handleHexString(rawValues[6]), value: handleHexString(rawValues[7]), data: handleData(rawValues[8]), // NOTE: I stringified to make it match the snapshot but it doesn't // match the signature of TransactionConfig which expects a number // @ts-expect-error chainId: (0, address_1.ensureLeading0x)(chainId.toString(16)), }; const { r, v: _v, s } = extractSignatureFromDecoded(rawValues); const v = parseInt(_v || '0x0', 16); const safeChainId = (0, address_1.trimLeading0x)(makeEven(trimLeadingZero((0, address_1.ensureLeading0x)(chainId.toString(16))))); const extraData = recovery < 35 ? [] : [(0, utils_1.hexToBytes)(safeChainId), (0, utils_1.hexToBytes)(''), (0, utils_1.hexToBytes)('')]; const signingData = rawValues.slice(0, 9).concat(extraData); const publicKey = extractPublicKeyFromHashAndSignature({ v, r, s, yParity: (v - chainIdTransformationForSigning(chainId)) }, signingData, TxTypeToPrefix['celo-legacy']); // @ts-expect-error LegacyCeloTx isn't compatible with CeloTx return [celoTx, (0, address_2.publicKeyToAddress)(publicKey)]; } function recoverEthereumLegacy(serializedTransaction) { const rawValues = RLP.decode(serializedTransaction); debug('signing-utils@recoverTransaction: values are %s', rawValues); const recovery = handleNumber(rawValues[6]); const chainId = (recovery - 35) >> 1; const celoTx = { type: 'ethereum-legacy', nonce: handleNumber(rawValues[0]), // NOTE: I used `handleNumber` to make it match the snapshot but we may // lose accuracy, should use `handleHexString` gasPrice: handleNumber(rawValues[1]), // NOTE: I used `handleNumber` to make it match the snapshot but we may // lose accuracy, should use `handleHexString` gas: handleNumber(rawValues[2]), to: handleHexString(rawValues[3]), value: handleHexString(rawValues[4]), data: handleData(rawValues[5]), // NOTE: I stringified to make it match the snapshot but it doesn't // match the signature of TransactionConfig which expects a number // @ts-expect-error chainId: (0, address_1.ensureLeading0x)(chainId.toString(16)), }; const { r, v: _v, s } = extractSignatureFromDecoded(rawValues); const v = parseInt(_v || '0x0', 16); const safeChainId = (0, address_1.trimLeading0x)(makeEven(trimLeadingZero((0, address_1.ensureLeading0x)(chainId.toString(16))))); const extraData = recovery < 35 ? [] : [(0, utils_1.hexToBytes)(safeChainId), (0, utils_1.hexToBytes)(''), (0, utils_1.hexToBytes)('')]; const signingData = rawValues.slice(0, 6).concat(extraData); const publicKey = extractPublicKeyFromHashAndSignature({ v, r, s, yParity: (v - chainIdTransformationForSigning(chainId)) }, signingData, TxTypeToPrefix['celo-legacy']); return [celoTx, (0, address_2.publicKeyToAddress)(publicKey)]; } function recoverMessageSigner(signingDataHex, signedData) { const dataBuff = toBuffer(signingDataHex); const msgHashBuff = hashPersonalMessage(dataBuff); const signature = fromRpcSig(signedData); const publicKey = ecrecover(msgHashBuff, signature.v, signature.r, signature.s); const address = (0, address_2.publicKeyToAddress)(publicKey.toString('hex')); return (0, address_1.ensureLeading0x)(address); } exports.recoverMessageSigner = recoverMessageSigner; function verifyEIP712TypedDataSigner(typedData, signedData, expectedAddress) { const dataHex = ethUtil.bufferToHex((0, sign_typed_data_utils_1.generateTypedDataHash)(typedData)); return verifySignatureWithoutPrefix(dataHex, signedData, expectedAddress); } exports.verifyEIP712TypedDataSigner = verifyEIP712TypedDataSigner; function verifySignatureWithoutPrefix(messageHash, signature, signer) { try { (0, signatureUtils_1.parseSignatureWithoutPrefix)(messageHash, signature, signer); return true; } catch (error) { return false; } } exports.verifySignatureWithoutPrefix = verifySignatureWithoutPrefix; function decodeSig(sig, addToV = 0) { const { recovery, r, s } = typeof sig === 'string' ? secp256k1_1.secp256k1.Signature.fromCompact(sig) : sig; return { v: recovery + addToV, r: Buffer.from(r.toString(16).padStart(64, '0'), 'hex'), s: Buffer.from(s.toString(16).padStart(64, '0'), 'hex'), }; } exports.decodeSig = decodeSig; function extractPublicKeyFromHashAndSignature({ r, s, yParity }, data, _prefix) { const signature = new secp256k1_1.secp256k1.Signature(BigInt(r), BigInt(s)).addRecoveryBit(yParity); const signingDataHex = _prefix ? concatHex([_prefix, rlpEncodeHex(data)]) : rlpEncodeHex(data); // const signingDataHex = rlpEncodeHex(data) const signingDataHash = getHashFromEncoded(signingDataHex); const publicKey = signature.recoverPublicKey((0, address_1.trimLeading0x)(signingDataHash)).toHex(false); return publicKey; } exports.extractPublicKeyFromHashAndSignature = extractPublicKeyFromHashAndSignature; function signTransaction(hash, privateKey, addToV = 0) { const signature = secp256k1_1.secp256k1.sign((0, address_1.trimLeading0x)(hash), (0, utils_1.hexToBytes)((0, address_1.trimLeading0x)(privateKey)), { lowS: true } // canonical:true ); return decodeSig(signature, addToV); } exports.signTransaction = signTransaction; function handleNumber(n) { const hex = `0x${(0, utils_1.bytesToHex)(n)}`; if (hex === '0x') return 0; return parseInt(hex, 16); } exports.handleNumber = handleNumber; function handleStringNumber(n) { const hex = `0x${(0, utils_1.bytesToHex)(n)}`; if (hex === '0x') return '0'; return BigInt(hex).toString(10); } exports.handleStringNumber = handleStringNumber; function handleBigInt(n) { const hex = `0x${(0, utils_1.bytesToHex)(n)}`; if (hex === '0x') return BigInt(0); return BigInt(hex); } exports.handleBigInt = handleBigInt; function handleHexString(adr) { if (!adr.length) { return '0x'; } const hex = `0x${(0, utils_1.bytesToHex)(adr)}`; return (0, address_1.normalizeAddressWith0x)(hex); } exports.handleHexString = handleHexString; function handleData(data) { if (!data.length) { return '0x'; } const hex = `0x${(0, utils_1.bytesToHex)(data)}`; return hex; } exports.handleData = handleData; function assertLength(transactionArray, type) { const signed = correctLengthOf(type, true); const unsigned = correctLengthOf(type, false); if (transactionArray.length !== signed && transactionArray.length !== unsigned) { throw new Error(`Invalid transaction length for type ${type.toUpperCase()}: ${transactionArray.length} instead of ${signed} or ${unsigned}. array: ${transactionArray}`); } } exports.assertLength = assertLength; //# sourceMappingURL=signing-utils.js.map