@ledgerhq/hw-app-btc 10.9.4-nightly.0 → 10.10.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.
@@ -1,7 +1,21 @@
1
1
  import { log } from "@ledgerhq/logs";
2
- import type { Transaction, TransactionInput, TransactionOutput } from "./types";
2
+ import type {
3
+ Transaction,
4
+ TransactionInput,
5
+ TransactionOutput,
6
+ SaplingData,
7
+ SaplingSpendDescriptionV5,
8
+ SaplingOutputDescriptionV5,
9
+ OrchardAction,
10
+ OrchardData,
11
+ } from "./types";
3
12
  import { getVarint } from "./varint";
4
13
  import { formatTransactionDebug } from "./debug";
14
+ import {
15
+ zCashOutCiphertextSize,
16
+ zCashEncCiphertextSize,
17
+ zCashProofsSaplingSize,
18
+ } from "./constants";
5
19
 
6
20
  export function splitTransaction(
7
21
  transactionHex: string,
@@ -101,6 +115,18 @@ export function splitTransaction(
101
115
  });
102
116
  }
103
117
 
118
+ let sapling: SaplingData | undefined;
119
+ let orchard: OrchardData | undefined;
120
+ if (hasExtraData) {
121
+ if (isZcashv5) {
122
+ ({ sapling, offset } = splitSaplingPart(transaction, offset));
123
+
124
+ ({ orchard, offset } = splitOrchardPart(transaction, offset));
125
+
126
+ extraData = transaction.subarray(offset);
127
+ }
128
+ }
129
+
104
130
  if (witness) {
105
131
  witnessScript = transaction.slice(offset, -4);
106
132
  locktime = transaction.slice(transaction.length - 4);
@@ -116,7 +142,9 @@ export function splitTransaction(
116
142
  }
117
143
 
118
144
  if (hasExtraData) {
119
- extraData = transaction.slice(offset);
145
+ if (!isZcashv5) {
146
+ extraData = transaction.slice(offset);
147
+ }
120
148
  }
121
149
 
122
150
  //Get witnesses for Decred
@@ -154,7 +182,212 @@ export function splitTransaction(
154
182
  nVersionGroupId,
155
183
  nExpiryHeight,
156
184
  extraData,
185
+ sapling,
186
+ orchard,
157
187
  };
158
188
  log("btc", `splitTransaction ${transactionHex}:\n${formatTransactionDebug(t)}`);
159
189
  return t;
160
190
  }
