@ledgerhq/live-common 21.16.1 → 21.16.2

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.
Files changed (80) hide show
  1. package/lib/__tests__/families/bitcoin/wallet-btc/utils.test.js +85 -0
  2. package/lib/__tests__/families/bitcoin/wallet-btc/utils.test.js.map +1 -1
  3. package/lib/__tests__/families/bitcoin/wallet-btc/wallet.estimateMaxSpendable.test.js +1 -1
  4. package/lib/__tests__/families/bitcoin/wallet-btc/wallet.estimateMaxSpendable.test.js.map +1 -1
  5. package/lib/__tests__/families/bitcoin/wallet-btc/wallet.integration.test.js +2 -2
  6. package/lib/__tests__/families/bitcoin/wallet-btc/wallet.integration.test.js.map +1 -1
  7. package/lib/__tests__/families/bitcoin/wallet-btc/xpub.pickingStrategies.integration.test.js +20 -10
  8. package/lib/__tests__/families/bitcoin/wallet-btc/xpub.pickingStrategies.integration.test.js.map +1 -1
  9. package/lib/__tests__/families/bitcoin/wallet-btc/xpub.txs.NP2WPKH.integration.test.js +1 -1
  10. package/lib/__tests__/families/bitcoin/wallet-btc/xpub.txs.NP2WPKH.integration.test.js.map +1 -1
  11. package/lib/__tests__/families/bitcoin/wallet-btc/xpub.txs.P2WPKH.integration.test.js +1 -1
  12. package/lib/__tests__/families/bitcoin/wallet-btc/xpub.txs.P2WPKH.integration.test.js.map +1 -1
  13. package/lib/__tests__/families/bitcoin/wallet-btc/xpub.txs.integration.test.js +2 -2
  14. package/lib/__tests__/families/bitcoin/wallet-btc/xpub.txs.integration.test.js.map +1 -1
  15. package/lib/apps/polyfill.d.ts.map +1 -1
  16. package/lib/apps/polyfill.js +3 -0
  17. package/lib/apps/polyfill.js.map +1 -1
  18. package/lib/currencies/support.d.ts.map +1 -1
  19. package/lib/currencies/support.js.map +1 -1
  20. package/lib/families/bitcoin/js-buildTransaction.d.ts.map +1 -1
  21. package/lib/families/bitcoin/js-buildTransaction.js +1 -1
  22. package/lib/families/bitcoin/js-buildTransaction.js.map +1 -1
  23. package/lib/families/bitcoin/js-estimateMaxSpendable.d.ts.map +1 -1
  24. package/lib/families/bitcoin/js-estimateMaxSpendable.js +1 -1
  25. package/lib/families/bitcoin/js-estimateMaxSpendable.js.map +1 -1
  26. package/lib/families/bitcoin/js-synchronisation.d.ts.map +1 -1
  27. package/lib/families/bitcoin/js-synchronisation.js +3 -2
  28. package/lib/families/bitcoin/js-synchronisation.js.map +1 -1
  29. package/lib/families/bitcoin/wallet-btc/pickingstrategies/CoinSelect.d.ts +2 -1
  30. package/lib/families/bitcoin/wallet-btc/pickingstrategies/CoinSelect.d.ts.map +1 -1
  31. package/lib/families/bitcoin/wallet-btc/pickingstrategies/CoinSelect.js +16 -12
  32. package/lib/families/bitcoin/wallet-btc/pickingstrategies/CoinSelect.js.map +1 -1
  33. package/lib/families/bitcoin/wallet-btc/pickingstrategies/DeepFirst.d.ts +2 -1
  34. package/lib/families/bitcoin/wallet-btc/pickingstrategies/DeepFirst.d.ts.map +1 -1
  35. package/lib/families/bitcoin/wallet-btc/pickingstrategies/DeepFirst.js +12 -7
  36. package/lib/families/bitcoin/wallet-btc/pickingstrategies/DeepFirst.js.map +1 -1
  37. package/lib/families/bitcoin/wallet-btc/pickingstrategies/Merge.d.ts +2 -1
  38. package/lib/families/bitcoin/wallet-btc/pickingstrategies/Merge.d.ts.map +1 -1
  39. package/lib/families/bitcoin/wallet-btc/pickingstrategies/Merge.js +12 -7
  40. package/lib/families/bitcoin/wallet-btc/pickingstrategies/Merge.js.map +1 -1
  41. package/lib/families/bitcoin/wallet-btc/pickingstrategies/types.d.ts +2 -1
  42. package/lib/families/bitcoin/wallet-btc/pickingstrategies/types.d.ts.map +1 -1
  43. package/lib/families/bitcoin/wallet-btc/pickingstrategies/types.js.map +1 -1
  44. package/lib/families/bitcoin/wallet-btc/utils.d.ts +38 -2
  45. package/lib/families/bitcoin/wallet-btc/utils.d.ts.map +1 -1
  46. package/lib/families/bitcoin/wallet-btc/utils.js +103 -35
  47. package/lib/families/bitcoin/wallet-btc/utils.js.map +1 -1
  48. package/lib/families/bitcoin/wallet-btc/wallet.d.ts +1 -1
  49. package/lib/families/bitcoin/wallet-btc/wallet.d.ts.map +1 -1
  50. package/lib/families/bitcoin/wallet-btc/wallet.js +6 -5
  51. package/lib/families/bitcoin/wallet-btc/wallet.js.map +1 -1
  52. package/lib/families/bitcoin/wallet-btc/xpub.js +2 -2
  53. package/lib/families/bitcoin/wallet-btc/xpub.js.map +1 -1
  54. package/lib/operation.js +1 -1
  55. package/lib/operation.js.map +1 -1
  56. package/package.json +5 -5
  57. package/src/.DS_Store +0 -0
  58. package/src/__tests__/__snapshots__/all.libcore.ts.snap +1188 -630
  59. package/src/__tests__/families/bitcoin/wallet-btc/utils.test.ts +105 -0
  60. package/src/__tests__/families/bitcoin/wallet-btc/wallet.estimateMaxSpendable.test.ts +3 -2
  61. package/src/__tests__/families/bitcoin/wallet-btc/wallet.integration.test.ts +2 -2
  62. package/src/__tests__/families/bitcoin/wallet-btc/xpub.pickingStrategies.integration.test.ts +35 -31
  63. package/src/__tests__/families/bitcoin/wallet-btc/xpub.txs.NP2WPKH.integration.test.ts +3 -2
  64. package/src/__tests__/families/bitcoin/wallet-btc/xpub.txs.P2WPKH.integration.test.ts +3 -2
  65. package/src/__tests__/families/bitcoin/wallet-btc/xpub.txs.integration.test.ts +6 -4
  66. package/src/apps/polyfill.ts +3 -0
  67. package/src/currencies/.DS_Store +0 -0
  68. package/src/currencies/__snapshots__/sortByMarketcap.test.ts.snap +3 -1
  69. package/src/currencies/support.ts +1 -0
  70. package/src/families/bitcoin/js-buildTransaction.ts +2 -1
  71. package/src/families/bitcoin/js-estimateMaxSpendable.ts +2 -1
  72. package/src/families/bitcoin/js-synchronisation.ts +4 -2
  73. package/src/families/bitcoin/wallet-btc/pickingstrategies/CoinSelect.ts +34 -22
  74. package/src/families/bitcoin/wallet-btc/pickingstrategies/DeepFirst.ts +30 -9
  75. package/src/families/bitcoin/wallet-btc/pickingstrategies/Merge.ts +30 -9
  76. package/src/families/bitcoin/wallet-btc/pickingstrategies/types.ts +3 -3
  77. package/src/families/bitcoin/wallet-btc/utils.ts +113 -41
  78. package/src/families/bitcoin/wallet-btc/wallet.ts +9 -6
  79. package/src/families/bitcoin/wallet-btc/xpub.ts +5 -5
  80. package/src/operation.ts +1 -1
