@ledgerhq/hw-app-eth 6.28.2 → 6.29.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +5 -2
- package/CHANGELOG.md +13 -0
- package/README.md +58 -16
- package/jest.config.ts +6 -0
- package/lib/Eth.d.ts +37 -0
- package/lib/Eth.d.ts.map +1 -1
- package/lib/Eth.js +66 -72
- package/lib/Eth.js.map +1 -1
- package/lib/modules/EIP712/EIP712.types.d.ts +44 -0
- package/lib/modules/EIP712/EIP712.types.d.ts.map +1 -0
- package/lib/modules/EIP712/EIP712.types.js +3 -0
- package/lib/modules/EIP712/EIP712.types.js.map +1 -0
- package/lib/modules/EIP712/EIP712.utils.d.ts +65 -0
- package/lib/modules/EIP712/EIP712.utils.d.ts.map +1 -0
- package/lib/modules/EIP712/EIP712.utils.js +217 -0
- package/lib/modules/EIP712/EIP712.utils.js.map +1 -0
- package/lib/modules/EIP712/index.d.ts +60 -0
- package/lib/modules/EIP712/index.d.ts.map +1 -0
- package/lib/modules/EIP712/index.js +554 -0
- package/lib/modules/EIP712/index.js.map +1 -0
- package/lib/utils.d.ts +15 -1
- package/lib/utils.d.ts.map +1 -1
- package/lib/utils.js +43 -3
- package/lib/utils.js.map +1 -1
- package/lib-es/Eth.d.ts +37 -0
- package/lib-es/Eth.d.ts.map +1 -1
- package/lib-es/Eth.js +42 -48
- package/lib-es/Eth.js.map +1 -1
- package/lib-es/modules/EIP712/EIP712.types.d.ts +44 -0
- package/lib-es/modules/EIP712/EIP712.types.d.ts.map +1 -0
- package/lib-es/modules/EIP712/EIP712.types.js +2 -0
- package/lib-es/modules/EIP712/EIP712.types.js.map +1 -0
- package/lib-es/modules/EIP712/EIP712.utils.d.ts +65 -0
- package/lib-es/modules/EIP712/EIP712.utils.d.ts.map +1 -0
- package/lib-es/modules/EIP712/EIP712.utils.js +211 -0
- package/lib-es/modules/EIP712/EIP712.utils.js.map +1 -0
- package/lib-es/modules/EIP712/index.d.ts +60 -0
- package/lib-es/modules/EIP712/index.d.ts.map +1 -0
- package/lib-es/modules/EIP712/index.js +549 -0
- package/lib-es/modules/EIP712/index.js.map +1 -0
- package/lib-es/services/ledger/contracts.d.ts +0 -0
- package/lib-es/services/ledger/contracts.d.ts.map +0 -0
- package/lib-es/services/ledger/contracts.js +0 -0
- package/lib-es/services/ledger/contracts.js.map +0 -0
- package/lib-es/services/ledger/erc20.d.ts +0 -0
- package/lib-es/services/ledger/erc20.d.ts.map +0 -0
- package/lib-es/services/ledger/erc20.js +0 -0
- package/lib-es/services/ledger/erc20.js.map +0 -0
- package/lib-es/services/ledger/index.d.ts +0 -0
- package/lib-es/services/ledger/index.d.ts.map +0 -0
- package/lib-es/services/ledger/index.js +0 -0
- package/lib-es/services/ledger/index.js.map +0 -0
- package/lib-es/services/ledger/loadConfig.d.ts +0 -0
- package/lib-es/services/ledger/loadConfig.d.ts.map +0 -0
- package/lib-es/services/ledger/loadConfig.js +0 -0
- package/lib-es/services/ledger/loadConfig.js.map +0 -0
- package/lib-es/services/ledger/nfts.d.ts +0 -0
- package/lib-es/services/ledger/nfts.d.ts.map +0 -0
- package/lib-es/services/ledger/nfts.js +0 -0
- package/lib-es/services/ledger/nfts.js.map +0 -0
- package/lib-es/services/types.d.ts +0 -0
- package/lib-es/services/types.d.ts.map +0 -0
- package/lib-es/services/types.js +0 -0
- package/lib-es/services/types.js.map +0 -0
- package/lib-es/utils.d.ts +15 -1
- package/lib-es/utils.d.ts.map +1 -1
- package/lib-es/utils.js +38 -2
- package/lib-es/utils.js.map +1 -1
- package/package.json +13 -8
- package/src/Eth.ts +59 -56
- package/src/modules/EIP712/EIP712.types.ts +54 -0
- package/src/modules/EIP712/EIP712.utils.ts +251 -0
- package/src/modules/EIP712/index.ts +409 -0
- package/src/utils.ts +42 -2
- package/tests/EIP712.unit.test.ts +760 -0
- package/tests/sample-messages/0.apdus +58 -0
- package/tests/sample-messages/0.json +44 -0
- package/tests/sample-messages/1.apdus +66 -0
- package/tests/sample-messages/1.json +50 -0
- package/tests/sample-messages/10.apdus +30 -0
- package/tests/sample-messages/10.json +23 -0
- package/tests/sample-messages/2.apdus +126 -0
- package/tests/sample-messages/2.json +153 -0
- package/tests/sample-messages/3.apdus +42 -0
- package/tests/sample-messages/3.json +31 -0
- package/tests/sample-messages/4.apdus +84 -0
- package/tests/sample-messages/4.json +110 -0
- package/tests/sample-messages/5.apdus +112 -0
- package/tests/sample-messages/5.json +92 -0
- package/tests/sample-messages/6.apdus +94 -0
- package/tests/sample-messages/6.json +78 -0
- package/tests/sample-messages/7.apdus +70 -0
- package/tests/sample-messages/7.json +55 -0
- package/tests/sample-messages/8.apdus +68 -0
- package/tests/sample-messages/8.json +50 -0
- package/tests/sample-messages/9.apdus +68 -0
- package/tests/sample-messages/9.json +50 -0
- package/LICENSE +0 -202
- package/lib-es/contracts.d.ts +0 -17
- package/lib-es/contracts.d.ts.map +0 -1
- package/lib-es/contracts.js +0 -103
- package/lib-es/contracts.js.map +0 -1
- package/lib-es/erc20.d.ts +0 -22
- package/lib-es/erc20.d.ts.map +0 -1
- package/lib-es/erc20.js +0 -64
- package/lib-es/erc20.js.map +0 -1
|
@@ -0,0 +1,409 @@
|
|
|
1
|
+
import Transport from "@ledgerhq/hw-transport";
|
|
2
|
+
import {
|
|
3
|
+
EIP712Message,
|
|
4
|
+
EIP712MessageTypes,
|
|
5
|
+
EIP712MessageTypesEntry,
|
|
6
|
+
StructDefData,
|
|
7
|
+
StructImplemData,
|
|
8
|
+
} from "./EIP712.types";
|
|
9
|
+
import { hexBuffer, intAsHexBytes, splitPath } from "../../utils";
|
|
10
|
+
import {
|
|
11
|
+
destructTypeFromString,
|
|
12
|
+
EIP712_TYPE_ENCODERS,
|
|
13
|
+
EIP712_TYPE_PROPERTIES,
|
|
14
|
+
makeTypeEntryStructBuffer,
|
|
15
|
+
} from "./EIP712.utils";
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* @ignore for the README
|
|
19
|
+
*
|
|
20
|
+
* Factory to create the recursive function that will pass on each
|
|
21
|
+
* field level and APDUs to describe its structure implementation
|
|
22
|
+
*
|
|
23
|
+
* @param {Eth["EIP712SendStructImplem"]} EIP712SendStructImplem
|
|
24
|
+
* @param {EIP712MessageTypes} types
|
|
25
|
+
* @returns {void}
|
|
26
|
+
*/
|
|
27
|
+
const makeRecursiveFieldStructImplem = (
|
|
28
|
+
transport: Transport,
|
|
29
|
+
types: EIP712MessageTypes
|
|
30
|
+
): ((
|
|
31
|
+
destructedType: ReturnType<typeof destructTypeFromString>,
|
|
32
|
+
data: unknown
|
|
33
|
+
) => void) => {
|
|
34
|
+
const typesMap = {} as Record<string, Record<string, string>>;
|
|
35
|
+
for (const type in types) {
|
|
36
|
+
typesMap[type] = types[type]?.reduce(
|
|
37
|
+
(acc, curr) => ({ ...acc, [curr.name]: curr.type }),
|
|
38
|
+
{}
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// This recursion will call itself to handle each level of each field
|
|
43
|
+
// in order to send APDUs for each of them
|
|
44
|
+
const recursiveFieldStructImplem = async (
|
|
45
|
+
destructedType: ReturnType<typeof destructTypeFromString>,
|
|
46
|
+
data
|
|
47
|
+
) => {
|
|
48
|
+
const [typeDescription, arrSizes] = destructedType;
|
|
49
|
+
const [currSize, ...restSizes] = arrSizes;
|
|
50
|
+
const isCustomType =
|
|
51
|
+
!EIP712_TYPE_PROPERTIES[typeDescription?.name?.toUpperCase() || ""];
|
|
52
|
+
|
|
53
|
+
if (Array.isArray(data) && typeof currSize !== "undefined") {
|
|
54
|
+
await EIP712SendStructImplem(transport, {
|
|
55
|
+
structType: "array",
|
|
56
|
+
value: data.length,
|
|
57
|
+
});
|
|
58
|
+
for (const entry of data) {
|
|
59
|
+
await recursiveFieldStructImplem([typeDescription, restSizes], entry);
|
|
60
|
+
}
|
|
61
|
+
} else if (isCustomType) {
|
|
62
|
+
for (const [fieldName, fieldValue] of Object.entries(
|
|
63
|
+
data as EIP712Message["message"]
|
|
64
|
+
)) {
|
|
65
|
+
const fieldType = typesMap?.[typeDescription?.name || ""][fieldName];
|
|
66
|
+
|
|
67
|
+
if (fieldType) {
|
|
68
|
+
await recursiveFieldStructImplem(
|
|
69
|
+
destructTypeFromString(fieldType),
|
|
70
|
+
fieldValue
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
} else {
|
|
75
|
+
await EIP712SendStructImplem(transport, {
|
|
76
|
+
structType: "field",
|
|
77
|
+
value: {
|
|
78
|
+
data,
|
|
79
|
+
type: typeDescription?.name || "",
|
|
80
|
+
sizeInBits: typeDescription?.bits,
|
|
81
|
+
},
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
return recursiveFieldStructImplem;
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* @ignore for the README
|
|
91
|
+
*
|
|
92
|
+
* This method is used to send the message definition with all its types.
|
|
93
|
+
* This method should be used before the EIP712SendStructImplem one
|
|
94
|
+
*
|
|
95
|
+
* @param {String} structType
|
|
96
|
+
* @param {String|Buffer} value
|
|
97
|
+
* @returns {Promise<void>}
|
|
98
|
+
*/
|
|
99
|
+
const EIP712SendStructDef = (
|
|
100
|
+
transport: Transport,
|
|
101
|
+
structDef: StructDefData
|
|
102
|
+
): Promise<Buffer> => {
|
|
103
|
+
enum APDU_FIELDS {
|
|
104
|
+
CLA = 0xe0,
|
|
105
|
+
INS = 0x1a,
|
|
106
|
+
P1_complete = 0x00,
|
|
107
|
+
P1_partial = 0x01,
|
|
108
|
+
P2_name = 0x00,
|
|
109
|
+
P2_field = 0xff,
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const { structType, value } = structDef;
|
|
113
|
+
const data =
|
|
114
|
+
structType === "name" && typeof value === "string"
|
|
115
|
+
? Buffer.from(value, "utf-8")
|
|
116
|
+
: (value as Buffer);
|
|
117
|
+
|
|
118
|
+
return transport.send(
|
|
119
|
+
APDU_FIELDS.CLA,
|
|
120
|
+
APDU_FIELDS.INS,
|
|
121
|
+
APDU_FIELDS.P1_complete,
|
|
122
|
+
structType === "name" ? APDU_FIELDS.P2_name : APDU_FIELDS.P2_field,
|
|
123
|
+
data
|
|
124
|
+
);
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* @ignore for the README
|
|
129
|
+
*
|
|
130
|
+
* This method provides a trusted new display name to use for the upcoming field.
|
|
131
|
+
* This method should be used after the EIP712SendStructDef one.
|
|
132
|
+
*
|
|
133
|
+
* If the method describes an empty name (length of 0), the upcoming field will be taken
|
|
134
|
+
* into account but won’t be shown on the device.
|
|
135
|
+
*
|
|
136
|
+
* The signature is computed on :
|
|
137
|
+
* json key length || json key || display name length || display name
|
|
138
|
+
*
|
|
139
|
+
* signed by the following secp256k1 public key:
|
|
140
|
+
* 0482bbf2f34f367b2e5bc21847b6566f21f0976b22d3388a9a5e446ac62d25cf725b62a2555b2dd464a4da0ab2f4d506820543af1d242470b1b1a969a27578f353
|
|
141
|
+
*
|
|
142
|
+
* @param {String} structType "root" | "array" | "field"
|
|
143
|
+
* @param {string | number | StructFieldData} value
|
|
144
|
+
* @returns {Promise<Buffer | void>}
|
|
145
|
+
*/
|
|
146
|
+
const EIP712SendStructImplem = async (
|
|
147
|
+
transport: Transport,
|
|
148
|
+
structImplem: StructImplemData
|
|
149
|
+
): Promise<Buffer | void> => {
|
|
150
|
+
enum APDU_FIELDS {
|
|
151
|
+
CLA = 0xe0,
|
|
152
|
+
INS = 0x1c,
|
|
153
|
+
P1_complete = 0x00,
|
|
154
|
+
P1_partial = 0x01,
|
|
155
|
+
P2_root = 0x00,
|
|
156
|
+
P2_array = 0x0f,
|
|
157
|
+
P2_field = 0xff,
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const { structType, value } = structImplem;
|
|
161
|
+
|
|
162
|
+
if (structType === "root") {
|
|
163
|
+
return transport.send(
|
|
164
|
+
APDU_FIELDS.CLA,
|
|
165
|
+
APDU_FIELDS.INS,
|
|
166
|
+
APDU_FIELDS.P1_complete,
|
|
167
|
+
APDU_FIELDS.P2_root,
|
|
168
|
+
Buffer.from(value, "utf-8")
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (structType === "array") {
|
|
173
|
+
return transport.send(
|
|
174
|
+
APDU_FIELDS.CLA,
|
|
175
|
+
APDU_FIELDS.INS,
|
|
176
|
+
APDU_FIELDS.P1_complete,
|
|
177
|
+
APDU_FIELDS.P2_array,
|
|
178
|
+
Buffer.from(intAsHexBytes(value, 1), "hex")
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (structType === "field") {
|
|
183
|
+
const { data: rawData, type, sizeInBits } = value;
|
|
184
|
+
const encodedData: Buffer | null = EIP712_TYPE_ENCODERS[
|
|
185
|
+
type.toUpperCase()
|
|
186
|
+
]?.(rawData, sizeInBits);
|
|
187
|
+
|
|
188
|
+
if (encodedData) {
|
|
189
|
+
// const dataLengthPer16Bits = (encodedData.length & 0xff00) >> 8;
|
|
190
|
+
const dataLengthPer16Bits = Math.floor(encodedData.length / 256);
|
|
191
|
+
// const dataLengthModulo16Bits = encodedData.length & 0xff;
|
|
192
|
+
const dataLengthModulo16Bits = encodedData.length % 256;
|
|
193
|
+
|
|
194
|
+
const data = Buffer.concat([
|
|
195
|
+
Buffer.from(intAsHexBytes(dataLengthPer16Bits, 1), "hex"),
|
|
196
|
+
Buffer.from(intAsHexBytes(dataLengthModulo16Bits, 1), "hex"),
|
|
197
|
+
encodedData,
|
|
198
|
+
]);
|
|
199
|
+
|
|
200
|
+
const bufferSlices = new Array(Math.ceil(data.length / 256))
|
|
201
|
+
.fill(null)
|
|
202
|
+
.map((_, i) => data.slice(i * 255, (i + 1) * 255));
|
|
203
|
+
|
|
204
|
+
for (const bufferSlice of bufferSlices) {
|
|
205
|
+
await transport.send(
|
|
206
|
+
APDU_FIELDS.CLA,
|
|
207
|
+
APDU_FIELDS.INS,
|
|
208
|
+
bufferSlice !== bufferSlices[bufferSlices.length - 1]
|
|
209
|
+
? APDU_FIELDS.P1_partial
|
|
210
|
+
: APDU_FIELDS.P1_complete,
|
|
211
|
+
APDU_FIELDS.P2_field,
|
|
212
|
+
bufferSlice
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
return Promise.resolve();
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* @ignore for the README
|
|
223
|
+
*
|
|
224
|
+
* Sign an EIP-721 formatted message following the specification here:
|
|
225
|
+
* https://github.com/LedgerHQ/app-ethereum/blob/develop/doc/ethapp.asc#sign-eth-eip-712
|
|
226
|
+
* @example
|
|
227
|
+
eth.signEIP721Message("44'/60'/0'/0/0", {
|
|
228
|
+
domain: {
|
|
229
|
+
chainId: 69,
|
|
230
|
+
name: "Da Domain",
|
|
231
|
+
verifyingContract: "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC",
|
|
232
|
+
version: "1"
|
|
233
|
+
},
|
|
234
|
+
types: {
|
|
235
|
+
"EIP712Domain": [
|
|
236
|
+
{ name: "name", type: "string" },
|
|
237
|
+
{ name: "version", type: "string" },
|
|
238
|
+
{ name: "chainId", type: "uint256" },
|
|
239
|
+
{ name: "verifyingContract", type: "address" }
|
|
240
|
+
],
|
|
241
|
+
"Test": [
|
|
242
|
+
{ name: "contents", type: "string" }
|
|
243
|
+
]
|
|
244
|
+
},
|
|
245
|
+
primaryType: "Test",
|
|
246
|
+
message: {contents: "Hello, Bob!"},
|
|
247
|
+
})
|
|
248
|
+
*
|
|
249
|
+
* @param {String} path derivationPath
|
|
250
|
+
* @param {Object} jsonMessage message to sign
|
|
251
|
+
* @param {Boolean} fullImplem use the legacy implementation
|
|
252
|
+
* @returns {Promise}
|
|
253
|
+
*/
|
|
254
|
+
export const signEIP712Message = async (
|
|
255
|
+
transport: Transport,
|
|
256
|
+
path: string,
|
|
257
|
+
jsonMessage: EIP712Message,
|
|
258
|
+
fullImplem = false
|
|
259
|
+
): Promise<{
|
|
260
|
+
v: number;
|
|
261
|
+
s: string;
|
|
262
|
+
r: string;
|
|
263
|
+
}> => {
|
|
264
|
+
enum APDU_FIELDS {
|
|
265
|
+
CLA = 0xe0,
|
|
266
|
+
INS = 0x0c,
|
|
267
|
+
P1 = 0x00,
|
|
268
|
+
P2_v0 = 0x00,
|
|
269
|
+
P2_full = 0x01,
|
|
270
|
+
}
|
|
271
|
+
const { primaryType, types, domain, message } = jsonMessage;
|
|
272
|
+
|
|
273
|
+
const typeEntries = Object.entries(types) as [
|
|
274
|
+
keyof EIP712MessageTypes,
|
|
275
|
+
EIP712MessageTypesEntry[]
|
|
276
|
+
][];
|
|
277
|
+
// Looping on all types entries and fields to send structures' definitions
|
|
278
|
+
for (const [typeName, entries] of typeEntries) {
|
|
279
|
+
await EIP712SendStructDef(transport, {
|
|
280
|
+
structType: "name",
|
|
281
|
+
value: typeName as string,
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
for (const { name, type } of entries) {
|
|
285
|
+
const typeEntryBuffer = makeTypeEntryStructBuffer({ name, type });
|
|
286
|
+
await EIP712SendStructDef(transport, {
|
|
287
|
+
structType: "field",
|
|
288
|
+
value: typeEntryBuffer,
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// Create the recursion that should pass on each entry
|
|
294
|
+
// of the domain fields and primaryType fields
|
|
295
|
+
const recursiveFieldStructImplem = makeRecursiveFieldStructImplem(
|
|
296
|
+
transport,
|
|
297
|
+
types
|
|
298
|
+
);
|
|
299
|
+
|
|
300
|
+
// Looping on all domain type entries and fields to send
|
|
301
|
+
// structures' implementations
|
|
302
|
+
const domainName = "EIP712Domain";
|
|
303
|
+
await EIP712SendStructImplem(transport, {
|
|
304
|
+
structType: "root",
|
|
305
|
+
value: domainName,
|
|
306
|
+
});
|
|
307
|
+
const domainTypeFields = types[domainName];
|
|
308
|
+
for (const { name, type } of domainTypeFields) {
|
|
309
|
+
const domainFieldValue = domain[name];
|
|
310
|
+
await recursiveFieldStructImplem(
|
|
311
|
+
destructTypeFromString(type as string),
|
|
312
|
+
domainFieldValue
|
|
313
|
+
);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// Looping on all primaryType type entries and fields to send
|
|
317
|
+
// structures' implementations
|
|
318
|
+
await EIP712SendStructImplem(transport, {
|
|
319
|
+
structType: "root",
|
|
320
|
+
value: primaryType,
|
|
321
|
+
});
|
|
322
|
+
const primaryTypeFields = types[primaryType];
|
|
323
|
+
for (const { name, type } of primaryTypeFields) {
|
|
324
|
+
const primaryTypeValue = message[name];
|
|
325
|
+
await recursiveFieldStructImplem(
|
|
326
|
+
destructTypeFromString(type as string),
|
|
327
|
+
primaryTypeValue
|
|
328
|
+
);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// Sending the final signature.
|
|
332
|
+
const paths = splitPath(path);
|
|
333
|
+
const signatureBuffer = Buffer.alloc(1 + paths.length * 4);
|
|
334
|
+
signatureBuffer[0] = paths.length;
|
|
335
|
+
paths.forEach((element, index) => {
|
|
336
|
+
signatureBuffer.writeUInt32BE(element, 1 + 4 * index);
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
return transport
|
|
340
|
+
.send(
|
|
341
|
+
APDU_FIELDS.CLA,
|
|
342
|
+
APDU_FIELDS.INS,
|
|
343
|
+
APDU_FIELDS.P1,
|
|
344
|
+
fullImplem ? APDU_FIELDS.P2_v0 : APDU_FIELDS.P2_full,
|
|
345
|
+
signatureBuffer
|
|
346
|
+
)
|
|
347
|
+
.then((response) => {
|
|
348
|
+
const v = response[0];
|
|
349
|
+
const r = response.slice(1, 1 + 32).toString("hex");
|
|
350
|
+
const s = response.slice(1 + 32, 1 + 32 + 32).toString("hex");
|
|
351
|
+
|
|
352
|
+
return {
|
|
353
|
+
v,
|
|
354
|
+
r,
|
|
355
|
+
s,
|
|
356
|
+
};
|
|
357
|
+
});
|
|
358
|
+
};
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* @ignore for the README
|
|
362
|
+
* Sign a prepared message following web3.eth.signTypedData specification. The host computes the domain separator and hashStruct(message)
|
|
363
|
+
* @example
|
|
364
|
+
eth.signEIP712HashedMessage("44'/60'/0'/0/0", Buffer.from("0101010101010101010101010101010101010101010101010101010101010101").toString("hex"), Buffer.from("0202020202020202020202020202020202020202020202020202020202020202").toString("hex")).then(result => {
|
|
365
|
+
var v = result['v'] - 27;
|
|
366
|
+
v = v.toString(16);
|
|
367
|
+
if (v.length < 2) {
|
|
368
|
+
v = "0" + v;
|
|
369
|
+
}
|
|
370
|
+
console.log("Signature 0x" + result['r'] + result['s'] + v);
|
|
371
|
+
})
|
|
372
|
+
*/
|
|
373
|
+
export const signEIP712HashedMessage = (
|
|
374
|
+
transport: Transport,
|
|
375
|
+
path: string,
|
|
376
|
+
domainSeparatorHex: string,
|
|
377
|
+
hashStructMessageHex: string
|
|
378
|
+
): Promise<{
|
|
379
|
+
v: number;
|
|
380
|
+
s: string;
|
|
381
|
+
r: string;
|
|
382
|
+
}> => {
|
|
383
|
+
const domainSeparator = hexBuffer(domainSeparatorHex);
|
|
384
|
+
const hashStruct = hexBuffer(hashStructMessageHex);
|
|
385
|
+
const paths = splitPath(path);
|
|
386
|
+
const buffer = Buffer.alloc(1 + paths.length * 4 + 32 + 32, 0);
|
|
387
|
+
let offset = 0;
|
|
388
|
+
buffer[0] = paths.length;
|
|
389
|
+
paths.forEach((element, index) => {
|
|
390
|
+
buffer.writeUInt32BE(element, 1 + 4 * index);
|
|
391
|
+
});
|
|
392
|
+
offset = 1 + 4 * paths.length;
|
|
393
|
+
domainSeparator.copy(buffer, offset);
|
|
394
|
+
offset += 32;
|
|
395
|
+
hashStruct.copy(buffer, offset);
|
|
396
|
+
|
|
397
|
+
return transport.send(0xe0, 0x0c, 0x00, 0x00, buffer).then((response) => {
|
|
398
|
+
const v = response[0];
|
|
399
|
+
const r = response.slice(1, 1 + 32).toString("hex");
|
|
400
|
+
const s = response.slice(1 + 32, 1 + 32 + 32).toString("hex");
|
|
401
|
+
return {
|
|
402
|
+
v,
|
|
403
|
+
r,
|
|
404
|
+
s,
|
|
405
|
+
};
|
|
406
|
+
});
|
|
407
|
+
};
|
|
408
|
+
|
|
409
|
+
export { EIP712Message } from "./EIP712.types";
|
package/src/utils.ts
CHANGED
|
@@ -1,7 +1,34 @@
|
|
|
1
1
|
import { encode, decode } from "@ethersproject/rlp";
|
|
2
2
|
import { BigNumber } from "bignumber.js";
|
|
3
3
|
|
|
4
|
-
export function
|
|
4
|
+
export function splitPath(path: string): number[] {
|
|
5
|
+
const result: number[] = [];
|
|
6
|
+
const components = path.split("/");
|
|
7
|
+
components.forEach((element) => {
|
|
8
|
+
let number = parseInt(element, 10);
|
|
9
|
+
if (isNaN(number)) {
|
|
10
|
+
return; // FIXME shouldn't it throws instead?
|
|
11
|
+
}
|
|
12
|
+
if (element.length > 1 && element[element.length - 1] === "'") {
|
|
13
|
+
number += 0x80000000;
|
|
14
|
+
}
|
|
15
|
+
result.push(number);
|
|
16
|
+
});
|
|
17
|
+
return result;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function hexBuffer(str: string): Buffer {
|
|
21
|
+
return Buffer.from(str.startsWith("0x") ? str.slice(2) : str, "hex");
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function maybeHexBuffer(
|
|
25
|
+
str: string | null | undefined
|
|
26
|
+
): Buffer | null | undefined {
|
|
27
|
+
if (!str) return null;
|
|
28
|
+
return hexBuffer(str);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export const decodeTxInfo = (rawTx: Buffer) => {
|
|
5
32
|
const VALID_TYPES = [1, 2];
|
|
6
33
|
const txType = VALID_TYPES.includes(rawTx[0]) ? rawTx[0] : null;
|
|
7
34
|
const rlpData = txType === null ? rawTx : rawTx.slice(1);
|
|
@@ -74,4 +101,17 @@ export function decodeTxInfo(rawTx: Buffer) {
|
|
|
74
101
|
chainIdTruncated,
|
|
75
102
|
vrsOffset,
|
|
76
103
|
};
|
|
77
|
-
}
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* @ignore for the README
|
|
108
|
+
*
|
|
109
|
+
* Helper to convert an integer as a hexadecimal string with the right amount of digits
|
|
110
|
+
* to respect the number of bytes given as parameter
|
|
111
|
+
*
|
|
112
|
+
* @param int Integer
|
|
113
|
+
* @param bytes Number of bytes it should be represented as (1 byte = 2 caraters)
|
|
114
|
+
* @returns The given integer as an hexa string padded with the right number of 0
|
|
115
|
+
*/
|
|
116
|
+
export const intAsHexBytes = (int: number, bytes: number): string =>
|
|
117
|
+
int.toString(16).padStart(2 * bytes, "0");
|