UNPKG

@waku/rln

Version:

RLN (Rate Limiting Nullifier) implementation for Waku

280 lines (277 loc) 10.6 kB
import { getAddress } from '../../address/lib.esm/index.js'; import { hexDataLength, arrayify, hexZeroPad, hexlify, hexDataSlice, splitSignature, stripZeros, hexConcat } from '../../bytes/lib.esm/index.js'; import { keccak256 } from '../../keccak256/lib.esm/index.js'; import { decode, encode } from '../../rlp/lib.esm/index.js'; import { recoverPublicKey, computePublicKey } from '../../signing-key/lib.esm/index.js'; import { Logger } from '../../logger/lib.esm/index.js'; import { version } from './_version.js'; import { BigNumber } from '../../bignumber/lib.esm/bignumber.js'; import { Zero } from '../../constants/lib.esm/bignumbers.js'; const logger = new Logger(version); var TransactionTypes; (function (TransactionTypes) { TransactionTypes[TransactionTypes["legacy"] = 0] = "legacy"; TransactionTypes[TransactionTypes["eip2930"] = 1] = "eip2930"; TransactionTypes[TransactionTypes["eip1559"] = 2] = "eip1559"; })(TransactionTypes || (TransactionTypes = {})); /////////////////////////////// function handleAddress(value) { if (value === "0x") { return null; } return getAddress(value); } function handleNumber(value) { if (value === "0x") { return Zero; } return BigNumber.from(value); } function computeAddress(key) { const publicKey = computePublicKey(key); return getAddress(hexDataSlice(keccak256(hexDataSlice(publicKey, 1)), 12)); } function recoverAddress(digest, signature) { return computeAddress(recoverPublicKey(arrayify(digest), signature)); } function formatNumber(value, name) { const result = stripZeros(BigNumber.from(value).toHexString()); if (result.length > 32) { logger.throwArgumentError("invalid length for " + name, ("transaction:" + name), value); } return result; } function accessSetify(addr, storageKeys) { return { address: getAddress(addr), storageKeys: (storageKeys || []).map((storageKey, index) => { if (hexDataLength(storageKey) !== 32) { logger.throwArgumentError("invalid access list storageKey", `accessList[${addr}:${index}]`, storageKey); } return storageKey.toLowerCase(); }) }; } function accessListify(value) { if (Array.isArray(value)) { return value.map((set, index) => { if (Array.isArray(set)) { if (set.length > 2) { logger.throwArgumentError("access list expected to be [ address, storageKeys[] ]", `value[${index}]`, set); } return accessSetify(set[0], set[1]); } return accessSetify(set.address, set.storageKeys); }); } const result = Object.keys(value).map((addr) => { const storageKeys = value[addr].reduce((accum, storageKey) => { accum[storageKey] = true; return accum; }, {}); return accessSetify(addr, Object.keys(storageKeys).sort()); }); result.sort((a, b) => (a.address.localeCompare(b.address))); return result; } function formatAccessList(value) { return accessListify(value).map((set) => [set.address, set.storageKeys]); } function _serializeEip1559(transaction, signature) { // If there is an explicit gasPrice, make sure it matches the // EIP-1559 fees; otherwise they may not understand what they // think they are setting in terms of fee. if (transaction.gasPrice != null) { const gasPrice = BigNumber.from(transaction.gasPrice); const maxFeePerGas = BigNumber.from(transaction.maxFeePerGas || 0); if (!gasPrice.eq(maxFeePerGas)) { logger.throwArgumentError("mismatch EIP-1559 gasPrice != maxFeePerGas", "tx", { gasPrice, maxFeePerGas }); } } const fields = [ formatNumber(transaction.chainId || 0, "chainId"), formatNumber(transaction.nonce || 0, "nonce"), formatNumber(transaction.maxPriorityFeePerGas || 0, "maxPriorityFeePerGas"), formatNumber(transaction.maxFeePerGas || 0, "maxFeePerGas"), formatNumber(transaction.gasLimit || 0, "gasLimit"), ((transaction.to != null) ? getAddress(transaction.to) : "0x"), formatNumber(transaction.value || 0, "value"), (transaction.data || "0x"), (formatAccessList(transaction.accessList || [])) ]; if (signature) { const sig = splitSignature(signature); fields.push(formatNumber(sig.recoveryParam, "recoveryParam")); fields.push(stripZeros(sig.r)); fields.push(stripZeros(sig.s)); } return hexConcat(["0x02", encode(fields)]); } function _serializeEip2930(transaction, signature) { const fields = [ formatNumber(transaction.chainId || 0, "chainId"), formatNumber(transaction.nonce || 0, "nonce"), formatNumber(transaction.gasPrice || 0, "gasPrice"), formatNumber(transaction.gasLimit || 0, "gasLimit"), ((transaction.to != null) ? getAddress(transaction.to) : "0x"), formatNumber(transaction.value || 0, "value"), (transaction.data || "0x"), (formatAccessList(transaction.accessList || [])) ]; if (signature) { const sig = splitSignature(signature); fields.push(formatNumber(sig.recoveryParam, "recoveryParam")); fields.push(stripZeros(sig.r)); fields.push(stripZeros(sig.s)); } return hexConcat(["0x01", encode(fields)]); } function _parseEipSignature(tx, fields, serialize) { try { const recid = handleNumber(fields[0]).toNumber(); if (recid !== 0 && recid !== 1) { throw new Error("bad recid"); } tx.v = recid; } catch (error) { logger.throwArgumentError("invalid v for transaction type: 1", "v", fields[0]); } tx.r = hexZeroPad(fields[1], 32); tx.s = hexZeroPad(fields[2], 32); try { const digest = keccak256(serialize(tx)); tx.from = recoverAddress(digest, { r: tx.r, s: tx.s, recoveryParam: tx.v }); } catch (error) { } } function _parseEip1559(payload) { const transaction = decode(payload.slice(1)); if (transaction.length !== 9 && transaction.length !== 12) { logger.throwArgumentError("invalid component count for transaction type: 2", "payload", hexlify(payload)); } const maxPriorityFeePerGas = handleNumber(transaction[2]); const maxFeePerGas = handleNumber(transaction[3]); const tx = { type: 2, chainId: handleNumber(transaction[0]).toNumber(), nonce: handleNumber(transaction[1]).toNumber(), maxPriorityFeePerGas: maxPriorityFeePerGas, maxFeePerGas: maxFeePerGas, gasPrice: null, gasLimit: handleNumber(transaction[4]), to: handleAddress(transaction[5]), value: handleNumber(transaction[6]), data: transaction[7], accessList: accessListify(transaction[8]), }; // Unsigned EIP-1559 Transaction if (transaction.length === 9) { return tx; } tx.hash = keccak256(payload); _parseEipSignature(tx, transaction.slice(9), _serializeEip1559); return tx; } function _parseEip2930(payload) { const transaction = decode(payload.slice(1)); if (transaction.length !== 8 && transaction.length !== 11) { logger.throwArgumentError("invalid component count for transaction type: 1", "payload", hexlify(payload)); } const tx = { type: 1, chainId: handleNumber(transaction[0]).toNumber(), nonce: handleNumber(transaction[1]).toNumber(), gasPrice: handleNumber(transaction[2]), gasLimit: handleNumber(transaction[3]), to: handleAddress(transaction[4]), value: handleNumber(transaction[5]), data: transaction[6], accessList: accessListify(transaction[7]) }; // Unsigned EIP-2930 Transaction if (transaction.length === 8) { return tx; } tx.hash = keccak256(payload); _parseEipSignature(tx, transaction.slice(8), _serializeEip2930); return tx; } // Legacy Transactions and EIP-155 function _parse(rawTransaction) { const transaction = decode(rawTransaction); if (transaction.length !== 9 && transaction.length !== 6) { logger.throwArgumentError("invalid raw transaction", "rawTransaction", rawTransaction); } const tx = { nonce: handleNumber(transaction[0]).toNumber(), gasPrice: handleNumber(transaction[1]), gasLimit: handleNumber(transaction[2]), to: handleAddress(transaction[3]), value: handleNumber(transaction[4]), data: transaction[5], chainId: 0 }; // Legacy unsigned transaction if (transaction.length === 6) { return tx; } try { tx.v = BigNumber.from(transaction[6]).toNumber(); } catch (error) { // @TODO: What makes snese to do? The v is too big return tx; } tx.r = hexZeroPad(transaction[7], 32); tx.s = hexZeroPad(transaction[8], 32); if (BigNumber.from(tx.r).isZero() && BigNumber.from(tx.s).isZero()) { // EIP-155 unsigned transaction tx.chainId = tx.v; tx.v = 0; } else { // Signed Transaction tx.chainId = Math.floor((tx.v - 35) / 2); if (tx.chainId < 0) { tx.chainId = 0; } let recoveryParam = tx.v - 27; const raw = transaction.slice(0, 6); if (tx.chainId !== 0) { raw.push(hexlify(tx.chainId)); raw.push("0x"); raw.push("0x"); recoveryParam -= tx.chainId * 2 + 8; } const digest = keccak256(encode(raw)); try { tx.from = recoverAddress(digest, { r: hexlify(tx.r), s: hexlify(tx.s), recoveryParam: recoveryParam }); } catch (error) { } tx.hash = keccak256(rawTransaction); } tx.type = null; return tx; } function parse(rawTransaction) { const payload = arrayify(rawTransaction); // Legacy and EIP-155 Transactions if (payload[0] > 0x7f) { return _parse(payload); } // Typed Transaction (EIP-2718) switch (payload[0]) { case 1: return _parseEip2930(payload); case 2: return _parseEip1559(payload); } return logger.throwError(`unsupported transaction type: ${payload[0]}`, Logger.errors.UNSUPPORTED_OPERATION, { operation: "parseTransaction", transactionType: payload[0] }); } export { TransactionTypes, accessListify, computeAddress, parse, recoverAddress };