@@ -6,14 +6,14 @@ import Xpub from "../xpub";
6
6
  import { PickingStrategy } from "./types";
7
7
  import * as utils from "../utils";
8
8
  import { log } from "@ledgerhq/logs";
9
+ import { OutputInfo } from "..";
9
10
 
10
11
  export class Merge extends PickingStrategy {
11
12
  // eslint-disable-next-line class-methods-use-this
12
13
  async selectUnspentUtxosToUse(
13
14
  xpub: Xpub,
14
- amount: BigNumber,
15
- feePerByte: number,
16
- nbOutputsWithoutChange: number
15
+ outputs: OutputInfo[],
16
+ feePerByte: number
17
17
  ) {
18
18
  // get the utxos to use as input
19
19
  // from all addresses of the account
@@ -31,19 +31,36 @@ export class Merge extends PickingStrategy {
31
31
  ).length
32
32
  );
33
33
 
34
+ const outputAddresses = outputs.map((o) => o.address);
35
+ const emptyTxSize = utils.maxTxSizeCeil(
36
+ 0,
37
+ [],
38
+ false,
39
+ this.crypto,
40
+ this.derivationMode
41
+ );
34
42
  const sizePerInput =
35
- utils.accurateTxSize(1, 0, this.crypto, this.derivationMode) -
36
- utils.accurateTxSize(0, 0, this.crypto, this.derivationMode);
43
+ utils.maxTxSize(1, [], false, this.crypto, this.derivationMode) -
44
+ emptyTxSize;
37
45
 
38
46
  const sizePerOutput =
39
- utils.accurateTxSize(0, 1, this.crypto, this.derivationMode) -
40
- utils.accurateTxSize(0, 0, this.crypto, this.derivationMode);
47
+ (outputAddresses[0]
48
+ ? utils.maxTxSize(
49
+ 0,
50
+ [outputAddresses[0]],
51
+ false,
52
+ this.crypto,
53
+ this.derivationMode
54
+ )
55
+ : utils.maxTxSize(0, [], true, this.crypto, this.derivationMode)) -
56
+ emptyTxSize;
41
57
 
42
58
  unspentUtxos = sortBy(unspentUtxos, (utxo) => parseInt(utxo.value, 10));
43
59
  // https://metamug.com/article/security/bitcoin-transaction-fee-satoshi-per-byte.html
44
- const txSizeNoInput = utils.accurateTxSize(
60
+ const txSizeNoInput = utils.maxTxSize(
45
61
  0,
46
- nbOutputsWithoutChange,
62
+ outputAddresses,
63
+ false,
47
64
  this.crypto,
48
65
  this.derivationMode
49
66
  );
@@ -53,6 +70,10 @@ export class Merge extends PickingStrategy {
53
70
  const unspentUtxoSelected: Output[] = [];
54
71
 
55
72
  let i = 0;
73
+ const amount = outputs.reduce(
74
+ (sum, output) => sum.plus(output.value),
75
+ new BigNumber(0)
76
+ );
56
77
  while (total.lt(amount.plus(fee))) {
57
78
  if (!unspentUtxos[i]) {
58
79
  throw new NotEnoughBalance();
@@ -1,4 +1,5 @@
1
1
  import BigNumber from "bignumber.js";
2
+ import { OutputInfo } from "..";
2
3
  import { ICrypto } from "../crypto/types";
3
4
  import { Output } from "../storage/types";
4
5
  import Xpub from "../xpub";
@@ -29,9 +30,8 @@ export abstract class PickingStrategy {
29
30
 
30
31
  abstract selectUnspentUtxosToUse(
31
32
  xpub: Xpub,
32
- amount: BigNumber,
33
- feePerByte: number,
34
- nbOutputsWithoutChange: number
33
+ outputs: OutputInfo[],
34
+ feePerByte: number
35
35
  ): Promise<{
36
36
  unspentUtxos: Output[];
37
37
  totalValue: BigNumber;
@@ -6,6 +6,7 @@ import { DerivationModes } from "./types";
6
6
  import { Currency, ICrypto } from "./crypto/types";
7
7
  import cryptoFactory from "./crypto/factory";
8
8
  import { fallbackValidateAddress } from "./crypto/base";
9
+ import { UnsupportedDerivation } from "../../../errors";
9
10
 
10
11
  export function parseHexString(str: any) {
11
12
  const result: Array<number> = [];
@@ -91,58 +92,129 @@ export function byteSize(count: number) {
91
92
  return 9;
92
93
  }
93
94
 
94
- // refer to https://github.com/LedgerHQ/lib-ledger-core/blob/fc9d762b83fc2b269d072b662065747a64ab2816/core/src/wallet/bitcoin/api_impl/BitcoinLikeTransactionApi.cpp#L217
95
- export function accurateTxSize(
96
- inputCount: number,
97
- outputCount: number,
98
- currency: ICrypto,
99
- derivationMode: string
100
- ) {
101
- let txSize = 0;
102
- let fixedSize = 0;
103
- // Fixed size computation
104
- fixedSize = 4; // Transaction version
105
- if (currency.network.usesTimestampedTransaction) fixedSize += 4; // Timestamp
106
- fixedSize += byteSize(inputCount); // Number of inputs
107
- fixedSize += byteSize(outputCount); // Number of outputs
108
- fixedSize += 4; // Timelock
95
+ const baseByte = 4;
96
+ function fixedWeight(currency: ICrypto, derivationMode: string): number {
97
+ let fixedWeight = 4 * baseByte; // Transaction version
98
+ if (currency.network.usesTimestampedTransaction) fixedWeight += 4 * baseByte; // Timestamp
99
+ fixedWeight += 4 * baseByte; // Timelock
109
100
  if (derivationMode !== DerivationModes.LEGACY) {
110
- fixedSize += 0.5; // Segwit marker & segwit flag
101
+ fixedWeight += 2; // Segwit marker & segwit flag, 1 WU per byte
111
102
  }
112
- // refer to https://medium.com/coinmonks/on-bitcoin-transaction-sizes-part-2-9445373d17f4
113
- // and https://bitcoin.stackexchange.com/questions/96017/what-are-the-sizes-of-single-sig-and-2-of-3-multisig-taproot-inputs
114
- // and https://bitcoinops.org/en/tools/calc-size/
103
+ return fixedWeight;
104
+ }
105
+
106
+ function inputWeight(derivationMode: string): number {
107
+ let inputWeight = (32 + 4 + 1 + 4) * baseByte;
115
108
  if (derivationMode === DerivationModes.TAPROOT) {
116
- txSize = fixedSize + 57.75 * inputCount + 43 * outputCount;
117
- return txSize;
109
+ inputWeight += 1 + 1 + 65;
110
+ } else if (derivationMode === DerivationModes.NATIVE_SEGWIT) {
111
+ inputWeight += 1 + 1 + 72 + 1 + 33;
112
+ } else if (derivationMode === DerivationModes.SEGWIT) {
113
+ inputWeight += 22 * baseByte + 1 + 1 + 72 + 1 + 33;
114
+ } else if (derivationMode === DerivationModes.LEGACY) {
115
+ inputWeight += 107 * baseByte;
116
+ } else {
117
+ throw UnsupportedDerivation(`Derivation mode ${derivationMode} unknown`);
118
118
  }
119
- const isSegwit =
120
- derivationMode === DerivationModes.NATIVE_SEGWIT ||
121
- derivationMode === DerivationModes.SEGWIT;
122
- if (isSegwit) {
123
- // Native Segwit: 32 PrevTxHash + 4 Index + 1 null byte + 4 sequence
124
- // P2SH: 32 PrevTxHash + 4 Index + 23 scriptPubKey + 4 sequence
125
- const isNativeSegwit = derivationMode === DerivationModes.NATIVE_SEGWIT;
126
- const inputSize = isNativeSegwit ? 41 : 63;
127
- const noWitness = fixedSize + inputSize * inputCount + 43 * outputCount; //43 is the biggest output(taproot output) size, we use the biggest one for safety
128
- // Include flag and marker size (one byte each)
129
- const witnessSize = noWitness + 108 * inputCount + 2;
130
- txSize = (noWitness * 3 + witnessSize) / 4;
119
+ return inputWeight;
120
+ }
121
+
122
+ function outputWeight(derivationMode: string): number {
123
+ let outputSize = 8 + 1; // amount + script size
124
+ if (derivationMode === DerivationModes.TAPROOT) {
125
+ outputSize += 34; // "1" + PUSH32 + <32 bytes>
126
+ } else if (derivationMode === DerivationModes.NATIVE_SEGWIT) {
127
+ outputSize += 22; // "0" + PUSH20 + <20 bytes>
128
+ } else if (derivationMode === DerivationModes.SEGWIT) {
129
+ outputSize += 23; // HASH160 + PUSH20 + <20 bytes> + EQUAL
130
+ } else if (derivationMode === DerivationModes.LEGACY) {
131
+ outputSize += 25; // DUP + HASH160 + PUSH20 + <20 bytes> + EQUALVERIFY + CHECKSIG
131
132
  } else {
132
- txSize = fixedSize + 148 * inputCount + 43 * outputCount;
133
+ throw UnsupportedDerivation(derivationMode);
133
134
  }
134
- return txSize;
135
+ return outputSize * 4;
136
+ }
137
+
138
+ export function outputSize(currency: ICrypto, addr: string): number {
139
+ const scriptLen = currency.toOutputScript(addr).length;
140
+ return 1 + 8 + scriptLen;
135
141
  }
136
142
 
137
- export function estimateTxSize(
143
+ /**
144
+ * Calculates the maximun size of an imaginary transaction in virtual bytes
145
+ * (vB). 1vB = 4 WU (weight units). If a cryptocurrency doesn't use segwit, then
146
+ * 1 byte = 1 vB. Weight units are used for segwit transactions, where certain
147
+ * bytes of the transaction is counted as 4 WU and some as 1 WU. The resulting
148
+ * size is then ceil(totalWU/4) vB.
149
+ *
150
+ * refer to https://medium.com/coinmonks/on-bitcoin-transaction-sizes-part-2-9445373d17f4
151
+ * and https://bitcoin.stackexchange.com/questions/96017/what-are-the-sizes-of-single-sig-and-2-of-3-multisig-taproot-inputs
152
+ * and https://bitcoinops.org/en/tools/calc-size/
153
+ *
154
+ * Suggested improvement: I assume that these calculations won't be exactly
155
+ * correct for altcoins (but I don't know). Therefore we should move this
156
+ * functionality into ICrypto, to make it easier to provide slightly different
157
+ * calculations for different cryptocurrencies.
158
+ *
159
+ * The reason for the name maxTxSize, instead of just txSize, is that the size
160
+ * of modern ecdsa signatures aren't know beforehand. They are either 71 or
161
+ * 72 bytes. This function always count with 72 byte signatures.
162
+ *
163
+ * The size of schnorr signatures is expected to be 65 bytes, since the Bitcoin
164
+ * hardware app appends the optional sighash type 01 at the end of the
165
+ * signature. If the app removes that optional byte, this functions should be
166
+ * updated to use 64 byte schnorr signatures, even if 65 still is an acceptable
167
+ * maximum.
168
+ *
169
+ * @param inputCount Number of inputs of the imaginary transaction
170
+ * @param outputAddrs The addresses of the outputs, excluding change.
171
+ * @param includeChange Indicates whether we should add a change output. The
172
+ * change output will have the same derivation mode as the inputs. See
173
+ * derivationMode parameter.
174
+ * @param currency The currency for which the calculation is done.
175
+ * @param derivationMode The derivation mode used for calculating the size of
176
+ * the inputs.
177
+ */
178
+ export function maxTxSize(
138
179
  inputCount: number,
139
- outputCount: number,
180
+ outputAddrs: string[],
181
+ includeChange: boolean,
140
182
  currency: ICrypto,
141
183
  derivationMode: string
142
- ) {
143
- return Math.ceil(
144
- accurateTxSize(inputCount, outputCount, currency, derivationMode)
145
- ); // We don't allow floating value
184
+ ): number {
185
+ const fixed = fixedWeight(currency, derivationMode);
186
+
187
+ let outputsWeight = byteSize(outputAddrs.length) * baseByte; // Number of outputs;
188
+ outputAddrs.forEach((addr) => {
189
+ outputsWeight += outputSize(currency, addr) * baseByte;
190
+ });
191
+ if (includeChange) {
192
+ outputsWeight += outputWeight(derivationMode);
193
+ }
194
+
195
+ // Input: 32 PrevTxHash + 4 Index + 1 scriptSigLength + 4 sequence
196
+ let inputsWeight = byteSize(inputCount) * baseByte; // Number of inputs
197
+ inputsWeight += inputWeight(derivationMode) * inputCount;
198
+
199
+ const txWeight = fixed + inputsWeight + outputsWeight;
200
+ return txWeight / 4;
201
+ }
202
+
203
+ export function maxTxSizeCeil(
204
+ inputCount: number,
205
+ outputAddrs: string[],
206
+ includeChange: boolean,
207
+ currency: ICrypto,
208
+ derivationMode: string
209
+ ): number {
210
+ const s = maxTxSize(
211
+ inputCount,
212
+ outputAddrs,
213
+ includeChange,
214
+ currency,
215
+ derivationMode
216
+ );
217
+ return Math.ceil(s);
146
218
  }
147
219
 
148
220
  // refer to https://github.com/LedgerHQ/lib-ledger-core/blob/fc9d762b83fc2b269d072b662065747a64ab2816/core/src/wallet/bitcoin/api_impl/BitcoinLikeTransactionApi.cpp#L253
@@ -122,7 +122,8 @@ class BitcoinLikeWallet {
122
122
  account: Account,
123
123
  feePerByte: number,
124
124
  excludeUTXOs: Array<{ hash: string; outputIndex: number }>,
125
- pickUnconfirmedRBF: boolean
125
+ pickUnconfirmedRBF: boolean,
126
+ outputAddresses: string[] = []
126
127
  ) {
127
128
  const addresses = await account.xpub.getXpubAddresses();
128
129
  const utxos = flatten(
@@ -133,8 +134,8 @@ class BitcoinLikeWallet {
133
134
  )
134
135
  );
135
136
  let balance = new BigNumber(0);
136
- let availableUtxoCount = 0;
137
137
  log("btcwallet", "estimateAccountMaxSpendable utxos", utxos);
138
+ let usableUtxoCount = 0;
138
139
  utxos.forEach((utxo) => {
139
140
  if (
140
141
  !excludeUTXOs.find(
@@ -144,20 +145,22 @@ class BitcoinLikeWallet {
144
145
  )
145
146
  ) {
146
147
  if ((pickUnconfirmedRBF && utxo.rbf) || utxo.block_height !== null) {
148
+ usableUtxoCount++;
147
149
  balance = balance.plus(utxo.value);
148
- availableUtxoCount += 1;
149
150
  }
150
151
  }
151
152
  });
152
153
  // fees if we use all utxo
153
154
  const fees =
154
155
  feePerByte *
155
- utils.estimateTxSize(
156
- availableUtxoCount,
157
- 1,
156
+ utils.maxTxSizeCeil(
157
+ usableUtxoCount,
158
+ outputAddresses,
159
+ outputAddresses.length == 0,
158
160
  account.xpub.crypto,
159
161
  account.xpub.derivationMode
160
162
  );
163
+
161
164
  log("btcwallet", "estimateAccountMaxSpendable balance", balance);
162
165
  log("btcwallet", "estimateAccountMaxSpendable fees", fees);
163
166
  const maxSpendable = balance.minus(fees);
@@ -288,9 +288,8 @@ class Xpub extends EventEmitter {
288
288
  needChangeoutput,
289
289
  } = await params.utxoPickingStrategy.selectUnspentUtxosToUse(
290
290
  this,
291
- params.amount,
292
- params.feePerByte,
293
- outputs.length
291
+ outputs,
292
+ params.feePerByte
294
293
  );
295
294
 
296
295
  const txHexs = await Promise.all(
@@ -320,9 +319,10 @@ class Xpub extends EventEmitter {
320
319
  (utxo, index) => [txs[index].account, txs[index].index]
321
320
  );
322
321
 
323
- const txSize = utils.estimateTxSize(
322
+ const txSize = utils.maxTxSizeCeil(
324
323
  unspentUtxoSelected.length,
325
- outputs.length + 1,
324
+ outputs.map((o) => o.address),
325
+ true,
326
326
  this.crypto,
327
327
  this.derivationMode
328
328
  );
package/src/operation.ts CHANGED
@@ -167,5 +167,5 @@ export const isConfirmedOperation = (
167
167
  confirmationsNb: number
168
168
  ): boolean =>
169
169
  operation.blockHeight
170
- ? account.blockHeight - operation.blockHeight + 1 > confirmationsNb
170
+ ? account.blockHeight - operation.blockHeight + 1 >= confirmationsNb
171
171
  : false;