191
+
192
+ /**
193
+ * Splits the Sapling part of a Zcash v5 transaction buffer according to https://zips.z.cash/zip-0225
194
+ */
195
+ function splitSaplingPart(
196
+ transaction: Buffer,
197
+ offset: number,
198
+ ): { sapling?: SaplingData; offset: number } {
199
+ let varint = getVarint(transaction, offset);
200
+ const nSpendsSapling = varint[0];
201
+ offset += varint[1];
202
+
203
+ const vSpendsSapling: SaplingSpendDescriptionV5[] = [];
204
+ for (let i = 0; i < nSpendsSapling; i++) {
205
+ const cv = transaction.slice(offset, offset + 32);
206
+ offset += 32;
207
+ const nullifier = transaction.slice(offset, offset + 32);
208
+ offset += 32;
209
+ const rk = transaction.slice(offset, offset + 32);
210
+ offset += 32;
211
+
212
+ vSpendsSapling.push({
213
+ cv,
214
+ nullifier,
215
+ rk,
216
+ } as SaplingSpendDescriptionV5);
217
+ }
218
+
219
+ varint = getVarint(transaction, offset);
220
+ const nOutputsSapling = varint[0];
221
+ offset += varint[1];
222
+ const vOutputSapling: SaplingOutputDescriptionV5[] = [];
223
+
224
+ for (let i = 0; i < nOutputsSapling; i++) {
225
+ const cv = transaction.slice(offset, offset + 32);
226
+
227
+ offset += 32;
228
+ const cmu = transaction.slice(offset, offset + 32);
229
+
230
+ offset += 32;
231
+ const ephemeralKey = transaction.slice(offset, offset + 32);
232
+ offset += 32;
233
+
234
+ const encCiphertext = transaction.slice(offset, offset + zCashEncCiphertextSize);
235
+ offset += zCashEncCiphertextSize;
236
+
237
+ const outCiphertext = transaction.slice(offset, offset + zCashOutCiphertextSize);
238
+ offset += zCashOutCiphertextSize;
239
+
240
+ vOutputSapling.push({
241
+ cv,
242
+ cmu,
243
+ ephemeralKey,
244
+ encCiphertext,
245
+ outCiphertext,
246
+ } as SaplingOutputDescriptionV5);
247
+ }
248
+
249
+ let valueBalanceSapling = Buffer.alloc(0);
250
+ if (nSpendsSapling + nOutputsSapling > 0) {
251
+ valueBalanceSapling = transaction.slice(offset, offset + 8);
252
+ offset += 8;
253
+ }
254
+
255
+ let anchorSapling = Buffer.alloc(0);
256
+ if (nSpendsSapling > 0) {
257
+ anchorSapling = transaction.slice(offset, offset + 32);
258
+ offset += 32;
259
+ }
260
+
261
+ let vSpendProofsSapling = Buffer.alloc(0);
262
+ let vSpendAuthSigsSapling = Buffer.alloc(0);
263
+ if (nSpendsSapling > 0) {
264
+ vSpendProofsSapling = transaction.slice(
265
+ offset,
266
+ offset + zCashProofsSaplingSize * nSpendsSapling,
267
+ );
268
+ offset += zCashProofsSaplingSize * nSpendsSapling;
269
+
270
+ vSpendAuthSigsSapling = transaction.slice(offset, offset + 64 * nSpendsSapling);
271
+ offset += 64 * nSpendsSapling;
272
+ }
273
+
274
+ let vOutputProofsSapling = Buffer.alloc(0);
275
+ if (nOutputsSapling > 0) {
276
+ vOutputProofsSapling = transaction.slice(
277
+ offset,
278
+ offset + zCashProofsSaplingSize * nOutputsSapling,
279
+ );
280
+ offset += zCashProofsSaplingSize * nOutputsSapling;
281
+ }
282
+
283
+ let bindingSigSapling = Buffer.alloc(0);
284
+ if (nSpendsSapling + nOutputsSapling > 0) {
285
+ bindingSigSapling = transaction.slice(offset, offset + 64);
286
+ offset += 64;
287
+ }
288
+
289
+ let sapling: SaplingData | undefined;
290
+ if (nSpendsSapling + nOutputsSapling > 0) {
291
+ sapling = {
292
+ nSpendsSapling,
293
+ vSpendsSapling,
294
+ nOutputsSapling,
295
+ vOutputSapling,
296
+ valueBalanceSapling,
297
+ anchorSapling,
298
+ vSpendProofsSapling,
299
+ vSpendAuthSigsSapling,
300
+ vOutputProofsSapling,
301
+ bindingSigSapling,
302
+ } as SaplingData;
303
+ }
304
+
305
+ return { sapling, offset };
306
+ }
307
+
308
+ /**
309
+ * Splits the Orchard part of a Zcash v5 transaction buffer according to https://zips.z.cash/zip-0225
310
+ */
311
+ function splitOrchardPart(
312
+ transaction: Buffer,
313
+ offset: number,
314
+ ): { orchard?: OrchardData; offset: number } {
315
+ // orchard
316
+ let varint = getVarint(transaction, offset);
317
+ const nActionsOrchard = varint[0];
318
+ offset += varint[1];
319
+
320
+ let orchard: OrchardData | undefined;
321
+ if (nActionsOrchard > 0) {
322
+ const actionsOrchard: OrchardAction[] = [];
323
+
324
+ for (let i = 0; i < nActionsOrchard; i++) {
325
+ const cv = transaction.subarray(offset, offset + 32);
326
+ offset += 32;
327
+ const nullifier = transaction.subarray(offset, offset + 32);
328
+ offset += 32;
329
+ const rk = transaction.subarray(offset, offset + 32);
330
+ offset += 32;
331
+ const cmx = transaction.subarray(offset, offset + 32);
332
+ offset += 32;
333
+ const ephemeralKey = transaction.subarray(offset, offset + 32);
334
+ offset += 32;
335
+ const encCiphertext = transaction.subarray(offset, offset + zCashEncCiphertextSize);
336
+ offset += zCashEncCiphertextSize;
337
+ const outCiphertext = transaction.subarray(offset, offset + zCashOutCiphertextSize);
338
+ offset += zCashOutCiphertextSize;
339
+
340
+ const action: OrchardAction = {
341
+ cv,
342
+ nullifier,
343
+ rk,
344
+ cmx,
345
+ ephemeralKey,
346
+ encCiphertext,
347
+ outCiphertext,
348
+ };
349
+
350
+ actionsOrchard.push(action);
351
+ }
352
+
353
+ // flag field
354
+ const flagsOrchard = transaction.subarray(offset, offset + 1);
355
+ offset += 1;
356
+ // value balance orchard
357
+ const valueBalanceOrchard = transaction.subarray(offset, offset + 8);
358
+ offset += 8;
359
+
360
+ const anchorOrchard = transaction.subarray(offset, offset + 32);
361
+ offset += 32;
362
+
363
+ // read the size of proof
364
+ varint = getVarint(transaction, offset);
365
+ const sizeProofsOrchard = transaction.subarray(offset, offset + varint[1]);
366
+ offset += varint[1];
367
+
368
+ // proof field
369
+ const proofsOrchard = transaction.subarray(offset, offset + varint[0]);
370
+ offset += varint[0];
371
+
372
+ // vSpendAuthSigsOrchard field
373
+ const vSpendAuthSigsOrchard = transaction.subarray(offset, offset + nActionsOrchard * 64);
374
+ offset += nActionsOrchard * 64;
375
+
376
+ // bindingSigOrchard
377
+ const bindingSigOrchard = transaction.subarray(offset, offset + 64);
378
+ offset += 64;
379
+
380
+ orchard = {
381
+ vActions: actionsOrchard,
382
+ flags: flagsOrchard,
383
+ valueBalance: valueBalanceOrchard,
384
+ anchor: anchorOrchard,
385
+ sizeProofs: sizeProofsOrchard,
386
+ proofs: proofsOrchard,
387
+ vSpendsAuthSigs: vSpendAuthSigsOrchard,
388
+ bindingSig: bindingSigOrchard,
389
+ } as OrchardData;
390
+ }
391
+
392
+ return { orchard, offset };
393
+ }
package/src/types.ts CHANGED
@@ -40,6 +40,8 @@ export interface Transaction {
40
40
  extraData?: Buffer;
41
41
  // zCash specific
42
42
  consensusBranchId?: Buffer;
43
+ sapling?: SaplingData;
44
+ orchard?: OrchardData;
43
45
  }
