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 +16 -0
- package/README.md +123 -4
- package/index.js +6 -0
- package/package.json +5 -5
- package/test/chainrpc-integration/test_subscribe_to_chain_address.js +1 -1
- package/test/macros/wait_for_channel.js +1 -1
- package/test/signerrpc-integration/test_begin_group_signing_session.js +440 -0
- package/test/signerrpc-integration/test_sign_taproot.js +359 -0
- package/test/signerrpc-integration/test_sign_transaction.js +1 -1
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-
|
|
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
|
|
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
|
|
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.
|
|
12
|
+
"express": "4.18.1",
|
|
13
13
|
"invoices": "2.0.6",
|
|
14
|
-
"lightning": "5.
|
|
14
|
+
"lightning": "5.16.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.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.
|
|
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('
|
|
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
|
+
});
|
|
@@ -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: '
|
|
18
|
+
output_script: '00147ab105a90ccd7e49d96672abcac2995bdb852baa',
|
|
19
19
|
output_tokens: 1e8,
|
|
20
20
|
sighash: Transaction.SIGHASH_ALL,
|
|
21
21
|
vin: 0,
|