/* Internal Imports */ import { add0x, remove0x, toVerifiedBytes, encodeHex, getLen } from '../common' import { Coder, Signature, Uint16, Uint8, Uint24, Address } from './types' /*********************** * TxTypes and TxData * **********************/ export enum TxType { EIP155 = 0, EthSign = 1, EthSign2 = 2, } export const txTypePlainText = { 0: TxType.EIP155, 1: TxType.EthSign, 2: TxType.EthSign2, EIP155: TxType.EIP155, EthSign: TxType.EthSign, } export interface DefaultEcdsaTxData { sig: Signature gasLimit: Uint16 gasPrice: Uint8 nonce: Uint24 target: Address data: string type: TxType } export interface EIP155TxData extends DefaultEcdsaTxData {} export interface EthSignTxData extends DefaultEcdsaTxData {} /*********************** * Encoding Positions * **********************/ /* * The positions in the tx data for the different transaction types */ export const TX_TYPE_POSITION = { start: 0, end: 1 } /* * The positions in the tx data for the EIP155TxData and EthSignTxData */ export const SIGNATURE_FIELD_POSITIONS = { r: { start: 1, end: 33 }, // 32 bytes s: { start: 33, end: 65 }, // 32 bytes v: { start: 65, end: 66 }, // 1 byte } export const DEFAULT_ECDSA_TX_FIELD_POSITIONS = { txType: TX_TYPE_POSITION, // 1 byte sig: SIGNATURE_FIELD_POSITIONS, // 65 bytes gasLimit: { start: 66, end: 69 }, // 3 bytes gasPrice: { start: 69, end: 72 }, // 3 byte nonce: { start: 72, end: 75 }, // 3 bytes target: { start: 75, end: 95 }, // 20 bytes data: { start: 95 }, // byte 95 onward } export const EIP155_TX_FIELD_POSITIONS = DEFAULT_ECDSA_TX_FIELD_POSITIONS export const ETH_SIGN_TX_FIELD_POSITIONS = DEFAULT_ECDSA_TX_FIELD_POSITIONS export const CTC_TX_GAS_PRICE_MULT_FACTOR = 1_000_000 /*************** * EcdsaCoders * **************/ class DefaultEcdsaTxCoder implements Coder { constructor(readonly txType: TxType) {} public encode(txData: DefaultEcdsaTxData): string { const txType = encodeHex( this.txType, getLen(DEFAULT_ECDSA_TX_FIELD_POSITIONS.txType) ) const r = toVerifiedBytes( txData.sig.r, getLen(DEFAULT_ECDSA_TX_FIELD_POSITIONS.sig.r) ) const s = toVerifiedBytes( txData.sig.s, getLen(DEFAULT_ECDSA_TX_FIELD_POSITIONS.sig.s) ) const v = encodeHex( txData.sig.v, getLen(DEFAULT_ECDSA_TX_FIELD_POSITIONS.sig.v) ) const gasLimit = encodeHex( txData.gasLimit, getLen(DEFAULT_ECDSA_TX_FIELD_POSITIONS.gasLimit) ) if (txData.gasPrice % CTC_TX_GAS_PRICE_MULT_FACTOR !== 0) { throw new Error(`Gas Price ${txData.gasPrice} cannot be encoded`) } const gasPrice = encodeHex( txData.gasPrice / CTC_TX_GAS_PRICE_MULT_FACTOR, getLen(DEFAULT_ECDSA_TX_FIELD_POSITIONS.gasPrice) ) const nonce = encodeHex( txData.nonce, getLen(DEFAULT_ECDSA_TX_FIELD_POSITIONS.nonce) ) const target = toVerifiedBytes( txData.target, getLen(DEFAULT_ECDSA_TX_FIELD_POSITIONS.target) ) // Make sure that the data is even if (txData.data.length % 2 !== 0) { throw new Error('Non-even hex string for tx data!') } const encoding = '0x' + txType + r + s + v + gasLimit + gasPrice + nonce + target + remove0x(txData.data) return encoding } public decode(txData: string): DefaultEcdsaTxData { txData = remove0x(txData) const sliceBytes = (position: { start; end? }): string => txData.slice(position.start * 2, position.end * 2) const pos = DEFAULT_ECDSA_TX_FIELD_POSITIONS if (parseInt(sliceBytes(pos.txType), 16) !== this.txType) { throw new Error('Invalid tx type') } return { sig: { r: add0x(sliceBytes(pos.sig.r)), s: add0x(sliceBytes(pos.sig.s)), v: parseInt(sliceBytes(pos.sig.v), 16), }, gasLimit: parseInt(sliceBytes(pos.gasLimit), 16), gasPrice: parseInt(sliceBytes(pos.gasPrice), 16) * CTC_TX_GAS_PRICE_MULT_FACTOR, nonce: parseInt(sliceBytes(pos.nonce), 16), target: add0x(sliceBytes(pos.target)), data: add0x(txData.slice(pos.data.start * 2)), type: this.txType, } } } class EthSignTxCoder extends DefaultEcdsaTxCoder { constructor() { super(TxType.EthSign) } public encode(txData: EthSignTxData): string { return super.encode(txData) } public decode(txData: string): EthSignTxData { return super.decode(txData) } } class EthSign2TxCoder extends DefaultEcdsaTxCoder { constructor() { super(TxType.EthSign2) } public encode(txData: EthSignTxData): string { return super.encode(txData) } public decode(txData: string): EthSignTxData { return super.decode(txData) } } class Eip155TxCoder extends DefaultEcdsaTxCoder { constructor() { super(TxType.EIP155) } public encode(txData: EIP155TxData): string { return super.encode(txData) } public decode(txData: string): EIP155TxData { return super.decode(txData) } } /************* * ctcCoder * ************/ function encode(data: EIP155TxData): string { if (data.type === TxType.EIP155) { return new Eip155TxCoder().encode(data) } if (data.type === TxType.EthSign) { return new EthSignTxCoder().encode(data) } return null } function decode(data: string | Buffer): EIP155TxData { if (Buffer.isBuffer(data)) { data = data.toString() } data = remove0x(data) const type = parseInt(data.slice(0, 2), 16) if (type === TxType.EIP155) { return new Eip155TxCoder().decode(data) } if (type === TxType.EthSign) { return new EthSignTxCoder().decode(data) } if (type === TxType.EthSign2) { return new EthSign2TxCoder().decode(data) } return null } /* * Encoding and decoding functions for all txData types. */ export const ctcCoder = { eip155TxData: new Eip155TxCoder(), ethSignTxData: new EthSignTxCoder(), ethSign2TxData: new EthSign2TxCoder(), encode, decode, }