1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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
174
175
176
177
178
179
180
181
182
183
184
185
186
187
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
/* 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,
}
export const txTypePlainText = {
0: TxType.EIP155,
1: TxType.EthSign,
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 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)
}
return null
}
/*
* Encoding and decoding functions for all txData types.
*/
export const ctcCoder = {
eip155TxData: new Eip155TxCoder(),
ethSignTxData: new EthSignTxCoder(),
encode,
decode,
}