UNPKG

48.2 kBJavaScriptView Raw
1"use strict";
2var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3 if (k2 === undefined) k2 = k;
4 var desc = Object.getOwnPropertyDescriptor(m, k);
5 if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6 desc = { enumerable: true, get: function() { return m[k]; } };
7 }
8 Object.defineProperty(o, k2, desc);
9}) : (function(o, m, k, k2) {
10 if (k2 === undefined) k2 = k;
11 o[k2] = m[k];
12}));
13var __exportStar = (this && this.__exportStar) || function(m, exports) {
14 for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15};
16var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
17 function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
18 return new (P || (P = Promise))(function (resolve, reject) {
19 function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
20 function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
21 function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
22 step((generator = generator.apply(thisArg, _arguments || [])).next());
23 });
24};
25var __importDefault = (this && this.__importDefault) || function (mod) {
26 return (mod && mod.__esModule) ? mod : { "default": mod };
27};
28Object.defineProperty(exports, "__esModule", { value: true });
29exports.ledgerService = void 0;
30const bignumber_js_1 = require("bignumber.js");
31const logs_1 = require("@ledgerhq/logs");
32const utils_1 = require("./utils");
33const Domains_1 = require("./modules/Domains");
34const ledger_1 = __importDefault(require("./services/ledger"));
35exports.ledgerService = ledger_1.default;
36const errors_1 = require("./errors");
37const EIP712_1 = require("./modules/EIP712");
38__exportStar(require("./utils"), exports);
39const starkQuantizationTypeMap = {
40 eth: 1,
41 erc20: 2,
42 erc721: 3,
43 erc20mintable: 4,
44 erc721mintable: 5,
45};
46const remapTransactionRelatedErrors = e => {
47 if (e && e.statusCode === 0x6a80) {
48 return new errors_1.EthAppPleaseEnableContractData("Please enable Blind signing or Contract data in the Ethereum app Settings");
49 }
50 return e;
51};
52/**
53 * Ethereum API
54 *
55 * @example
56 * import Eth from "@ledgerhq/hw-app-eth";
57 * const eth = new Eth(transport)
58 */
59class Eth {
60 setLoadConfig(loadConfig) {
61 this.loadConfig = loadConfig;
62 }
63 constructor(transport, scrambleKey = "w0w", loadConfig = {}) {
64 this.transport = transport;
65 this.loadConfig = loadConfig;
66 transport.decorateAppAPIMethods(this, [
67 // "getChallange", | ⚠️
68 // "provideERC20TokenInformation", | Those methods are not decorated as they're
69 // "setExternalPlugin", | being used inside of the `signTransaction` flow
70 // "setPlugin", | and shouldn't be locking the transport
71 // "provideDomainName", | ⚠️
72 // "provideNFTInformation", |
73 "getAddress",
74 "signTransaction",
75 "signPersonalMessage",
76 "getAppConfiguration",
77 "signEIP712Message",
78 "signEIP712HashedMessage",
79 "starkGetPublicKey",
80 "starkSignOrder",
81 "starkSignOrder_v2",
82 "starkSignTransfer",
83 "starkSignTransfer_v2",
84 "starkProvideQuantum",
85 "starkProvideQuantum_v2",
86 "starkUnsafeSign",
87 "eth2GetPublicKey",
88 "eth2SetWithdrawalIndex",
89 "getEIP1024PublicEncryptionKey",
90 "getEIP1024SharedSecret",
91 ], scrambleKey);
92 }
93 /**
94 * get Ethereum address for a given BIP 32 path.
95 * @param path a path in BIP 32 format
96 * @option boolDisplay optionally enable or not the display
97 * @option boolChaincode optionally enable or not the chaincode request
98 * @option chainId optionally display the network clearly on a Stax device
99 * @return an object with a publicKey, address and (optionally) chainCode
100 * @example
101 * eth.getAddress("44'/60'/0'/0/0").then(o => o.address)
102 */
103 getAddress(path, boolDisplay, boolChaincode, chainId) {
104 const paths = (0, utils_1.splitPath)(path);
105 let buffer = Buffer.alloc(1 + paths.length * 4);
106 buffer[0] = paths.length;
107 paths.forEach((element, index) => {
108 buffer.writeUInt32BE(element, 1 + 4 * index);
109 });
110 if (chainId) {
111 const chainIdBufferMask = Buffer.alloc(8, 0);
112 const chainIdBuffer = Buffer.from((0, utils_1.padHexString)(new bignumber_js_1.BigNumber(chainId).toString(16)), "hex");
113 chainIdBufferMask.write(chainIdBuffer.toString("hex"), chainIdBufferMask.length - chainIdBuffer.length, "hex");
114 buffer = Buffer.concat([buffer, chainIdBufferMask]);
115 }
116 return this.transport
117 .send(0xe0, 0x02, boolDisplay ? 0x01 : 0x00, boolChaincode ? 0x01 : 0x00, buffer)
118 .then(response => {
119 const publicKeyLength = response[0];
120 const addressLength = response[1 + publicKeyLength];
121 return {
122 publicKey: response.slice(1, 1 + publicKeyLength).toString("hex"),
123 address: "0x" +
124 response
125 .slice(1 + publicKeyLength + 1, 1 + publicKeyLength + 1 + addressLength)
126 .toString("ascii"),
127 chainCode: boolChaincode
128 ? response
129 .slice(1 + publicKeyLength + 1 + addressLength, 1 + publicKeyLength + 1 + addressLength + 32)
130 .toString("hex")
131 : undefined,
132 };
133 });
134 }
135 /**
136 * You can sign a transaction and retrieve v, r, s given the raw transaction and the BIP 32 path of the account to sign.
137 *
138 * @param path: the BIP32 path to sign the transaction on
139 * @param rawTxHex: the raw ethereum transaction in hexadecimal to sign
140 * @param resolution: resolution is an object with all "resolved" metadata necessary to allow the device to clear sign information. This includes: ERC20 token information, plugins, contracts, NFT signatures,... You must explicitly provide something to avoid having a warning. By default, you can use Ledger's service or your own resolution service. See services/types.js for the contract. Setting the value to "null" will fallback everything to blind signing but will still allow the device to sign the transaction.
141 * @example
142 import { ledgerService } from "@ledgerhq/hw-app-eth"
143 const tx = "e8018504e3b292008252089428ee52a8f3d6e5d15f8b131996950d7f296c7952872bd72a2487400080"; // raw tx to sign
144 const resolution = await ledgerService.resolveTransaction(tx);
145 const result = eth.signTransaction("44'/60'/0'/0/0", tx, resolution);
146 console.log(result);
147 */
148 signTransaction(path, rawTxHex, resolution) {
149 return __awaiter(this, void 0, void 0, function* () {
150 if (resolution === undefined) {
151 console.warn("hw-app-eth: signTransaction(path, rawTxHex, resolution): " +
152 "please provide the 'resolution' parameter. " +
153 "See https://github.com/LedgerHQ/ledgerjs/blob/master/packages/hw-app-eth/README.md " +
154 "– the previous signature is deprecated and providing the 3rd 'resolution' parameter explicitly will become mandatory so you have the control on the resolution and the fallback mecanism (e.g. fallback to blind signing or not)." +
155 "// Possible solution:\n" +
156 " + import { ledgerService } from '@ledgerhq/hw-app-eth';\n" +
157 " + const resolution = await ledgerService.resolveTransaction(rawTxHex);");
158 resolution = yield ledger_1.default
159 .resolveTransaction(rawTxHex, this.loadConfig, {
160 externalPlugins: true,
161 erc20: true,
162 })
163 .catch(e => {
164 console.warn("an error occurred in resolveTransaction => fallback to blind signing: " + String(e));
165 return null;
166 });
167 }
168 // provide to the device resolved information to make it clear sign the signature
169 if (resolution) {
170 for (const domainDescriptor of resolution.domains) {
171 yield (0, Domains_1.domainResolutionFlow)(this, domainDescriptor).catch(e => {
172 // error during the domain flow shouldn't be blocking the signature in case of failure
173 (0, logs_1.log)("error", "domainResolutionFlow failed", {
174 domainDescriptor,
175 error: e,
176 });
177 });
178 }
179 for (const plugin of resolution.plugin) {
180 yield this.setPlugin(plugin);
181 }
182 for (const { payload, signature } of resolution.externalPlugin) {
183 yield this.setExternalPlugin(payload, signature);
184 }
185 for (const nft of resolution.nfts) {
186 yield this.provideNFTInformation(nft);
187 }
188 for (const data of resolution.erc20Tokens) {
189 yield this.provideERC20TokenInformation(data);
190 }
191 }
192 const rawTx = Buffer.from(rawTxHex, "hex");
193 const { vrsOffset, txType, chainId, chainIdTruncated } = (0, utils_1.decodeTxInfo)(rawTx);
194 const paths = (0, utils_1.splitPath)(path);
195 let response;
196 let offset = 0;
197 while (offset !== rawTx.length) {
198 const first = offset === 0;
199 const maxChunkSize = first ? 150 - 1 - paths.length * 4 : 150;
200 let chunkSize = offset + maxChunkSize > rawTx.length ? rawTx.length - offset : maxChunkSize;
201 if (vrsOffset != 0 && offset + chunkSize >= vrsOffset) {
202 // Make sure that the chunk doesn't end right on the EIP 155 marker if set
203 chunkSize = rawTx.length - offset;
204 }
205 const buffer = Buffer.alloc(first ? 1 + paths.length * 4 + chunkSize : chunkSize);
206 if (first) {
207 buffer[0] = paths.length;
208 paths.forEach((element, index) => {
209 buffer.writeUInt32BE(element, 1 + 4 * index);
210 });
211 rawTx.copy(buffer, 1 + 4 * paths.length, offset, offset + chunkSize);
212 }
213 else {
214 rawTx.copy(buffer, 0, offset, offset + chunkSize);
215 }
216 response = yield this.transport
217 .send(0xe0, 0x04, first ? 0x00 : 0x80, 0x00, buffer)
218 .catch(e => {
219 throw remapTransactionRelatedErrors(e);
220 });
221 offset += chunkSize;
222 }
223 const response_byte = response[0];
224 let v = "";
225 if (chainId.times(2).plus(35).plus(1).isGreaterThan(255)) {
226 const oneByteChainId = (chainIdTruncated * 2 + 35) % 256;
227 const ecc_parity = Math.abs(response_byte - oneByteChainId);
228 if (txType != null) {
229 // For EIP2930 and EIP1559 tx, v is simply the parity.
230 v = ecc_parity % 2 == 1 ? "00" : "01";
231 }
232 else {
233 // Legacy type transaction with a big chain ID
234 v = chainId.times(2).plus(35).plus(ecc_parity).toString(16);
235 }
236 }
237 else {
238 v = response_byte.toString(16);
239 }
240 // Make sure v has is prefixed with a 0 if its length is odd ("1" -> "01").
241 if (v.length % 2 == 1) {
242 v = "0" + v;
243 }
244 const r = response.slice(1, 1 + 32).toString("hex");
245 const s = response.slice(1 + 32, 1 + 32 + 32).toString("hex");
246 return { v, r, s };
247 });
248 }
249 /**
250 * Helper to get resolution and signature of a transaction in a single method
251 *
252 * @param path: the BIP32 path to sign the transaction on
253 * @param rawTxHex: the raw ethereum transaction in hexadecimal to sign
254 * @param resolutionConfig: configuration about what should be clear signed in the transaction
255 * @param throwOnError: optional parameter to determine if a failing resolution of the transaction should throw an error or not
256 * @example
257 const tx = "e8018504e3b292008252089428ee52a8f3d6e5d15f8b131996950d7f296c7952872bd72a2487400080"; // raw tx to sign
258 const result = eth.clearSignTransaction("44'/60'/0'/0/0", tx, { erc20: true, externalPlugins: true, nft: true});
259 console.log(result);
260 */
261 clearSignTransaction(path, rawTxHex, resolutionConfig, throwOnError = false) {
262 return __awaiter(this, void 0, void 0, function* () {
263 const resolution = yield ledger_1.default
264 .resolveTransaction(rawTxHex, this.loadConfig, resolutionConfig)
265 .catch(e => {
266 console.warn("an error occurred in resolveTransaction => fallback to blind signing: " + String(e));
267 if (throwOnError) {
268 throw e;
269 }
270 return null;
271 });
272 return this.signTransaction(path, rawTxHex, resolution);
273 });
274 }
275 /**
276 */
277 getAppConfiguration() {
278 return this.transport.send(0xe0, 0x06, 0x00, 0x00).then(response => {
279 return {
280 arbitraryDataEnabled: response[0] & 0x01,
281 erc20ProvisioningNecessary: response[0] & 0x02,
282 starkEnabled: response[0] & 0x04,
283 starkv2Supported: response[0] & 0x08,
284 version: "" + response[1] + "." + response[2] + "." + response[3],
285 };
286 });
287 }
288 /**
289 * You can sign a message according to eth_sign RPC call and retrieve v, r, s given the message and the BIP 32 path of the account to sign.
290 * @example
291 eth.signPersonalMessage("44'/60'/0'/0/0", Buffer.from("test").toString("hex")).then(result => {
292 var v = result['v'] - 27;
293 v = v.toString(16);
294 if (v.length < 2) {
295 v = "0" + v;
296 }
297 console.log("Signature 0x" + result['r'] + result['s'] + v);
298 })
299 */
300 signPersonalMessage(path, messageHex) {
301 return __awaiter(this, void 0, void 0, function* () {
302 const paths = (0, utils_1.splitPath)(path);
303 let offset = 0;
304 const message = Buffer.from(messageHex, "hex");
305 let response;
306 while (offset !== message.length) {
307 const maxChunkSize = offset === 0 ? 150 - 1 - paths.length * 4 - 4 : 150;
308 const chunkSize = offset + maxChunkSize > message.length ? message.length - offset : maxChunkSize;
309 const buffer = Buffer.alloc(offset === 0 ? 1 + paths.length * 4 + 4 + chunkSize : chunkSize);
310 if (offset === 0) {
311 buffer[0] = paths.length;
312 paths.forEach((element, index) => {
313 buffer.writeUInt32BE(element, 1 + 4 * index);
314 });
315 buffer.writeUInt32BE(message.length, 1 + 4 * paths.length);
316 message.copy(buffer, 1 + 4 * paths.length + 4, offset, offset + chunkSize);
317 }
318 else {
319 message.copy(buffer, 0, offset, offset + chunkSize);
320 }
321 response = yield this.transport.send(0xe0, 0x08, offset === 0 ? 0x00 : 0x80, 0x00, buffer);
322 offset += chunkSize;
323 }
324 const v = response[0];
325 const r = response.slice(1, 1 + 32).toString("hex");
326 const s = response.slice(1 + 32, 1 + 32 + 32).toString("hex");
327 return { v, r, s };
328 });
329 }
330 /**
331 * Sign a prepared message following web3.eth.signTypedData specification. The host computes the domain separator and hashStruct(message)
332 * @example
333 eth.signEIP712HashedMessage("44'/60'/0'/0/0", Buffer.from("0101010101010101010101010101010101010101010101010101010101010101").toString("hex"), Buffer.from("0202020202020202020202020202020202020202020202020202020202020202").toString("hex")).then(result => {
334 var v = result['v'] - 27;
335 v = v.toString(16);
336 if (v.length < 2) {
337 v = "0" + v;
338 }
339 console.log("Signature 0x" + result['r'] + result['s'] + v);
340 })
341 */
342 signEIP712HashedMessage(path, domainSeparatorHex, hashStructMessageHex) {
343 return (0, EIP712_1.signEIP712HashedMessage)(this.transport, path, domainSeparatorHex, hashStructMessageHex);
344 }
345 /**
346 * Sign an EIP-721 formatted message following the specification here:
347 * https://github.com/LedgerHQ/app-ethereum/blob/develop/doc/ethapp.asc#sign-eth-eip-712
348 * ⚠️ This method is not compatible with nano S (LNS). Make sure to use a try/catch to fallback on the signEIP712HashedMessage method ⚠️
349 @example
350 eth.signEIP721Message("44'/60'/0'/0/0", {
351 domain: {
352 chainId: 69,
353 name: "Da Domain",
354 verifyingContract: "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC",
355 version: "1"
356 },
357 types: {
358 "EIP712Domain": [
359 { name: "name", type: "string" },
360 { name: "version", type: "string" },
361 { name: "chainId", type: "uint256" },
362 { name: "verifyingContract", type: "address" }
363 ],
364 "Test": [
365 { name: "contents", type: "string" }
366 ]
367 },
368 primaryType: "Test",
369 message: {contents: "Hello, Bob!"},
370 })
371 *
372 * @param {String} path derivationPath
373 * @param {Object} jsonMessage message to sign
374 * @param {Boolean} fullImplem use the legacy implementation
375 * @returns {Promise}
376 */
377 signEIP712Message(path, jsonMessage, fullImplem = false) {
378 return __awaiter(this, void 0, void 0, function* () {
379 return (0, EIP712_1.signEIP712Message)(this.transport, path, jsonMessage, fullImplem, this.loadConfig);
380 });
381 }
382 /**
383 * Method returning a 4 bytes TLV challenge as an hexa string
384 *
385 * @returns {Promise<string>}
386 */
387 getChallenge() {
388 return __awaiter(this, void 0, void 0, function* () {
389 let APDU_FIELDS;
390 (function (APDU_FIELDS) {
391 APDU_FIELDS[APDU_FIELDS["CLA"] = 224] = "CLA";
392 APDU_FIELDS[APDU_FIELDS["INS"] = 32] = "INS";
393 APDU_FIELDS[APDU_FIELDS["P1"] = 0] = "P1";
394 APDU_FIELDS[APDU_FIELDS["P2"] = 0] = "P2";
395 APDU_FIELDS[APDU_FIELDS["LC"] = 0] = "LC";
396 })(APDU_FIELDS || (APDU_FIELDS = {}));
397 return this.transport
398 .send(APDU_FIELDS.CLA, APDU_FIELDS.INS, APDU_FIELDS.P1, APDU_FIELDS.P2)
399 .then(res => {
400 const [, fourBytesChallenge, statusCode] = new RegExp("(.*)(.{4}$)").exec(res.toString("hex")) || [];
401 if (statusCode !== "9000") {
402 throw new Error(`An error happened while generating the challenge. Status code: ${statusCode}`);
403 }
404 return `0x${fourBytesChallenge}`;
405 })
406 .catch(e => {
407 (0, logs_1.log)("error", "couldn't request a challenge", e);
408 throw e;
409 });
410 });
411 }
412 /**
413 * get Stark public key for a given BIP 32 path.
414 * @param path a path in BIP 32 format
415 * @option boolDisplay optionally enable or not the display
416 * @return the Stark public key
417 */
418 starkGetPublicKey(path, boolDisplay) {
419 const paths = (0, utils_1.splitPath)(path);
420 const buffer = Buffer.alloc(1 + paths.length * 4);
421 buffer[0] = paths.length;
422 paths.forEach((element, index) => {
423 buffer.writeUInt32BE(element, 1 + 4 * index);
424 });
425 return this.transport
426 .send(0xf0, 0x02, boolDisplay ? 0x01 : 0x00, 0x00, buffer)
427 .then(response => {
428 return response.slice(0, response.length - 2);
429 });
430 }
431 /**
432 * sign a Stark order
433 * @param path a path in BIP 32 format
434 * @option sourceTokenAddress contract address of the source token (not present for ETH)
435 * @param sourceQuantization quantization used for the source token
436 * @option destinationTokenAddress contract address of the destination token (not present for ETH)
437 * @param destinationQuantization quantization used for the destination token
438 * @param sourceVault ID of the source vault
439 * @param destinationVault ID of the destination vault
440 * @param amountSell amount to sell
441 * @param amountBuy amount to buy
442 * @param nonce transaction nonce
443 * @param timestamp transaction validity timestamp
444 * @return the signature
445 */
446 starkSignOrder(path, sourceTokenAddress, sourceQuantization, destinationTokenAddress, destinationQuantization, sourceVault, destinationVault, amountSell, amountBuy, nonce, timestamp) {
447 const sourceTokenAddressHex = (0, utils_1.maybeHexBuffer)(sourceTokenAddress);
448 const destinationTokenAddressHex = (0, utils_1.maybeHexBuffer)(destinationTokenAddress);
449 const paths = (0, utils_1.splitPath)(path);
450 const buffer = Buffer.alloc(1 + paths.length * 4 + 20 + 32 + 20 + 32 + 4 + 4 + 8 + 8 + 4 + 4, 0);
451 let offset = 0;
452 buffer[0] = paths.length;
453 paths.forEach((element, index) => {
454 buffer.writeUInt32BE(element, 1 + 4 * index);
455 });
456 offset = 1 + 4 * paths.length;
457 if (sourceTokenAddressHex) {
458 sourceTokenAddressHex.copy(buffer, offset);
459 }
460 offset += 20;
461 Buffer.from(sourceQuantization.toString(16).padStart(64, "0"), "hex").copy(buffer, offset);
462 offset += 32;
463 if (destinationTokenAddressHex) {
464 destinationTokenAddressHex.copy(buffer, offset);
465 }
466 offset += 20;
467 Buffer.from(destinationQuantization.toString(16).padStart(64, "0"), "hex").copy(buffer, offset);
468 offset += 32;
469 buffer.writeUInt32BE(sourceVault, offset);
470 offset += 4;
471 buffer.writeUInt32BE(destinationVault, offset);
472 offset += 4;
473 Buffer.from(amountSell.toString(16).padStart(16, "0"), "hex").copy(buffer, offset);
474 offset += 8;
475 Buffer.from(amountBuy.toString(16).padStart(16, "0"), "hex").copy(buffer, offset);
476 offset += 8;
477 buffer.writeUInt32BE(nonce, offset);
478 offset += 4;
479 buffer.writeUInt32BE(timestamp, offset);
480 return this.transport.send(0xf0, 0x04, 0x01, 0x00, buffer).then(response => {
481 const r = response.slice(1, 1 + 32).toString("hex");
482 const s = response.slice(1 + 32, 1 + 32 + 32).toString("hex");
483 return {
484 r,
485 s,
486 };
487 });
488 }
489 /**
490 * sign a Stark order using the Starkex V2 protocol
491 * @param path a path in BIP 32 format
492 * @option sourceTokenAddress contract address of the source token (not present for ETH)
493 * @param sourceQuantizationType quantization type used for the source token
494 * @option sourceQuantization quantization used for the source token (not present for erc 721 or mintable erc 721)
495 * @option sourceMintableBlobOrTokenId mintable blob (mintable erc 20 / mintable erc 721) or token id (erc 721) associated to the source token
496 * @option destinationTokenAddress contract address of the destination token (not present for ETH)
497 * @param destinationQuantizationType quantization type used for the destination token
498 * @option destinationQuantization quantization used for the destination token (not present for erc 721 or mintable erc 721)
499 * @option destinationMintableBlobOrTokenId mintable blob (mintable erc 20 / mintable erc 721) or token id (erc 721) associated to the destination token
500 * @param sourceVault ID of the source vault
501 * @param destinationVault ID of the destination vault
502 * @param amountSell amount to sell
503 * @param amountBuy amount to buy
504 * @param nonce transaction nonce
505 * @param timestamp transaction validity timestamp
506 * @return the signature
507 */
508 starkSignOrder_v2(path, sourceTokenAddress, sourceQuantizationType, sourceQuantization, sourceMintableBlobOrTokenId, destinationTokenAddress, destinationQuantizationType, destinationQuantization, destinationMintableBlobOrTokenId, sourceVault, destinationVault, amountSell, amountBuy, nonce, timestamp) {
509 const sourceTokenAddressHex = (0, utils_1.maybeHexBuffer)(sourceTokenAddress);
510 const destinationTokenAddressHex = (0, utils_1.maybeHexBuffer)(destinationTokenAddress);
511 if (!(sourceQuantizationType in starkQuantizationTypeMap)) {
512 throw new Error("eth.starkSignOrderv2 invalid source quantization type=" + sourceQuantizationType);
513 }
514 if (!(destinationQuantizationType in starkQuantizationTypeMap)) {
515 throw new Error("eth.starkSignOrderv2 invalid destination quantization type=" + destinationQuantizationType);
516 }
517 const paths = (0, utils_1.splitPath)(path);
518 const buffer = Buffer.alloc(1 + paths.length * 4 + 1 + 20 + 32 + 32 + 1 + 20 + 32 + 32 + 4 + 4 + 8 + 8 + 4 + 4, 0);
519 let offset = 0;
520 buffer[0] = paths.length;
521 paths.forEach((element, index) => {
522 buffer.writeUInt32BE(element, 1 + 4 * index);
523 });
524 offset = 1 + 4 * paths.length;
525 buffer[offset] = starkQuantizationTypeMap[sourceQuantizationType];
526 offset++;
527 if (sourceTokenAddressHex) {
528 sourceTokenAddressHex.copy(buffer, offset);
529 }
530 offset += 20;
531 if (sourceQuantization) {
532 Buffer.from(sourceQuantization.toString(16).padStart(64, "0"), "hex").copy(buffer, offset);
533 }
534 offset += 32;
535 if (sourceMintableBlobOrTokenId) {
536 Buffer.from(sourceMintableBlobOrTokenId.toString(16).padStart(64, "0"), "hex").copy(buffer, offset);
537 }
538 offset += 32;
539 buffer[offset] = starkQuantizationTypeMap[destinationQuantizationType];
540 offset++;
541 if (destinationTokenAddressHex) {
542 destinationTokenAddressHex.copy(buffer, offset);
543 }
544 offset += 20;
545 if (destinationQuantization) {
546 Buffer.from(destinationQuantization.toString(16).padStart(64, "0"), "hex").copy(buffer, offset);
547 }
548 offset += 32;
549 if (destinationMintableBlobOrTokenId) {
550 Buffer.from(destinationMintableBlobOrTokenId.toString(16).padStart(64, "0"), "hex").copy(buffer, offset);
551 }
552 offset += 32;
553 buffer.writeUInt32BE(sourceVault, offset);
554 offset += 4;
555 buffer.writeUInt32BE(destinationVault, offset);
556 offset += 4;
557 Buffer.from(amountSell.toString(16).padStart(16, "0"), "hex").copy(buffer, offset);
558 offset += 8;
559 Buffer.from(amountBuy.toString(16).padStart(16, "0"), "hex").copy(buffer, offset);
560 offset += 8;
561 buffer.writeUInt32BE(nonce, offset);
562 offset += 4;
563 buffer.writeUInt32BE(timestamp, offset);
564 return this.transport.send(0xf0, 0x04, 0x03, 0x00, buffer).then(response => {
565 const r = response.slice(1, 1 + 32).toString("hex");
566 const s = response.slice(1 + 32, 1 + 32 + 32).toString("hex");
567 return {
568 r,
569 s,
570 };
571 });
572 }
573 /**
574 * sign a Stark transfer
575 * @param path a path in BIP 32 format
576 * @option transferTokenAddress contract address of the token to be transferred (not present for ETH)
577 * @param transferQuantization quantization used for the token to be transferred
578 * @param targetPublicKey target Stark public key
579 * @param sourceVault ID of the source vault
580 * @param destinationVault ID of the destination vault
581 * @param amountTransfer amount to transfer
582 * @param nonce transaction nonce
583 * @param timestamp transaction validity timestamp
584 * @return the signature
585 */
586 starkSignTransfer(path, transferTokenAddress, transferQuantization, targetPublicKey, sourceVault, destinationVault, amountTransfer, nonce, timestamp) {
587 const transferTokenAddressHex = (0, utils_1.maybeHexBuffer)(transferTokenAddress);
588 const targetPublicKeyHex = (0, utils_1.hexBuffer)(targetPublicKey);
589 const paths = (0, utils_1.splitPath)(path);
590 const buffer = Buffer.alloc(1 + paths.length * 4 + 20 + 32 + 32 + 4 + 4 + 8 + 4 + 4, 0);
591 let offset = 0;
592 buffer[0] = paths.length;
593 paths.forEach((element, index) => {
594 buffer.writeUInt32BE(element, 1 + 4 * index);
595 });
596 offset = 1 + 4 * paths.length;
597 if (transferTokenAddressHex) {
598 transferTokenAddressHex.copy(buffer, offset);
599 }
600 offset += 20;
601 Buffer.from(transferQuantization.toString(16).padStart(64, "0"), "hex").copy(buffer, offset);
602 offset += 32;
603 targetPublicKeyHex.copy(buffer, offset);
604 offset += 32;
605 buffer.writeUInt32BE(sourceVault, offset);
606 offset += 4;
607 buffer.writeUInt32BE(destinationVault, offset);
608 offset += 4;
609 Buffer.from(amountTransfer.toString(16).padStart(16, "0"), "hex").copy(buffer, offset);
610 offset += 8;
611 buffer.writeUInt32BE(nonce, offset);
612 offset += 4;
613 buffer.writeUInt32BE(timestamp, offset);
614 return this.transport.send(0xf0, 0x04, 0x02, 0x00, buffer).then(response => {
615 const r = response.slice(1, 1 + 32).toString("hex");
616 const s = response.slice(1 + 32, 1 + 32 + 32).toString("hex");
617 return {
618 r,
619 s,
620 };
621 });
622 }
623 /**
624 * sign a Stark transfer or conditional transfer using the Starkex V2 protocol
625 * @param path a path in BIP 32 format
626 * @option transferTokenAddress contract address of the token to be transferred (not present for ETH)
627 * @param transferQuantizationType quantization type used for the token to be transferred
628 * @option transferQuantization quantization used for the token to be transferred (not present for erc 721 or mintable erc 721)
629 * @option transferMintableBlobOrTokenId mintable blob (mintable erc 20 / mintable erc 721) or token id (erc 721) associated to the token to be transferred
630 * @param targetPublicKey target Stark public key
631 * @param sourceVault ID of the source vault
632 * @param destinationVault ID of the destination vault
633 * @param amountTransfer amount to transfer
634 * @param nonce transaction nonce
635 * @param timestamp transaction validity timestamp
636 * @option conditionalTransferAddress onchain address of the condition for a conditional transfer
637 * @option conditionalTransferFact fact associated to the condition for a conditional transfer
638 * @return the signature
639 */
640 starkSignTransfer_v2(path, transferTokenAddress, transferQuantizationType, transferQuantization, transferMintableBlobOrTokenId, targetPublicKey, sourceVault, destinationVault, amountTransfer, nonce, timestamp, conditionalTransferAddress, conditionalTransferFact) {
641 const transferTokenAddressHex = (0, utils_1.maybeHexBuffer)(transferTokenAddress);
642 const targetPublicKeyHex = (0, utils_1.hexBuffer)(targetPublicKey);
643 const conditionalTransferAddressHex = (0, utils_1.maybeHexBuffer)(conditionalTransferAddress);
644 if (!(transferQuantizationType in starkQuantizationTypeMap)) {
645 throw new Error("eth.starkSignTransferv2 invalid quantization type=" + transferQuantizationType);
646 }
647 const paths = (0, utils_1.splitPath)(path);
648 const buffer = Buffer.alloc(1 +
649 paths.length * 4 +
650 1 +
651 20 +
652 32 +
653 32 +
654 32 +
655 4 +
656 4 +
657 8 +
658 4 +
659 4 +
660 (conditionalTransferAddressHex ? 32 + 20 : 0), 0);
661 let offset = 0;
662 buffer[0] = paths.length;
663 paths.forEach((element, index) => {
664 buffer.writeUInt32BE(element, 1 + 4 * index);
665 });
666 offset = 1 + 4 * paths.length;
667 buffer[offset] = starkQuantizationTypeMap[transferQuantizationType];
668 offset++;
669 if (transferTokenAddressHex) {
670 transferTokenAddressHex.copy(buffer, offset);
671 }
672 offset += 20;
673 if (transferQuantization) {
674 Buffer.from(transferQuantization.toString(16).padStart(64, "0"), "hex").copy(buffer, offset);
675 }
676 offset += 32;
677 if (transferMintableBlobOrTokenId) {
678 Buffer.from(transferMintableBlobOrTokenId.toString(16).padStart(64, "0"), "hex").copy(buffer, offset);
679 }
680 offset += 32;
681 targetPublicKeyHex.copy(buffer, offset);
682 offset += 32;
683 buffer.writeUInt32BE(sourceVault, offset);
684 offset += 4;
685 buffer.writeUInt32BE(destinationVault, offset);
686 offset += 4;
687 Buffer.from(amountTransfer.toString(16).padStart(16, "0"), "hex").copy(buffer, offset);
688 offset += 8;
689 buffer.writeUInt32BE(nonce, offset);
690 offset += 4;
691 buffer.writeUInt32BE(timestamp, offset);
692 if (conditionalTransferAddressHex && conditionalTransferFact) {
693 offset += 4;
694 Buffer.from(conditionalTransferFact.toString(16).padStart(64, "0"), "hex").copy(buffer, offset);
695 offset += 32;
696 conditionalTransferAddressHex.copy(buffer, offset);
697 }
698 return this.transport
699 .send(0xf0, 0x04, conditionalTransferAddressHex ? 0x05 : 0x04, 0x00, buffer)
700 .then(response => {
701 const r = response.slice(1, 1 + 32).toString("hex");
702 const s = response.slice(1 + 32, 1 + 32 + 32).toString("hex");
703 return {
704 r,
705 s,
706 };
707 });
708 }
709 /**
710 * provide quantization information before singing a deposit or withdrawal Stark powered contract call
711 *
712 * It shall be run following a provideERC20TokenInformation call for the given contract
713 *
714 * @param operationContract contract address of the token to be transferred (not present for ETH)
715 * @param operationQuantization quantization used for the token to be transferred
716 */
717 starkProvideQuantum(operationContract, operationQuantization) {
718 const operationContractHex = (0, utils_1.maybeHexBuffer)(operationContract);
719 const buffer = Buffer.alloc(20 + 32, 0);
720 if (operationContractHex) {
721 operationContractHex.copy(buffer, 0);
722 }
723 Buffer.from(operationQuantization.toString(16).padStart(64, "0"), "hex").copy(buffer, 20);
724 return this.transport.send(0xf0, 0x08, 0x00, 0x00, buffer).then(() => true, e => {
725 if (e && e.statusCode === 0x6d00) {
726 // this case happen for ETH application versions not supporting Stark extensions
727 return false;
728 }
729 throw e;
730 });
731 }
732 /**
733 * provide quantization information before singing a deposit or withdrawal Stark powered contract call using the Starkex V2 protocol
734 *
735 * It shall be run following a provideERC20TokenInformation call for the given contract
736 *
737 * @param operationContract contract address of the token to be transferred (not present for ETH)
738 * @param operationQuantizationType quantization type of the token to be transferred
739 * @option operationQuantization quantization used for the token to be transferred (not present for erc 721 or mintable erc 721)
740 * @option operationMintableBlobOrTokenId mintable blob (mintable erc 20 / mintable erc 721) or token id (erc 721) of the token to be transferred
741 */
742 starkProvideQuantum_v2(operationContract, operationQuantizationType, operationQuantization, operationMintableBlobOrTokenId) {
743 const operationContractHex = (0, utils_1.maybeHexBuffer)(operationContract);
744 if (!(operationQuantizationType in starkQuantizationTypeMap)) {
745 throw new Error("eth.starkProvideQuantumV2 invalid quantization type=" + operationQuantizationType);
746 }
747 const buffer = Buffer.alloc(20 + 32 + 32, 0);
748 let offset = 0;
749 if (operationContractHex) {
750 operationContractHex.copy(buffer, offset);
751 }
752 offset += 20;
753 if (operationQuantization) {
754 Buffer.from(operationQuantization.toString(16).padStart(64, "0"), "hex").copy(buffer, offset);
755 }
756 offset += 32;
757 if (operationMintableBlobOrTokenId) {
758 Buffer.from(operationMintableBlobOrTokenId.toString(16).padStart(64, "0"), "hex").copy(buffer, offset);
759 }
760 return this.transport
761 .send(0xf0, 0x08, starkQuantizationTypeMap[operationQuantizationType], 0x00, buffer)
762 .then(() => true, e => {
763 if (e && e.statusCode === 0x6d00) {
764 // this case happen for ETH application versions not supporting Stark extensions
765 return false;
766 }
767 throw e;
768 });
769 }
770 /**
771 * sign the given hash over the Stark curve
772 * It is intended for speed of execution in case an unknown Stark model is pushed and should be avoided as much as possible.
773 * @param path a path in BIP 32 format
774 * @param hash hexadecimal hash to sign
775 * @return the signature
776 */
777 starkUnsafeSign(path, hash) {
778 const hashHex = (0, utils_1.hexBuffer)(hash);
779 const paths = (0, utils_1.splitPath)(path);
780 const buffer = Buffer.alloc(1 + paths.length * 4 + 32);
781 let offset = 0;
782 buffer[0] = paths.length;
783 paths.forEach((element, index) => {
784 buffer.writeUInt32BE(element, 1 + 4 * index);
785 });
786 offset = 1 + 4 * paths.length;
787 hashHex.copy(buffer, offset);
788 return this.transport.send(0xf0, 0x0a, 0x00, 0x00, buffer).then(response => {
789 const r = response.slice(1, 1 + 32).toString("hex");
790 const s = response.slice(1 + 32, 1 + 32 + 32).toString("hex");
791 return {
792 r,
793 s,
794 };
795 });
796 }
797 /**
798 * get an Ethereum 2 BLS-12 381 public key for a given BIP 32 path.
799 * @param path a path in BIP 32 format
800 * @option boolDisplay optionally enable or not the display
801 * @return an object with a publicKey
802 * @example
803 * eth.eth2GetPublicKey("12381/3600/0/0").then(o => o.publicKey)
804 */
805 eth2GetPublicKey(path, boolDisplay) {
806 const paths = (0, utils_1.splitPath)(path);
807 const buffer = Buffer.alloc(1 + paths.length * 4);
808 buffer[0] = paths.length;
809 paths.forEach((element, index) => {
810 buffer.writeUInt32BE(element, 1 + 4 * index);
811 });
812 return this.transport
813 .send(0xe0, 0x0e, boolDisplay ? 0x01 : 0x00, 0x00, buffer)
814 .then(response => {
815 return {
816 publicKey: response.slice(0, -2).toString("hex"),
817 };
818 });
819 }
820 /**
821 * Set the index of a Withdrawal key used as withdrawal credentials in an ETH 2 deposit contract call signature
822 *
823 * It shall be run before the ETH 2 deposit transaction is signed. If not called, the index is set to 0
824 *
825 * @param withdrawalIndex index path in the EIP 2334 path m/12381/3600/withdrawalIndex/0
826 * @return True if the method was executed successfully
827 */
828 eth2SetWithdrawalIndex(withdrawalIndex) {
829 const buffer = Buffer.alloc(4, 0);
830 buffer.writeUInt32BE(withdrawalIndex, 0);
831 return this.transport.send(0xe0, 0x10, 0x00, 0x00, buffer).then(() => true, e => {
832 if (e && e.statusCode === 0x6d00) {
833 // this case happen for ETH application versions not supporting ETH 2
834 return false;
835 }
836 throw e;
837 });
838 }
839 /**
840 * get a public encryption key on Curve25519 according to EIP 1024
841 * @param path a path in BIP 32 format
842 * @option boolDisplay optionally enable or not the display
843 * @return an object with a publicKey
844 * @example
845 * eth.getEIP1024PublicEncryptionKey("44'/60'/0'/0/0").then(o => o.publicKey)
846 */
847 getEIP1024PublicEncryptionKey(path, boolDisplay) {
848 const paths = (0, utils_1.splitPath)(path);
849 const buffer = Buffer.alloc(1 + paths.length * 4);
850 buffer[0] = paths.length;
851 paths.forEach((element, index) => {
852 buffer.writeUInt32BE(element, 1 + 4 * index);
853 });
854 return this.transport
855 .send(0xe0, 0x18, boolDisplay ? 0x01 : 0x00, 0x00, buffer)
856 .then(response => {
857 return {
858 publicKey: response.slice(0, -2).toString("hex"),
859 };
860 });
861 }
862 /**
863 * get a shared secret on Curve25519 according to EIP 1024
864 * @param path a path in BIP 32 format
865 * @param remotePublicKeyHex remote Curve25519 public key
866 * @option boolDisplay optionally enable or not the display
867 * @return an object with a shared secret
868 * @example
869 * eth.getEIP1024SharedSecret("44'/60'/0'/0/0", "87020e80af6e07a6e4697f091eacadb9e7e6629cb7e5a8a371689a3ed53b3d64").then(o => o.sharedSecret)
870 */
871 getEIP1024SharedSecret(path, remotePublicKeyHex, boolDisplay) {
872 const paths = (0, utils_1.splitPath)(path);
873 const remotePublicKey = (0, utils_1.hexBuffer)(remotePublicKeyHex);
874 const buffer = Buffer.alloc(1 + paths.length * 4 + 32);
875 let offset = 0;
876 buffer[0] = paths.length;
877 paths.forEach((element, index) => {
878 buffer.writeUInt32BE(element, 1 + 4 * index);
879 });
880 offset = 1 + 4 * paths.length;
881 remotePublicKey.copy(buffer, offset);
882 return this.transport
883 .send(0xe0, 0x18, boolDisplay ? 0x01 : 0x00, 0x01, buffer)
884 .then(response => {
885 return {
886 sharedSecret: response.slice(0, -2).toString("hex"),
887 };
888 });
889 }
890 /**
891 * provides a trusted description of an ERC 20 token to associate a contract address with a ticker and number of decimals.
892 *
893 * @param data stringified buffer of ERC20 signature
894 * @returns a boolean
895 */
896 provideERC20TokenInformation(data) {
897 const buffer = Buffer.from(data, "hex");
898 return this.transport.send(0xe0, 0x0a, 0x00, 0x00, buffer).then(() => true, e => {
899 if (e && e.statusCode === 0x6d00) {
900 // this case happen for older version of ETH app, since older app version had the ERC20 data hardcoded, it's fine to assume it worked.
901 // we return a flag to know if the call was effective or not
902 return false;
903 }
904 throw e;
905 });
906 }
907 /**
908 * provides the name of a trusted binding of a plugin with a contract address and a supported method selector. This plugin will be called to interpret contract data in the following transaction signing command.
909 *
910 * @param payload external plugin data
911 * @param signature signature for the plugin
912 * @returns a boolean
913 */
914 setExternalPlugin(payload, signature) {
915 const payloadBuffer = Buffer.from(payload, "hex");
916 const signatureBuffer = Buffer.from(signature, "hex");
917 const buffer = Buffer.concat([payloadBuffer, signatureBuffer]);
918 return this.transport.send(0xe0, 0x12, 0x00, 0x00, buffer).then(() => true, e => {
919 if (e && e.statusCode === 0x6a80) {
920 // this case happen when the plugin name is too short or too long
921 return false;
922 }
923 else if (e && e.statusCode === 0x6984) {
924 // this case happen when the plugin requested is not installed on the device
925 return false;
926 }
927 else if (e && e.statusCode === 0x6d00) {
928 // this case happen for older version of ETH app
929 return false;
930 }
931 throw e;
932 });
933 }
934 /**
935 * provides the name of a trusted binding of a plugin with a contract address and a supported method selector. This plugin will be called to interpret contract data in the following transaction signing command.
936 *
937 * @param data stringified buffer of plugin signature
938 * @returns a boolean
939 */
940 setPlugin(data) {
941 const buffer = Buffer.from(data, "hex");
942 return this.transport.send(0xe0, 0x16, 0x00, 0x00, buffer).then(() => true, e => {
943 if (e && e.statusCode === 0x6a80) {
944 // this case happen when the plugin name is too short or too long
945 return false;
946 }
947 else if (e && e.statusCode === 0x6984) {
948 // this case happen when the plugin requested is not installed on the device
949 return false;
950 }
951 else if (e && e.statusCode === 0x6d00) {
952 // this case happen for older version of ETH app
953 return false;
954 }
955 throw e;
956 });
957 }
958 /**
959 * provides a trusted description of an NFT to associate a contract address with a collectionName.
960 *
961 * @param data stringified buffer of the NFT description
962 * @returns a boolean
963 */
964 provideNFTInformation(data) {
965 const buffer = Buffer.from(data, "hex");
966 return this.transport.send(0xe0, 0x14, 0x00, 0x00, buffer).then(() => true, e => {
967 if (e && e.statusCode === 0x6d00) {
968 // older version of ETH app => error because we don't allow blind sign when NFT is explicitly requested to be resolved.
969 throw new errors_1.EthAppNftNotSupported();
970 }
971 throw e;
972 });
973 }
974 /**
975 * provides a domain name (like ENS) to be displayed during transactions in place of the address it is associated to. It shall be run just before a transaction involving the associated address that would be displayed on the device.
976 *
977 * @param data an stringied buffer of some TLV encoded data to represent the domain
978 * @returns a boolean
979 */
980 provideDomainName(data) {
981 return __awaiter(this, void 0, void 0, function* () {
982 let APDU_FIELDS;
983 (function (APDU_FIELDS) {
984 APDU_FIELDS[APDU_FIELDS["CLA"] = 224] = "CLA";
985 APDU_FIELDS[APDU_FIELDS["INS"] = 34] = "INS";
986 APDU_FIELDS[APDU_FIELDS["P1_FIRST_CHUNK"] = 1] = "P1_FIRST_CHUNK";
987 APDU_FIELDS[APDU_FIELDS["P1_FOLLOWING_CHUNK"] = 0] = "P1_FOLLOWING_CHUNK";
988 APDU_FIELDS[APDU_FIELDS["P2"] = 0] = "P2";
989 })(APDU_FIELDS || (APDU_FIELDS = {}));
990 const buffer = Buffer.from(data, "hex");
991 const payload = Buffer.concat([Buffer.from((0, utils_1.intAsHexBytes)(buffer.length, 2), "hex"), buffer]);
992 const bufferChunks = new Array(Math.ceil(payload.length / 256))
993 .fill(null)
994 .map((_, i) => payload.slice(i * 255, (i + 1) * 255));
995 for (const chunk of bufferChunks) {
996 const isFirstChunk = chunk === bufferChunks[0];
997 yield this.transport.send(APDU_FIELDS.CLA, APDU_FIELDS.INS, isFirstChunk ? APDU_FIELDS.P1_FIRST_CHUNK : APDU_FIELDS.P1_FOLLOWING_CHUNK, APDU_FIELDS.P2, chunk);
998 }
999 return true;
1000 });
1001 }
1002}
1003exports.default = Eth;
1004//# sourceMappingURL=Eth.js.map
\No newline at end of file