44
46
 
45
47
  export interface TrustedInput {
@@ -47,3 +49,52 @@ export interface TrustedInput {
47
49
  value: Buffer;
48
50
  sequence: Buffer;
49
51
  }
52
+
53
+ // zCash specific
54
+ /**
55
+ */
56
+ export interface SaplingData {
57
+ vSpendsSapling: SaplingSpendDescriptionV5[];
58
+ vOutputSapling: SaplingOutputDescriptionV5[];
59
+ valueBalanceSapling: Buffer;
60
+ anchorSapling: Buffer;
61
+ vSpendProofsSapling: Buffer;
62
+ vSpendAuthSigsSapling: Buffer;
63
+ vOutputProofsSapling: Buffer;
64
+ bindingSigSapling: Buffer;
65
+ }
66
+
67
+ export interface SaplingSpendDescriptionV5 {
68
+ cv: Buffer;
69
+ nullifier: Buffer;
70
+ rk: Buffer;
71
+ }
72
+
73
+ export interface SaplingOutputDescriptionV5 {
74
+ cv: Buffer;
75
+ cmu: Buffer;
76
+ ephemeralKey: Buffer;
77
+ encCiphertext: Buffer;
78
+ outCiphertext: Buffer;
79
+ }
80
+
81
+ export interface OrchardAction {
82
+ cv: Buffer;
83
+ nullifier: Buffer;
84
+ rk: Buffer;
85
+ cmx: Buffer;
86
+ ephemeralKey: Buffer;
87
+ encCiphertext: Buffer;
88
+ outCiphertext: Buffer;
89
+ }
90
+
91
+ export interface OrchardData {
92
+ vActions: OrchardAction[];
93
+ flags: Buffer;
94
+ valueBalance: Buffer;
95
+ anchor: Buffer;
96
+ sizeProofs: Buffer;
97
+ proofs: Buffer;
98
+ vSpendsAuthSigs: Buffer;
99
+ bindingSig: Buffer;
100
+ }