ln-service 53.15.0 → 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,21 @@
1
1
  # Versions
2
2
 
3
+ ## 53.17.0
4
+
5
+ - `signTransaction`: Add `root_hash` to support Taproot signatures with scripts
6
+
7
+ ## 53.16.1
8
+
9
+ - `getFailedPayments`, `getPayments`, `getPendingPayments`: Remove
10
+ `confirmed_at` date when a payment is not confirmed, add `created_at` and
11
+ `failed_at` dates for attempt start and attempt failed dates.
12
+
13
+ ## 53.16.0
14
+
15
+ - `beginGroupSigningSession`: Add method to start a MuSig2 signing session
16
+ - `endGroupSigningSession`: Add method to complete a MuSig2 signing session
17
+ - `updateGroupSigningSession`: Add method to add nonces to a MuSig2 session
18
+
3
19
  ## 53.15.0
4
20
 
5
21
  - `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
@@ -1746,7 +1819,9 @@ Requires `offchain:read` permission
1746
1819
  message: <Error Message String>
1747
1820
  }
1748
1821
  [index]: <Payment Add Index Number>
1749
- [confirmed_at]: <Payment Confirmed At ISO 8601 Date String>
1822
+ [confirmed_at]: <Payment Attempt Succeeded At ISO 8601 Date String>
1823
+ created_at: <Attempt Was Started At ISO 8601 Date String>
1824
+ [failed_at]: <Payment Attempt Failed At ISO 8601 Date String>
1750
1825
  is_confirmed: <Payment Attempt Succeeded Bool>
1751
1826
  is_failed: <Payment Attempt Failed Bool>
1752
1827
  is_pending: <Payment Attempt is Waiting For Resolution Bool>
@@ -2529,6 +2604,9 @@ Requires `offchain:read` permission
2529
2604
  }
2530
2605
  message: <Error Message String>
2531
2606
  }
2607
+ [confirmed_at]: <Payment Attempt Succeeded At ISO 8601 Date String>
2608
+ created_at: <Attempt Was Started At ISO 8601 Date String>
2609
+ [failed_at]: <Payment Attempt Failed At ISO 8601 Date String>
2532
2610
  is_confirmed: <Payment Attempt Succeeded Bool>
2533
2611
  is_failed: <Payment Attempt Failed Bool>
2534
2612
  is_pending: <Payment Attempt is Waiting For Resolution Bool>
@@ -2744,7 +2822,9 @@ Requires `offchain:read` permission
2744
2822
  message: <Error Message String>
2745
2823
  }
2746
2824
  [index]: <Payment Add Index Number>
2747
- [confirmed_at]: <Payment Confirmed At ISO 8601 Date String>
2825
+ [confirmed_at]: <Payment Attempt Succeeded At ISO 8601 Date String>
2826
+ created_at: <Attempt Was Started At ISO 8601 Date String>
2827
+ [failed_at]: <Payment Attempt Failed At ISO 8601 Date String>
2748
2828
  is_confirmed: <Payment Attempt Succeeded Bool>
2749
2829
  is_failed: <Payment Attempt Failed Bool>
2750
2830
  is_pending: <Payment Attempt is Waiting For Resolution Bool>
@@ -4701,6 +4781,7 @@ Requires LND built with `signrpc` build tag
4701
4781
 
4702
4782
  Requires `signer:generate` permission
4703
4783
 
4784
+ `root_hash` is not supported in LND 0.14.3 and below
4704
4785
  `spending` is not supported in LND 0.14.3 and below
4705
4786
 
4706
4787
  {
@@ -4709,9 +4790,10 @@ Requires `signer:generate` permission
4709
4790
  key_index: <Key Index Number>
4710
4791
  output_script: <Output Script Hex String>
4711
4792
  output_tokens: <Output Tokens Number>
4793
+ [root_hash]: <Taproot Root Hash Hex String>
4712
4794
  sighash: <Sighash Type Number>
4713
4795
  vin: <Input Index To Sign Number>
4714
- witness_script: <Witness Script Hex String>
4796
+ [witness_script]: <Witness Script Hex String>
4715
4797
  }]
4716
4798
  lnd: <Authenticated LND API Object>
4717
4799
  [spending]: [{
@@ -6761,6 +6843,43 @@ await updateConnectedWatchtower({
6761
6843
  });
6762
6844
  ```
6763
6845
 
6846
+ ### updateGroupSigningSession
6847
+
6848
+ Update a MuSig2 signing session with nonces and generate a partial sig
6849
+
6850
+ All remote nonces are expected to be passed
6851
+
6852
+ Requires LND built with `signrpc` build tag
6853
+
6854
+ Requires `signer:generate` permission
6855
+
6856
+ This method is not supported in LND 0.14.3 and below
6857
+
6858
+ {
6859
+ hash: <Hash to Sign Hex String>
6860
+ id: <MuSig2 Session Id Hex String>
6861
+ lnd: <Authenticated LND API Object>
6862
+ nonces: [<Nonce Hex String>]
6863
+ }
6864
+
6865
+ @returns via cbk or Promise
6866
+ {
6867
+ signature: <Partial Signature Hex String>
6868
+ }
6869
+
6870
+ Example:
6871
+
6872
+ ```node
6873
+ const {updateGroupSigningSession} = require('ln-service');
6874
+
6875
+ const {signature} = await updateGroupSigningSession({
6876
+ lnd,
6877
+ hash: v1TxDigestHash,
6878
+ id: sessionId,
6879
+ nonces: [externalNonce],
6880
+ });
6881
+ ```
6882
+
6764
6883
  ### updatePathfindingSettings
6765
6884
 
6766
6885
  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.16.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.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.15.0"
73
+ "version": "53.17.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
@@ -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,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
+ });
@@ -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,