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-
|
|
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.
|
|
12
|
+
"express": "4.18.1",
|
|
13
13
|
"invoices": "2.0.6",
|
|
14
|
-
"lightning": "5.
|
|
14
|
+
"lightning": "5.15.0",
|
|
15
15
|
"macaroon": "3.0.4",
|
|
16
16
|
"morgan": "1.10.0",
|
|
17
|
-
"ws": "8.
|
|
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.
|
|
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.
|
|
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('
|
|
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
|
+
});
|