ln-service 53.16.1 → 53.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # Versions
2
2
 
3
+ ## 53.17.0
4
+
5
+ - `signTransaction`: Add `root_hash` to support Taproot signatures with scripts
6
+
3
7
  ## 53.16.1
4
8
 
5
9
  - `getFailedPayments`, `getPayments`, `getPendingPayments`: Remove
package/README.md CHANGED
@@ -4781,6 +4781,7 @@ Requires LND built with `signrpc` build tag
4781
4781
 
4782
4782
  Requires `signer:generate` permission
4783
4783
 
4784
+ `root_hash` is not supported in LND 0.14.3 and below
4784
4785
  `spending` is not supported in LND 0.14.3 and below
4785
4786
 
4786
4787
  {
@@ -4789,9 +4790,10 @@ Requires `signer:generate` permission
4789
4790
  key_index: <Key Index Number>
4790
4791
  output_script: <Output Script Hex String>
4791
4792
  output_tokens: <Output Tokens Number>
4793
+ [root_hash]: <Taproot Root Hash Hex String>
4792
4794
  sighash: <Sighash Type Number>
4793
4795
  vin: <Input Index To Sign Number>
4794
- witness_script: <Witness Script Hex String>
4796
+ [witness_script]: <Witness Script Hex String>
4795
4797
  }]
4796
4798
  lnd: <Authenticated LND API Object>
4797
4799
  [spending]: [{
package/package.json CHANGED
@@ -11,7 +11,7 @@
11
11
  "cors": "2.8.5",
12
12
  "express": "4.18.1",
13
13
  "invoices": "2.0.6",
14
- "lightning": "5.15.1",
14
+ "lightning": "5.16.0",
15
15
  "macaroon": "3.0.4",
16
16
  "morgan": "1.10.0",
17
17
  "ws": "8.6.0"
@@ -28,7 +28,7 @@
28
28
  "bn.js": "5.2.0",
29
29
  "bs58check": "2.1.2",
30
30
  "ecpair": "2.0.1",
31
- "ln-docker-daemons": "2.2.10",
31
+ "ln-docker-daemons": "2.2.11",
32
32
  "p2tr": "1.3.1",
33
33
  "portfinder": "1.0.28",
34
34
  "psbt": "2.0.1",
@@ -70,5 +70,5 @@
70
70
  "integration-test-0.12.0": "DOCKER_LND_VERSION=v0.12.0-beta npm run test",
71
71
  "test": "echo $DOCKER_LND_VERSION && tap -j 2 --branches=1 --functions=1 --lines=1 --statements=1 -t 200 test/autopilotrpc-integration/*.js test/chainrpc-integration/*.js test/integration/*.js test/invoicesrpc-integration/*.js test/peersrpc-integration/*.js test/routerrpc-integration/*.js test/signerrpc-integration/*.js test/tower_clientrpc-integration/*.js test/tower_serverrpc-integration/*.js test/walletrpc-integration/*.js"
72
72
  },
73
- "version": "53.16.1"
73
+ "version": "53.17.0"
74
74
  }
@@ -5,7 +5,7 @@ const {returnResult} = require('asyncjs-util');
5
5
  const {getChannel} = require('./../../');
6
6
  const {getChannels} = require('./../../');
7
7
 
8
- const interval = 10;
8
+ const interval = 20;
9
9
  const times = 10000;
10
10
 
