ln-service 53.9.0 → 53.9.3
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 +4 -0
- package/README.md +2 -0
- package/package.json +5 -4
- package/test/walletrpc-integration/test_fund_psbt.js +213 -25
package/CHANGELOG.md
CHANGED
package/README.md
CHANGED
|
@@ -5433,6 +5433,8 @@ const {secret} = await once(sub, 'confirmed');
|
|
|
5433
5433
|
|
|
5434
5434
|
Subscribe to successful outgoing payments
|
|
5435
5435
|
|
|
5436
|
+
Payments may be omitted if LND does not finalize the payment record
|
|
5437
|
+
|
|
5436
5438
|
Requires `offchain:read` permission
|
|
5437
5439
|
|
|
5438
5440
|
Note: Method not supported on LND 0.13.4 and below
|
package/package.json
CHANGED
|
@@ -9,9 +9,9 @@
|
|
|
9
9
|
"dependencies": {
|
|
10
10
|
"bolt07": "1.8.0",
|
|
11
11
|
"cors": "2.8.5",
|
|
12
|
-
"express": "4.17.
|
|
12
|
+
"express": "4.17.3",
|
|
13
13
|
"invoices": "2.0.4",
|
|
14
|
-
"lightning": "5.8.
|
|
14
|
+
"lightning": "5.8.2",
|
|
15
15
|
"macaroon": "3.0.4",
|
|
16
16
|
"morgan": "1.10.0",
|
|
17
17
|
"ws": "8.5.0"
|
|
@@ -29,11 +29,12 @@
|
|
|
29
29
|
"bs58check": "2.1.2",
|
|
30
30
|
"ecpair": "2.0.1",
|
|
31
31
|
"ln-docker-daemons": "2.2.4",
|
|
32
|
+
"p2tr": "1.3.0",
|
|
32
33
|
"portfinder": "1.0.28",
|
|
33
34
|
"psbt": "2.0.0",
|
|
34
35
|
"rimraf": "3.0.2",
|
|
35
36
|
"secp256k1": "4.0.3",
|
|
36
|
-
"tiny-secp256k1": "2.2.
|
|
37
|
+
"tiny-secp256k1": "2.2.1",
|
|
37
38
|
"uuid": "8.3.2",
|
|
38
39
|
"varuint-bitcoin": "1.1.2"
|
|
39
40
|
},
|
|
@@ -68,5 +69,5 @@
|
|
|
68
69
|
"integration-test-0.12.0": "DOCKER_LND_VERSION=v0.12.0-beta npm run test",
|
|
69
70
|
"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/routerrpc-integration/*.js test/signerrpc-integration/*.js test/tower_clientrpc-integration/*.js test/tower_serverrpc-integration/*.js test/walletrpc-integration/*.js"
|
|
70
71
|
},
|
|
71
|
-
"version": "53.9.
|
|
72
|
+
"version": "53.9.3"
|
|
72
73
|
}
|
|
@@ -1,14 +1,21 @@
|
|
|
1
1
|
const asyncRetry = require('async/retry');
|
|
2
2
|
const {address} = require('bitcoinjs-lib');
|
|
3
|
+
const {controlBlock} = require('p2tr');
|
|
3
4
|
const {createPsbt} = require('psbt');
|
|
4
|
-
const {crypto} = require('bitcoinjs-lib');
|
|
5
5
|
const {decodePsbt} = require('psbt');
|
|
6
|
+
const {hashForTree} = require('p2tr');
|
|
7
|
+
const {leafHash} = require('p2tr');
|
|
6
8
|
const {networks} = require('bitcoinjs-lib');
|
|
9
|
+
const {pointAdd} = require('tiny-secp256k1');
|
|
10
|
+
const {privateAdd} = require('tiny-secp256k1');
|
|
7
11
|
const {script} = require('bitcoinjs-lib');
|
|
12
|
+
const {signHash} = require('p2tr');
|
|
13
|
+
const {signSchnorr} = require('tiny-secp256k1');
|
|
8
14
|
const {spawnLightningCluster} = require('ln-docker-daemons');
|
|
9
15
|
const {test} = require('@alexbosworth/tap');
|
|
10
16
|
const tinysecp = require('tiny-secp256k1');
|
|
11
17
|
const {Transaction} = require('bitcoinjs-lib');
|
|
18
|
+
const {v1OutputScript} = require('p2tr');
|
|
12
19
|
|
|
13
20
|
const {broadcastChainTransaction} = require('./../../');
|
|
14
21
|
const {createChainAddress} = require('./../../');
|
|
@@ -23,25 +30,17 @@ const chainAddressRowType = 'chain_address';
|
|
|
23
30
|
const {compile} = script;
|
|
24
31
|
const confirmationCount = 6;
|
|
25
32
|
const count = 100;
|
|
33
|
+
const defaultInternalKey = '0350929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0';
|
|
26
34
|
const description = 'description';
|
|
27
|
-
const
|
|
35
|
+
const {from} = Buffer;
|
|
28
36
|
const {fromBech32} = address;
|
|
29
37
|
const {fromHex} = Transaction;
|
|
30
38
|
const {fromOutputScript} = address;
|
|
31
39
|
const hexAsBuffer = hex => Buffer.from(hex, 'hex');
|
|
32
40
|
const interval = retryCount => 10 * Math.pow(2, retryCount);
|
|
33
|
-
const
|
|
34
|
-
const makeTaprootKey = (k, h) => tinysecp.xOnlyPointAddTweak(k, h).xOnlyPubkey;
|
|
35
|
-
const nLess1 = 'fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140';
|
|
36
|
-
const one256BitBigEndian = '0000000000000000000000000000000000000000000000000000000000000001';
|
|
37
|
-
const OP_1 = 81;
|
|
38
|
-
const {privateAdd} = tinysecp;
|
|
39
|
-
const {privateSub} = tinysecp;
|
|
41
|
+
const OP_CHECKSIG = 172;
|
|
40
42
|
const regtestBech32AddressHrp = 'bcrt';
|
|
41
|
-
const shortKey = keyPair => keyPair.publicKey.slice(1, 33);
|
|
42
|
-
const {signSchnorr} = tinysecp;
|
|
43
43
|
const smallTokens = 2e5;
|
|
44
|
-
const tapHash = k => crypto.taggedHash('TapTweak', k.publicKey.slice(1, 33));
|
|
45
44
|
const times = 20;
|
|
46
45
|
const {toOutputScript} = address;
|
|
47
46
|
const tokens = 1e6;
|
|
@@ -120,22 +119,123 @@ test(`Fund PSBT`, async ({end, equal}) => {
|
|
|
120
119
|
equal(!!decodedInput.witness_utxo.script_pub, true, 'PSBT input address');
|
|
121
120
|
equal(decodedInput.witness_utxo.tokens, 5000000000, 'PSBT has input tokens');
|
|
122
121
|
|
|
123
|
-
// A Taproot
|
|
122
|
+
// A Taproot script can be funded and spent with internal key + script hash
|
|
123
|
+
try {
|
|
124
|
+
await generate({count});
|
|
125
|
+
|
|
126
|
+
const keyPair1 = ecp.makeRandom({network: networks.regtest});
|
|
127
|
+
const keyPair2 = ecp.makeRandom({network: networks.regtest});
|
|
128
|
+
const unusedKey = ecp.makeRandom({network: networks.regtest});
|
|
129
|
+
|
|
130
|
+
const witnessScript = compile([unusedKey.publicKey.slice(1), OP_CHECKSIG]);
|
|
131
|
+
|
|
132
|
+
const branches = [{script: witnessScript.toString('hex')}];
|
|
133
|
+
|
|
134
|
+
const {hash} = hashForTree({branches});
|
|
135
|
+
|
|
136
|
+
// Create a combined key using public key material
|
|
137
|
+
const combinedPoint = pointAdd(keyPair1.publicKey, keyPair2.publicKey);
|
|
138
|
+
|
|
139
|
+
const output = v1OutputScript({
|
|
140
|
+
hash,
|
|
141
|
+
internal_key: Buffer.from(combinedPoint).toString('hex'),
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
const [utxo] = (await getUtxos({lnd})).utxos.reverse();
|
|
145
|
+
|
|
146
|
+
// Make a PSBT paying to the Taproot output
|
|
147
|
+
const {psbt} = createPsbt({
|
|
148
|
+
outputs: [{tokens, script: output.script}],
|
|
149
|
+
utxos: [{id: utxo.transaction_id, vout: utxo.transaction_vout}],
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
// Sign the PSBT
|
|
153
|
+
const signed = await signPsbt({
|
|
154
|
+
lnd,
|
|
155
|
+
psbt: (await fundPsbt({lnd, psbt})).psbt,
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
// Send the tx to the chain
|
|
159
|
+
await broadcastChainTransaction({lnd, transaction: signed.transaction});
|
|
160
|
+
|
|
161
|
+
// Make a new tx that will spend the output back into the wallet
|
|
162
|
+
const tx = new Transaction();
|
|
163
|
+
|
|
164
|
+
// The new tx spends the Taproot output
|
|
165
|
+
tx.addInput(
|
|
166
|
+
fromHex(signed.transaction).getHash(),
|
|
167
|
+
fromHex(signed.transaction).outs.findIndex(n => n.value === tokens)
|
|
168
|
+
);
|
|
169
|
+
|
|
170
|
+
// Make an output to pay back into the wallet
|
|
171
|
+
const chainOutput = toOutputScript(
|
|
172
|
+
(await createChainAddress({lnd})).address,
|
|
173
|
+
networks.regtest
|
|
174
|
+
);
|
|
175
|
+
|
|
176
|
+
// Add output to the pay back transaction
|
|
177
|
+
tx.addOutput(chainOutput, smallTokens);
|
|
178
|
+
|
|
179
|
+
const [hashToSign] = tx.ins.map((input, i) => {
|
|
180
|
+
return tx.hashForWitnessV1(
|
|
181
|
+
i,
|
|
182
|
+
[hexAsBuffer(output.script)],
|
|
183
|
+
[tokens],
|
|
184
|
+
Transaction.SIGHASH_DEFAULT,
|
|
185
|
+
);
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
// Ready for private key combining
|
|
189
|
+
const combinedKey = privateAdd(keyPair1.privateKey, keyPair2.privateKey);
|
|
190
|
+
|
|
191
|
+
const signedInput = signHash({
|
|
192
|
+
hash,
|
|
193
|
+
private_key: Buffer.from(combinedKey).toString('hex'),
|
|
194
|
+
public_key: Buffer.from(combinedPoint).toString('hex'),
|
|
195
|
+
sign_hash: hashToSign.toString('hex'),
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
const signature = hexAsBuffer(signedInput.signature);
|
|
199
|
+
|
|
200
|
+
// Add the signature to the input
|
|
201
|
+
tx.ins.forEach((input, i) => tx.setWitness(i, [signature]));
|
|
202
|
+
|
|
203
|
+
await broadcastChainTransaction({lnd, transaction: tx.toHex()});
|
|
204
|
+
|
|
205
|
+
await asyncRetry({interval, times}, async () => {
|
|
206
|
+
await generate({});
|
|
207
|
+
|
|
208
|
+
const {utxos} = await getUtxos({lnd});
|
|
209
|
+
|
|
210
|
+
const utxo = utxos.find(n => n.transaction_id === tx.getId());
|
|
211
|
+
|
|
212
|
+
if (!utxo || !utxo.confirmation_count) {
|
|
213
|
+
throw new Error('ExpectedReceivedTaprootSpend');
|
|
214
|
+
}
|
|
215
|
+
});
|
|
216
|
+
} catch (err) {
|
|
217
|
+
equal(err, null, 'Expected no error');
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// A Taproot script output should be funded and spent with script
|
|
124
221
|
try {
|
|
125
222
|
await generate({count});
|
|
126
223
|
|
|
127
224
|
const keyPair = ecp.makeRandom({network: networks.regtest});
|
|
128
225
|
|
|
129
|
-
const
|
|
130
|
-
|
|
226
|
+
const witnessScript = compile([keyPair.publicKey.slice(1), OP_CHECKSIG]);
|
|
227
|
+
|
|
228
|
+
const branches = [{script: witnessScript.toString('hex')}];
|
|
229
|
+
|
|
230
|
+
const {hash} = hashForTree({branches});
|
|
131
231
|
|
|
132
|
-
const
|
|
232
|
+
const output = v1OutputScript({hash, internal_key: defaultInternalKey});
|
|
133
233
|
|
|
134
234
|
const [utxo] = (await getUtxos({lnd})).utxos.reverse();
|
|
135
235
|
|
|
136
236
|
// Make a PSBT paying to the Taproot output
|
|
137
237
|
const {psbt} = createPsbt({
|
|
138
|
-
outputs: [{tokens, script:
|
|
238
|
+
outputs: [{tokens, script: output.script}],
|
|
139
239
|
utxos: [{id: utxo.transaction_id, vout: utxo.transaction_vout}],
|
|
140
240
|
});
|
|
141
241
|
|
|
@@ -169,22 +269,110 @@ test(`Fund PSBT`, async ({end, equal}) => {
|
|
|
169
269
|
const [hashToSign] = tx.ins.map((input, i) => {
|
|
170
270
|
return tx.hashForWitnessV1(
|
|
171
271
|
i,
|
|
172
|
-
[
|
|
272
|
+
[hexAsBuffer(output.script)],
|
|
173
273
|
[tokens],
|
|
174
274
|
Transaction.SIGHASH_DEFAULT,
|
|
275
|
+
hexAsBuffer(leafHash({script: witnessScript.toString('hex')}).hash),
|
|
175
276
|
);
|
|
176
277
|
});
|
|
177
278
|
|
|
178
|
-
const
|
|
179
|
-
|
|
180
|
-
const
|
|
279
|
+
const signature = from(signSchnorr(hashToSign, keyPair.privateKey));
|
|
280
|
+
|
|
281
|
+
const {block} = controlBlock({
|
|
282
|
+
external_key: output.external_key,
|
|
283
|
+
leaf_script: witnessScript.toString('hex'),
|
|
284
|
+
script_branches: branches,
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
// Add the signature to the input
|
|
288
|
+
tx.ins.forEach((input, i) => {
|
|
289
|
+
return tx.setWitness(i, [
|
|
290
|
+
signature,
|
|
291
|
+
witnessScript,
|
|
292
|
+
hexAsBuffer(block),
|
|
293
|
+
]);
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
await broadcastChainTransaction({lnd, transaction: tx.toHex()});
|
|
297
|
+
|
|
298
|
+
await asyncRetry({interval, times}, async () => {
|
|
299
|
+
await generate({});
|
|
300
|
+
|
|
301
|
+
const {utxos} = await getUtxos({lnd});
|
|
302
|
+
|
|
303
|
+
const utxo = utxos.find(n => n.transaction_id === tx.getId());
|
|
304
|
+
|
|
305
|
+
if (!utxo || !utxo.confirmation_count) {
|
|
306
|
+
throw new Error('ExpectedReceivedTaprootSpend');
|
|
307
|
+
}
|
|
308
|
+
});
|
|
309
|
+
} catch (err) {
|
|
310
|
+
equal(err, null, 'Expected no error');
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// A Taproot output should be funded for a regular key spend
|
|
314
|
+
try {
|
|
315
|
+
await generate({count});
|
|
316
|
+
|
|
317
|
+
const keyPair = ecp.makeRandom({network: networks.regtest});
|
|
318
|
+
|
|
319
|
+
const output = v1OutputScript({
|
|
320
|
+
internal_key: keyPair.publicKey.toString('hex'),
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
const outputScript = hexAsBuffer(output.script);
|
|
324
|
+
|
|
325
|
+
const [utxo] = (await getUtxos({lnd})).utxos.reverse();
|
|
326
|
+
|
|
327
|
+
// Make a PSBT paying to the Taproot output
|
|
328
|
+
const {psbt} = createPsbt({
|
|
329
|
+
outputs: [{tokens, script: outputScript.toString('hex')}],
|
|
330
|
+
utxos: [{id: utxo.transaction_id, vout: utxo.transaction_vout}],
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
// Sign the PSBT
|
|
334
|
+
const signed = await signPsbt({
|
|
335
|
+
lnd,
|
|
336
|
+
psbt: (await fundPsbt({lnd, psbt})).psbt,
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
// Send the tx to the chain
|
|
340
|
+
await broadcastChainTransaction({lnd, transaction: signed.transaction});
|
|
341
|
+
|
|
342
|
+
// Make a new tx that will spend the output back into the wallet
|
|
343
|
+
const tx = new Transaction();
|
|
344
|
+
|
|
345
|
+
// The new tx spends the Taproot output
|
|
346
|
+
tx.addInput(
|
|
347
|
+
fromHex(signed.transaction).getHash(),
|
|
348
|
+
fromHex(signed.transaction).outs.findIndex(n => n.value === tokens)
|
|
349
|
+
);
|
|
350
|
+
|
|
351
|
+
// Make an output to pay back into the wallet
|
|
352
|
+
const chainOutput = toOutputScript(
|
|
353
|
+
(await createChainAddress({lnd})).address,
|
|
354
|
+
networks.regtest
|
|
355
|
+
);
|
|
181
356
|
|
|
182
|
-
|
|
357
|
+
// Add output to the pay back transaction
|
|
358
|
+
tx.addOutput(chainOutput, smallTokens);
|
|
183
359
|
|
|
184
|
-
|
|
185
|
-
|
|
360
|
+
const [hashToSign] = tx.ins.map((input, i) => {
|
|
361
|
+
return tx.hashForWitnessV1(
|
|
362
|
+
i,
|
|
363
|
+
[outputScript],
|
|
364
|
+
[tokens],
|
|
365
|
+
Transaction.SIGHASH_DEFAULT,
|
|
366
|
+
);
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
const signedInput = signHash({
|
|
370
|
+
private_key: keyPair.privateKey.toString('hex'),
|
|
371
|
+
public_key: keyPair.publicKey.toString('hex'),
|
|
372
|
+
sign_hash: hashToSign.toString('hex'),
|
|
373
|
+
});
|
|
186
374
|
|
|
187
|
-
const signature =
|
|
375
|
+
const signature = hexAsBuffer(signedInput.signature);
|
|
188
376
|
|
|
189
377
|
// Add the signature to the input
|
|
190
378
|
tx.ins.forEach((input, i) => tx.setWitness(i, [Buffer.from(signature)]));
|