@celo/wallet-base
Version:
Wallet base implementation
688 lines • 34.3 kB
JavaScript
;
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