ecdsa-coder.ts 5.88 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11
/* 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,
Mark Tyneway's avatar
Mark Tyneway committed
12
  EthSign2 = 2,
13 14 15 16 17
}

export const txTypePlainText = {
  0: TxType.EIP155,
  1: TxType.EthSign,
Mark Tyneway's avatar
Mark Tyneway committed
18
  2: TxType.EthSign2,
19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173
  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)
  }
}

Mark Tyneway's avatar
Mark Tyneway committed
174 175 176 177 178 179 180 181 182 183 184 185 186 187
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)
  }
}

188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227
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)
  }
Mark Tyneway's avatar
Mark Tyneway committed
228 229 230
  if (type === TxType.EthSign2) {
    return new EthSign2TxCoder().decode(data)
  }
231 232 233 234 235 236 237 238 239
  return null
}

/*
 * Encoding and decoding functions for all txData types.
 */
export const ctcCoder = {
  eip155TxData: new Eip155TxCoder(),
  ethSignTxData: new EthSignTxCoder(),
Mark Tyneway's avatar
Mark Tyneway committed
240
  ethSign2TxData: new EthSign2TxCoder(),
241 242 243
  encode,
  decode,
}