@ledgerhq/hw-app-btc 6.20.0 → 6.25.1-alpha.3

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.
@@ -0,0 +1,419 @@
1
+ // @flow
2
+
3
+ import { log } from "@ledgerhq/logs";
4
+ import type Transport from "@ledgerhq/hw-transport";
5
+ import { hashPublicKey } from "./hashPublicKey";
6
+ import { getWalletPublicKey } from "./getWalletPublicKey";
7
+ import type { AddressFormat } from "./getWalletPublicKey";
8
+ import { getTrustedInput } from "./getTrustedInput";
9
+ import { startUntrustedHashTransactionInput } from "./startUntrustedHashTransactionInput";
10
+ import { serializeTransaction } from "./serializeTransaction";
11
+ import { getTrustedInputBIP143 } from "./getTrustedInputBIP143";
12
+ import { compressPublicKey } from "./compressPublicKey";
13
+ import { signTransaction } from "./signTransaction";
14
+ import { hashOutputFull, provideOutputFullChangePath } from "./finalizeInput";
15
+ import { getAppAndVersion } from "./getAppAndVersion";
16
+ import type { TransactionOutput, Transaction } from "./types";
17
+ import {
18
+ DEFAULT_LOCKTIME,
19
+ DEFAULT_SEQUENCE,
20
+ SIGHASH_ALL,
21
+ OP_DUP,
22
+ OP_HASH160,
23
+ HASH_SIZE,
24
+ OP_EQUALVERIFY,
25
+ OP_CHECKSIG,
26
+ } from "./constants";
27
+ import { shouldUseTrustedInputForSegwit } from "./shouldUseTrustedInputForSegwit";
28
+
29
+ export type { AddressFormat };
30
+
31
+ const defaultsSignTransaction = {
32
+ lockTime: DEFAULT_LOCKTIME,
33
+ sigHashType: SIGHASH_ALL,
34
+ segwit: false,
35
+ additionals: [],
36
+ onDeviceStreaming: (_e) => {},
37
+ onDeviceSignatureGranted: () => {},
38
+ onDeviceSignatureRequested: () => {},
39
+ };
40
+
41
+ /**
42
+ *
43
+ */
44
+ export type CreateTransactionArg = {
45
+ inputs: Array<[Transaction, number, ?string, ?number]>,
46
+ associatedKeysets: string[],
47
+ changePath?: string,
48
+ outputScriptHex: string,
49
+ lockTime?: number,
50
+ sigHashType?: number,
51
+ segwit?: boolean,
52
+ initialTimestamp?: number,
53
+ additionals: Array<string>,
54
+ expiryHeight?: Buffer,
55
+ useTrustedInputForSegwit?: boolean,
56
+ onDeviceStreaming?: ({
57
+ progress: number,
58
+ total: number,
59
+ index: number,
60
+ }) => void,
61
+ onDeviceSignatureRequested?: () => void,
62
+ onDeviceSignatureGranted?: () => void,
63
+ };
64
+
65
+ export async function createTransaction(
66
+ transport: Transport<*>,
67
+ arg: CreateTransactionArg
68
+ ) {
69
+ let {
70
+ inputs,
71
+ associatedKeysets,
72
+ changePath,
73
+ outputScriptHex,
74
+ lockTime,
75
+ sigHashType,
76
+ segwit,
77
+ initialTimestamp,
78
+ additionals,
79
+ expiryHeight,
80
+ useTrustedInputForSegwit,
81
+ onDeviceStreaming,
82
+ onDeviceSignatureGranted,
83
+ onDeviceSignatureRequested,
84
+ } = {
85
+ ...defaultsSignTransaction,
86
+ ...arg,
87
+ };
88
+
89
+ if (useTrustedInputForSegwit === undefined) {
90
+ try {
91
+ const a = await getAppAndVersion(transport);
92
+ useTrustedInputForSegwit = shouldUseTrustedInputForSegwit(a);
93
+ } catch (e) {
94
+ if (e.statusCode === 0x6d00) {
95
+ useTrustedInputForSegwit = false;
96
+ } else {
97
+ throw e;
98
+ }
99
+ }
100
+ }
101
+
102
+ // loop: 0 or 1 (before and after)
103
+ // i: index of the input being streamed
104
+ // i goes on 0...n, inluding n. in order for the progress value to go to 1
105
+ // we normalize the 2 loops to make a global percentage
106
+ const notify = (loop, i) => {
107
+ const { length } = inputs;
108
+ if (length < 3) return; // there is not enough significant event to worth notifying (aka just use a spinner)
109
+ const index = length * loop + i;
110
+ const total = 2 * length;
111
+ const progress = index / total;
112
+ onDeviceStreaming({ progress, total, index });
113
+ };
114
+
115
+ const isDecred = additionals.includes("decred");
116
+ const isXST = additionals.includes("stealthcoin");
117
+ let startTime = Date.now();
118
+ const sapling = additionals.includes("sapling");
119
+ const bech32 = segwit && additionals.includes("bech32");
120
+ let useBip143 =
121
+ segwit ||
122
+ (!!additionals &&
123
+ (additionals.includes("abc") ||
124
+ additionals.includes("gold") ||
125
+ additionals.includes("bip143"))) ||
126
+ (!!expiryHeight && !isDecred);
127
+ // Inputs are provided as arrays of [transaction, output_index, optional redeem script, optional sequence]
128
+ // associatedKeysets are provided as arrays of [path]
129
+ const nullScript = Buffer.alloc(0);
130
+ const nullPrevout = Buffer.alloc(0);
131
+ const defaultVersion = Buffer.alloc(4);
132
+ !!expiryHeight && !isDecred
133
+ ? defaultVersion.writeUInt32LE(sapling ? 0x80000004 : 0x80000003, 0)
134
+ : isXST
135
+ ? defaultVersion.writeUInt32LE(2, 0)
136
+ : defaultVersion.writeUInt32LE(1, 0); // Default version to 2 for XST not to have timestamp
137
+ const trustedInputs: Array<*> = [];
138
+ const regularOutputs: Array<TransactionOutput> = [];
139
+ const signatures = [];
140
+ const publicKeys = [];
141
+ let firstRun = true;
142
+ const resuming = false;
143
+ const targetTransaction: Transaction = {
144
+ inputs: [],
145
+ version: defaultVersion,
146
+ timestamp: Buffer.alloc(0),
147
+ };
148
+
149
+ const getTrustedInputCall =
150
+ useBip143 && !useTrustedInputForSegwit
151
+ ? getTrustedInputBIP143
152
+ : getTrustedInput;
153
+ const outputScript = Buffer.from(outputScriptHex, "hex");
154
+
155
+ notify(0, 0);
156
+
157
+ // first pass on inputs to get trusted inputs
158
+ for (let input of inputs) {
159
+ if (!resuming) {
160
+ const trustedInput = await getTrustedInputCall(
161
+ transport,
162
+ input[1],
163
+ input[0],
164
+ additionals
165
+ );
166
+ log("hw", "got trustedInput=" + trustedInput);
167
+ let sequence = Buffer.alloc(4);
168
+ sequence.writeUInt32LE(
169
+ input.length >= 4 && typeof input[3] === "number"
170
+ ? input[3]
171
+ : DEFAULT_SEQUENCE,
172
+ 0
173
+ );
174
+ trustedInputs.push({
175
+ trustedInput: true,
176
+ value: Buffer.from(trustedInput, "hex"),
177
+ sequence,
178
+ });
179
+ }
180
+
181
+ const { outputs } = input[0];
182
+ const index = input[1];
183
+ if (outputs && index <= outputs.length - 1) {
184
+ regularOutputs.push(outputs[index]);
185
+ }
186
+
187
+ if (expiryHeight && !isDecred) {
188
+ targetTransaction.nVersionGroupId = Buffer.from(
189
+ sapling ? [0x85, 0x20, 0x2f, 0x89] : [0x70, 0x82, 0xc4, 0x03]
190
+ );
191
+ targetTransaction.nExpiryHeight = expiryHeight;
192
+ // For sapling : valueBalance (8), nShieldedSpend (1), nShieldedOutput (1), nJoinSplit (1)
193
+ // Overwinter : use nJoinSplit (1)
194
+ targetTransaction.extraData = Buffer.from(
195
+ sapling
196
+ ? [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
197
+ : [0x00]
198
+ );
199
+ } else if (isDecred) {
200
+ targetTransaction.nExpiryHeight = expiryHeight;
201
+ }
202
+ }
203
+
204
+ targetTransaction.inputs = inputs.map((input) => {
205
+ let sequence = Buffer.alloc(4);
206
+ sequence.writeUInt32LE(
207
+ input.length >= 4 && typeof input[3] === "number"
208
+ ? input[3]
209
+ : DEFAULT_SEQUENCE,
210
+ 0
211
+ );
212
+ return {
213
+ script: nullScript,
214
+ prevout: nullPrevout,
215
+ sequence,
216
+ };
217
+ });
218
+
219
+ if (!resuming) {
220
+ // Collect public keys
221
+ const result = [];
222
+ for (let i = 0; i < inputs.length; i++) {
223
+ const r = await getWalletPublicKey(transport, {
224
+ path: associatedKeysets[i],
225
+ });
226
+ notify(0, i + 1);
227
+ result.push(r);
228
+ }
229
+ for (let i = 0; i < result.length; i++) {
230
+ publicKeys.push(
231
+ compressPublicKey(Buffer.from(result[i].publicKey, "hex"))
232
+ );
233
+ }
234
+ }
235
+
236
+ if (initialTimestamp !== undefined) {
237
+ targetTransaction.timestamp = Buffer.alloc(4);
238
+ targetTransaction.timestamp.writeUInt32LE(
239
+ Math.floor(initialTimestamp + (Date.now() - startTime) / 1000),
240
+ 0
241
+ );
242
+ }
243
+
244
+ onDeviceSignatureRequested();
245
+
246
+ if (useBip143) {
247
+ // Do the first run with all inputs
248
+ await startUntrustedHashTransactionInput(
249
+ transport,
250
+ true,
251
+ targetTransaction,
252
+ trustedInputs,
253
+ true,
254
+ !!expiryHeight,
255
+ additionals,
256
+ useTrustedInputForSegwit
257
+ );
258
+
259
+ if (!resuming && changePath) {
260
+ await provideOutputFullChangePath(transport, changePath);
261
+ }
262
+
263
+ await hashOutputFull(transport, outputScript);
264
+ }
265
+
266
+ if (!!expiryHeight && !isDecred) {
267
+ await signTransaction(transport, "", lockTime, SIGHASH_ALL, expiryHeight);
268
+ }
269
+
270
+ // Do the second run with the individual transaction
271
+ for (let i = 0; i < inputs.length; i++) {
272
+ const input = inputs[i];
273
+ let script =
274
+ inputs[i].length >= 3 && typeof input[2] === "string"
275
+ ? Buffer.from(input[2], "hex")
276
+ : !segwit
277
+ ? regularOutputs[i].script
278
+ : Buffer.concat([
279
+ Buffer.from([OP_DUP, OP_HASH160, HASH_SIZE]),
280
+ hashPublicKey(publicKeys[i]),
281
+ Buffer.from([OP_EQUALVERIFY, OP_CHECKSIG]),
282
+ ]);
283
+ let pseudoTX = Object.assign({}, targetTransaction);
284
+ let pseudoTrustedInputs = useBip143 ? [trustedInputs[i]] : trustedInputs;
285
+ if (useBip143) {
286
+ pseudoTX.inputs = [{ ...pseudoTX.inputs[i], script }];
287
+ } else {
288
+ pseudoTX.inputs[i].script = script;
289
+ }
290
+
291
+ await startUntrustedHashTransactionInput(
292
+ transport,
293
+ !useBip143 && firstRun,
294
+ pseudoTX,
295
+ pseudoTrustedInputs,
296
+ useBip143,
297
+ !!expiryHeight && !isDecred,
298
+ additionals,
299
+ useTrustedInputForSegwit
300
+ );
301
+
302
+ if (!useBip143) {
303
+ if (!resuming && changePath) {
304
+ await provideOutputFullChangePath(transport, changePath);
305
+ }
306
+ await hashOutputFull(transport, outputScript, additionals);
307
+ }
308
+
309
+ if (firstRun) {
310
+ onDeviceSignatureGranted();
311
+ notify(1, 0);
312
+ }
313
+
314
+ const signature = await signTransaction(
315
+ transport,
316
+ associatedKeysets[i],
317
+ lockTime,
318
+ sigHashType,
319
+ expiryHeight,
320
+ additionals
321
+ );
322
+ notify(1, i + 1);
323
+
324
+ signatures.push(signature);
325
+ targetTransaction.inputs[i].script = nullScript;
326
+ if (firstRun) {
327
+ firstRun = false;
328
+ }
329
+ }
330
+
331
+ // Populate the final input scripts
332
+ for (let i = 0; i < inputs.length; i++) {
333
+ if (segwit) {
334
+ targetTransaction.witness = Buffer.alloc(0);
335
+ if (!bech32) {
336
+ targetTransaction.inputs[i].script = Buffer.concat([
337
+ Buffer.from("160014", "hex"),
338
+ hashPublicKey(publicKeys[i]),
339
+ ]);
340
+ }
341
+ } else {
342
+ const signatureSize = Buffer.alloc(1);
343
+ const keySize = Buffer.alloc(1);
344
+ signatureSize[0] = signatures[i].length;
345
+ keySize[0] = publicKeys[i].length;
346
+ targetTransaction.inputs[i].script = Buffer.concat([
347
+ signatureSize,
348
+ signatures[i],
349
+ keySize,
350
+ publicKeys[i],
351
+ ]);
352
+ }
353
+ let offset = useBip143 && !useTrustedInputForSegwit ? 0 : 4;
354
+ targetTransaction.inputs[i].prevout = trustedInputs[i].value.slice(
355
+ offset,
356
+ offset + 0x24
357
+ );
358
+ }
359
+
360
+ const lockTimeBuffer = Buffer.alloc(4);
361
+ lockTimeBuffer.writeUInt32LE(lockTime, 0);
362
+
363
+ var result = Buffer.concat([
364
+ serializeTransaction(
365
+ targetTransaction,
366
+ false,
367
+ targetTransaction.timestamp,
368
+ additionals
369
+ ),
370
+ outputScript,
371
+ ]);
372
+
373
+ if (segwit && !isDecred) {
374
+ var witness = Buffer.alloc(0);
375
+ for (var i = 0; i < inputs.length; i++) {
376
+ var tmpScriptData = Buffer.concat([
377
+ Buffer.from("02", "hex"),
378
+ Buffer.from([signatures[i].length]),
379
+ signatures[i],
380
+ Buffer.from([publicKeys[i].length]),
381
+ publicKeys[i],
382
+ ]);
383
+ witness = Buffer.concat([witness, tmpScriptData]);
384
+ }
385
+ result = Buffer.concat([result, witness]);
386
+ }
387
+
388
+ // FIXME: In ZEC or KMD sapling lockTime is serialized before expiryHeight.
389
+ // expiryHeight is used only in overwinter/sapling so I moved lockTimeBuffer here
390
+ // and it should not break other coins because expiryHeight is false for them.
391
+ // Don't know about Decred though.
392
+ result = Buffer.concat([result, lockTimeBuffer]);
393
+
394
+ if (expiryHeight) {
395
+ result = Buffer.concat([
396
+ result,
397
+ targetTransaction.nExpiryHeight || Buffer.alloc(0),
398
+ targetTransaction.extraData || Buffer.alloc(0),
399
+ ]);
400
+ }
401
+
402
+ if (isDecred) {
403
+ let decredWitness = Buffer.from([targetTransaction.inputs.length]);
404
+ inputs.forEach((input, inputIndex) => {
405
+ decredWitness = Buffer.concat([
406
+ decredWitness,
407
+ Buffer.from([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]),
408
+ Buffer.from([0x00, 0x00, 0x00, 0x00]), //Block height
409
+ Buffer.from([0xff, 0xff, 0xff, 0xff]), //Block index
410
+ Buffer.from([targetTransaction.inputs[inputIndex].script.length]),
411
+ targetTransaction.inputs[inputIndex].script,
412
+ ]);
413
+ });
414
+
415
+ result = Buffer.concat([result, decredWitness]);
416
+ }
417
+
418
+ return result.toString("hex");
419
+ }
@@ -0,0 +1,41 @@
1
+ // @flow
2
+ import type { Transaction } from "./types";
3
+
4
+ export function formatTransactionDebug(transaction: Transaction) {
5
+ let str = "TX";
6
+ str += " version " + transaction.version.toString("hex");
7
+ if (transaction.locktime) {
8
+ str += " locktime " + transaction.locktime.toString("hex");
9
+ }
10
+ if (transaction.witness) {
11
+ str += " witness " + transaction.witness.toString("hex");
12
+ }
13
+ if (transaction.timestamp) {
14
+ str += " timestamp " + transaction.timestamp.toString("hex");
15
+ }
16
+ if (transaction.nVersionGroupId) {
17
+ str += " nVersionGroupId " + transaction.nVersionGroupId.toString("hex");
18
+ }
19
+ if (transaction.nExpiryHeight) {
20
+ str += " nExpiryHeight " + transaction.nExpiryHeight.toString("hex");
21
+ }
22
+ if (transaction.extraData) {
23
+ str += " extraData " + transaction.extraData.toString("hex");
24
+ }
25
+ transaction.inputs.forEach(({ prevout, script, sequence }, i) => {
26
+ str += `\ninput ${i}:`;
27
+ str += ` prevout ${prevout.toString("hex")}`;
28
+ str += ` script ${script.toString("hex")}`;
29
+ str += ` sequence ${sequence.toString("hex")}`;
30
+ });
31
+ (transaction.outputs || []).forEach(({ amount, script }, i) => {
32
+ str += `\noutput ${i}:`;
33
+ str += ` amount ${amount.toString("hex")}`;
34
+ str += ` script ${script.toString("hex")}`;
35
+ });
36
+ return str;
37
+ }
38
+
39
+ export function displayTransactionDebug(transaction: Transaction) {
40
+ console.log(formatTransactionDebug(transaction));
41
+ }
@@ -0,0 +1,38 @@
1
+ // @flow
2
+ import Transport from "@ledgerhq/hw-transport";
3
+ import { bip32asBuffer } from "./bip32";
4
+ import { MAX_SCRIPT_BLOCK } from "./constants";
5
+
6
+ export function provideOutputFullChangePath(
7
+ transport: Transport<*>,
8
+ path: string
9
+ ): Promise<string> {
10
+ let buffer = bip32asBuffer(path);
11
+ return transport.send(0xe0, 0x4a, 0xff, 0x00, buffer);
12
+ }
13
+
14
+ export async function hashOutputFull(
15
+ transport: Transport<*>,
16
+ outputScript: Buffer,
17
+ additionals: Array<string> = []
18
+ ): Promise<void> {
19
+ let offset = 0;
20
+ let p1 = 0x80;
21
+ const isDecred = additionals.includes("decred");
22
+ ///WARNING: Decred works only with one call (without chunking)
23
+ //TODO: test without this for Decred
24
+ if (isDecred) {
25
+ return transport.send(0xe0, 0x4a, p1, 0x00, outputScript);
26
+ }
27
+
28
+ while (offset < outputScript.length) {
29
+ let blockSize =
30
+ offset + MAX_SCRIPT_BLOCK >= outputScript.length
31
+ ? outputScript.length - offset
32
+ : MAX_SCRIPT_BLOCK;
33
+ let p1 = offset + blockSize === outputScript.length ? 0x80 : 0x00;
34
+ let data = outputScript.slice(offset, offset + blockSize);
35
+ await transport.send(0xe0, 0x4a, p1, 0x00, data);
36
+ offset += blockSize;
37
+ }
38
+ }
@@ -0,0 +1,19 @@
1
+ // @flow
2
+ import invariant from "invariant";
3
+ import Transport from "@ledgerhq/hw-transport";
4
+
5
+ export const getAppAndVersion = async (
6
+ transport: Transport<*>
7
+ ): Promise<{ name: string, version: string, flags: number }> => {
8
+ const r = await transport.send(0xb0, 0x01, 0x00, 0x00);
9
+ let i = 0;
10
+ const format = r[i++];
11
+ invariant(format === 1, "getAppAndVersion: format not supported");
12
+ const nameLength = r[i++];
13
+ const name = r.slice(i, (i += nameLength)).toString("ascii");
14
+ const versionLength = r[i++];
15
+ const version = r.slice(i, (i += versionLength)).toString("ascii");
16
+ const flagLength = r[i++];
17
+ const flags = r.slice(i, (i += flagLength));
18
+ return { name, version, flags };
19
+ };
@@ -0,0 +1,163 @@
1
+ // @flow
2
+ import invariant from "invariant";
3
+ import type Transport from "@ledgerhq/hw-transport";
4
+ import type { Transaction } from "./types";
5
+ import { MAX_SCRIPT_BLOCK } from "./constants";
6
+ import { createVarint } from "./varint";
7
+
8
+ export async function getTrustedInputRaw(
9
+ transport: Transport<*>,
10
+ transactionData: Buffer,
11
+ indexLookup: ?number
12
+ ): Promise<string> {
13
+ let data;
14
+ let firstRound = false;
15
+ if (typeof indexLookup === "number") {
16
+ firstRound = true;
17
+ const prefix = Buffer.alloc(4);
18
+ prefix.writeUInt32BE(indexLookup, 0);
19
+ data = Buffer.concat([prefix, transactionData], transactionData.length + 4);
20
+ } else {
21
+ data = transactionData;
22
+ }
23
+ const trustedInput = await transport.send(
24
+ 0xe0,
25
+ 0x42,
26
+ firstRound ? 0x00 : 0x80,
27
+ 0x00,
28
+ data
29
+ );
30
+
31
+ const res = trustedInput.slice(0, trustedInput.length - 2).toString("hex");
32
+ return res;
33
+ }
34
+
35
+ export async function getTrustedInput(
36
+ transport: Transport<*>,
37
+ indexLookup: number,
38
+ transaction: Transaction,
39
+ additionals: Array<string> = []
40
+ ): Promise<string> {
41
+ const {
42
+ version,
43
+ inputs,
44
+ outputs,
45
+ locktime,
46
+ nExpiryHeight,
47
+ extraData,
48
+ } = transaction;
49
+ if (!outputs || !locktime) {
50
+ throw new Error("getTrustedInput: locktime & outputs is expected");
51
+ }
52
+ const isDecred = additionals.includes("decred");
53
+ const isXST = additionals.includes("stealthcoin");
54
+
55
+ const processScriptBlocks = async (script, sequence) => {
56
+ const seq = sequence || Buffer.alloc(0);
57
+ const scriptBlocks = [];
58
+ let offset = 0;
59
+ while (offset !== script.length) {
60
+ let blockSize =
61
+ script.length - offset > MAX_SCRIPT_BLOCK
62
+ ? MAX_SCRIPT_BLOCK
63
+ : script.length - offset;
64
+ if (offset + blockSize !== script.length) {
65
+ scriptBlocks.push(script.slice(offset, offset + blockSize));
66
+ } else {
67
+ scriptBlocks.push(
68
+ Buffer.concat([script.slice(offset, offset + blockSize), seq])
69
+ );
70
+ }
71
+ offset += blockSize;
72
+ }
73
+
74
+ // Handle case when no script length: we still want to pass the sequence
75
+ // relatable: https://github.com/LedgerHQ/ledger-live-desktop/issues/1386
76
+ if (script.length === 0) {
77
+ scriptBlocks.push(seq);
78
+ }
79
+
80
+ let res;
81
+ for (let scriptBlock of scriptBlocks) {
82
+ res = await getTrustedInputRaw(transport, scriptBlock);
83
+ }
84
+ return res;
85
+ };
86
+
87
+ const processWholeScriptBlock = (block) =>
88
+ getTrustedInputRaw(transport, block);
89
+
90
+ await getTrustedInputRaw(
91
+ transport,
92
+ Buffer.concat([
93
+ transaction.version,
94
+ transaction.timestamp || Buffer.alloc(0),
95
+ transaction.nVersionGroupId || Buffer.alloc(0),
96
+ createVarint(inputs.length),
97
+ ]),
98
+ indexLookup
99
+ );
100
+
101
+ for (let input of inputs) {
102
+ const isXSTV2 =
103
+ isXST &&
104
+ Buffer.compare(version, Buffer.from([0x02, 0x00, 0x00, 0x00])) === 0;
105
+ const treeField = isDecred
106
+ ? input.tree || Buffer.from([0x00])
107
+ : Buffer.alloc(0);
108
+ const data = Buffer.concat([
109
+ input.prevout,
110
+ treeField,
111
+ isXSTV2 ? Buffer.from([0x00]) : createVarint(input.script.length),
112
+ ]);
113
+ await getTrustedInputRaw(transport, data);
114
+
115
+ // iteration (eachSeries) ended
116
+ // TODO notify progress
117
+ // deferred.notify("input");
118
+ // Reference: https://github.com/StealthSend/Stealth/commit/5be35d6c2c500b32ed82e5d6913d66d18a4b0a7f#diff-e8db9b851adc2422aadfffca88f14c91R566
119
+ await (isDecred
120
+ ? processWholeScriptBlock(Buffer.concat([input.script, input.sequence]))
121
+ : isXSTV2
122
+ ? processWholeScriptBlock(input.sequence)
123
+ : processScriptBlocks(input.script, input.sequence));
124
+ }
125
+
126
+ await getTrustedInputRaw(transport, createVarint(outputs.length));
127
+
128
+ for (let output of outputs) {
129
+ const data = Buffer.concat([
130
+ output.amount,
131
+ isDecred ? Buffer.from([0x00, 0x00]) : Buffer.alloc(0), //Version script
132
+ createVarint(output.script.length),
133
+ output.script,
134
+ ]);
135
+ await getTrustedInputRaw(transport, data);
136
+ }
137
+
138
+ const endData = [];
139
+
140
+ if (nExpiryHeight && nExpiryHeight.length > 0) {
141
+ endData.push(nExpiryHeight);
142
+ }
143
+
144
+ if (extraData && extraData.length > 0) {
145
+ endData.push(extraData);
146
+ }
147
+
148
+ let extraPart;
149
+ if (endData.length) {
150
+ const data = Buffer.concat(endData);
151
+ extraPart = isDecred
152
+ ? data
153
+ : Buffer.concat([createVarint(data.length), data]);
154
+ }
155
+
156
+ const res = await processScriptBlocks(
157
+ Buffer.concat([locktime, extraPart || Buffer.alloc(0)])
158
+ );
159
+
160
+ invariant(res, "missing result in processScriptBlocks");
161
+
162
+ return res;
163
+ }