11
11
  /** Wait for channel to be open
@@ -0,0 +1,359 @@
1
+ const asyncRetry = require('async/retry');
2
+ const {address} = require('bitcoinjs-lib');
3
+ const {controlBlock} = require('p2tr');
4
+ const {createPsbt} = require('psbt');
5
+ const {hashForTree} = require('p2tr');
6
+ const {networks} = require('bitcoinjs-lib');
7
+ const {script} = require('bitcoinjs-lib');
8
+ const {spawnLightningCluster} = require('ln-docker-daemons');
9
+ const {test} = require('@alexbosworth/tap');
10
+ const tinysecp = require('tiny-secp256k1');
11
+ const {Transaction} = require('bitcoinjs-lib');
12
+ const {v1OutputScript} = require('p2tr');
13
+
14
+ const {beginGroupSigningSession} = require('./../../');
15
+ const {broadcastChainTransaction} = require('./../../');
16
+ const {createChainAddress} = require('./../../');
17
+ const {fundPsbt} = require('./../../');
18
+ const {getPublicKey} = require('./../../');
19
+ const {getUtxos} = require('./../../');
20
+ const {signPsbt} = require('./../../');
21
+ const {signTransaction} = require('./../../');
22
+
23
+ const {compile} = script;
24
+ const count = 100;
25
+ const defaultInternalKey = '0350929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0';
26
+ const {fromHex} = Transaction;
27
+ const hexAsBuffer = hex => Buffer.from(hex, 'hex');
28
+ const interval = retryCount => 10 * Math.pow(2, retryCount);
29
+ const OP_CHECKSIG = 172;
30
+ const smallTokens = 2e5;
31
+ const times = 20;
32
+ const {toOutputScript} = address;
33
+ const tokens = 1e6;
34
+
35
+ // Signing a taproot transaction should result in a valid signature
36
+ test(`Sign a taproot transaction`, async ({end, equal}) => {
37
+ const ecp = (await import('ecpair')).ECPairFactory(tinysecp);
38
+ const {kill, nodes} = await spawnLightningCluster({});
39
+
40
+ const [{generate, lnd}] = nodes;
41
+
42
+ try {
43
+ await beginGroupSigningSession({
44
+ lnd,
45
+ is_key_spend: true,
46
+ key_family: 0,
47
+ key_index: 0,
48
+ public_keys: [Buffer.alloc(33, 2).toString('hex')],
49
+ });
50
+ } catch (err) {
51
+ // On LND 0.14.3 and below, taproot signing is not supported
52
+ if (err.slice().shift() === 501) {
53
+ await kill({});
54
+
55
+ return end();
56
+ }
57
+
58
+ throw err;
59
+ }
60
+
61
+ await generate({count});
62
+
63
+ const {address} = await createChainAddress({lnd});
64
+ const [utxo] = (await getUtxos({lnd})).utxos;
65
+
66
+ const funded = await asyncRetry({interval, times}, async () => {
67
+ try {
68
+ return await fundPsbt({
69
+ lnd,
70
+ inputs: [{
71
+ transaction_id: utxo.transaction_id,
72
+ transaction_vout: utxo.transaction_vout,
73
+ }],
74
+ outputs: [{address, tokens}],
75
+ });
76
+ } catch (err) {
77
+ // On LND 0.11.1 and below, funding a PSBT is not supported
78
+ if (err.slice().shift() === 501) {
79
+ return;
80
+ }
81
+
82
+ throw err;
83
+ }
84
+ });
85
+
86
+ // A Taproot script output should be funded and spent with script
87
+ try {
88
+ await generate({count});
89
+
90
+ const scriptKey = await getPublicKey({lnd, family: 805});
91
+
92
+ const publicKey = hexAsBuffer(scriptKey.public_key);
93
+
94
+ const witnessScript = compile([publicKey.slice(1), OP_CHECKSIG]);
95
+
96
+ const branches = [{script: witnessScript.toString('hex')}];
97
+
98
+ const {hash} = hashForTree({branches});
99
+
100
+ const output = v1OutputScript({hash, internal_key: defaultInternalKey});
101
+
102
+ const [utxo] = (await getUtxos({lnd})).utxos.reverse();
103
+
104
+ // Make a PSBT paying to the Taproot output
105
+ const {psbt} = createPsbt({
106
+ outputs: [{tokens, script: output.script}],
107
+ utxos: [{id: utxo.transaction_id, vout: utxo.transaction_vout}],
108
+ });
109
+
110
+ // Sign the PSBT
111
+ const signed = await signPsbt({
112
+ lnd,
113
+ psbt: (await fundPsbt({lnd, psbt})).psbt,
114
+ });
115
+
116
+ // Send the tx to the chain
117
+ await broadcastChainTransaction({lnd, transaction: signed.transaction});
118
+
119
+ // Make a new tx that will spend the output back into the wallet
120
+ const tx = new Transaction();
121
+
122
+ // The new tx spends the Taproot output
123
+ tx.addInput(
124
+ fromHex(signed.transaction).getHash(),
125
+ fromHex(signed.transaction).outs.findIndex(n => n.value === tokens)
126
+ );
127
+
128
+ // Make an output to pay back into the wallet
129
+ const chainOutput = toOutputScript(
130
+ (await createChainAddress({lnd})).address,
131
+ networks.regtest
132
+ );
133
+
134
+ // Add output to the pay back transaction
135
+ tx.addOutput(chainOutput, smallTokens);
136
+
137
+ const {signatures} = await signTransaction({
138
+ lnd,
139
+ inputs: [{
140
+ key_family: 805,
141
+ key_index: scriptKey.index,
142
+ output_script: output.script,
143
+ output_tokens: tokens,
144
+ root_hash: hash,
145
+ sighash: Transaction.SIGHASH_DEFAULT,
146
+ vin: 0,
147
+ witness_script: witnessScript.toString('hex'),
148
+ }],
149
+ transaction: tx.toHex(),
150
+ });
151
+
152
+ const [signature] = signatures.map(hexAsBuffer);
153
+
154
+ const {block} = controlBlock({
155
+ external_key: output.external_key,
156
+ leaf_script: witnessScript.toString('hex'),
157
+ script_branches: branches,
158
+ });
159
+
160
+ // Add the signature to the input
161
+ tx.ins.forEach((input, i) => {
162
+ return tx.setWitness(i, [
163
+ signature,
164
+ witnessScript,
165
+ hexAsBuffer(block),
166
+ ]);
167
+ });
168
+
169
+ await broadcastChainTransaction({lnd, transaction: tx.toHex()});
170
+
171
+ await asyncRetry({interval, times}, async () => {
172
+ await generate({});
173
+
174
+ const {utxos} = await getUtxos({lnd});
175
+
176
+ const utxo = utxos.find(n => n.transaction_id === tx.getId());
177
+
178
+ if (!utxo || !utxo.confirmation_count) {
179
+ throw new Error('ExpectedReceivedTaprootSpend');
180
+ }
181
+ });
182
+ } catch (err) {
183
+ equal(err, null, 'Expected no error');
184
+ }
185
+
186
+ // A Taproot script can be funded and spent with internal key + script hash
187
+ try {
188
+ await generate({count});
189
+
190
+ const topLevelKey = await getPublicKey({lnd, family: 805});
191
+
192
+ const unusedKey = ecp.makeRandom({network: networks.regtest});
193
+
194
+ const witnessScript = compile([unusedKey.publicKey.slice(1), OP_CHECKSIG]);
195
+
196
+ const branches = [{script: witnessScript.toString('hex')}];
197
+
198
+ const {hash} = hashForTree({branches});
199
+
200
+ const output = v1OutputScript({
201
+ hash,
202
+ internal_key: topLevelKey.public_key,
203
+ });
204
+
205
+ const [utxo] = (await getUtxos({lnd})).utxos.reverse();
206
+
207
+ // Make a PSBT paying to the Taproot output
208
+ const {psbt} = createPsbt({
209
+ outputs: [{tokens, script: output.script}],
210
+ utxos: [{id: utxo.transaction_id, vout: utxo.transaction_vout}],
211
+ });
212
+
213
+ // Sign the PSBT
214
+ const signed = await signPsbt({
215
+ lnd,
216
+ psbt: (await fundPsbt({lnd, psbt})).psbt,
217
+ });
218
+
219
+ // Send the tx to the chain
220
+ await broadcastChainTransaction({lnd, transaction: signed.transaction});
221
+
222
+ // Make a new tx that will spend the output back into the wallet
223
+ const tx = new Transaction();
224
+
225
+ // The new tx spends the Taproot output
226
+ tx.addInput(
227
+ fromHex(signed.transaction).getHash(),
228
+ fromHex(signed.transaction).outs.findIndex(n => n.value === tokens)
229
+ );
230
+
231
+ // Make an output to pay back into the wallet
232
+ const chainOutput = toOutputScript(
233
+ (await createChainAddress({lnd})).address,
234
+ networks.regtest
235
+ );
236
+
237
+ // Add output to the pay back transaction
238
+ tx.addOutput(chainOutput, smallTokens);
239
+
240
+ const {signatures} = await signTransaction({
241
+ lnd,
242
+ inputs: [{
243
+ key_family: 805,
244
+ key_index: topLevelKey.index,
245
+ output_script: output.script,
246
+ output_tokens: tokens,
247
+ root_hash: hash,
248
+ sighash: Transaction.SIGHASH_DEFAULT,
249
+ vin: 0,
250
+ }],
251
+ transaction: tx.toHex(),
252
+ });
253
+
254
+ const [signature] = signatures.map(hexAsBuffer);
255
+
256
+ // Add the signature to the input
257
+ tx.ins.forEach((input, i) => tx.setWitness(i, [signature]));
258
+
259
+ await broadcastChainTransaction({lnd, transaction: tx.toHex()});
260
+
261
+ await asyncRetry({interval, times}, async () => {
262
+ await generate({});
263
+
264
+ const {utxos} = await getUtxos({lnd});
265
+
266
+ const utxo = utxos.find(n => n.transaction_id === tx.getId());
267
+
268
+ if (!utxo || !utxo.confirmation_count) {
269
+ throw new Error('ExpectedReceivedTaprootSpend');
270
+ }
271
+ });
272
+ } catch (err) {
273
+ equal(err, null, 'Expected no error');
274
+ }
275
+
276
+ // A Taproot script can be funded and spent with bip86 internal key
277
+ try {
278
+ await generate({count});
279
+
280
+ const topLevelKey = await getPublicKey({lnd, family: 805});
281
+
282
+ const output = v1OutputScript({
283
+ internal_key: topLevelKey.public_key,
284
+ });
285
+
286
+ const [utxo] = (await getUtxos({lnd})).utxos.reverse();
287
+
288
+ // Make a PSBT paying to the Taproot output
289
+ const {psbt} = createPsbt({
290
+ outputs: [{tokens, script: output.script}],
291
+ utxos: [{id: utxo.transaction_id, vout: utxo.transaction_vout}],
292
+ });
293
+
294
+ // Sign the PSBT
295
+ const signed = await signPsbt({
296
+ lnd,
297
+ psbt: (await fundPsbt({lnd, psbt})).psbt,
298
+ });
299
+
300
+ // Send the tx to the chain
301
+ await broadcastChainTransaction({lnd, transaction: signed.transaction});
302
+
303
+ // Make a new tx that will spend the output back into the wallet
304
+ const tx = new Transaction();
305
+
306
+ // The new tx spends the Taproot output
307
+ tx.addInput(
308
+ fromHex(signed.transaction).getHash(),
309
+ fromHex(signed.transaction).outs.findIndex(n => n.value === tokens)
310
+ );
311
+
312
+ // Make an output to pay back into the wallet
313
+ const chainOutput = toOutputScript(
314
+ (await createChainAddress({lnd})).address,
315
+ networks.regtest
316
+ );
317
+
318
+ // Add output to the pay back transaction
319
+ tx.addOutput(chainOutput, smallTokens);
320
+
321
+ const {signatures} = await signTransaction({
322
+ lnd,
323
+ inputs: [{
324
+ key_family: 805,
325
+ key_index: topLevelKey.index,
326
+ output_script: output.script,
327
+ output_tokens: tokens,
328
+ sighash: Transaction.SIGHASH_DEFAULT,
329
+ vin: 0,
330
+ }],
331
+ transaction: tx.toHex(),
332
+ });
333
+
334
+ const [signature] = signatures.map(hexAsBuffer);
335
+
336
+ // Add the signature to the input
337
+ tx.ins.forEach((input, i) => tx.setWitness(i, [signature]));
338
+
339
+ await broadcastChainTransaction({lnd, transaction: tx.toHex()});
340
+
341
+ await asyncRetry({interval, times}, async () => {
342
+ await generate({});
343
+
344
+ const {utxos} = await getUtxos({lnd});
345
+
346
+ const utxo = utxos.find(n => n.transaction_id === tx.getId());
347
+
348
+ if (!utxo || !utxo.confirmation_count) {
349
+ throw new Error('ExpectedReceivedTaprootSpend');
350
+ }
351
+ });
352
+ } catch (err) {
353
+ equal(err, null, 'Expected no error');
354
+ }
355
+
356
+ await kill({});
357
+
358
+ return end();
359
+ });
@@ -15,7 +15,7 @@ test(`Sign transaction`, async ({end, equal}) => {
15
15
  inputs: [{
16
16
  key_family: 6,
17
17
  key_index: 1,
18
- output_script: '00',
18
+ output_script: '00147ab105a90ccd7e49d96672abcac2995bdb852baa',
19
19
  output_tokens: 1e8,
20
20
  sighash: Transaction.SIGHASH_ALL,
21
21
  vin: 0,