ln-service 53.15.0 → 53.16.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,11 @@
1
1
  # Versions
2
2
 
3
+ ## 53.16.0
4
+
5
+ - `beginGroupSigningSession`: Add method to start a MuSig2 signing session
6
+ - `endGroupSigningSession`: Add method to complete a MuSig2 signing session
7
+ - `updateGroupSigningSession`: Add method to add nonces to a MuSig2 session
8
+
3
9
  ## 53.15.0
4
10
 
5
11
  - `getRouteToDestination`, `isDestinationPayable`, `pay`,
package/README.md CHANGED
@@ -105,6 +105,7 @@ for `unlocker` methods.
105
105
  - [addExternalSocket](#addexternalsocket) - Advertise a new p2p host:ip address
106
106
  - [addPeer](#addpeer) - Connect to a peer
107
107
  - [authenticatedLndGrpc](#authenticatedlndgrpc) - LND API Object
108
+ - [beginGroupSigningSession](#begingroupsigningsession) - Start MuSig2 session
108
109
  - [broadcastChainTransaction](#broadcastchaintransaction) - Push a chain tx
109
110
  - [cancelHodlInvoice](#cancelhodlinvoice) - Cancel a held or any open invoice
110
111
  - [cancelPendingChannel](#cancelpendingchannel) - Cancel a pending open channel
@@ -132,6 +133,7 @@ for `unlocker` methods.
132
133
  - [disableChannel](#disablechannel) - Disable a channel for outgoing payments
133
134
  - [disconnectWatchtower](#disconnectwatchtower) - Disconnect a watchtower
134
135
  - [enableChannel](#enablechannel) - Enable a channel for outgoing payments
136
+ - [endGroupSigningSession](#endgroupsigningsession) - Complete MuSig2 session
135
137
  - [fundPendingChannels](#fundpendingchannels) - Fund pending open channels
136
138
  - [fundPsbt](#fundpsbt) - Create an unsigned PSBT with funding inputs and
137
139
  spending outputs
@@ -256,6 +258,7 @@ for `unlocker` methods.
256
258
  transaction
257
259
  - [updateColor](#updatecolor) - Update node graph color value
258
260
  - [updateConnectedWatchtower](#updateconnectedwatchtower) - Update watchtower
261
+ - [updateGroupSigningSession](#updategroupsigningsession) - Sign with MuSig2
259
262
  - [updatePathfindingSettings](#updatepathfindingsettings) - Update pathfinding
260
263
  configuration
261
264
  - [updateRoutingFees](#updateroutingfees) - Change routing fees
@@ -277,7 +280,7 @@ for `unlocker` methods.
277
280
  - [ln-accounting](https://npmjs.com/package/ln-accounting) - accounting records
278
281
  - [ln-docker-daemons](https://github.com/alexbosworth/ln-docker-daemons) -
279
282
  run regtest integration tests
280
- - [ln-pathfinding](https://npmjs.com/package/ln-accounting) - pathfinding
283
+ - [ln-pathfinding](https://npmjs.com/package/ln-pathfinding) - pathfinding
281
284
  utilities
282
285
  - [ln-sync](https://www.npmjs.com/package/ln-sync) - metadata helper methods
283
286
  - [probing](https://npmjs.com/package/probing) - payment probing utilities
@@ -380,6 +383,46 @@ const {lnd} = lnService.authenticatedLndGrpc({
380
383
  const wallet = await lnService.getWalletInfo({lnd});
381
384
  ```
382
385
 
386
+ ### beginGroupSigningSession
387
+
388
+ Start a MuSig2 group signing session
389
+
390
+ Requires LND built with `signrpc`, `walletrpc` build tags
391
+
392
+ Requires `address:read`, `signer:generate` permissions
393
+
394
+ This method is not supported in LND 0.14.3 and below
395
+
396
+ {
397
+ lnd: <Authenticated LND API Object>
398
+ [is_key_spend]: <Key Is BIP 86 Key Spend Key Bool>
399
+ key_family: <HD Seed Key Family Number>
400
+ key_index: <Key Index Number>
401
+ public_keys: [<External Public Key Hex String>]
402
+ [root_hash]: <Taproot Script Root Hash Hex String>
403
+ }
404
+
405
+ @returns via cbk or Promise
406
+ {
407
+ external_key: <Final Script or Top Level Public Key Hex String>
408
+ id: <Session Id Hex String>
409
+ [internal_key]: <Internal Top Level Public Key Hex String>
410
+ nonce: <Session Compound Nonces Hex String>
411
+ }
412
+
413
+ Example:
414
+
415
+ ```node
416
+ const {beginGroupSigningSession} = require('ln-service');
417
+
418
+ const session = await beginGroupSigningSession({
419
+ lnd,
420
+ key_family: 0,
421
+ key_index: 0,
422
+ public_keys: [externalPublicKey],
423
+ });
424
+ ```
425
+
383
426
  ### broadcastChainTransaction
384
427
 
385
428
  Publish a raw blockchain transaction to Blockchain network peers
@@ -1118,6 +1161,36 @@ await enableChannel({
1118
1161
  });
1119
1162
  ```
1120
1163
 
1164
+ ### endGroupSigningSession
1165
+
1166
+ Complete a MuSig2 signing session
1167
+
1168
+ Requires LND built with `signrpc` build tag
1169
+
1170
+ Requires `signer:generate` permission
1171
+
1172
+ This method is not supported in LND 0.14.3 and below
1173
+
1174
+ {
1175
+ id: <Session Id Hex String>
1176
+ lnd: <Authenticated LND API Object>
1177
+ [signatures]: [<Combine External Partial Signature Hex String>]
1178
+ }
1179
+
1180
+ @returns via cbk or Promise
1181
+ {
1182
+ [signature]: <Combined Signature Hex String>
1183
+ }
1184
+
1185
+ Example:
1186
+
1187
+ ```node
1188
+ const {endGroupSigningSession} = require('ln-service');
1189
+
1190
+ // Cancel a group signing session
1191
+ await endGroupSigningSession({id, lnd});
1192
+ ```
1193
+
1121
1194
  ### fundPendingChannels
1122
1195
 
1123
1196
  Fund pending channels
@@ -6761,6 +6834,43 @@ await updateConnectedWatchtower({
6761
6834
  });
6762
6835
  ```
6763
6836
 
6837
+ ### updateGroupSigningSession
6838
+
6839
+ Update a MuSig2 signing session with nonces and generate a partial sig
6840
+
6841
+ All remote nonces are expected to be passed
6842
+
6843
+ Requires LND built with `signrpc` build tag
6844
+
6845
+ Requires `signer:generate` permission
6846
+
6847
+ This method is not supported in LND 0.14.3 and below
6848
+
6849
+ {
6850
+ hash: <Hash to Sign Hex String>
6851
+ id: <MuSig2 Session Id Hex String>
6852
+ lnd: <Authenticated LND API Object>
6853
+ nonces: [<Nonce Hex String>]
6854
+ }
6855
+
6856
+ @returns via cbk or Promise
6857
+ {
6858
+ signature: <Partial Signature Hex String>
6859
+ }
6860
+
6861
+ Example:
6862
+
6863
+ ```node
6864
+ const {updateGroupSigningSession} = require('ln-service');
6865
+
6866
+ const {signature} = await updateGroupSigningSession({
6867
+ lnd,
6868
+ hash: v1TxDigestHash,
6869
+ id: sessionId,
6870
+ nonces: [externalNonce],
6871
+ });
6872
+ ```
6873
+
6764
6874
  ### updatePathfindingSettings
6765
6875
 
6766
6876
  Update current pathfinding settings
package/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  const {addExternalSocket} = require('lightning');
2
2
  const {addPeer} = require('lightning');
3
3
  const {authenticatedLndGrpc} = require('lightning');
4
+ const {beginGroupSigningSession} = require('lightning');
4
5
  const {broadcastChainTransaction} = require('lightning');
5
6
  const {cancelHodlInvoice} = require('lightning');
6
7
  const {cancelPendingChannel} = require('lightning');
@@ -25,6 +26,7 @@ const {diffieHellmanComputeSecret} = require('lightning');
25
26
  const {disableChannel} = require('lightning');
26
27
  const {disconnectWatchtower} = require('lightning');
27
28
  const {enableChannel} = require('lightning');
29
+ const {endGroupSigningSession} = require('lightning');
28
30
  const {fundPendingChannels} = require('lightning');
29
31
  const {fundPsbt} = require('lightning');
30
32
  const {getAccessIds} = require('lightning');
@@ -135,6 +137,7 @@ const {updateAlias} = require('lightning');
135
137
  const {updateChainTransaction} = require('lightning');
136
138
  const {updateColor} = require('lightning');
137
139
  const {updateConnectedWatchtower} = require('lightning');
140
+ const {updateGroupSigningSession} = require('lightning');
138
141
  const {updatePathfindingSettings} = require('lightning');
139
142
  const {updateRoutingFees} = require('lightning');
140
143
  const {verifyAccess} = require('lightning');
@@ -147,6 +150,7 @@ module.exports = {
147
150
  addExternalSocket,
148
151
  addPeer,
149
152
  authenticatedLndGrpc,
153
+ beginGroupSigningSession,
150
154
  broadcastChainTransaction,
151
155
  cancelHodlInvoice,
152
156
  cancelPendingChannel,
@@ -171,6 +175,7 @@ module.exports = {
171
175
  disableChannel,
172
176
  disconnectWatchtower,
173
177
  enableChannel,
178
+ endGroupSigningSession,
174
179
  fundPendingChannels,
175
180
  fundPsbt,
176
181
  getAccessIds,
@@ -281,6 +286,7 @@ module.exports = {
281
286
  updateChainTransaction,
282
287
  updateColor,
283
288
  updateConnectedWatchtower,
289
+ updateGroupSigningSession,
284
290
  updatePathfindingSettings,
285
291
  updateRoutingFees,
286
292
  verifyAccess,
package/package.json CHANGED
@@ -9,12 +9,12 @@
9
9
  "dependencies": {
10
10
  "bolt07": "1.8.1",
11
11
  "cors": "2.8.5",
12
- "express": "4.17.3",
12
+ "express": "4.18.1",
13
13
  "invoices": "2.0.6",
14
- "lightning": "5.14.0",
14
+ "lightning": "5.15.0",
15
15
  "macaroon": "3.0.4",
16
16
  "morgan": "1.10.0",
17
- "ws": "8.5.0"
17
+ "ws": "8.6.0"
18
18
  },
19
19
  "description": "Interaction helper for your Lightning Network daemon",
20
20
  "devDependencies": {
@@ -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.9",
31
+ "ln-docker-daemons": "2.2.10",
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.15.0"
73
+ "version": "53.16.0"
74
74
  }
@@ -66,7 +66,7 @@ test(`Subscribe to chain transactions`, async ({end, equal, fail}) => {
66
66
  .filter(n => !!n.is_outgoing);
67
67
 
68
68
  if (!transaction) {
69
- throw new Error('ExpectedTrnasaction');
69
+ throw new Error('ExpectedTransactionInSubscribeToChainAddressResult');
70
70
  }
71
71
 
72
72
  // Wait for generation to be over
@@ -0,0 +1,440 @@
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 {leafHash} = require('p2tr');
7
+ const {networks} = require('bitcoinjs-lib');
8
+ const {script} = require('bitcoinjs-lib');
9
+ const {spawnLightningCluster} = require('ln-docker-daemons');
10
+ const {test} = require('@alexbosworth/tap');
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 {endGroupSigningSession} = require('./../../');
18
+ const {fundPsbt} = require('./../../');
19
+ const {getPublicKey} = require('./../../');
20
+ const {getUtxos} = require('./../../');
21
+ const {signPsbt} = require('./../../');
22
+ const {updateGroupSigningSession} = require('./../../');
23
+
24
+ const {compile} = script;
25
+ const count = 100;
26
+ const defaultInternalKey = '0350929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0';
27
+ const {from} = Buffer;
28
+ const {fromHex} = Transaction;
29
+ const hexAsBuffer = hex => Buffer.from(hex, 'hex');
30
+ const interval = 100;
31
+ const OP_CHECKSIG = 172;
32
+ const smallTokens = 2e5;
33
+ const times = 20;
34
+ const {toOutputScript} = address;
35
+ const tokens = 1e6;
36
+
37
+ // Starting a group signing session should result in a new MuSig2 session
38
+ test(`Begin group signing session`, async ({end, equal}) => {
39
+ const {kill, nodes} = await spawnLightningCluster({size: 2});
40
+
41
+ const [{generate, lnd}, target] = nodes;
42
+
43
+ try {
44
+ await beginGroupSigningSession({
45
+ lnd,
46
+ is_key_spend: true,
47
+ key_family: 0,
48
+ key_index: 0,
49
+ public_keys: [Buffer.alloc(33, 2).toString('hex')],
50
+ });
51
+ } catch (err) {
52
+ // On LND 0.14.3 and below, group signing is not supported
53
+ if (err.slice().shift() === 501) {
54
+ await kill({});
55
+
56
+ return end();
57
+ }
58
+
59
+ throw err;
60
+ }
61
+
62
+ // A Taproot script can be funded and spent with MuSig2
63
+ try {
64
+ await generate({count});
65
+
66
+ const controlKey = await getPublicKey({lnd, family: 0});
67
+ const targetKey = await getPublicKey({family: 0, lnd: target.lnd});
68
+
69
+ const controlGroup = await beginGroupSigningSession({
70
+ lnd,
71
+ is_key_spend: true,
72
+ key_family: 0,
73
+ key_index: controlKey.index,
74
+ public_keys: [targetKey.public_key],
75
+ });
76
+
77
+ const targetGroup = await beginGroupSigningSession({
78
+ is_key_spend: true,
79
+ key_family: 0,
80
+ key_index: targetKey.index,
81
+ lnd: target.lnd,
82
+ public_keys: [controlKey.public_key],
83
+ });
84
+
85
+ equal(controlGroup.external_key, targetGroup.external_key, 'Equal e-keys');
86
+ equal(controlGroup.internal_key, targetGroup.internal_key, 'Equal i-keys');
87
+
88
+ const script = Buffer.concat([
89
+ Buffer.from([81]),
90
+ Buffer.from([32]),
91
+ hexAsBuffer(controlGroup.external_key),
92
+ ]);
93
+
94
+ const [utxo] = (await getUtxos({lnd})).utxos.reverse();
95
+
96
+ // Make a PSBT paying to the Taproot output
97
+ const {psbt} = createPsbt({
98
+ outputs: [{tokens, script: script.toString('hex')}],
99
+ utxos: [{id: utxo.transaction_id, vout: utxo.transaction_vout}],
100
+ });
101
+
102
+ // Sign the PSBT
103
+ const signed = await signPsbt({
104
+ lnd,
105
+ psbt: (await fundPsbt({lnd, psbt})).psbt,
106
+ });
107
+
108
+ // Send the tx to the chain
109
+ await broadcastChainTransaction({lnd, transaction: signed.transaction});
110
+
111
+ // Make a new tx that will spend the output back into the wallet
112
+ const tx = new Transaction();
113
+
114
+ // The new tx spends the Taproot output
115
+ tx.addInput(
116
+ fromHex(signed.transaction).getHash(),
117
+ fromHex(signed.transaction).outs.findIndex(n => n.value === tokens)
118
+ );
119
+
120
+ // Make an output to pay back into the wallet
121
+ const chainOutput = toOutputScript(
122
+ (await createChainAddress({lnd})).address,
123
+ networks.regtest
124
+ );
125
+
126
+ // Add output to the pay back transaction
127
+ tx.addOutput(chainOutput, smallTokens);
128
+
129
+ const [hashToSign] = tx.ins.map((input, i) => {
130
+ return tx.hashForWitnessV1(
131
+ i,
132
+ [script],
133
+ [tokens],
134
+ Transaction.SIGHASH_DEFAULT,
135
+ );
136
+ });
137
+
138
+ const controlSign = await updateGroupSigningSession({
139
+ lnd,
140
+ hash: hashToSign.toString('hex'),
141
+ id: controlGroup.id,
142
+ nonces: [targetGroup.nonce],
143
+ });
144
+
145
+ const targetSign = await updateGroupSigningSession({
146
+ hash: hashToSign.toString('hex'),
147
+ id: targetGroup.id,
148
+ lnd: target.lnd,
149
+ nonces: [controlGroup.nonce],
150
+ });
151
+
152
+ await endGroupSigningSession({lnd: target.lnd, id: targetGroup.id});
153
+
154
+ const {signature} = await endGroupSigningSession({
155
+ lnd,
156
+ id: controlGroup.id,
157
+ signatures: [targetSign.signature],
158
+ });
159
+
160
+ // Add the signature to the input
161
+ tx.ins.forEach((input, i) => tx.setWitness(i, [hexAsBuffer(signature)]));
162
+
163
+ await broadcastChainTransaction({lnd, transaction: tx.toHex()});
164
+
165
+ await asyncRetry({interval, times}, async () => {
166
+ await generate({});
167
+
168
+ const {utxos} = await getUtxos({lnd});
169
+
170
+ const utxo = utxos.find(n => n.transaction_id === tx.getId());
171
+
172
+ if (!utxo || !utxo.confirmation_count) {
173
+ throw new Error('ExpectedReceivedTaprootSpend');
174
+ }
175
+ });
176
+ } catch (err) {
177
+ equal(err, null, 'Expected no error');
178
+ }
179
+
180
+ // A Taproot script can be funded and spent with MuSig2 for a script output
181
+ try {
182
+ await generate({count});
183
+
184
+ const controlKey = await getPublicKey({lnd, family: 0});
185
+ const targetKey = await getPublicKey({family: 0, lnd: target.lnd});
186
+ const unusedKey = (await getPublicKey({lnd, family: 805})).public_key;
187
+
188
+ const xOnlyUnused = hexAsBuffer(unusedKey.slice(2));
189
+
190
+ const witnessScript = compile([xOnlyUnused, OP_CHECKSIG]);
191
+
192
+ const branches = [{script: witnessScript.toString('hex')}];
193
+
194
+ const {hash} = hashForTree({branches});
195
+
196
+ const controlGroup = await beginGroupSigningSession({
197
+ lnd,
198
+ key_family: 0,
199
+ key_index: controlKey.index,
200
+ public_keys: [targetKey.public_key],
201
+ root_hash: hash,
202
+ });
203
+
204
+ const targetGroup = await beginGroupSigningSession({
205
+ key_family: 0,
206
+ key_index: targetKey.index,
207
+ lnd: target.lnd,
208
+ public_keys: [controlKey.public_key],
209
+ root_hash: hash,
210
+ });
211
+
212
+ equal(controlGroup.external_key, targetGroup.external_key, 'Equal e-keys');
213
+ equal(controlGroup.internal_key, targetGroup.internal_key, 'Equal i-keys');
214
+
215
+ const script = Buffer.concat([
216
+ from([81]),
217
+ from([32]),
218
+ hexAsBuffer(controlGroup.external_key),
219
+ ]);
220
+
221
+ const [utxo] = (await getUtxos({lnd})).utxos.reverse();
222
+
223
+ // Make a PSBT paying to the Taproot output
224
+ const {psbt} = createPsbt({
225
+ outputs: [{tokens, script: script.toString('hex')}],
226
+ utxos: [{id: utxo.transaction_id, vout: utxo.transaction_vout}],
227
+ });
228
+
229
+ // Sign the PSBT
230
+ const signed = await signPsbt({
231
+ lnd,
232
+ psbt: (await fundPsbt({lnd, psbt})).psbt,
233
+ });
234
+
235
+ // Send the tx to the chain
236
+ await broadcastChainTransaction({lnd, transaction: signed.transaction});
237
+
238
+ // Make a new tx that will spend the output back into the wallet
239
+ const tx = new Transaction();
240
+
241
+ // The new tx spends the Taproot output
242
+ tx.addInput(
243
+ fromHex(signed.transaction).getHash(),
244
+ fromHex(signed.transaction).outs.findIndex(n => n.value === tokens)
245
+ );
246
+
247
+ // Make an output to pay back into the wallet
248
+ const chainOutput = toOutputScript(
249
+ (await createChainAddress({lnd})).address,
250
+ networks.regtest
251
+ );
252
+
253
+ // Add output to the pay back transaction
254
+ tx.addOutput(chainOutput, smallTokens);
255
+
256
+ const [hashToSign] = tx.ins.map((input, i) => {
257
+ return tx.hashForWitnessV1(
258
+ i,
259
+ [script],
260
+ [tokens],
261
+ Transaction.SIGHASH_DEFAULT,
262
+ );
263
+ });
264
+
265
+ const controlSign = await updateGroupSigningSession({
266
+ lnd,
267
+ hash: hashToSign.toString('hex'),
268
+ id: controlGroup.id,
269
+ nonces: [targetGroup.nonce],
270
+ });
271
+
272
+ const targetSign = await updateGroupSigningSession({
273
+ hash: hashToSign.toString('hex'),
274
+ id: targetGroup.id,
275
+ lnd: target.lnd,
276
+ nonces: [controlGroup.nonce],
277
+ });
278
+
279
+ await endGroupSigningSession({lnd: target.lnd, id: targetGroup.id});
280
+
281
+ const {signature} = await endGroupSigningSession({
282
+ lnd,
283
+ id: controlGroup.id,
284
+ signatures: [targetSign.signature],
285
+ });
286
+
287
+ // Add the signature to the input
288
+ tx.ins.forEach((input, i) => tx.setWitness(i, [hexAsBuffer(signature)]));
289
+
290
+ await broadcastChainTransaction({lnd, transaction: tx.toHex()});
291
+
292
+ await asyncRetry({interval, times}, async () => {
293
+ await generate({});
294
+
295
+ const {utxos} = await getUtxos({lnd});
296
+
297
+ const utxo = utxos.find(n => n.transaction_id === tx.getId());
298
+
299
+ if (!utxo || !utxo.confirmation_count) {
300
+ throw new Error('ExpectedReceivedTaprootSpend');
301
+ }
302
+ });
303
+ } catch (err) {
304
+ equal(err, null, 'Expected no error');
305
+ }
306
+
307
+ // A Taproot script can be funded and spent on the script path
308
+ try {
309
+ await generate({count});
310
+
311
+ const controlKey = await getPublicKey({lnd, family: 0});
312
+ const targetKey = await getPublicKey({family: 0, lnd: target.lnd});
313
+
314
+ const controlGroup = await beginGroupSigningSession({
315
+ lnd,
316
+ key_family: 0,
317
+ key_index: controlKey.index,
318
+ public_keys: [targetKey.public_key],
319
+ });
320
+
321
+ const targetGroup = await beginGroupSigningSession({
322
+ key_family: 0,
323
+ key_index: targetKey.index,
324
+ lnd: target.lnd,
325
+ public_keys: [controlKey.public_key],
326
+ });
327
+
328
+ const scriptKey = hexAsBuffer(controlGroup.external_key);
329
+
330
+ const witnessScript = compile([scriptKey, OP_CHECKSIG]);
331
+
332
+ const branches = [{script: witnessScript.toString('hex')}];
333
+
334
+ const {hash} = hashForTree({branches});
335
+
336
+ const output = v1OutputScript({hash, internal_key: defaultInternalKey});
337
+
338
+ const [utxo] = (await getUtxos({lnd})).utxos.reverse();
339
+
340
+ // Make a PSBT paying to the Taproot output
341
+ const {psbt} = createPsbt({
342
+ outputs: [{tokens, script: output.script}],
343
+ utxos: [{id: utxo.transaction_id, vout: utxo.transaction_vout}],
344
+ });
345
+
346
+ // Sign the PSBT
347
+ const signed = await signPsbt({
348
+ lnd,
349
+ psbt: (await fundPsbt({lnd, psbt})).psbt,
350
+ });
351
+
352
+ // Send the tx to the chain
353
+ await broadcastChainTransaction({lnd, transaction: signed.transaction});
354
+
355
+ // Make a new tx that will spend the output back into the wallet
356
+ const tx = new Transaction();
357
+
358
+ // The new tx spends the Taproot output
359
+ tx.addInput(
360
+ fromHex(signed.transaction).getHash(),
361
+ fromHex(signed.transaction).outs.findIndex(n => n.value === tokens)
362
+ );
363
+
364
+ // Make an output to pay back into the wallet
365
+ const chainOutput = toOutputScript(
366
+ (await createChainAddress({lnd})).address,
367
+ networks.regtest
368
+ );
369
+
370
+ // Add output to the pay back transaction
371
+ tx.addOutput(chainOutput, smallTokens);
372
+
373
+ const [hashToSign] = tx.ins.map((input, i) => {
374
+ return tx.hashForWitnessV1(
375
+ i,
376
+ [hexAsBuffer(output.script)],
377
+ [tokens],
378
+ Transaction.SIGHASH_DEFAULT,
379
+ hexAsBuffer(leafHash({script: witnessScript.toString('hex')}).hash),
380
+ );
381
+ });
382
+
383
+ const controlSign = await updateGroupSigningSession({
384
+ lnd,
385
+ hash: hashToSign.toString('hex'),
386
+ id: controlGroup.id,
387
+ nonces: [targetGroup.nonce],
388
+ });
389
+
390
+ const targetSign = await updateGroupSigningSession({
391
+ hash: hashToSign.toString('hex'),
392
+ id: targetGroup.id,
393
+ lnd: target.lnd,
394
+ nonces: [controlGroup.nonce],
395
+ });
396
+
397
+ await endGroupSigningSession({lnd: target.lnd, id: targetGroup.id});
398
+
399
+ const {signature} = await endGroupSigningSession({
400
+ lnd,
401
+ id: controlGroup.id,
402
+ signatures: [targetSign.signature],
403
+ });
404
+
405
+ const {block} = controlBlock({
406
+ external_key: output.external_key,
407
+ leaf_script: witnessScript.toString('hex'),
408
+ script_branches: branches,
409
+ });
410
+
411
+ // Add the signature to the input
412
+ tx.ins.forEach((input, i) => {
413
+ return tx.setWitness(i, [
414
+ hexAsBuffer(signature),
415
+ witnessScript,
416
+ hexAsBuffer(block),
417
+ ]);
418
+ });
419
+
420
+ await broadcastChainTransaction({lnd, transaction: tx.toHex()});
421
+
422
+ await asyncRetry({interval, times}, async () => {
423
+ await generate({});
424
+
425
+ const {utxos} = await getUtxos({lnd});
426
+
427
+ const utxo = utxos.find(n => n.transaction_id === tx.getId());
428
+
429
+ if (!utxo || !utxo.confirmation_count) {
430
+ throw new Error('ExpectedReceivedTaprootSpend');
431
+ }
432
+ });
433
+ } catch (err) {
434
+ equal(err, null, 'Expected no error');
435
+ }
436
+
437
+ await kill({});
438
+
439
+ return end();
440
+ });